Skip to content

Commit f09ddc4

Browse files
authoredJan 18, 2024
feat: add system logger (#457)
Adds a system logger for internal usage, closely modeled after the edge-functions one.
1 parent 3ce8520 commit f09ddc4

File tree

4 files changed

+122
-1
lines changed

4 files changed

+122
-1
lines changed
 

‎src/internal.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// this file is exported as @netlify/functions/internal,
2+
// and only meant for consumption by Netlify Teams.
3+
// While we try to adhere to semver, this file is not considered part of the public API.
4+
5+
export { systemLogger, LogLevel } from './lib/system_logger.js'

‎src/lib/system_logger.ts

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
const systemLogTag = '__nfSystemLog'
2+
3+
const serializeError = (error: Error): Record<string, unknown> => {
4+
const cause = error?.cause instanceof Error ? serializeError(error.cause) : error.cause
5+
6+
return {
7+
error: error.message,
8+
error_cause: cause,
9+
error_stack: error.stack,
10+
}
11+
}
12+
13+
// eslint pretends there's a different enum at the same place - it's wrong!
14+
// eslint-disable-next-line no-shadow
15+
export enum LogLevel {
16+
Debug = 1,
17+
Log,
18+
Error,
19+
}
20+
21+
class SystemLogger {
22+
private readonly fields: Record<string, unknown>
23+
private readonly logLevel: LogLevel
24+
25+
constructor(fields: Record<string, unknown> = {}, logLevel = LogLevel.Log) {
26+
this.fields = fields
27+
this.logLevel = logLevel
28+
}
29+
30+
private doLog(logger: typeof console.log, message: string) {
31+
logger(systemLogTag, JSON.stringify({ msg: message, fields: this.fields }))
32+
}
33+
34+
log(message: string) {
35+
if (this.logLevel > LogLevel.Log) {
36+
return
37+
}
38+
39+
this.doLog(console.log, message)
40+
}
41+
42+
debug(message: string) {
43+
if (this.logLevel > LogLevel.Debug) {
44+
return
45+
}
46+
47+
this.doLog(console.debug, message)
48+
}
49+
50+
error(message: string) {
51+
if (this.logLevel > LogLevel.Error) {
52+
return
53+
}
54+
55+
this.doLog(console.error, message)
56+
}
57+
58+
withLogLevel(level: LogLevel) {
59+
return new SystemLogger(this.fields, level)
60+
}
61+
62+
withFields(fields: Record<string, unknown>) {
63+
return new SystemLogger(
64+
{
65+
...this.fields,
66+
...fields,
67+
},
68+
this.logLevel,
69+
)
70+
}
71+
72+
withError(error: unknown) {
73+
const fields = error instanceof Error ? serializeError(error) : { error }
74+
75+
return this.withFields(fields)
76+
}
77+
}
78+
79+
export const systemLogger = new SystemLogger()

‎test/unit/system_logger.js

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
const test = require('ava')
2+
3+
const { systemLogger, LogLevel } = require('../../dist/internal')
4+
5+
test('Log Level', (t) => {
6+
const originalDebug = console.debug
7+
8+
const debugLogs = []
9+
console.debug = (...message) => debugLogs.push(message)
10+
11+
systemLogger.debug('hello!')
12+
t.is(debugLogs.length, 0)
13+
14+
systemLogger.withLogLevel(LogLevel.Debug).debug('hello!')
15+
t.is(debugLogs.length, 1)
16+
17+
systemLogger.withLogLevel(LogLevel.Log).debug('hello!')
18+
t.is(debugLogs.length, 1)
19+
20+
console.debug = originalDebug
21+
})
22+
23+
test('Fields', (t) => {
24+
const originalLog = console.log
25+
const logs = []
26+
console.log = (...message) => logs.push(message)
27+
systemLogger.withError(new Error('boom')).withFields({ foo: 'bar' }).log('hello!')
28+
t.is(logs.length, 1)
29+
t.is(logs[0][0], '__nfSystemLog')
30+
const log = JSON.parse(logs[0][1])
31+
t.is(log.msg, 'hello!')
32+
t.is(log.fields.foo, 'bar')
33+
t.is(log.fields.error, 'boom')
34+
t.is(log.fields.error_stack.split('\n').length > 2, true)
35+
36+
console.log = originalLog
37+
})

‎tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
1212

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

0 commit comments

Comments
 (0)