@dxos/log 0.8.4-main.f9ba587 → 0.8.4-main.fcfe5033a5

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.
Files changed (75) hide show
  1. package/dist/lib/browser/chunk-IEP6GGEX.mjs +23 -0
  2. package/dist/lib/browser/chunk-IEP6GGEX.mjs.map +7 -0
  3. package/dist/lib/browser/chunk-M2YHSBML.mjs +133 -0
  4. package/dist/lib/browser/chunk-M2YHSBML.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +227 -184
  6. package/dist/lib/browser/index.mjs.map +4 -4
  7. package/dist/lib/browser/meta.json +1 -1
  8. package/dist/lib/browser/platform/browser/index.mjs +26 -0
  9. package/dist/lib/browser/platform/browser/index.mjs.map +7 -0
  10. package/dist/lib/browser/platform/node/index.mjs +21 -0
  11. package/dist/lib/browser/platform/node/index.mjs.map +7 -0
  12. package/dist/lib/browser/processors/console-processor.mjs +107 -0
  13. package/dist/lib/browser/processors/console-processor.mjs.map +7 -0
  14. package/dist/lib/browser/processors/console-stub.mjs +9 -0
  15. package/dist/lib/browser/processors/console-stub.mjs.map +7 -0
  16. package/dist/lib/node-esm/chunk-2SZHAWBN.mjs +24 -0
  17. package/dist/lib/node-esm/chunk-2SZHAWBN.mjs.map +7 -0
  18. package/dist/lib/node-esm/chunk-62VKC2WQ.mjs +135 -0
  19. package/dist/lib/node-esm/chunk-62VKC2WQ.mjs.map +7 -0
  20. package/dist/lib/node-esm/index.mjs +224 -266
  21. package/dist/lib/node-esm/index.mjs.map +4 -4
  22. package/dist/lib/node-esm/meta.json +1 -1
  23. package/dist/lib/node-esm/platform/browser/index.mjs +27 -0
  24. package/dist/lib/node-esm/platform/browser/index.mjs.map +7 -0
  25. package/dist/lib/node-esm/platform/node/index.mjs +22 -0
  26. package/dist/lib/node-esm/platform/node/index.mjs.map +7 -0
  27. package/dist/lib/node-esm/processors/console-processor.mjs +108 -0
  28. package/dist/lib/node-esm/processors/console-processor.mjs.map +7 -0
  29. package/dist/lib/node-esm/processors/console-stub.mjs +10 -0
  30. package/dist/lib/node-esm/processors/console-stub.mjs.map +7 -0
  31. package/dist/types/src/config.d.ts +2 -3
  32. package/dist/types/src/config.d.ts.map +1 -1
  33. package/dist/types/src/context.d.ts +1 -1
  34. package/dist/types/src/context.d.ts.map +1 -1
  35. package/dist/types/src/dbg.d.ts +23 -0
  36. package/dist/types/src/dbg.d.ts.map +1 -0
  37. package/dist/types/src/decorators.d.ts +1 -1
  38. package/dist/types/src/decorators.d.ts.map +1 -1
  39. package/dist/types/src/index.d.ts +4 -3
  40. package/dist/types/src/index.d.ts.map +1 -1
  41. package/dist/types/src/log-buffer.d.ts +40 -0
  42. package/dist/types/src/log-buffer.d.ts.map +1 -0
  43. package/dist/types/src/log-buffer.test.d.ts +2 -0
  44. package/dist/types/src/log-buffer.test.d.ts.map +1 -0
  45. package/dist/types/src/log.d.ts +14 -18
  46. package/dist/types/src/log.d.ts.map +1 -1
  47. package/dist/types/src/options.d.ts +1 -6
  48. package/dist/types/src/options.d.ts.map +1 -1
  49. package/dist/types/src/platform/index.d.ts +1 -1
  50. package/dist/types/src/platform/index.d.ts.map +1 -1
  51. package/dist/types/src/processors/browser-processor.d.ts.map +1 -1
  52. package/dist/types/src/processors/console-processor.d.ts +1 -1
  53. package/dist/types/src/processors/console-processor.d.ts.map +1 -1
  54. package/dist/types/src/processors/file-processor.d.ts.map +1 -1
  55. package/dist/types/src/processors/index.d.ts +3 -3
  56. package/dist/types/src/processors/index.d.ts.map +1 -1
  57. package/dist/types/tsconfig.tsbuildinfo +1 -1
  58. package/package.json +32 -14
  59. package/src/config.ts +3 -2
  60. package/src/context.ts +38 -8
  61. package/src/dbg.ts +34 -0
  62. package/src/decorators.ts +3 -3
  63. package/src/experimental/classes.test.ts +1 -1
  64. package/src/index.ts +4 -4
  65. package/src/log-buffer.test.ts +155 -0
  66. package/src/log-buffer.ts +117 -0
  67. package/src/log.test.ts +58 -23
  68. package/src/log.ts +104 -59
  69. package/src/options.ts +27 -11
  70. package/src/platform/index.ts +1 -1
  71. package/src/processors/browser-processor.ts +6 -2
  72. package/src/processors/console-processor.ts +8 -6
  73. package/src/processors/file-processor.ts +3 -1
  74. package/src/processors/index.ts +3 -3
  75. package/src/scope.ts +1 -1
