Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support custom raw logger #461

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
// and only meant for consumption by Netlify Teams.
// While we try to adhere to semver, this file is not considered part of the public API.

export { systemLogger, LogLevel } from './lib/system_logger.js'
export { systemLogger, LogLevel, StructuredLogger } from './lib/system_logger.js'
40 changes: 19 additions & 21 deletions src/lib/system_logger.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { env } from 'process'

const systemLogTag = '__nfSystemLog'

const serializeError = (error: Error): Record<string, unknown> => {
Expand All @@ -18,61 +16,61 @@ export enum LogLevel {
Debug = 1,
Log,
Error,
Silent = Number.POSITIVE_INFINITY,
}

class SystemLogger {
type RawLogger = (...data: unknown[]) => void

export class StructuredLogger {
private readonly fields: Record<string, unknown>
private readonly logLevel: LogLevel
private readonly rawLogger?: RawLogger

constructor(fields: Record<string, unknown> = {}, logLevel = LogLevel.Log) {
constructor(logLevel: LogLevel, rawLogger?: RawLogger, fields: Record<string, unknown> = {}) {
this.fields = fields
this.logLevel = logLevel
this.rawLogger = rawLogger
}

private doLog(logger: typeof console.log, message: string) {
if (env.NETLIFY_DEV && !env.NETLIFY_ENABLE_SYSTEM_LOGGING) {
return
}
private doLog(message: string, level: string, defaultLogger: RawLogger) {
const logger = this.rawLogger ?? defaultLogger

logger(systemLogTag, JSON.stringify({ msg: message, fields: this.fields }))
logger(systemLogTag, JSON.stringify({ msg: message, fields: this.fields, level }))
}

log(message: string) {
if (this.logLevel > LogLevel.Log) {
return
}

this.doLog(console.log, message)
this.doLog(message, 'log', console.log)
}

debug(message: string) {
if (this.logLevel > LogLevel.Debug) {
return
}

this.doLog(console.debug, message)
this.doLog(message, 'debug', console.debug)
}

error(message: string) {
if (this.logLevel > LogLevel.Error) {
return
}

this.doLog(console.error, message)
this.doLog(message, 'error', console.error)
}

withLogLevel(level: LogLevel) {
return new SystemLogger(this.fields, level)
return new StructuredLogger(level, this.rawLogger, this.fields)
}

withFields(fields: Record<string, unknown>) {
return new SystemLogger(
{
...this.fields,
...fields,
},
this.logLevel,
)
return new StructuredLogger(this.logLevel, this.rawLogger, {
...this.fields,
...fields,
})
}

withError(error: unknown) {
Expand All @@ -82,4 +80,4 @@ class SystemLogger {
}
}

export const systemLogger = new SystemLogger()
export const systemLogger = new StructuredLogger(LogLevel.Log)
102 changes: 70 additions & 32 deletions test/unit/system_logger.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,44 @@
const process = require("process")

const test = require('ava')

const { systemLogger, LogLevel } = require('../../dist/internal')
const { systemLogger, LogLevel, StructuredLogger } = require('../../dist/internal')

const consoleDebug = console.debug
const consoleError = console.error
const consoleLog = console.log

test.afterEach(() => {
console.debug = consoleDebug
console.error = consoleError
console.log = consoleLog
})

test('Log Level', (t) => {
const originalDebug = console.debug
test('Log levels', (t) => {
const logs = {
debug: [],
error: [],
log: [],
}
console.debug = (...message) => logs.debug.push(message)
console.error = (...message) => logs.error.push(message)
console.log = (...message) => logs.log.push(message)

const debugLogs = []
console.debug = (...message) => debugLogs.push(message)
systemLogger.debug('debug 1')
t.is(logs.debug.length, 0)

systemLogger.debug('hello!')
t.is(debugLogs.length, 0)
systemLogger.log('log 1')
t.is(logs.log.length, 1)

systemLogger.withLogLevel(LogLevel.Debug).debug('hello!')
t.is(debugLogs.length, 1)
systemLogger.withLogLevel(LogLevel.Debug).debug('debug 2')
t.is(logs.debug.length, 1)

systemLogger.withLogLevel(LogLevel.Log).debug('hello!')
t.is(debugLogs.length, 1)
systemLogger.withLogLevel(LogLevel.Debug).error('error 1')
t.is(logs.error.length, 1)

console.debug = originalDebug
systemLogger.withLogLevel(LogLevel.Silent).error('error 2')
t.is(logs.error.length, 1)
})

test('Fields', (t) => {
const originalLog = console.log
const logs = []
console.log = (...message) => logs.push(message)
systemLogger.withError(new Error('boom')).withFields({ foo: 'bar' }).log('hello!')
Expand All @@ -34,26 +49,49 @@ test('Fields', (t) => {
t.is(log.fields.foo, 'bar')
t.is(log.fields.error, 'boom')
t.is(log.fields.error_stack.split('\n').length > 2, true)

console.log = originalLog
t.is(log.level, 'log')
})

test('Local Dev', (t) => {
const originalLog = console.log
const logs = []
console.log = (...message) => logs.push(message)
systemLogger.log('hello!')
t.is(logs.length, 1)
test('Accepts a custom raw logger', (t) => {
const logs = {
debug: [],
error: [],
log: [],
}
console.debug = () => {
throw new Error('Unexpected `console.debug` call')
}
console.error = () => {
throw new Error('Unexpected `console.error` call')
}
console.log = () => {
throw new Error('Unexpected `console.log` call')
}
const rawLogger = (tag, payload) => {
t.is(tag, '__nfSystemLog')

process.env.NETLIFY_DEV= "true"
systemLogger.log('hello!')
t.is(logs.length, 1)
const { msg, fields, level } = JSON.parse(payload)
const bucket = logs[level]

t.truthy(bucket)

bucket.push({ fields, msg })
}

const logger = new StructuredLogger(LogLevel.Log, rawLogger, {})

logger.debug('debug 1')
t.is(logs.debug.length, 0)

logger.log('log 1')
t.is(logs.log.length, 1)

logger.withLogLevel(LogLevel.Debug).debug('debug 2')
t.is(logs.debug.length, 1)

process.env.NETLIFY_ENABLE_SYSTEM_LOGGING= "true"
systemLogger.log('hello!')
t.is(logs.length, 2)
logger.withLogLevel(LogLevel.Debug).error('error 1')
t.is(logs.error.length, 1)

delete process.env.NETLIFY_DEV
delete process.env.NETLIFY_ENABLE_SYSTEM_LOGGING
console.log = originalLog
logger.withLogLevel(LogLevel.Silent).error('error 2')
t.is(logs.error.length, 1)
})