@holz/stream-backend 0.8.0 → 0.8.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Jesse Gibson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@holz/stream-backend",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "description": "Print logs to stdout or a file.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -22,8 +22,7 @@
22
22
  "license": "MIT",
23
23
  "sideEffects": false,
24
24
  "files": [
25
- "dist",
26
- "src"
25
+ "dist"
27
26
  ],
28
27
  "keywords": [
29
28
  "holz-backend",
@@ -40,7 +39,7 @@
40
39
  "@holz/core": "^0.8.0"
41
40
  },
42
41
  "devDependencies": {
43
- "@holz/core": "^0.8.0",
42
+ "@holz/core": "^0.8.2",
44
43
  "@types/node": "^22.0.0",
45
44
  "@vitest/coverage-v8": "^3.0.8",
46
45
  "typescript": "^5.8.2",
@@ -48,5 +47,6 @@
48
47
  "vite-plugin-dts": "^4.5.3",
49
48
  "vite-tsconfig-paths": "^5.1.4",
50
49
  "vitest": "^3.0.8"
51
- }
52
- }
50
+ },
51
+ "gitHead": "f099a09cd01834de9bc1714908183cbb7b828c85"
52
+ }
@@ -1,149 +0,0 @@
1
- import { Writable } from 'node:stream';
2
- import { createLogger } from '@holz/core';
3
- import { createStreamBackend } from '../stream-backend';
4
-
5
- const CURRENT_TIME = new Date('2020-06-15T12:00:00.000Z');
6
-
7
- describe('Stream backend', () => {
8
- const 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 = createStreamBackend({ stream });
36
-
37
- const logger = createLogger(backend);
38
- logger.trace('scream');
39
- logger.debug('shout');
40
- logger.info('normal');
41
- logger.warn('hmmmm');
42
- logger.error('oh no');
43
- logger.fatal('goodbye');
44
-
45
- expect(getOutput()).toMatchInlineSnapshot(`
46
- "2020-06-15T12:00:00.000Z TRACE scream
47
- 2020-06-15T12:00:00.000Z DEBUG shout
48
- 2020-06-15T12:00:00.000Z INFO normal
49
- 2020-06-15T12:00:00.000Z WARN hmmmm
50
- 2020-06-15T12:00:00.000Z ERROR oh no
51
- 2020-06-15T12:00:00.000Z FATAL goodbye
52
- "
53
- `);
54
- });
55
-
56
- it('includes the log namespace', () => {
57
- const { stream, getOutput } = createStream();
58
- const backend = createStreamBackend({ stream });
59
- const logger = createLogger(backend)
60
- .namespace('my-lib')
61
- .namespace('MyClass');
62
-
63
- logger.debug('initialized');
64
-
65
- expect(getOutput()).toContain('[my-lib:MyClass]');
66
- });
67
-
68
- it('does not print the log namespace if it is empty', () => {
69
- const { stream, getOutput } = createStream();
70
- const backend = createStreamBackend({ stream });
71
- const logger = createLogger(backend);
72
-
73
- logger.debug('orphan log');
74
-
75
- expect(getOutput()).not.toContain('[]');
76
- });
77
-
78
- it('includes the log context', () => {
79
- const { stream, getOutput } = createStream();
80
- const backend = createStreamBackend({ stream });
81
- const logger = createLogger(backend);
82
-
83
- logger.info('creating session', { sessionId: 3109, enabled: true });
84
-
85
- expect(getOutput()).toContain('sessionId=3109');
86
- expect(getOutput()).toContain('enabled=true');
87
- });
88
-
89
- it('includes the timestamp for each log', () => {
90
- const { stream, getOutput } = createStream();
91
- const backend = createStreamBackend({ stream });
92
- const logger = createLogger(backend);
93
-
94
- logger.info('traveling through time');
95
-
96
- expect(getOutput()).toContain(CURRENT_TIME.toISOString());
97
- });
98
-
99
- it('wraps strings in log context with quotes', () => {
100
- const { stream, getOutput } = createStream();
101
- const backend = createStreamBackend({ stream });
102
- const logger = createLogger(backend);
103
-
104
- logger.info('creating session', { code: 'ENOBACON' });
105
-
106
- expect(getOutput()).toContain('code="ENOBACON"');
107
- });
108
-
109
- it('joins arrays in log context', () => {
110
- const { stream, getOutput } = createStream();
111
- const backend = createStreamBackend({ stream });
112
- const logger = createLogger(backend);
113
-
114
- logger.info('adding tags', { tags: ['important', 'urgent'] });
115
-
116
- expect(getOutput()).toContain('tags=["important","urgent"]');
117
- });
118
-
119
- it('indents multi-line log statements', () => {
120
- const { stream, getOutput } = createStream();
121
- const backend = createStreamBackend({ stream });
122
- const logger = createLogger(backend);
123
-
124
- logger.info('multi-line log\r\nwith a second line\nand a third line');
125
-
126
- expect(getOutput()).toMatchInlineSnapshot(`
127
- "2020-06-15T12:00:00.000Z INFO multi-line log
128
- with a second line
129
- and a third line
130
- "
131
- `);
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
- });
149
- });
package/src/index.ts DELETED
@@ -1 +0,0 @@
1
- export { createStreamBackend } from './stream-backend';
@@ -1,70 +0,0 @@
1
- import type { Writable } from 'node:stream';
2
- import { EOL } from 'node:os';
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';
11
-
12
- /**
13
- * Prints logs to a writable stream in plaintext. Optimized for log files.
14
- *
15
- * @example
16
- * createStreamBackend({
17
- * stream: fs.createWriteStream('my-app.log', { flags: 'a' }),
18
- * })
19
- */
20
- export const createStreamBackend = ({ stream }: Config): LogProcessor => {
21
- return (log: Log) => {
22
- const { error, ...plainContext } = log.context;
23
- const time = new Date(log.timestamp).toISOString();
24
- const level = LOG_LEVELS[log.level];
25
- const context = stringifyContext(plainContext);
26
- const namespace = log.origin.length ? `[${log.origin.join(':')}] ` : '';
27
-
28
- const header = `${time} ${level} ${namespace}`;
29
- const message = multilineIndent(header.length, log.message);
30
- const output = `${header}${message}${context ? ' ' + context : ''}`;
31
- const errorMessage = error ? inspect(error, { colors: false }) + EOL : '';
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
- stream.write(`${output}${EOL}${errorMessage}`);
40
- };
41
- };
42
-
43
- /**
44
- * Some messages will ruin your output without proper indentation. Stack
45
- * traces are a good example of this.
46
- *
47
- * Supports Unix + DOS line endings.
48
- */
49
- const multilineIndent = (offset: number, message: string) =>
50
- message.replace(/(\r?\n)/g, (newline) => newline + ' '.repeat(offset));
51
-
52
- // { id: 123, type: 'article' } -> 'id=123 type="article"'
53
- const stringifyContext = (context: LogContext) =>
54
- Object.entries(context)
55
- .map(([key, value]) => `${key}=${JSON.stringify(value)}`)
56
- .join(' ');
57
-
58
- const LOG_LEVELS: Record<LogLevel, string> = {
59
- [level.trace]: 'TRACE',
60
- [level.debug]: 'DEBUG',
61
- [level.info]: 'INFO ',
62
- [level.warn]: 'WARN ',
63
- [level.error]: 'ERROR',
64
- [level.fatal]: 'FATAL',
65
- };
66
-
67
- interface Config {
68
- /** Where to print logs. Normally `process.stderr`. */
69
- stream: Writable;
70
- }