@holz/stream-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
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 c=require("node:os"),g=require("node:util"),n=require("@holz/core"),p=({stream:r})=>e=>{const{error:t,...i}=e.context,a=new Date(e.timestamp).toISOString(),l=S[e.level],o=O(i),u=e.origin.length?`[${e.origin.join(":")}] `:"",s=`${a} ${l} ${u}`,$=v(s.length,e.message),m=`${s}${$}${o?" "+o:""}`,d=t?g.inspect(t,{colors:!1})+c.EOL:"";r.write(`${m}${c.EOL}${d}`)},v=(r,e)=>e.replace(/(\r?\n)/g,t=>t+" ".repeat(r)),O=r=>Object.entries(r).map(([e,t])=>`${e}=${JSON.stringify(t)}`).join(" "),S={[n.level.trace]:"TRACE",[n.level.debug]:"DEBUG",[n.level.info]:"INFO ",[n.level.warn]:"WARN ",[n.level.error]:"ERROR",[n.level.fatal]:"FATAL"};exports.createStreamBackend=p;
|
|
@@ -14,6 +14,6 @@ declare interface Config {
|
|
|
14
14
|
* stream: fs.createWriteStream('my-app.log', { flags: 'a' }),
|
|
15
15
|
* })
|
|
16
16
|
*/
|
|
17
|
-
export declare
|
|
17
|
+
export declare const createStreamBackend: ({ stream }: Config) => LogProcessor;
|
|
18
18
|
|
|
19
19
|
export { }
|
|
@@ -1,23 +1,17 @@
|
|
|
1
|
-
import { EOL as
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
const g = {
|
|
16
|
-
[r.Debug]: "DEBUG",
|
|
17
|
-
[r.Info]: "INFO ",
|
|
18
|
-
[r.Warn]: "WARN ",
|
|
19
|
-
[r.Error]: "ERROR"
|
|
1
|
+
import { EOL as c } from "node:os";
|
|
2
|
+
import { inspect as g } from "node:util";
|
|
3
|
+
import { level as n } from "@holz/core";
|
|
4
|
+
const S = ({ stream: r }) => (e) => {
|
|
5
|
+
const { error: t, ...i } = e.context, a = new Date(e.timestamp).toISOString(), m = u[e.level], o = E(i), p = e.origin.length ? `[${e.origin.join(":")}] ` : "", s = `${a} ${m} ${p}`, $ = O(s.length, e.message), l = `${s}${$}${o ? " " + o : ""}`, f = t ? g(t, { colors: !1 }) + c : "";
|
|
6
|
+
r.write(`${l}${c}${f}`);
|
|
7
|
+
}, O = (r, e) => e.replace(/(\r?\n)/g, (t) => t + " ".repeat(r)), E = (r) => Object.entries(r).map(([e, t]) => `${e}=${JSON.stringify(t)}`).join(" "), u = {
|
|
8
|
+
[n.trace]: "TRACE",
|
|
9
|
+
[n.debug]: "DEBUG",
|
|
10
|
+
[n.info]: "INFO ",
|
|
11
|
+
[n.warn]: "WARN ",
|
|
12
|
+
[n.error]: "ERROR",
|
|
13
|
+
[n.fatal]: "FATAL"
|
|
20
14
|
};
|
|
21
15
|
export {
|
|
22
|
-
|
|
16
|
+
S as createStreamBackend
|
|
23
17
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@holz/stream-backend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0-rc.2",
|
|
4
4
|
"description": "Print logs to stdout or a file.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -37,16 +37,17 @@
|
|
|
37
37
|
"test:types": "tsc"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
|
-
"@holz/core": "^0.
|
|
40
|
+
"@holz/core": "^0.8.0-rc.2"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@holz/core": "^0.
|
|
43
|
+
"@holz/core": "^0.8.0-rc.2",
|
|
44
44
|
"@types/node": "^22.0.0",
|
|
45
45
|
"@vitest/coverage-v8": "^3.0.8",
|
|
46
|
-
"typescript": "^5.
|
|
46
|
+
"typescript": "^5.8.2",
|
|
47
47
|
"vite": "^6.0.0",
|
|
48
48
|
"vite-plugin-dts": "^4.5.3",
|
|
49
49
|
"vite-tsconfig-paths": "^5.1.4",
|
|
50
50
|
"vitest": "^3.0.8"
|
|
51
|
-
}
|
|
51
|
+
},
|
|
52
|
+
"stableVersion": "0.7.0"
|
|
52
53
|
}
|
|
@@ -5,7 +5,7 @@ import { createStreamBackend } from '../stream-backend';
|
|
|
5
5
|
const CURRENT_TIME = new Date('2020-06-15T12:00:00.000Z');
|
|
6
6
|
|
|
7
7
|
describe('Stream 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('Stream backend', () => {
|
|
|
18
18
|
getOutput: () => output,
|
|
19
19
|
stream,
|
|
20
20
|
};
|
|
21
|
-
}
|
|
21
|
+
};
|
|
22
22
|
|
|
23
23
|
beforeEach(() => {
|
|
24
24
|
vi.useFakeTimers({
|
|
@@ -35,16 +35,20 @@ describe('Stream backend', () => {
|
|
|
35
35
|
const backend = createStreamBackend({ stream });
|
|
36
36
|
|
|
37
37
|
const logger = createLogger(backend);
|
|
38
|
+
logger.trace('scream');
|
|
38
39
|
logger.debug('shout');
|
|
39
40
|
logger.info('normal');
|
|
40
41
|
logger.warn('hmmmm');
|
|
41
42
|
logger.error('oh no');
|
|
43
|
+
logger.fatal('goodbye');
|
|
42
44
|
|
|
43
45
|
expect(getOutput()).toMatchInlineSnapshot(`
|
|
44
|
-
"2020-06-15T12:00:00.000Z
|
|
46
|
+
"2020-06-15T12:00:00.000Z TRACE scream
|
|
47
|
+
2020-06-15T12:00:00.000Z DEBUG shout
|
|
45
48
|
2020-06-15T12:00:00.000Z INFO normal
|
|
46
49
|
2020-06-15T12:00:00.000Z WARN hmmmm
|
|
47
50
|
2020-06-15T12:00:00.000Z ERROR oh no
|
|
51
|
+
2020-06-15T12:00:00.000Z FATAL goodbye
|
|
48
52
|
"
|
|
49
53
|
`);
|
|
50
54
|
});
|
|
@@ -126,4 +130,20 @@ describe('Stream backend', () => {
|
|
|
126
130
|
"
|
|
127
131
|
`);
|
|
128
132
|
});
|
|
133
|
+
|
|
134
|
+
it('serializes errors included in log context', () => {
|
|
135
|
+
const { stream, getOutput } = createStream();
|
|
136
|
+
const backend = createStreamBackend({ stream });
|
|
137
|
+
const logger = createLogger(backend);
|
|
138
|
+
|
|
139
|
+
const error = new Error('Testing error serialization');
|
|
140
|
+
logger.error('something went wrong', { error });
|
|
141
|
+
|
|
142
|
+
// Make sure it shows up, but don't test the stack trace. Too volatile.
|
|
143
|
+
expect(getOutput().split('\n').slice(0, 2).join('\n'))
|
|
144
|
+
.toMatchInlineSnapshot(`
|
|
145
|
+
"2020-06-15T12:00:00.000Z ERROR something went wrong
|
|
146
|
+
Error: Testing error serialization"
|
|
147
|
+
`);
|
|
148
|
+
});
|
|
129
149
|
});
|
package/src/stream-backend.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import type { Writable } from 'node:stream';
|
|
2
2
|
import { EOL } from 'node:os';
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
3
|
+
import { inspect } from 'node:util';
|
|
4
|
+
import {
|
|
5
|
+
level,
|
|
6
|
+
type LogLevel,
|
|
7
|
+
type Log,
|
|
8
|
+
type LogContext,
|
|
9
|
+
type LogProcessor,
|
|
10
|
+
} from '@holz/core';
|
|
5
11
|
|
|
6
12
|
/**
|
|
7
13
|
* Prints logs to a writable stream in plaintext. Optimized for log files.
|
|
@@ -11,16 +17,18 @@ import type { Log, LogContext, LogProcessor } from '@holz/core';
|
|
|
11
17
|
* stream: fs.createWriteStream('my-app.log', { flags: 'a' }),
|
|
12
18
|
* })
|
|
13
19
|
*/
|
|
14
|
-
export
|
|
20
|
+
export const createStreamBackend = ({ stream }: Config): LogProcessor => {
|
|
15
21
|
return (log: Log) => {
|
|
16
|
-
const
|
|
22
|
+
const { error, ...plainContext } = log.context;
|
|
23
|
+
const time = new Date(log.timestamp).toISOString();
|
|
17
24
|
const level = LOG_LEVELS[log.level];
|
|
18
|
-
const context = stringifyContext(
|
|
25
|
+
const context = stringifyContext(plainContext);
|
|
19
26
|
const namespace = log.origin.length ? `[${log.origin.join(':')}] ` : '';
|
|
20
27
|
|
|
21
|
-
const header = `${
|
|
28
|
+
const header = `${time} ${level} ${namespace}`;
|
|
22
29
|
const message = multilineIndent(header.length, log.message);
|
|
23
30
|
const output = `${header}${message}${context ? ' ' + context : ''}`;
|
|
31
|
+
const errorMessage = error ? inspect(error, { colors: false }) + EOL : '';
|
|
24
32
|
|
|
25
33
|
// NOTE: If the stream applies backpressure, we will lose logs. I believe
|
|
26
34
|
// this is the right tradeoff. We can't prevent the app from generating
|
|
@@ -28,9 +36,9 @@ export function createStreamBackend({ stream }: Config): LogProcessor {
|
|
|
28
36
|
// crashing the process.
|
|
29
37
|
//
|
|
30
38
|
// It is unlikely that a file or tty will apply backpressure in practice.
|
|
31
|
-
stream.write(`${output}${EOL}`);
|
|
39
|
+
stream.write(`${output}${EOL}${errorMessage}`);
|
|
32
40
|
};
|
|
33
|
-
}
|
|
41
|
+
};
|
|
34
42
|
|
|
35
43
|
/**
|
|
36
44
|
* Some messages will ruin your output without proper indentation. Stack
|
|
@@ -38,22 +46,22 @@ export function createStreamBackend({ stream }: Config): LogProcessor {
|
|
|
38
46
|
*
|
|
39
47
|
* Supports Unix + DOS line endings.
|
|
40
48
|
*/
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
49
|
+
const multilineIndent = (offset: number, message: string) =>
|
|
50
|
+
message.replace(/(\r?\n)/g, (newline) => newline + ' '.repeat(offset));
|
|
44
51
|
|
|
45
52
|
// { id: 123, type: 'article' } -> 'id=123 type="article"'
|
|
46
|
-
|
|
47
|
-
|
|
53
|
+
const stringifyContext = (context: LogContext) =>
|
|
54
|
+
Object.entries(context)
|
|
48
55
|
.map(([key, value]) => `${key}=${JSON.stringify(value)}`)
|
|
49
56
|
.join(' ');
|
|
50
|
-
}
|
|
51
57
|
|
|
52
58
|
const LOG_LEVELS: Record<LogLevel, string> = {
|
|
53
|
-
[
|
|
54
|
-
[
|
|
55
|
-
[
|
|
56
|
-
[
|
|
59
|
+
[level.trace]: 'TRACE',
|
|
60
|
+
[level.debug]: 'DEBUG',
|
|
61
|
+
[level.info]: 'INFO ',
|
|
62
|
+
[level.warn]: 'WARN ',
|
|
63
|
+
[level.error]: 'ERROR',
|
|
64
|
+
[level.fatal]: 'FATAL',
|
|
57
65
|
};
|
|
58
66
|
|
|
59
67
|
interface Config {
|