@holz/json-backend 0.4.0

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.
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const o=require("node:os");class t{constructor(e){this.stream=e.stream}processLog(e){const s=JSON.stringify({level:e.level,time:new Date().toISOString(),msg:e.message,ctx:Object.keys(e.context).length>0?e.context:void 0});this.stream.write(`${s}${o.EOL}`)}}exports.JsonBackend=t;exports.default=t;
@@ -0,0 +1,19 @@
1
+ import { EOL as s } from "node:os";
2
+ class c {
3
+ constructor(t) {
4
+ this.stream = t.stream;
5
+ }
6
+ processLog(t) {
7
+ const e = JSON.stringify({
8
+ level: t.level,
9
+ time: new Date().toISOString(),
10
+ msg: t.message,
11
+ ctx: Object.keys(t.context).length > 0 ? t.context : void 0
12
+ });
13
+ this.stream.write(`${e}${s}`);
14
+ }
15
+ }
16
+ export {
17
+ c as JsonBackend,
18
+ c as default
19
+ };
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@holz/json-backend",
3
+ "version": "0.4.0",
4
+ "description": "Print logs as newline-delimited JSON.",
5
+ "type": "module",
6
+ "main": "./dist/holz-json-backend.cjs",
7
+ "module": "./dist/holz-json-backend.js",
8
+ "types": "./src/index.ts",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/PsychoLlama/holz",
12
+ "directory": "packages/holz-json-backend"
13
+ },
14
+ "exports": {
15
+ ".": {
16
+ "require": "./dist/holz-json-backend.cjs",
17
+ "import": "./dist/holz-json-backend.js"
18
+ }
19
+ },
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "author": "Jesse Gibson",
24
+ "license": "MIT",
25
+ "sideEffects": false,
26
+ "files": [
27
+ "dist",
28
+ "src"
29
+ ],
30
+ "keywords": [
31
+ "holz-backend",
32
+ "ndjson",
33
+ "structured",
34
+ "logging"
35
+ ],
36
+ "scripts": {
37
+ "prepack": "vite build",
38
+ "test:unit": "vitest --color --passWithNoTests",
39
+ "test:types": "tsc"
40
+ },
41
+ "devDependencies": {
42
+ "@holz/core": "0.2.0",
43
+ "@types/node": "^18.14.0",
44
+ "@vitest/coverage-c8": "0.28.5",
45
+ "typescript": "4.9.5",
46
+ "vite": "^4.0.0",
47
+ "vitest": "^0.28.5"
48
+ }
49
+ }
@@ -0,0 +1,9 @@
1
+ // Vitest Snapshot v1
2
+
3
+ exports[`JSON backend > prints the logs to the writable stream 1`] = `
4
+ "{\\"level\\":\\"debug\\",\\"time\\":\\"2020-06-15T12:00:00.000Z\\",\\"msg\\":\\"shout\\"}
5
+ {\\"level\\":\\"info\\",\\"time\\":\\"2020-06-15T12:00:00.000Z\\",\\"msg\\":\\"normal\\"}
6
+ {\\"level\\":\\"warn\\",\\"time\\":\\"2020-06-15T12:00:00.000Z\\",\\"msg\\":\\"hmmmm\\"}
7
+ {\\"level\\":\\"error\\",\\"time\\":\\"2020-06-15T12:00:00.000Z\\",\\"msg\\":\\"oh no\\"}
8
+ "
9
+ `;
@@ -0,0 +1,67 @@
1
+ import { Writable } from 'node:stream';
2
+ import { createLogger } from '@holz/core';
3
+ import JsonBackend from '../json-backend';
4
+
5
+ const CURRENT_TIME = new Date('2020-06-15T12:00:00.000Z');
6
+
7
+ describe('JSON backend', () => {
8
+ function createStream() {
9
+ let output = '';
10
+ const stream = new Writable({
11
+ write(chunk, _encoding, callback) {
12
+ output += String(chunk);
13
+ callback();
14
+ },
15
+ });
16
+
17
+ return {
18
+ getOutput: () => output,
19
+ stream,
20
+ };
21
+ }
22
+
23
+ beforeEach(() => {
24
+ vi.useFakeTimers({
25
+ now: CURRENT_TIME,
26
+ });
27
+ });
28
+
29
+ afterEach(() => {
30
+ vi.useRealTimers();
31
+ });
32
+
33
+ it('prints the logs to the writable stream', () => {
34
+ const { stream, getOutput } = createStream();
35
+ const backend = new JsonBackend({ stream });
36
+
37
+ const logger = createLogger(backend);
38
+ logger.debug('shout');
39
+ logger.info('normal');
40
+ logger.warn('hmmmm');
41
+ logger.error('oh no');
42
+
43
+ expect(getOutput()).toMatchSnapshot();
44
+ });
45
+
46
+ it('escapes newlines and carriage returns', () => {
47
+ const { stream, getOutput } = createStream();
48
+ const backend = new JsonBackend({ stream });
49
+ const logger = createLogger(backend);
50
+
51
+ logger.debug('sneaky log\nwith newlines\rand carriage returns\r\n');
52
+
53
+ expect(getOutput()).toContain(
54
+ 'sneaky log\\nwith newlines\\rand carriage returns\\r\\n'
55
+ );
56
+ });
57
+
58
+ it('includes log context', () => {
59
+ const { stream, getOutput } = createStream();
60
+ const backend = new JsonBackend({ stream });
61
+ const logger = createLogger(backend);
62
+
63
+ logger.debug('hello', { reqId: 'abc' });
64
+
65
+ expect(getOutput()).toContain('"reqId":"abc"');
66
+ });
67
+ });
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export { default, default as JsonBackend } from './json-backend';
@@ -0,0 +1,46 @@
1
+ import type { Writable } from 'node:stream';
2
+ import { EOL } from 'node:os';
3
+ import type { Log, LogProcessor } from '@holz/core';
4
+
5
+ /**
6
+ * Prints structured logs to a writable stream in NDJSON form. Optimized for
7
+ * log files.
8
+ *
9
+ * @example
10
+ * new JsonBackend({
11
+ * stream: fs.createWriteStream('my-app.log', { flags: 'a' }),
12
+ * })
13
+ *
14
+ * @see http://ndjson.org
15
+ */
16
+ export default class JsonBackend implements LogProcessor {
17
+ private stream: Writable;
18
+
19
+ constructor(options: Config) {
20
+ this.stream = options.stream;
21
+ }
22
+
23
+ processLog(log: Log) {
24
+ // Follow the order of typical log statements. Be kind to the human
25
+ // reader.
26
+ const output = JSON.stringify({
27
+ level: log.level,
28
+ time: new Date().toISOString(),
29
+ msg: log.message,
30
+ ctx: Object.keys(log.context).length > 0 ? log.context : undefined,
31
+ });
32
+
33
+ // NOTE: If the stream applies backpressure, we will lose logs. I believe
34
+ // this is the right tradeoff. We can't prevent the app from generating
35
+ // more logs, and if we buffered it would risk running out of memory and
36
+ // crashing the process.
37
+ //
38
+ // It is unlikely that a file or tty will apply backpressure in practice.
39
+ this.stream.write(`${output}${EOL}`);
40
+ }
41
+ }
42
+
43
+ interface Config {
44
+ /** Where to print logs. */
45
+ stream: Writable;
46
+ }