Skip to content

Commit

Permalink
feat: add system logger (#457)
Browse files Browse the repository at this point in the history
Adds a system logger for internal usage, closely modeled after the
edge-functions one.
  • Loading branch information
Skn0tt committed Jan 18, 2024
1 parent 3ce8520 commit f09ddc4
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 1 deletion.
5 changes: 5 additions & 0 deletions src/internal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// this file is exported as @netlify/functions/internal,
// 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'
79 changes: 79 additions & 0 deletions src/lib/system_logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
const systemLogTag = '__nfSystemLog'

const serializeError = (error: Error): Record<string, unknown> => {
const cause = error?.cause instanceof Error ? serializeError(error.cause) : error.cause

return {
error: error.message,
error_cause: cause,
error_stack: error.stack,
}
}

// eslint pretends there's a different enum at the same place - it's wrong!
// eslint-disable-next-line no-shadow
export enum LogLevel {
Debug = 1,
Log,
Error,
}

class SystemLogger {
private readonly fields: Record<string, unknown>
private readonly logLevel: LogLevel

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

private doLog(logger: typeof console.log, message: string) {
logger(systemLogTag, JSON.stringify({ msg: message, fields: this.fields }))
}

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

this.doLog(console.log, message)
}

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

this.doLog(console.debug, message)
}

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

this.doLog(console.error, message)
}

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

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

withError(error: unknown) {
const fields = error instanceof Error ? serializeError(error) : { error }

return this.withFields(fields)
}
}

export const systemLogger = new SystemLogger()
37 changes: 37 additions & 0 deletions test/unit/system_logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const test = require('ava')

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

test('Log Level', (t) => {
const originalDebug = console.debug

const debugLogs = []
console.debug = (...message) => debugLogs.push(message)

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

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

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

console.debug = originalDebug
})

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!')
t.is(logs.length, 1)
t.is(logs[0][0], '__nfSystemLog')
const log = JSON.parse(logs[0][1])
t.is(log.msg, 'hello!')
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
})
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */

/* Language and Environment */
"target": "ES2020" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
"target": "ES2022" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
Expand Down

0 comments on commit f09ddc4

Please sign in to comment.