@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
@@ -14,7 +14,7 @@ import * as fs from 'fs';
14
14
  createLogger(
15
15
  createStreamBackend({
16
16
  stream: fs.createWriteStream('server.log', { flags: 'a' }),
17
- })
17
+ }),
18
18
  );
19
19
  ```
20
20
 
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const g=require("node:os"),r=require("@holz/core");function l({stream:t}){return e=>{const n=new Date().toISOString(),i=m[e.level],o=$(e.context),s=e.origin.length?`[${e.origin.join(":")}] `:"",c=`${n} ${i} ${s}`,a=L(c.length,e.message),u=`${c}${a}${o?" "+o:""}`;t.write(`${u}${g.EOL}`)}}function L(t,e){return e.replace(/(\r?\n)/g,n=>n+" ".repeat(t))}function $(t){return Object.entries(t).map(([e,n])=>`${e}=${JSON.stringify(n)}`).join(" ")}const m={[r.LogLevel.Debug]:"DEBUG",[r.LogLevel.Info]:"INFO ",[r.LogLevel.Warn]:"WARN ",[r.LogLevel.Error]:"ERROR"};exports.createStreamBackend=l;
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 function createStreamBackend({ stream }: Config): LogProcessor;
17
+ export declare const createStreamBackend: ({ stream }: Config) => LogProcessor;
18
18
 
19
19
  export { }
@@ -1,23 +1,17 @@
1
- import { EOL as u } from "node:os";
2
- import { LogLevel as r } from "@holz/core";
3
- function l({ stream: t }) {
4
- return (e) => {
5
- const n = (/* @__PURE__ */ new Date()).toISOString(), c = g[e.level], o = f(e.context), s = e.origin.length ? `[${e.origin.join(":")}] ` : "", i = `${n} ${c} ${s}`, a = $(i.length, e.message), m = `${i}${a}${o ? " " + o : ""}`;
6
- t.write(`${m}${u}`);
7
- };
8
- }
9
- function $(t, e) {
10
- return e.replace(/(\r?\n)/g, (n) => n + " ".repeat(t));
11
- }
12
- function f(t) {
13
- return Object.entries(t).map(([e, n]) => `${e}=${JSON.stringify(n)}`).join(" ");
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
- l as createStreamBackend
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.7.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.7.0"
40
+ "@holz/core": "^0.8.0-rc.2"
41
41
  },
42
42
  "devDependencies": {
43
- "@holz/core": "^0.7.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.0.0",
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
- function createStream() {
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 DEBUG shout
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
  });
@@ -1,7 +1,13 @@
1
1
  import type { Writable } from 'node:stream';
2
2
  import { EOL } from 'node:os';
3
- import { LogLevel } from '@holz/core';
4
- import type { Log, LogContext, LogProcessor } from '@holz/core';
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 function createStreamBackend({ stream }: Config): LogProcessor {
20
+ export const createStreamBackend = ({ stream }: Config): LogProcessor => {
15
21
  return (log: Log) => {
16
- const currentTime = new Date().toISOString();
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(log.context);
25
+ const context = stringifyContext(plainContext);
19
26
  const namespace = log.origin.length ? `[${log.origin.join(':')}] ` : '';
20
27
 
21
- const header = `${currentTime} ${level} ${namespace}`;
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
- function multilineIndent(offset: number, message: string) {
42
- return message.replace(/(\r?\n)/g, (newline) => newline + ' '.repeat(offset));
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
- function stringifyContext(context: LogContext) {
47
- return Object.entries(context)
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
- [LogLevel.Debug]: 'DEBUG',
54
- [LogLevel.Info]: 'INFO ',
55
- [LogLevel.Warn]: 'WARN ',
56
- [LogLevel.Error]: 'ERROR',
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 {