444 lines
9.5 KiB
TypeScript
444 lines
9.5 KiB
TypeScript
import {nanoid} from 'nanoid/non-secure'
|
|
import {jest, describe, expect, test, beforeAll} from '@jest/globals'
|
|
import {Native as Sentry} from 'sentry-expo'
|
|
|
|
import {Logger, LogLevel, sentryTransport} from '#/logger'
|
|
|
|
jest.mock('#/env', () => ({
|
|
IS_TEST: true,
|
|
IS_DEV: false,
|
|
IS_PROD: false,
|
|
/*
|
|
* Forces debug mode for tests using the default logger. Most tests create
|
|
* their own logger instance.
|
|
*/
|
|
LOG_LEVEL: 'debug',
|
|
LOG_DEBUG: '',
|
|
}))
|
|
|
|
jest.mock('sentry-expo', () => ({
|
|
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.enabled).toBeFalsy()
|
|
expect(logger.level).toEqual(LogLevel.Debug) // mocked above
|
|
})
|
|
|
|
test('can override default params', () => {
|
|
const logger = new Logger({
|
|
enabled: true,
|
|
level: LogLevel.Info,
|
|
})
|
|
expect(logger.enabled).toBeTruthy()
|
|
expect(logger.level).toEqual(LogLevel.Info)
|
|
})
|
|
|
|
test('disabled logger does not report', () => {
|
|
const logger = new Logger({
|
|
enabled: false,
|
|
level: LogLevel.Debug,
|
|
})
|
|
|
|
const mockTransport = jest.fn()
|
|
|
|
logger.addTransport(mockTransport)
|
|
logger.debug('message')
|
|
|
|
expect(mockTransport).not.toHaveBeenCalled()
|
|
})
|
|
|
|
test('disablement', () => {
|
|
const logger = new Logger({
|
|
enabled: true,
|
|
level: LogLevel.Debug,
|
|
})
|
|
|
|
logger.disable()
|
|
|
|
const mockTransport = jest.fn()
|
|
|
|
logger.addTransport(mockTransport)
|
|
logger.debug('message')
|
|
|
|
expect(mockTransport).not.toHaveBeenCalled()
|
|
})
|
|
|
|
test('passing debug contexts automatically enables debug mode', () => {
|
|
const logger = new Logger({debug: 'specific'})
|
|
expect(logger.level).toEqual(LogLevel.Debug)
|
|
})
|
|
|
|
test('supports extra metadata', () => {
|
|
const timestamp = Date.now()
|
|
const logger = new Logger({enabled: true})
|
|
|
|
const mockTransport = jest.fn()
|
|
|
|
logger.addTransport(mockTransport)
|
|
|
|
const extra = {foo: true}
|
|
logger.warn('message', extra)
|
|
|
|
expect(mockTransport).toHaveBeenCalledWith(
|
|
LogLevel.Warn,
|
|
'message',
|
|
extra,
|
|
timestamp,
|
|
)
|
|
})
|
|
|
|
test('supports nullish/falsy metadata', () => {
|
|
const timestamp = Date.now()
|
|
const logger = new Logger({enabled: true})
|
|
|
|
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,
|
|
'a',
|
|
{},
|
|
timestamp,
|
|
)
|
|
|
|
// @ts-expect-error testing the JS case
|
|
logger.warn('b', false)
|
|
expect(mockTransport).toHaveBeenCalledWith(
|
|
LogLevel.Warn,
|
|
'b',
|
|
{},
|
|
timestamp,
|
|
)
|
|
|
|
// @ts-expect-error testing the JS case
|
|
logger.warn('c', 0)
|
|
expect(mockTransport).toHaveBeenCalledWith(
|
|
LogLevel.Warn,
|
|
'c',
|
|
{},
|
|
timestamp,
|
|
)
|
|
|
|
remove()
|
|
|
|
logger.addTransport((level, 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, message, {}, timestamp)
|
|
expect(Sentry.addBreadcrumb).toHaveBeenCalledWith({
|
|
message,
|
|
data: {},
|
|
type: 'default',
|
|
level: LogLevel.Debug,
|
|
timestamp: sentryTimestamp,
|
|
})
|
|
|
|
sentryTransport(
|
|
LogLevel.Info,
|
|
message,
|
|
{type: 'info', prop: true},
|
|
timestamp,
|
|
)
|
|
expect(Sentry.addBreadcrumb).toHaveBeenCalledWith({
|
|
message,
|
|
data: {prop: true},
|
|
type: 'info',
|
|
level: LogLevel.Info,
|
|
timestamp: sentryTimestamp,
|
|
})
|
|
|
|
sentryTransport(LogLevel.Log, message, {}, timestamp)
|
|
expect(Sentry.addBreadcrumb).toHaveBeenCalledWith({
|
|
message,
|
|
data: {},
|
|
type: 'default',
|
|
level: 'debug', // Sentry bug, log becomes debug
|
|
timestamp: sentryTimestamp,
|
|
})
|
|
expect(Sentry.captureMessage).toHaveBeenCalledWith(message, {
|
|
level: 'log',
|
|
tags: undefined,
|
|
extra: {},
|
|
})
|
|
|
|
sentryTransport(LogLevel.Warn, message, {}, timestamp)
|
|
expect(Sentry.addBreadcrumb).toHaveBeenCalledWith({
|
|
message,
|
|
data: {},
|
|
type: 'default',
|
|
level: 'warning',
|
|
timestamp: sentryTimestamp,
|
|
})
|
|
expect(Sentry.captureMessage).toHaveBeenCalledWith(message, {
|
|
level: 'warning',
|
|
tags: undefined,
|
|
extra: {},
|
|
})
|
|
|
|
const e = new Error('error')
|
|
const tags = {
|
|
prop: 'prop',
|
|
}
|
|
|
|
sentryTransport(
|
|
LogLevel.Error,
|
|
e,
|
|
{
|
|
tags,
|
|
prop: true,
|
|
},
|
|
timestamp,
|
|
)
|
|
|
|
expect(Sentry.captureException).toHaveBeenCalledWith(e, {
|
|
tags,
|
|
extra: {
|
|
prop: true,
|
|
},
|
|
})
|
|
})
|
|
|
|
test('sentryTransport serializes errors', () => {
|
|
const message = 'message'
|
|
const timestamp = Date.now()
|
|
const sentryTimestamp = timestamp / 1000
|
|
|
|
sentryTransport(
|
|
LogLevel.Debug,
|
|
message,
|
|
{error: new Error('foo')},
|
|
timestamp,
|
|
)
|
|
expect(Sentry.addBreadcrumb).toHaveBeenCalledWith({
|
|
message,
|
|
data: {error: 'Error: foo'},
|
|
type: 'default',
|
|
level: LogLevel.Debug,
|
|
timestamp: sentryTimestamp,
|
|
})
|
|
})
|
|
|
|
test('add/remove transport', () => {
|
|
const timestamp = Date.now()
|
|
const logger = new Logger({enabled: true})
|
|
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,
|
|
'warn',
|
|
{},
|
|
timestamp,
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('debug contexts', () => {
|
|
const mockTransport = jest.fn()
|
|
|
|
test('specific', () => {
|
|
const timestamp = Date.now()
|
|
const message = nanoid()
|
|
const logger = new Logger({
|
|
enabled: true,
|
|
debug: 'specific',
|
|
})
|
|
|
|
logger.addTransport(mockTransport)
|
|
logger.debug(message, {}, 'specific')
|
|
|
|
expect(mockTransport).toHaveBeenCalledWith(
|
|
LogLevel.Debug,
|
|
message,
|
|
{},
|
|
timestamp,
|
|
)
|
|
})
|
|
|
|
test('namespaced', () => {
|
|
const timestamp = Date.now()
|
|
const message = nanoid()
|
|
const logger = new Logger({
|
|
enabled: true,
|
|
debug: 'namespace*',
|
|
})
|
|
|
|
logger.addTransport(mockTransport)
|
|
logger.debug(message, {}, 'namespace')
|
|
|
|
expect(mockTransport).toHaveBeenCalledWith(
|
|
LogLevel.Debug,
|
|
message,
|
|
{},
|
|
timestamp,
|
|
)
|
|
})
|
|
|
|
test('ignores inactive', () => {
|
|
const timestamp = Date.now()
|
|
const message = nanoid()
|
|
const logger = new Logger({
|
|
enabled: true,
|
|
debug: 'namespace:foo:*',
|
|
})
|
|
|
|
logger.addTransport(mockTransport)
|
|
logger.debug(message, {}, 'namespace:bar:baz')
|
|
|
|
expect(mockTransport).not.toHaveBeenCalledWith(
|
|
LogLevel.Debug,
|
|
message,
|
|
{},
|
|
timestamp,
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('supports levels', () => {
|
|
test('debug', () => {
|
|
const timestamp = Date.now()
|
|
const logger = new Logger({
|
|
enabled: true,
|
|
level: LogLevel.Debug,
|
|
})
|
|
const message = nanoid()
|
|
const mockTransport = jest.fn()
|
|
|
|
logger.addTransport(mockTransport)
|
|
|
|
logger.debug(message)
|
|
expect(mockTransport).toHaveBeenCalledWith(
|
|
LogLevel.Debug,
|
|
message,
|
|
{},
|
|
timestamp,
|
|
)
|
|
|
|
logger.info(message)
|
|
expect(mockTransport).toHaveBeenCalledWith(
|
|
LogLevel.Info,
|
|
message,
|
|
{},
|
|
timestamp,
|
|
)
|
|
|
|
logger.warn(message)
|
|
expect(mockTransport).toHaveBeenCalledWith(
|
|
LogLevel.Warn,
|
|
message,
|
|
{},
|
|
timestamp,
|
|
)
|
|
|
|
const e = new Error(message)
|
|
logger.error(e)
|
|
expect(mockTransport).toHaveBeenCalledWith(LogLevel.Error, e, {}, timestamp)
|
|
})
|
|
|
|
test('info', () => {
|
|
const timestamp = Date.now()
|
|
const logger = new Logger({
|
|
enabled: true,
|
|
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,
|
|
message,
|
|
{},
|
|
timestamp,
|
|
)
|
|
})
|
|
|
|
test('warn', () => {
|
|
const timestamp = Date.now()
|
|
const logger = new Logger({
|
|
enabled: true,
|
|
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,
|
|
message,
|
|
{},
|
|
timestamp,
|
|
)
|
|
})
|
|
|
|
test('error', () => {
|
|
const timestamp = Date.now()
|
|
const logger = new Logger({
|
|
enabled: true,
|
|
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, e, {}, timestamp)
|
|
})
|
|
})
|