Files
bsky-social-app/src/logger/__tests__/logger.test.ts
T
2026-08-03 14:50:07 +03:00

560 lines
12 KiB
TypeScript

import {beforeAll, describe, expect, jest, test} from '@jest/globals'
import * as Sentry from '@sentry/react-native'
import {nanoid} from 'nanoid/non-secure'
import {Logger} from '#/logger'
import {sentryTransport} from '#/logger/transports/sentry'
import {LogLevel} from '#/logger/types'
jest.mock('@sentry/react-native', () => ({
addBreadcrumb: jest.fn(),
captureException: jest.fn(),
captureMessage: jest.fn(),
}))
beforeAll(() => {
jest.useFakeTimers()
})
describe('general functionality', () => {
test('default params', () => {
const logger = new Logger()
expect(logger.level).toEqual(LogLevel.Info)
})
test('can override default params', () => {
const logger = new Logger({
level: LogLevel.Debug,
})
expect(logger.level).toEqual(LogLevel.Debug)
})
test('contextFilter overrides level', () => {
const logger = new Logger({
level: LogLevel.Info,
contextFilter: 'test',
})
expect(logger.level).toEqual(LogLevel.Debug)
})
test('supports extra metadata', () => {
const timestamp = Date.now()
const logger = new Logger({})
const mockTransport = jest.fn()
logger.addTransport(mockTransport)
const extra = {foo: true, __metadata__: {}}
logger.warn('message', extra)
expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Warn,
undefined,
'message',
extra,
timestamp,
)
})
test('supports inherited metadata', () => {
const timestamp = Date.now()
const logger = new Logger({
metadata: {bar: true},
})
const mockTransport = jest.fn()
logger.addTransport(mockTransport)
const extra = {foo: true, __metadata__: {bar: true}}
logger.warn('message', extra)
expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Warn,
undefined,
'message',
extra,
timestamp,
)
})
test('supports nullish/falsy metadata', () => {
const timestamp = Date.now()
const logger = new Logger({})
const mockTransport = jest.fn()
const remove = logger.addTransport(mockTransport)
// @ts-expect-error testing the JS case
logger.warn('a', null)
expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Warn,
undefined,
'a',
{__metadata__: {}},
timestamp,
)
// @ts-expect-error testing the JS case
logger.warn('b', false)
expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Warn,
undefined,
'b',
{__metadata__: {}},
timestamp,
)
// @ts-expect-error testing the JS case
logger.warn('c', 0)
expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Warn,
undefined,
'c',
{__metadata__: {}},
timestamp,
)
remove()
logger.addTransport((level, context, message, metadata) => {
expect(typeof metadata).toEqual('object')
})
// @ts-expect-error testing the JS case
logger.warn('message', null)
})
test('sentryTransport', () => {
const message = 'message'
const timestamp = Date.now()
const sentryTimestamp = timestamp / 1000
/*
sentryTransport(
LogLevel.Debug,
Logger.Context.Default,
message,
{},
timestamp,
)
expect(Sentry.addBreadcrumb).toHaveBeenCalledWith({
category: Logger.Context.Default,
message,
data: {__context__: 'logger'},
type: 'default',
level: LogLevel.Debug,
timestamp: sentryTimestamp,
})
*/
sentryTransport(
LogLevel.Info,
Logger.Context.Default,
message,
{type: 'info', prop: true},
timestamp,
)
expect(Sentry.addBreadcrumb).toHaveBeenCalledWith({
category: Logger.Context.Default,
message,
data: {prop: true, __context__: 'logger'},
type: 'info',
level: LogLevel.Info,
timestamp: sentryTimestamp,
})
sentryTransport(
LogLevel.Log,
Logger.Context.Default,
message,
{},
timestamp,
)
expect(Sentry.addBreadcrumb).toHaveBeenCalledWith({
category: Logger.Context.Default,
message,
data: {__context__: 'logger'},
type: 'default',
level: 'log',
timestamp: sentryTimestamp,
})
jest.runAllTimers()
expect(Sentry.captureMessage).not.toHaveBeenCalled()
sentryTransport(
LogLevel.Warn,
Logger.Context.Default,
message,
{},
timestamp,
)
expect(Sentry.addBreadcrumb).toHaveBeenCalledWith({
category: Logger.Context.Default,
message,
data: {__context__: 'logger'},
type: 'default',
level: 'warning',
timestamp: sentryTimestamp,
})
jest.runAllTimers()
expect(Sentry.captureMessage).not.toHaveBeenCalled()
sentryTransport(
LogLevel.Error,
Logger.Context.Default,
message,
{},
timestamp,
)
jest.runAllTimers()
expect(Sentry.captureMessage).toHaveBeenCalledWith(message, {
level: 'error',
tags: {category: 'logger'},
extra: {__context__: 'logger'},
})
const e = new Error('error')
const tags = {
prop: 'prop',
}
sentryTransport(
LogLevel.Error,
Logger.Context.Default,
e,
{
tags,
prop: true,
},
timestamp,
)
expect(Sentry.captureException).toHaveBeenCalledWith(e, {
tags: {
...tags,
category: 'logger',
},
extra: {
prop: true,
__context__: 'logger',
},
})
const fingerprint = ['{{ default }}', 'report-dialog:upstream-fetch']
sentryTransport(
LogLevel.Error,
Logger.Context.ReportDialog,
e,
{fingerprint},
timestamp,
)
expect(Sentry.captureException).toHaveBeenLastCalledWith(e, {
tags: {category: 'report-dialog'},
extra: {__context__: 'report-dialog'},
fingerprint,
})
})
test('sentryTransport serializes errors', () => {
const message = 'message'
const timestamp = Date.now()
const sentryTimestamp = timestamp / 1000
sentryTransport(
LogLevel.Info,
undefined,
message,
{error: new Error('foo')},
timestamp,
)
expect(Sentry.addBreadcrumb).toHaveBeenCalledWith({
message,
data: {error: 'Error: foo'},
type: 'default',
level: LogLevel.Info,
timestamp: sentryTimestamp,
})
})
test('sentryTransport filters network errors', () => {
jest.clearAllMocks()
const timestamp = Date.now()
// network error in the message itself
sentryTransport(
LogLevel.Error,
Logger.Context.Default,
'Network request failed',
{},
timestamp,
)
// network error in metadata, message is something else
sentryTransport(
LogLevel.Error,
Logger.Context.Default,
'poll failed',
{safeMessage: new Error('Network request failed')},
timestamp,
)
jest.runAllTimers()
expect(Sentry.captureMessage).not.toHaveBeenCalled()
// network Error object
sentryTransport(
LogLevel.Error,
Logger.Context.Default,
new Error('Network request failed'),
{},
timestamp,
)
expect(Sentry.captureException).not.toHaveBeenCalled()
})
test('add/remove transport', () => {
const timestamp = Date.now()
const logger = new Logger({})
const mockTransport = jest.fn()
const remove = logger.addTransport(mockTransport)
logger.warn('warn')
remove()
logger.warn('warn')
// only called once bc it was removed
expect(mockTransport).toHaveBeenNthCalledWith(
1,
LogLevel.Warn,
undefined,
'warn',
{__metadata__: {}},
timestamp,
)
})
})
describe('create', () => {
test('create', () => {
const mockTransport = jest.fn()
const timestamp = Date.now()
const message = nanoid()
const logger = Logger.create(Logger.Context.Default)
logger.addTransport(mockTransport)
logger.info(message, {})
expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Info,
Logger.Context.Default,
message,
{__metadata__: {}},
timestamp,
)
})
})
describe('debug contexts', () => {
test('specific', () => {
const mockTransport = jest.fn()
const timestamp = Date.now()
const message = nanoid()
const logger = new Logger({
// @ts-ignore
context: 'specific',
level: LogLevel.Debug,
})
logger.addTransport(mockTransport)
logger.debug(message, {})
expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Debug,
'specific',
message,
{__metadata__: {}},
timestamp,
)
})
test('namespaced', () => {
const mockTransport = jest.fn()
const timestamp = Date.now()
const message = nanoid()
const logger = new Logger({
// @ts-ignore
context: 'namespace:foo',
contextFilter: 'namespace:*',
level: LogLevel.Debug,
})
logger.addTransport(mockTransport)
logger.debug(message, {})
expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Debug,
'namespace:foo',
message,
{__metadata__: {}},
timestamp,
)
})
test('ignores inactive', () => {
const mockTransport = jest.fn()
const timestamp = Date.now()
const message = nanoid()
const logger = new Logger({
// @ts-ignore
context: 'namespace:bar:baz',
contextFilter: 'namespace:foo:*',
})
logger.addTransport(mockTransport)
logger.debug(message, {})
expect(mockTransport).not.toHaveBeenCalledWith(
LogLevel.Debug,
'namespace:bar:baz',
message,
{__metadata__: {}},
timestamp,
)
})
})
describe('supports levels', () => {
test('debug', () => {
const timestamp = Date.now()
const logger = new Logger({
level: LogLevel.Debug,
})
const message = nanoid()
const mockTransport = jest.fn()
logger.addTransport(mockTransport)
logger.debug(message)
expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Debug,
undefined,
message,
{__metadata__: {}},
timestamp,
)
logger.info(message)
expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Info,
undefined,
message,
{__metadata__: {}},
timestamp,
)
logger.warn(message)
expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Warn,
undefined,
message,
{__metadata__: {}},
timestamp,
)
const e = new Error(message)
logger.error(e)
expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Error,
undefined,
e,
{__metadata__: {}},
timestamp,
)
})
test('info', () => {
const timestamp = Date.now()
const logger = new Logger({
level: LogLevel.Info,
})
const message = nanoid()
const mockTransport = jest.fn()
logger.addTransport(mockTransport)
logger.debug(message)
expect(mockTransport).not.toHaveBeenCalled()
logger.info(message)
expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Info,
undefined,
message,
{__metadata__: {}},
timestamp,
)
})
test('warn', () => {
const timestamp = Date.now()
const logger = new Logger({
level: LogLevel.Warn,
})
const message = nanoid()
const mockTransport = jest.fn()
logger.addTransport(mockTransport)
logger.debug(message)
expect(mockTransport).not.toHaveBeenCalled()
logger.info(message)
expect(mockTransport).not.toHaveBeenCalled()
logger.warn(message)
expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Warn,
undefined,
message,
{__metadata__: {}},
timestamp,
)
})
test('error', () => {
const timestamp = Date.now()
const logger = new Logger({
level: LogLevel.Error,
})
const message = nanoid()
const mockTransport = jest.fn()
logger.addTransport(mockTransport)
logger.debug(message)
expect(mockTransport).not.toHaveBeenCalled()
logger.info(message)
expect(mockTransport).not.toHaveBeenCalled()
logger.warn(message)
expect(mockTransport).not.toHaveBeenCalled()
const e = new Error('original message')
logger.error(e)
expect(mockTransport).toHaveBeenCalledWith(
LogLevel.Error,
undefined,
e,
{__metadata__: {}},
timestamp,
)
})
})