@holz/json-backend 0.7.0 → 0.8.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/holz-json-backend.cjs +1 -1
- package/dist/holz-json-backend.d.ts +1 -1
- package/dist/holz-json-backend.js +32 -10
- package/package.json +5 -4
- package/src/__tests__/json-backend.test.ts +52 -2
- package/src/json-backend.ts +37 -9
package/README.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const n=require("node:os"),s={fatal:60,error:50,warn:40,info:30,debug:20,trace:10},c=({stream:t})=>e=>{const r=JSON.stringify({level:a[e.level],time:new Date(e.timestamp).toISOString(),msg:e.message,ctx:Object.keys(e.context).length>0?e.context:void 0},o);t.write(`${r}${n.EOL}`)},o=(t,e)=>e instanceof Error?{...e,name:e.name,message:e.message,cause:e.cause}:e,a=Object.fromEntries(Object.entries(s).map(([t,e])=>[e,t]));exports.createJsonBackend=c;
|
|
@@ -1,15 +1,37 @@
|
|
|
1
|
-
import { EOL as
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { EOL as n } from "node:os";
|
|
2
|
+
const s = {
|
|
3
|
+
/** A critical failure happened and the program must exit. */
|
|
4
|
+
fatal: 60,
|
|
5
|
+
/** Something failed, but we can keep going. */
|
|
6
|
+
error: 50,
|
|
7
|
+
/** Cause for concern, but we can keep going. */
|
|
8
|
+
warn: 40,
|
|
9
|
+
/** High-level progress updates. */
|
|
10
|
+
info: 30,
|
|
11
|
+
/** Verbose update about events or control flow (usually hidden). */
|
|
12
|
+
debug: 20,
|
|
13
|
+
/** Extremely detailed progress updates (usually hidden). */
|
|
14
|
+
trace: 10
|
|
15
|
+
}, i = ({ stream: t }) => (e) => {
|
|
16
|
+
const r = JSON.stringify(
|
|
17
|
+
{
|
|
18
|
+
level: o[e.level],
|
|
19
|
+
time: new Date(e.timestamp).toISOString(),
|
|
7
20
|
msg: e.message,
|
|
8
21
|
ctx: Object.keys(e.context).length > 0 ? e.context : void 0
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
22
|
+
},
|
|
23
|
+
c
|
|
24
|
+
);
|
|
25
|
+
t.write(`${r}${n}`);
|
|
26
|
+
}, c = (t, e) => e instanceof Error ? {
|
|
27
|
+
...e,
|
|
28
|
+
// Some custom errors have important custom properties.
|
|
29
|
+
name: e.name,
|
|
30
|
+
message: e.message,
|
|
31
|
+
cause: e.cause
|
|
32
|
+
} : e, o = Object.fromEntries(
|
|
33
|
+
Object.entries(s).map(([t, e]) => [e, t])
|
|
34
|
+
);
|
|
13
35
|
export {
|
|
14
36
|
i as createJsonBackend
|
|
15
37
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@holz/json-backend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0-rc.2",
|
|
4
4
|
"description": "Print logs as newline-delimited JSON.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -37,13 +37,14 @@
|
|
|
37
37
|
"test:types": "tsc"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@holz/core": "^0.
|
|
40
|
+
"@holz/core": "^0.8.0-rc.2",
|
|
41
41
|
"@types/node": "^22.0.0",
|
|
42
42
|
"@vitest/coverage-v8": "^3.0.8",
|
|
43
|
-
"typescript": "^5.
|
|
43
|
+
"typescript": "^5.8.2",
|
|
44
44
|
"vite": "^6.0.0",
|
|
45
45
|
"vite-plugin-dts": "^4.5.3",
|
|
46
46
|
"vite-tsconfig-paths": "^5.1.4",
|
|
47
47
|
"vitest": "^3.0.8"
|
|
48
|
-
}
|
|
48
|
+
},
|
|
49
|
+
"stableVersion": "0.7.0"
|
|
49
50
|
}
|
|
@@ -5,7 +5,7 @@ import { createJsonBackend } from '../json-backend';
|
|
|
5
5
|
const CURRENT_TIME = new Date('2020-06-15T12:00:00.000Z');
|
|
6
6
|
|
|
7
7
|
describe('JSON backend', () => {
|
|
8
|
-
|
|
8
|
+
const createStream = () => {
|
|
9
9
|
let output = '';
|
|
10
10
|
const stream = new Writable({
|
|
11
11
|
write(chunk, _encoding, callback) {
|
|
@@ -18,7 +18,7 @@ describe('JSON backend', () => {
|
|
|
18
18
|
getOutput: () => output,
|
|
19
19
|
stream,
|
|
20
20
|
};
|
|
21
|
-
}
|
|
21
|
+
};
|
|
22
22
|
|
|
23
23
|
beforeEach(() => {
|
|
24
24
|
vi.useFakeTimers({
|
|
@@ -64,4 +64,54 @@ describe('JSON backend', () => {
|
|
|
64
64
|
|
|
65
65
|
expect(getOutput()).toContain('"reqId":"abc"');
|
|
66
66
|
});
|
|
67
|
+
|
|
68
|
+
it('pulls structure from errors', () => {
|
|
69
|
+
const { stream, getOutput } = createStream();
|
|
70
|
+
const backend = createJsonBackend({ stream });
|
|
71
|
+
const logger = createLogger(backend);
|
|
72
|
+
|
|
73
|
+
logger.error('content', {
|
|
74
|
+
error: new RangeError('Testing NDJSON errors'),
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const output = getOutput();
|
|
78
|
+
expect(output).toContain(
|
|
79
|
+
'"error":{"name":"RangeError","message":"Testing NDJSON errors"}',
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('detects and includes error causes', () => {
|
|
84
|
+
const { stream, getOutput } = createStream();
|
|
85
|
+
const backend = createJsonBackend({ stream });
|
|
86
|
+
const logger = createLogger(backend);
|
|
87
|
+
|
|
88
|
+
logger.error('content', {
|
|
89
|
+
error: new Error('Testing NDJSON errors', {
|
|
90
|
+
cause: new Error('Cause'),
|
|
91
|
+
}),
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const output = getOutput();
|
|
95
|
+
expect(output).toContain(
|
|
96
|
+
'"error":{"name":"Error","message":"Testing NDJSON errors","cause":{"name":"Error","message":"Cause"}}',
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('includes any enumerable properties on the object', () => {
|
|
101
|
+
class CustomError extends Error {
|
|
102
|
+
name = 'CustomError';
|
|
103
|
+
status = 418;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const { stream, getOutput } = createStream();
|
|
107
|
+
const backend = createJsonBackend({ stream });
|
|
108
|
+
const logger = createLogger(backend);
|
|
109
|
+
|
|
110
|
+
logger.error('content', {
|
|
111
|
+
error: new CustomError('Testing NDJSON errors'),
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const output = getOutput();
|
|
115
|
+
expect(output).toContain('"status":418');
|
|
116
|
+
});
|
|
67
117
|
});
|
package/src/json-backend.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Writable } from 'node:stream';
|
|
2
2
|
import { EOL } from 'node:os';
|
|
3
|
-
import type
|
|
3
|
+
import { level, type LogLevel, type Log, type LogProcessor } from '@holz/core';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Prints structured logs to a writable stream in NDJSON form. Optimized for
|
|
@@ -13,16 +13,19 @@ import type { Log, LogProcessor } from '@holz/core';
|
|
|
13
13
|
*
|
|
14
14
|
* @see http://ndjson.org
|
|
15
15
|
*/
|
|
16
|
-
export
|
|
16
|
+
export const createJsonBackend = ({ stream }: Config): LogProcessor => {
|
|
17
17
|
return (log: Log) => {
|
|
18
18
|
// Follow the order of typical log statements. Be kind to the human
|
|
19
19
|
// reader.
|
|
20
|
-
const output = JSON.stringify(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
const output = JSON.stringify(
|
|
21
|
+
{
|
|
22
|
+
level: labelForLevel[log.level],
|
|
23
|
+
time: new Date(log.timestamp).toISOString(),
|
|
24
|
+
msg: log.message,
|
|
25
|
+
ctx: Object.keys(log.context).length > 0 ? log.context : undefined,
|
|
26
|
+
},
|
|
27
|
+
errorSerializer,
|
|
28
|
+
);
|
|
26
29
|
|
|
27
30
|
// NOTE: If the stream applies backpressure, we will lose logs. I believe
|
|
28
31
|
// this is the right tradeoff. We can't prevent the app from generating
|
|
@@ -32,7 +35,32 @@ export function createJsonBackend({ stream }: Config): LogProcessor {
|
|
|
32
35
|
// It is unlikely that a file or tty will apply backpressure in practice.
|
|
33
36
|
stream.write(`${output}${EOL}`);
|
|
34
37
|
};
|
|
35
|
-
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Errors are not JSON serializable, but errors are naturally a crucial aspect
|
|
42
|
+
* of any logging system. This unwraps errors into a JSON representation.
|
|
43
|
+
*/
|
|
44
|
+
const errorSerializer = (_key: string, value: unknown) => {
|
|
45
|
+
if (value instanceof Error) {
|
|
46
|
+
return {
|
|
47
|
+
...value, // Some custom errors have important custom properties.
|
|
48
|
+
name: value.name,
|
|
49
|
+
message: value.message,
|
|
50
|
+
cause: value.cause,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return value;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Slightly heavier than logging the number, but easier to process
|
|
59
|
+
* programmatically which is somewhat implied by a JSON backend.
|
|
60
|
+
*/
|
|
61
|
+
const labelForLevel = Object.fromEntries(
|
|
62
|
+
Object.entries(level).map(([key, value]) => [value, key]),
|
|
63
|
+
) as Record<LogLevel, string>;
|
|
36
64
|
|
|
37
65
|
interface Config {
|
|
38
66
|
/** Where to print logs. */
|