package/package.json CHANGED
@@ -1,27 +1,47 @@
1
1
  {
2
2
  "name": "@dxos/log",
3
- "version": "0.8.4-main.f9ba587",
3
+ "version": "0.8.4-main.fcfe5033a5",
4
4
  "description": "Logger",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/dxos/dxos"
10
+ },
7
11
  "license": "MIT",
8
12
  "author": "DXOS.org",
9
13
  "sideEffects": true,
10
14
  "type": "module",
15
+ "imports": {
16
+ "#platform": {
17
+ "source": {
18
+ "browser": "./src/platform/browser/index.ts",
19
+ "default": "./src/platform/node/index.ts"
20
+ },
21
+ "types": "./dist/types/src/platform/node/index.d.ts",
22
+ "browser": "./dist/lib/browser/platform/browser/index.mjs",
23
+ "default": "./dist/lib/node-esm/platform/node/index.mjs"
24
+ },
25
+ "#console-processor": {
26
+ "source": {
27
+ "browser": "./src/processors/console-stub.ts",
28
+ "default": "./src/processors/console-processor.ts"
29
+ },
30
+ "types": "./dist/types/src/processors/console-processor.d.ts",
31
+ "browser": "./dist/lib/browser/processors/console-stub.mjs",
32
+ "default": "./dist/lib/node-esm/processors/console-processor.mjs"
33
+ }
34
+ },
11
35
  "exports": {
12
36
  ".": {
37
+ "types": "./dist/types/src/index.d.ts",
13
38
  "browser": "./dist/lib/browser/index.mjs",
14
39
  "node": {
15
40
  "require": "./dist/lib/node/index.cjs",
16
41
  "default": "./dist/lib/node-esm/index.mjs"
17
- },
18
- "types": "./dist/types/src/index.d.ts"
42
+ }
19
43
  }
20
44
  },
21
- "browser": {
22
- "./src/platform/node/index.ts": "./src/platform/browser/index.ts",
23
- "./src/processors/console-processor.ts": "./src/processors/console-stub.ts"
24
- },
25
45
  "types": "dist/types/src/index.d.ts",
26
46
  "typesVersions": {
27
47
  "*": {}
@@ -31,17 +51,15 @@
31
51
  "src"
32
52
  ],
33
53
  "dependencies": {
34
- "chalk": "^4.1.0",
35
- "js-yaml": "^4.1.0",
54
+ "chalk": "^4.1.2",
55
+ "js-yaml": "4.1.1",
36
56
  "lodash.defaultsdeep": "^4.6.1",
37
- "lodash.omit": "^4.5.0",
38
- "@dxos/node-std": "0.8.4-main.f9ba587",
39
- "@dxos/util": "0.8.4-main.f9ba587"
57
+ "@dxos/node-std": "0.8.4-main.fcfe5033a5",
58
+ "@dxos/util": "0.8.4-main.fcfe5033a5"
40
59
  },
