@holz/stream-backend 0.9.1-rc.9 → 0.9.2-rc.0.sha-87f4046

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@holz/stream-backend",
3
- "version": "0.9.1-rc.9+7993ae6",
3
+ "version": "0.9.2-rc.0.sha-87f4046",
4
4
  "description": "Print logs to stdout or a file.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -10,6 +10,7 @@
10
10
  },
11
11
  "exports": {
12
12
  ".": {
13
+ "source": "./src/index.ts",
13
14
  "types": "./dist/holz-stream-backend.d.ts",
14
15
  "require": "./dist/holz-stream-backend.cjs",
15
16
  "import": "./dist/holz-stream-backend.js"
@@ -22,7 +23,9 @@
22
23
  "license": "MIT",
23
24
  "sideEffects": false,
24
25
  "files": [
25
- "dist"
26
+ "dist",
27
+ "src",
28
+ "!src/**/__tests__"
26
29
  ],
27
30
  "keywords": [
28
31
  "holz-backend",
@@ -37,18 +40,18 @@
37
40
  "test:types": "tsc"
38
41
  },
39
42
  "peerDependencies": {
40
- "@holz/core": "^0.8.0"
43
+ "@holz/core": "^0.8.0 || ^0.9.0"
41
44
  },
42
45
  "devDependencies": {
43
- "@holz/core": "^0.9.1-rc.9+7993ae6",
46
+ "@holz/core": "^0.9.2-rc.0.sha-87f4046",
44
47
  "@microsoft/api-extractor": "^7.58.8",
45
48
  "@types/node": "^24.0.0",
46
- "@vitest/coverage-v8": "^4.0.0",
49
+ "@vitest/coverage-v8": "^5.0.0",
47
50
  "typescript": "^6.0.0",
48
51
  "vite": "^8.0.0",
49
52
  "vite-plugin-dts": "^5.0.0",
50
53
  "vite-tsconfig-paths": "^6.0.0",
51
- "vitest": "^4.0.0"
54
+ "vitest": "^5.0.0"
52
55
  },
53
- "gitHead": "7993ae6757f530d908f8ce2fdd0654767dd3b595"
56
+ "gitHead": "87f40467fe9aa798fa894c04a208751403c45e8c"
54
57
  }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export { createStreamBackend } from './stream-backend';
@@ -0,0 +1,70 @@
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
+ }