41
60
  "devDependencies": {
42
61
  "@types/js-yaml": "^4.0.5",
43
- "@types/lodash.defaultsdeep": "^4.6.6",
44
- "@types/lodash.omit": "^4.5.7"
62
+ "@types/lodash.defaultsdeep": "^4.6.6"
45
63
  },
46
64
  "publishConfig": {
47
65
  "access": "public"
package/src/config.ts CHANGED
@@ -6,8 +6,8 @@ import { type LogProcessor } from './context';
6
6
 
7
7
  /**
8
8
  * Standard levels.
9
+ * NOTE: Keep aligned with LogLevel in @dxos/protocols.
9
10
  */
10
- // NOTE: Keep aligned with LogLevel in @dxos/protocols.
11
11
  // TODO(burdon): Update numbers?
12
12
  export enum LogLevel {
13
13
  TRACE = 5,
@@ -18,7 +18,8 @@ export enum LogLevel {
18
18
  ERROR = 14,
19
19
  }
20
20
 
21
- export const levels: { [index: string]: LogLevel } = {
21
+ export const levels: Record<string, LogLevel> = {
22
+ '*': LogLevel.TRACE,
22
23
  trace: LogLevel.TRACE,
23
24
  debug: LogLevel.DEBUG,
24
25
  verbose: LogLevel.VERBOSE,
package/src/context.ts CHANGED
@@ -16,7 +16,7 @@ export type LogContext = Record<string, any> | Error | any;
16
16
  */
17
17
  export interface LogEntry {
18
18
  level: LogLevel;
19
- message: string;
19
+ message?: string;
20
20
  context?: LogContext;
21
21
  meta?: CallMetadata;
22
22
  error?: Error;
@@ -27,8 +27,33 @@ export interface LogEntry {
27
27
  */
28
28
  export type LogProcessor = (config: LogConfig, entry: LogEntry) => void;
29
29
 
30
- const matchFilter = (filter: LogFilter, level: LogLevel, path: string) => {
31
- return level >= filter.level && (!filter.pattern || path.includes(filter.pattern));
30
+ /**
31
+ * Returns:
32
+ * true if the log entry matches the filter,
33
+ * false if should be excluded, or
34
+ * undefined if it the filter doesn't match the level.
35
+ */
36
+ const matchFilter = (filter: LogFilter, level: LogLevel, path?: string): boolean | undefined => {
37
+ // TODO(burdon): Support regexp.
38
+ if (filter.pattern?.startsWith('-')) {
39
+ // Exclude.
40
+ if (path?.includes(filter.pattern.slice(1))) {
41
+ if (level >= filter.level) {
42
+ return false;
43
+ }
44
+ }
45
+ } else {
46
+ // Include.
47
+ if (filter.pattern?.length) {
48
+ if (path?.includes(filter.pattern)) {
49
+ return level >= filter.level;
50
+ }
51
+ } else {
52
+ if (level >= filter.level) {
53
+ return true;
54
+ }
55
+ }
56
+ }
32
57
  };
33
58
 
34
59
  /**
@@ -36,10 +61,16 @@ const matchFilter = (filter: LogFilter, level: LogLevel, path: string) => {
36
61
  */
37
62
  export const shouldLog = (entry: LogEntry, filters?: LogFilter[]): boolean => {
38
63
  if (filters === undefined) {
39
- return true;
40
- } else {
41
- return filters.some((filter) => matchFilter(filter, entry.level, entry.meta?.F ?? ''));
64
+ return false;
42
65
  }
66
+
67
+ const results = filters
68
+ .map((filter) => matchFilter(filter, entry.level, entry.meta?.F))
69
+ .filter((result): result is boolean => result !== undefined);
70
+
71
+ // Skip if any are explicitely false.
72
+ // console.log({ level: entry.level, path: entry.meta?.F }, filters, results, results.length);
73
+ return results.length > 0 && !results.some((results) => results === false);
43
74
  };
44
75
 
45
76
  export const getContextFromEntry = (entry: LogEntry): Record<string, any> | undefined => {
@@ -64,8 +95,7 @@ export const getContextFromEntry = (entry: LogEntry): Record<string, any> | unde
64
95
  }
65
96
 
66
97
  if (entry.error) {
67
- const errorContext = (entry.error as any).context;
68
- context = Object.assign(context ?? {}, { error: entry.error, ...errorContext });
98
+ context = Object.assign(context ?? {}, { error: entry.error });
69
99
  }
70
100
 
71
101
  return context && Object.keys(context).length > 0 ? context : undefined;
package/src/dbg.ts ADDED
@@ -0,0 +1,34 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { type CallMetadata } from './meta';
6
+ /**
7
+ * Debug-log value to console.
8
+ * Log's the expression being evaluated.
9
+ *
10
+ * If only one argument is provided, it will also be returned.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * dbg(foo, bar);
15
+ * // foo = 1
16
+ * // bar = 2
17
+ *
18
+ * bar = dbg(foo * 2);
19
+ * // foo * 2 = 2
20
+ * ```
21
+ *
22
+ * NOTE: The second argument is injected by the log transform plugin.
23
+ */
24
+ export const dbg: {
25
+ <T>(value: T, _meta?: CallMetadata): T;
26
+ } = <T>(arg: T, meta?: CallMetadata): T => {
27
+ if (meta?.A) {
28
+ console.log(`${meta.A[0]} =`, arg);
29
+ } else {
30
+ console.log(arg);
31
+ }
32
+
33
+ return arg;
34
+ };
package/src/decorators.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  import chalk from 'chalk';
6
6
  import { inspect } from 'node:util';
7
7
 
8
- import type { LogMethods } from './log';
8
+ import { type LogMethods } from './log';
9
9
  import { type CallMetadata } from './meta';
10
10
 
11
11
  let nextPromiseId = 0;
@@ -190,13 +190,13 @@ const logAsyncRejected = (
190
190
  );
191
191
  };
192
192
 
193
+ const COLOR_FUNCTION = [220, 220, 170] as const;
194
+
193
195
  // https://github.com/dxos/dxos/issues/7286
194
196
  const greenCheck = typeof chalk.green === 'function' ? chalk.green('✔') : '✔';
195
197
 
196
198
  const formatTimeElapsed = (startTime: number) => chalk.gray(`${(performance.now() - startTime).toFixed(0)}ms`);
197
199
 
198
- const COLOR_FUNCTION = [220, 220, 170] as const;
199
-
200
200
  const formatFunction = (name: string) => chalk.bold(chalk.rgb(...COLOR_FUNCTION)(name));
201
201
 
202
202
  const formatPromise = (id: number) => chalk.blue(`Promise#${id}`);
@@ -4,8 +4,8 @@
4
4
 
5
5
  import { describe, test } from 'vitest';
6
6
 
7
- import { debugInfo, ownershipClass } from './ownership';
8
7
  import { log } from '../log';
8
+ import { debugInfo, ownershipClass } from './ownership';
9
9
 
10
10
  describe('classes', function () {
11
11
  test('field instance', function () {
package/src/index.ts CHANGED
@@ -2,9 +2,7 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import omit from 'lodash.omit';
6
-
7
- import { pick } from '@dxos/util';
5
+ import { omit, pick } from '@dxos/util';
8
6
 
9
7
  export { omit, pick };
10
8
 
@@ -14,6 +12,8 @@ export * from './log';
14
12
  export { parseFilter } from './options';
15
13
  export * from './processors';
16
14
  export * from './scope';
17
- export * from './meta';
15
+ export type * from './meta';
16
+ export { dbg } from './dbg';
17
+ export * from './log-buffer';
18
18
 
19
19
  export { getCurrentOwnershipScope } from './experimental/ownership';
@@ -0,0 +1,155 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { describe, test } from 'vitest';
6
+
7
+ import { type LogConfig, type LogEntry, LogLevel } from './index';
8
+ import { LogBuffer } from './log-buffer';
9
+
10
+ const baseConfig: LogConfig = {
11
+ options: {},
12
+ filters: [{ level: LogLevel.DEBUG }],
13
+ processors: [],
14
+ };
15
+
16
+ const createEntry = (overrides: Partial<LogEntry> = {}): LogEntry => ({
17
+ level: LogLevel.INFO,
18
+ message: 'test message',
19
+ ...overrides,
20
+ });
21
+
22
+ describe('LogBuffer', () => {
23
+ test('pushes and serializes log entries', ({ expect }) => {
24
+ const buffer = new LogBuffer(10);
25
+ buffer.logProcessor(baseConfig, createEntry({ message: 'hello' }));
26
+ buffer.logProcessor(baseConfig, createEntry({ message: 'world' }));
27
+
28
+ expect(buffer.size).toBe(2);
29
+ const lines = buffer.serialize().split('\n');
30
+ expect(lines).toHaveLength(2);
31
+
32
+ const first = JSON.parse(lines[0]);
33
+ expect(first.m).toBe('hello');
34
+ expect(first.l).toBe('I');
35
+ expect(first.t).toBeDefined();
36
+
37
+ const second = JSON.parse(lines[1]);
38
+ expect(second.m).toBe('world');
39
+ });
40
+
41
+ test('evicts oldest entries when buffer is full', ({ expect }) => {
42
+ const buffer = new LogBuffer(3);
43
+ buffer.logProcessor(baseConfig, createEntry({ message: 'a' }));
44
+ buffer.logProcessor(baseConfig, createEntry({ message: 'b' }));
45
+ buffer.logProcessor(baseConfig, createEntry({ message: 'c' }));
46
+ buffer.logProcessor(baseConfig, createEntry({ message: 'd' }));
47
+
48
+ expect(buffer.size).toBe(3);
49
+ const lines = buffer.serialize().split('\n');
50
+ const messages = lines.map((line) => JSON.parse(line).m);
51
+ expect(messages).toEqual(['b', 'c', 'd']);
52
+ });
53
+
54
+ test('skips TRACE-level logs', ({ expect }) => {
55
+ const buffer = new LogBuffer(10);
56
+ buffer.logProcessor(baseConfig, createEntry({ level: LogLevel.TRACE, message: 'trace' }));
57
+ expect(buffer.size).toBe(0);
58
+ });
59
+
60
+ test('captures DEBUG-level and above', ({ expect }) => {
61
+ const buffer = new LogBuffer(10);
62
+ buffer.logProcessor(baseConfig, createEntry({ level: LogLevel.DEBUG, message: 'debug' }));
63
+ buffer.logProcessor(baseConfig, createEntry({ level: LogLevel.WARN, message: 'warn' }));
64
+ buffer.logProcessor(baseConfig, createEntry({ level: LogLevel.ERROR, message: 'error' }));
65
+
66
+ expect(buffer.size).toBe(3);
67
+ const lines = buffer.serialize().split('\n');
68
+ expect(JSON.parse(lines[0]).l).toBe('D');
69
+ expect(JSON.parse(lines[1]).l).toBe('W');
70
+ expect(JSON.parse(lines[2]).l).toBe('E');
71
+ });
72
+
73
+ test('captures file and line metadata', ({ expect }) => {
74
+ const buffer = new LogBuffer(10);
75
+ buffer.logProcessor(
76
+ baseConfig,
77
+ createEntry({
78
+ meta: { F: '/home/user/project/packages/sdk/test.ts', L: 42, S: undefined },
79
+ }),
80
+ );
81
+
82
+ const lines = buffer.serialize().split('\n');
83
+ const record = JSON.parse(lines[0]);
84
+ expect(record.f).toBe('packages/sdk/test.ts');
85
+ expect(record.n).toBe(42);
86
+ });
87
+
88
+ test('captures error stack', ({ expect }) => {
89
+ const buffer = new LogBuffer(10);
90
+ const error = new Error('boom');
91
+ buffer.logProcessor(baseConfig, createEntry({ error }));
92
+
93
+ const lines = buffer.serialize().split('\n');
94
+ const record = JSON.parse(lines[0]);
95
+ expect(record.e).toContain('boom');
96
+ });
97
+
98
+ test('truncates context to 500 chars', ({ expect }) => {
99
+ const buffer = new LogBuffer(10);
100
+ const longValue = 'x'.repeat(1000);
101
+ buffer.logProcessor(baseConfig, createEntry({ context: { data: longValue } }));
102
+
103
+ const lines = buffer.serialize().split('\n');
104
+ const record = JSON.parse(lines[0]);
105
+ expect(record.c).toBeDefined();
106
+ expect(record.c!.length).toBe(500);
107
+ });
108
+
109
+ test('skips Error context objects', ({ expect }) => {
110
+ const buffer = new LogBuffer(10);
111
+ buffer.logProcessor(baseConfig, createEntry({ context: new Error('ctx error') }));
112
+
113
+ const lines = buffer.serialize().split('\n');
114
+ const record = JSON.parse(lines[0]);
115
+ expect(record.c).toBeUndefined();
116
+ });
117
+
118
+ test('handles non-serializable context gracefully', ({ expect }) => {
119
+ const buffer = new LogBuffer(10);
120
+ const circular: Record<string, any> = {};
121
+ circular.self = circular;
122
+ buffer.logProcessor(baseConfig, createEntry({ context: circular }));
123
+
124
+ const lines = buffer.serialize().split('\n');
125
+ const record = JSON.parse(lines[0]);
126
+ expect(record.c).toBeUndefined();
127
+ });
128
+
129
+ test('serialize returns empty string for empty buffer', ({ expect }) => {
130
+ const buffer = new LogBuffer(10);
131
+ expect(buffer.serialize()).toBe('');
132
+ });
133
+
134
+ test('clear discards all entries', ({ expect }) => {
135
+ const buffer = new LogBuffer(10);
136
+ buffer.logProcessor(baseConfig, createEntry({ message: 'a' }));
137
+ buffer.logProcessor(baseConfig, createEntry({ message: 'b' }));
138
+ expect(buffer.size).toBe(2);
139
+
140
+ buffer.clear();
141
+ expect(buffer.size).toBe(0);
142
+ expect(buffer.serialize()).toBe('');
143
+ });
144
+
145
+ test('clear allows new entries', ({ expect }) => {
146
+ const buffer = new LogBuffer(10);
147
+ buffer.logProcessor(baseConfig, createEntry({ message: 'old' }));
148
+ buffer.clear();
149
+ buffer.logProcessor(baseConfig, createEntry({ message: 'new' }));
150
+
151
+ expect(buffer.size).toBe(1);
152
+ const record = JSON.parse(buffer.serialize());
153
+ expect(record.m).toBe('new');
154
+ });
155
+ });
@@ -0,0 +1,117 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { CircularBuffer, getDebugName } from '@dxos/util';
6
+
7
+ import { type LogConfig, LogLevel, shortLevelName } from './config';
8
+ import { type LogEntry, type LogProcessor } from './context';
9
+
10
+ const DEFAULT_BUFFER_SIZE = 2_000;
11
+ const MAX_CONTEXT_LENGTH = 500;
12
+
13
+ /**
14
+ * Compact log record with short property names for small serialized size.
15
+ */
16
+ export type LogRecord = {
17
+ /** ISO timestamp. */
18
+ t: string;
19
+ /** Level letter (D, V, I, W, E). */
20
+ l: string;
21
+ /** Message. */
22
+ m: string;
23
+ /** File path. */
24
+ f?: string;
25
+ /** Line number. */
26
+ n?: number;
27
+ /* Object from which the log was emitted. */
28
+ o?: string;
29
+ /** Error stack. */
30
+ e?: string;
31
+ /** Context JSON. */
32
+ c?: string;
33
+ };
34
+
35
+ /**
36
+ * Captures recent log entries in a circular buffer for debug log dump.
37
+ */
38
+ export class LogBuffer {
39
+ private readonly _buffer: CircularBuffer<LogRecord>;
40
+
41
+ constructor(size = DEFAULT_BUFFER_SIZE) {
42
+ this._buffer = new CircularBuffer<LogRecord>(size);
43
+ }
44
+
45
+ /**
46
+ * Log processor that can be registered with `log.runtimeConfig.processors`.
47
+ * Captures every level except TRACE (does not apply `shouldLog` / filter; use for full debug dumps).
48
+ */
49
+ readonly logProcessor: LogProcessor = (_config: LogConfig, entry: LogEntry) => {
50
+ if (entry.level <= LogLevel.TRACE) {
51
+ return;
52
+ }
53
+
54
+ const record: LogRecord = {
55
+ t: new Date().toISOString(),
56
+ l: shortLevelName[entry.level] ?? '?',
57
+ m: entry.message ?? '',
58
+ };
59
+
60
+ if (entry.meta) {
61
+ record.f = getRelativeFilename(entry.meta.F);
62
+ record.n = entry.meta.L;
63
+ }
64
+
65
+ if (entry.error) {
66
+ record.e = entry.error.stack ?? entry.error.message;
67
+ }
68
+
69
+ if (entry.context != null) {
70
+ try {
71
+ const ctx = typeof entry.context === 'function' ? entry.context() : entry.context;
72
+ if (ctx != null && !(ctx instanceof Error)) {
73
+ let json = JSON.stringify(ctx);
74
+ if (json.length > MAX_CONTEXT_LENGTH) {
75
+ json = json.slice(0, MAX_CONTEXT_LENGTH);
76
+ }
77
+ record.c = json;
78
+ }
79
+ } catch {
80
+ // Skip context that throws or is non-serializable.
81
+ }
82
+ }
83
+ const scope = entry.meta?.S;
84
+ if (typeof scope === 'object' && scope !== null && Object.getPrototypeOf(scope) !== Object.prototype) {
85
+ record.o = getDebugName(scope);
86
+ }
87
+
88
+ this._buffer.push(record);
89
+ };
90
+
91
+ /** Number of entries currently in the buffer. */
92
+ get size(): number {
93
+ return this._buffer.elementCount;
94
+ }
95
+
96
+ /** Discard all buffered entries. */
97
+ clear(): void {
98
+ this._buffer.clear();
99
+ }
100
+
101
+ /** Serialize buffer contents as NDJSON (newline-delimited JSON). */
102
+ serialize(): string {
103
+ const lines: string[] = [];
104
+ for (const record of this._buffer) {
105
+ lines.push(JSON.stringify(record));
106
+ }
107
+ return lines.join('\n');
108
+ }
109
+ }
110
+
111
+ const getRelativeFilename = (filename: string): string => {
112
+ const match = filename.match(/.+\/(packages\/.+\/.+)/);
113
+ if (match) {
114
+ return match[1];
115
+ }
116
+ return filename;
117
+ };
package/src/log.test.ts CHANGED
@@ -3,10 +3,11 @@
3
3
  //
4
4
 
5
5
  import path from 'node:path';
6
- import { describe, test } from 'vitest';
6
+ import { beforeEach, describe, test } from 'vitest';
7
7
 
8
8
  import { LogLevel } from './config';
9
- import { log } from './log';
9
+ import { shouldLog } from './context';
10
+ import { type Log, createLog } from './log';
10
11
 
11
12
  class LogError extends Error {
12
13
  constructor(
@@ -24,14 +25,53 @@ class LogError extends Error {
24
25
  }
25
26
  }
26
27
 
27
- log.config({
28
- filter: LogLevel.DEBUG,
29
- });
28
+ describe('log', () => {
29
+ let log!: Log;
30
+
31
+ beforeEach(() => {
32
+ log = createLog();
33
+ log.config({
34
+ filter: LogLevel.DEBUG,
35
+ });
36
+ });
30
37
 
31
- /* eslint-disable @stayradiated/prefer-arrow-functions/prefer-arrow-functions */
38
+ test('filters', ({ expect }) => {
39
+ const tests = [
40
+ { expected: 0, filter: 'ERROR' },
41
+ { expected: 2, filter: 'INFO' },
42
+ { expected: 1, filter: 'foo:INFO' },
43
+ { expected: 4, filter: 'DEBUG' },
44
+ { expected: 2, filter: 'DEBUG,-foo:*' },
45
+ { expected: 1, filter: 'INFO,-foo:*' },
46
+ { expected: 3, filter: 'DEBUG,-foo:INFO' },
47
+ { expected: 3, filter: 'foo:DEBUG,bar:INFO' },
48
+ ];
49
+
50
+ for (const test of tests) {
51
+ let count = 0;
52
+ const log = createLog();
53
+ const remove = log.addProcessor((config, entry) => {
54
+ if (shouldLog(entry, config.filters)) {
55
+ count++;
56
+ }
57
+ });
58
+ log.config({
59
+ filter: test.filter,
60
+ });
61
+
62
+ console.group(`Filter: "${test.filter}"`);
63
+ log.debug('line 1', {}, { F: 'foo.ts', L: 1, S: undefined });
64
+ log.info('line 2', {}, { F: 'foo.ts', L: 2, S: undefined });
65
+ log.debug('line 3', {}, { F: 'bar.ts', L: 3, S: undefined });
66
+ log.info('line 4', {}, { F: 'bar.ts', L: 4, S: undefined });
67
+ console.groupEnd();
68
+
69
+ expect(count, `Filter: "${test.filter}"`).toBe(test.expected);
70
+ remove();
71
+ }
72
+ });
32
73
 
33
- describe('log', function () {
34
- test('throws an error', function () {
74
+ test('throws an error', () => {
35
75
  try {
36
76
  throw new LogError('Test failed', { value: 1 });
37
77
  } catch (err: any) {
@@ -39,7 +79,7 @@ describe('log', function () {
39
79
  }
40
80
  });
41
81
 
42
- test('throws an error showing stacktrace', function () {
82
+ test('throws an error showing stacktrace', () => {
43
83
  try {
44
84
  throw new LogError('Test failed', { value: 2 });
45
85
  } catch (err: any) {
@@ -47,7 +87,7 @@ describe('log', function () {
47
87
  }
48
88
  });
49
89
 
50
- test('catches an error', function () {
90
+ test('catches an error', () => {
51
91
  try {
52
92
  throw new LogError('ERROR ON LINE 21', { value: 3 });
53
93
  } catch (err: any) {
@@ -55,17 +95,7 @@ describe('log', function () {
55
95
  }
56
96
  });
57
97
 
58
- test('config', function () {
59
- log.config({
60
- filter: LogLevel.INFO,
61
- });
62
-
63
- log.debug('Debug level log message');
64
- log.info('Info level log message');
65
- log.warn('Warn level log message');
66
- });
67
-
68
- test('config file', function () {
98
+ test('config file', () => {
69
99
  log.config({
70
100
  file: path.join('packages/common/log/test-config.yml'),
71
101
  });
@@ -75,7 +105,7 @@ describe('log', function () {
75
105
  log.warn('Warn level log message');
76
106
  });
77
107
 
78
- test('levels', function () {
108
+ test('levels', () => {
79
109
  log('Default level log message');
80
110
  log.debug('Debug level log message');
81
111
  log.info('Info level log message');
@@ -83,10 +113,15 @@ describe('log', function () {
83
113
  log.error('Error level log message');
84
114
  });
85
115
 
86
- test('context', function () {
116
+ test('context', () => {
87
117
  log.info('Message with context', {
88
118
  title: 'test',
89
119
  context: 123,
90
120
  });
91
121
  });
122
+
123
+ test('error', () => {
124
+ const myError = new Error('Test error', { cause: new Error('Cause') });
125
+ log.catch(myError);
126
+ });
92
127
  });