@dxos/log 0.8.4-main.fd6878d → 0.8.4-staging.ac66bdf99f

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-DTUPLFUN.mjs +311 -0
  2. package/dist/lib/browser/chunk-DTUPLFUN.mjs.map +7 -0
  3. package/dist/lib/browser/chunk-IEP6GGEX.mjs +23 -0
  4. package/dist/lib/browser/chunk-IEP6GGEX.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +243 -211
  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 +102 -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-KOTHKRYW.mjs +313 -0
  19. package/dist/lib/node-esm/chunk-KOTHKRYW.mjs.map +7 -0
  20. package/dist/lib/node-esm/index.mjs +245 -300
  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 +103 -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 +78 -2
  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 +3 -2
  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/platform/node/index.d.ts.map +1 -1
  52. package/dist/types/src/processors/browser-processor.d.ts.map +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 +30 -12
  59. package/src/config.ts +3 -2
  60. package/src/context.ts +278 -7
  61. package/src/dbg.ts +34 -0
  62. package/src/decorators.ts +4 -5
  63. package/src/experimental/classes.test.ts +0 -1
  64. package/src/index.ts +3 -3
  65. package/src/log-buffer.test.ts +158 -0
  66. package/src/log-buffer.ts +110 -0
  67. package/src/log.test.ts +48 -19
  68. package/src/log.ts +97 -58
  69. package/src/options.ts +26 -10
  70. package/src/platform/index.ts +1 -1
  71. package/src/platform/node/index.ts +1 -2
  72. package/src/processors/browser-processor.ts +28 -27
  73. package/src/processors/console-processor.ts +5 -13
  74. package/src/processors/file-processor.ts +9 -9
  75. package/src/processors/index.ts +3 -3
@@ -0,0 +1,110 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { CircularBuffer } 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 { filename, line, context: scopeName } = entry.computedMeta;
55
+
56
+ const record: LogRecord = {
57
+ t: new Date(entry.timestamp).toISOString(),
58
+ l: shortLevelName[entry.level] ?? '?',
59
+ m: entry.message ?? '',
60
+ };
61
+
62
+ if (filename !== undefined) {
63
+ record.f = filename;
64
+ }
65
+ if (line !== undefined) {
66
+ record.n = line;
67
+ }
68
+ if (scopeName !== undefined) {
69
+ record.o = scopeName;
70
+ }
71
+
72
+ if (entry.computedError !== undefined) {
73
+ record.e = entry.computedError;
74
+ }
75
+
76
+ const computedContext = entry.computedContext;
77
+ if (Object.keys(computedContext).length > 0) {
78
+ try {
79
+ let json = JSON.stringify(computedContext);
80
+ if (json.length > MAX_CONTEXT_LENGTH) {
81
+ json = json.slice(0, MAX_CONTEXT_LENGTH);
82
+ }
83
+ record.c = json;
84
+ } catch {
85
+ // Skip context that throws or is non-serializable.
86
+ }
87
+ }
88
+
89
+ this._buffer.push(record);
90
+ };
91
+
92
+ /** Number of entries currently in the buffer. */
93
+ get size(): number {
94
+ return this._buffer.elementCount;
95
+ }
96
+
97
+ /** Discard all buffered entries. */
98
+ clear(): void {
99
+ this._buffer.clear();
100
+ }
101
+
102
+ /** Serialize buffer contents as NDJSON (newline-delimited JSON). */
103
+ serialize(): string {
104
+ const lines: string[] = [];
105
+ for (const record of this._buffer) {
106
+ lines.push(JSON.stringify(record));
107
+ }
108
+ return lines.join('\n');
109
+ }
110
+ }
package/src/log.test.ts CHANGED
@@ -3,11 +3,11 @@
3
3
  //
4
4
 
5
5
  import path from 'node:path';
6
-
7
- import { describe, test } from 'vitest';
6
+ import { beforeEach, describe, test } from 'vitest';
8
7
 
9
8
  import { LogLevel } from './config';
10
- import { log } from './log';
9
+ import { shouldLog } from './context';
10
+ import { type Log, createLog } from './log';
11
11
 
12
12
  class LogError extends Error {
13
13
  constructor(
@@ -25,13 +25,52 @@ class LogError extends Error {
25
25
  }
26
26
  }
27
27
 
28
- log.config({
29
- filter: LogLevel.DEBUG,
30
- });
28
+ describe('log', () => {
29
+ let log!: Log;
31
30
 
32
- /* eslint-disable prefer-arrow-functions/prefer-arrow-functions */
31
+ beforeEach(() => {
32
+ log = createLog();
33
+ log.config({
34
+ filter: LogLevel.DEBUG,
35
+ });
36
+ });
37
+
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
+ });
33
73
 
34
- describe('log', () => {
35
74
  test('throws an error', () => {
36
75
  try {
37
76
  throw new LogError('Test failed', { value: 1 });
@@ -56,16 +95,6 @@ describe('log', () => {
56
95
  }
57
96
  });
58
97
 
59
- test('config', () => {
60
- log.config({
61
- filter: LogLevel.INFO,
62
- });
63
-
64
- log.debug('Debug level log message');
65
- log.info('Info level log message');
66
- log.warn('Warn level log message');
67
- });
68
-
69
98
  test('config file', () => {
70
99
  log.config({
71
100
  file: path.join('packages/common/log/test-config.yml'),
@@ -91,7 +120,7 @@ describe('log', () => {
91
120
  });
92
121
  });
93
122
 
94
- test('error', function () {
123
+ test('error', () => {
95
124
  const myError = new Error('Test error', { cause: new Error('Cause') });
96
125
  log.catch(myError);
97
126
  });
package/src/log.ts CHANGED
@@ -3,10 +3,19 @@
3
3
  //
4
4
 
5
5
  import { type LogConfig, LogLevel, type LogOptions } from './config';
6
- import { type LogContext, type LogProcessor } from './context';
6
+ import { type LogContext, LogEntry, type LogProcessor } from './context';
7
7
  import { createFunctionLogDecorator, createMethodLogDecorator } from './decorators';
8
8
  import { type CallMetadata } from './meta';
9
- import { DEFAULT_PROCESSORS, getConfig } from './options';
9
+ import { createConfig } from './options';
10
+
11
+ /**
12
+ * Accessible from browser console.
13
+ * Example: `DX_LOG.config({ filter: 'ERROR' })`
14
+ * NOTE: File level filtering isn't supported in storybooks.
15
+ */
16
+ declare global {
17
+ const DX_LOG: Log;
18
+ }
10
19
 
11
20
  /**
12
21
  * Logging function.
@@ -17,6 +26,9 @@ type LogFunction = (message: string, context?: LogContext, meta?: CallMetadata)
17
26
  * Logging methods.
18
27
  */
19
28
  export interface LogMethods {
29
+ config: (options?: LogOptions) => Log;
30
+ addProcessor: (processor: LogProcessor, addDefault?: boolean) => () => void;
31
+
20
32
  trace: LogFunction;
21
33
  debug: LogFunction;
22
34
  verbose: LogFunction;
@@ -24,70 +36,56 @@ export interface LogMethods {
24
36
  warn: LogFunction;
25
37
  error: LogFunction;
26
38
  catch: (error: Error | any, context?: LogContext, meta?: CallMetadata) => void;
27
- break: () => void;
28
- stack: (message?: string, context?: never, meta?: CallMetadata) => void;
39
+
29
40
  method: (arg0?: never, arg1?: never, meta?: CallMetadata) => MethodDecorator;
30
- func: <F extends (...args: any[]) => any>(
41
+ function: <F extends (...args: any[]) => any>(
31
42
  name: string,
32
43
  fn: F,
33
- opts?: { transformOutput?: (result: ReturnType<F>) => Promise<any> | any },
44
+ opts?: {
45
+ transformOutput?: (result: ReturnType<F>) => Promise<any> | any;
46
+ },
34
47
  ) => F;
48
+
49
+ break: () => void;
50
+ stack: (message?: string, context?: never, meta?: CallMetadata) => void;
35
51
  }
36
52
 
37
53
  /**
38
54
  * Properties accessible on the logging function.
55
+ * @internal
39
56
  */
40
- interface Log extends LogMethods, LogFunction {
41
- config: (options: LogOptions) => void;
42
- addProcessor: (processor: LogProcessor) => void;
43
- runtimeConfig: LogConfig;
57
+ export interface Log extends LogFunction, LogMethods {
58
+ readonly runtimeConfig: LogConfig;
44
59
  }
45
60
 
61
+ /**
62
+ * @internal
63
+ */
46
64
  interface LogImp extends Log {
65
+ _id: string;
47
66
  _config: LogConfig;
48
67
  }
49
68
 
50
- const createLog = (): LogImp => {
51
- const log: LogImp = ((...params) => processLog(LogLevel.DEBUG, ...params)) as LogImp;
52
-
53
- log._config = getConfig();
54
- Object.defineProperty(log, 'runtimeConfig', { get: () => log._config });
55
-
56
- log.addProcessor = (processor: LogProcessor) => {
57
- if (DEFAULT_PROCESSORS.filter((p) => p === processor).length === 0) {
58
- DEFAULT_PROCESSORS.push(processor);
59
- }
60
- if (log._config.processors.filter((p) => p === processor).length === 0) {
61
- log._config.processors.push(processor);
62
- }
63
- };
64
-
65
- // Set config.
66
- log.config = (options: LogOptions) => {
67
- log._config = getConfig(options);
68
- };
69
-
70
- // TODO(burdon): API to set context and separate error object.
71
- // E.g., log.warn('failed', { key: 123 }, err);
72
-
73
- log.trace = (...params) => processLog(LogLevel.TRACE, ...params);
74
- log.debug = (...params) => processLog(LogLevel.DEBUG, ...params);
75
- log.verbose = (...params) => processLog(LogLevel.VERBOSE, ...params);
76
- log.info = (...params) => processLog(LogLevel.INFO, ...params);
77
- log.warn = (...params) => processLog(LogLevel.WARN, ...params);
78
- log.error = (...params) => processLog(LogLevel.ERROR, ...params);
69
+ let logCount = 0;
79
70
 
80
- // Catch only shows error message, not stacktrace.
81
- log.catch = (error: Error | any, context, meta) => processLog(LogLevel.ERROR, undefined, context, meta, error);
82
-
83
- // Show break.
84
- log.break = () => log.info('——————————————————————————————————————————————————');
71
+ /**
72
+ * Create a logging function with properties.
73
+ * @internal
74
+ */
75
+ export const createLog = (): LogImp => {
76
+ // Default function.
77
+ const log: LogImp = ((...params) => processLog(LogLevel.DEBUG, ...params)) as LogImp;
85
78
 
86
- log.stack = (message, context, meta) =>
87
- processLog(LogLevel.INFO, `${message ?? 'Stack Dump'}\n${getFormattedStackTrace()}`, context, meta);
79
+ // Add private properties.
80
+ Object.assign<LogImp, Partial<LogImp>>(log, {
81
+ _id: `log-${++logCount}`,
82
+ _config: createConfig(),
83
+ });
88
84
 
89
- log.method = createMethodLogDecorator(log);
90
- log.func = createFunctionLogDecorator(log);
85
+ // TODO(burdon): Document.
86
+ Object.defineProperty(log, 'runtimeConfig', {
87
+ get: () => log._config,
88
+ });
91
89
 
92
90
  /**
93
91
  * Process the current log call.
@@ -99,16 +97,65 @@ const createLog = (): LogImp => {
99
97
  meta?: CallMetadata,
100
98
  error?: Error,
101
99
  ) => {
102
- log._config.processors.forEach((processor) => processor(log._config, { level, message, context, meta, error }));
100
+ // TODO(burdon): Do the filter matching upstream (here) rather than in each processor?
101
+ const entry = new LogEntry({ level, message, context, meta, error });
102
+ log._config.processors.forEach((processor) => processor(log._config, entry));
103
103
  };
104
104
 
105
+ /**
106
+ * API.
107
+ */
108
+ Object.assign<Log, LogMethods>(log, {
109
+ /**
110
+ * Update config.
111
+ * NOTE: Preserves any processors that were already added to this logger instance
112
+ * unless an explicit processor option is provided.
113
+ */
114
+ config: ({ processor, ...options } = {}) => {
115
+ const config = createConfig(options);
116
+ // TODO(burdon): This could be buggy since the behavior is not reentrant.
117
+ const processors = processor ? config.processors : log._config.processors;
118
+ log._config = { ...config, processors };
119
+ return log;
120
+ },
121
+
122
+ /**
123
+ * Adds a processor to the logger.
124
+ */
125
+ addProcessor: (processor) => {
126
+ if (log._config.processors.filter((p) => p === processor).length === 0) {
127
+ log._config.processors.push(processor);
128
+ }
129
+
130
+ return () => {
131
+ log._config.processors = log._config.processors.filter((p) => p !== processor);
132
+ };
133
+ },
134
+
135
+ trace: (...params) => processLog(LogLevel.TRACE, ...params),
136
+ debug: (...params) => processLog(LogLevel.DEBUG, ...params),
137
+ verbose: (...params) => processLog(LogLevel.VERBOSE, ...params),
138
+ info: (...params) => processLog(LogLevel.INFO, ...params),
139
+ warn: (...params) => processLog(LogLevel.WARN, ...params),
140
+ error: (...params) => processLog(LogLevel.ERROR, ...params),
141
+ catch: (error, context, meta) => processLog(LogLevel.ERROR, undefined, context, meta, error),
142
+
143
+ method: createMethodLogDecorator(log),
144
+ function: createFunctionLogDecorator(log),
145
+
146
+ break: () => log.info('-'.repeat(80)),
147
+ stack: (message, context, meta) => {
148
+ return processLog(LogLevel.INFO, `${message ?? 'Stack Dump'}\n${getFormattedStackTrace()}`, context, meta);
149
+ },
150
+ });
151
+
105
152
  return log;
106
153
  };
107
154
 
108
155
  /**
109
156
  * Global logging function.
110
157
  */
111
- export const log: Log = ((globalThis as any).dx_log ??= createLog());
158
+ export const log: Log = ((globalThis as any).DX_LOG ??= createLog());
112
159
 
113
160
  const start = Date.now();
114
161
  let last = start;
@@ -128,12 +175,4 @@ export const debug = (label?: any, args?: any) => {
128
175
  last = Date.now();
129
176
  };
130
177
 
131
- /**
132
- * Accessible from browser console.
133
- */
134
- declare global {
135
- // eslint-disable-next-line camelcase
136
- const dx_log: Log;
137
- }
138
-
139
178
  const getFormattedStackTrace = () => new Error().stack!.split('\n').slice(3).join('\n');
package/src/options.ts CHANGED
@@ -12,32 +12,48 @@ import { BROWSER_PROCESSOR, CONSOLE_PROCESSOR, DEBUG_PROCESSOR } from './process
12
12
  /**
13
13
  * Processor variants.
14
14
  */
15
- export const processors: { [index: string]: LogProcessor } = {
15
+ export const processors: Record<string, LogProcessor> = {
16
16
  [LogProcessorType.CONSOLE]: CONSOLE_PROCESSOR,
17
17
  [LogProcessorType.BROWSER]: BROWSER_PROCESSOR,
18
18
  [LogProcessorType.DEBUG]: DEBUG_PROCESSOR,
19
19
  };
20
20
 
21
- const IS_BROWSER = typeof window !== 'undefined' || typeof navigator !== 'undefined';
21
+ const browser =
22
+ (typeof window !== 'undefined' || typeof navigator !== 'undefined') &&
23
+ !(typeof process !== 'undefined' && process?.env?.VITEST);
22
24
 
23
- export const DEFAULT_PROCESSORS = [IS_BROWSER ? BROWSER_PROCESSOR : CONSOLE_PROCESSOR];
25
+ export const DEFAULT_PROCESSORS = [browser ? BROWSER_PROCESSOR : CONSOLE_PROCESSOR];
24
26
 
27
+ const parseLogLevel = (level: string, defValue = LogLevel.WARN) => levels[level.toLowerCase()] ?? defValue;
28
+
29
+ /**
30
+ * @internal
31
+ */
25
32
  export const parseFilter = (filter: string | string[] | LogLevel): LogFilter[] => {
26
33
  if (typeof filter === 'number') {
27
34
  return [{ level: filter }];
28
35
  }
29
36
 
30
- const parseLogLevel = (level: string, defValue = LogLevel.WARN) => levels[level.toLowerCase()] ?? defValue;
31
-
32
37
  const lines = typeof filter === 'string' ? filter.split(/,\s*/) : filter;
33
38
  return lines.map((filter) => {
34
39
  const [pattern, level] = filter.split(':');
35
- return level ? { level: parseLogLevel(level), pattern } : { level: parseLogLevel(pattern) };
40
+ return level
41
+ ? {
42
+ level: parseLogLevel(level),
43
+ pattern,
44
+ }
45
+ : {
46
+ level: parseLogLevel(pattern),
47
+ };
36
48
  });
37
49
  };
38
50
 
39
- export const getConfig = (options?: LogOptions): LogConfig => {
40
- const nodeOptions: LogOptions | undefined =
51
+ /**
52
+ * @internal
53
+ */
54
+ export const createConfig = (options?: LogOptions): LogConfig => {
55
+ // Node only.
56
+ const envOptions: LogOptions | undefined =
41
57
  'process' in globalThis
42
58
  ? {
43
59
  file: process!.env.LOG_CONFIG,
@@ -46,12 +62,12 @@ export const getConfig = (options?: LogOptions): LogConfig => {
46
62
  }
47
63
  : undefined;
48
64
 
49
- const mergedOptions: LogOptions = defaultsDeep({}, loadOptions(nodeOptions?.file), nodeOptions, options);
65
+ const mergedOptions: LogOptions = defaultsDeep({}, loadOptions(envOptions?.file), envOptions, options);
50
66
  return {
51
67
  options: mergedOptions,
52
68
  filters: parseFilter(mergedOptions.filter ?? LogLevel.INFO),
53
69
  captureFilters: parseFilter(mergedOptions.captureFilter ?? LogLevel.WARN),
54
- processors: mergedOptions.processor ? [processors[mergedOptions.processor]] : DEFAULT_PROCESSORS,
70
+ processors: mergedOptions.processor ? [processors[mergedOptions.processor]] : [...DEFAULT_PROCESSORS],
55
71
  prefix: mergedOptions.prefix,
56
72
  };
57
73
  };
@@ -2,4 +2,4 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- export * from './node';
5
+ export * from '#platform';
@@ -2,9 +2,8 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import fs from 'node:fs';
6
-
7
5
  import yaml from 'js-yaml';
6
+ import fs from 'node:fs';
8
7
 
9
8
  import { type LogOptions } from '../../config';
10
9
 
@@ -2,23 +2,11 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import { getDebugName, safariCheck } from '@dxos/util';
5
+ import { safariCheck } from '@dxos/util';
6
6
 
7
7
  import { LogLevel } from '../config';
8
8
  import { type LogProcessor, getContextFromEntry, shouldLog } from '../context';
9
9
 
10
- const getRelativeFilename = (filename: string) => {
11
- // TODO(burdon): Hack uses "packages" as an anchor (pre-parse NX?)
12
- // Including `packages/` part of the path so that excluded paths (e.g. from dist) are clickable in vscode.
13
- const match = filename.match(/.+\/(packages\/.+\/.+)/);
14
- if (match) {
15
- const [, filePath] = match;
16
- return filePath;
17
- }
18
-
19
- return filename;
20
- };
21
-
22
10
  type Config = {
23
11
  useTestProcessor: boolean;
24
12
  printFileLinks: boolean;
@@ -47,23 +35,26 @@ const APP_BROWSER_PROCESSOR: LogProcessor = (config, entry) => {
47
35
  // const LOG_BROWSER_CSS = ['color:gray; font-size:10px; padding-bottom: 4px', 'color:#B97852; font-size:14px;'];
48
36
  const LOG_BROWSER_CSS: string[] = [];
49
37
 
38
+ const { filename, line: lineNumber, context: scopeDebugName } = entry.computedMeta;
39
+
50
40
  let link = '';
51
- if (entry.meta) {
52
- const filename = getRelativeFilename(entry.meta.F);
41
+ if (filename !== undefined && lineNumber !== undefined) {
53
42
  const filepath = `${LOG_BROWSER_PREFIX.replace(/\/$/, '')}/${filename}`;
54
43
  // TODO(burdon): Line numbers not working for app link, even with colons.
55
44
  // https://stackoverflow.com/a/54459820/2804332
56
- link = `${filepath}#L${entry.meta.L}`;
45
+ link = `${filepath}#L${lineNumber}`;
57
46
  }
58
47
 
59
48
  let args = [];
60
49
 
61
- if (entry.meta?.S) {
62
- const scope = entry.meta?.S;
63
- const scopeName = scope.name || getDebugName(scope);
64
- const processPrefix = entry.meta.S?.hostSessionId ? '[worker] ' : '';
65
- // TODO(dmaretskyi): Those can be made clickable with a custom formatter.
66
- args.push(`%c${processPrefix}${scopeName}`, 'color:#C026D3;font-weight:bold');
50
+ const scope = entry.meta?.S;
51
+ if (scope) {
52
+ const scopeName = scope.name || scopeDebugName;
53
+ if (scopeName) {
54
+ const processPrefix = scope.hostSessionId ? '[worker] ' : '';
55
+ // TODO(dmaretskyi): Those can be made clickable with a custom formatter.
56
+ args.push(`%c${processPrefix}${scopeName}`, 'color:#C026D3;font-weight:bold');
57
+ }
67
58
  }
68
59
 
69
60
  if (entry.message) {
@@ -73,7 +64,9 @@ const APP_BROWSER_PROCESSOR: LogProcessor = (config, entry) => {
73
64
  const context = getContextFromEntry(entry);
74
65
  if (context) {
75
66
  if (Object.keys(context).length === 1 && 'error' in context) {
76
- args.push(context.error);
67
+ args.push(unwrapEffectError(context.error));
68
+ } else if (Object.keys(context).length === 1 && 'err' in context) {
69
+ args.push(unwrapEffectError(context.err));
77
70
  } else {
78
71
  args.push(context);
79
72
  }
@@ -112,10 +105,8 @@ const TEST_BROWSER_PROCESSOR: LogProcessor = (config, entry) => {
112
105
  return;
113
106
  }
114
107
 
115
- let path = '';
116
- if (entry.meta) {
117
- path = `${getRelativeFilename(entry.meta.F)}:${entry.meta.L}`;
118
- }
108
+ const { filename, line: lineNumber } = entry.computedMeta;
109
+ const path = filename !== undefined && lineNumber !== undefined ? `${filename}:${lineNumber}` : '';
119
110
 
120
111
  let args = [];
121
112
 
@@ -146,3 +137,13 @@ const TEST_BROWSER_PROCESSOR: LogProcessor = (config, entry) => {
146
137
  };
147
138
 
148
139
  export const BROWSER_PROCESSOR: LogProcessor = CONFIG.useTestProcessor ? TEST_BROWSER_PROCESSOR : APP_BROWSER_PROCESSOR;
140
+
141
+ // effect-specific
142
+ const originalSymbol = Symbol.for('effect/OriginalAnnotation');
143
+
144
+ const unwrapEffectError = (error: any) => {
145
+ if (typeof error === 'object' && error !== null && originalSymbol in error) {
146
+ return error[originalSymbol];
147
+ }
148
+ return error;
149
+ };
@@ -2,17 +2,14 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import { inspect } from 'node:util';
6
-
7
5
  import chalk from 'chalk';
6
+ import { inspect } from 'node:util';
8
7
 
9
8
  import { getPrototypeSpecificInstanceId, pickBy } from '@dxos/util';
10
9
 
11
10
  import { type LogConfig, LogLevel, shortLevelName } from '../config';
12
11
  import { type LogProcessor, getContextFromEntry, shouldLog } from '../context';
13
12
 
14
- import { getRelativeFilename } from './common';
15
-
16
13
  const LEVEL_COLORS: Record<LogLevel, typeof chalk.ForegroundColor> = {
17
14
  [LogLevel.TRACE]: 'gray',
18
15
  [LogLevel.DEBUG]: 'gray',
@@ -95,22 +92,17 @@ export const CONSOLE_PROCESSOR: LogProcessor = (config, entry) => {
95
92
  return;
96
93
  }
97
94
 
95
+ const { filename, line: lineNumber } = entry.computedMeta;
98
96
  const parts: FormatParts = {
99
97
  level,
100
98
  message,
101
99
  error,
102
- path: undefined,
103
- line: undefined,
104
- scope: undefined,
100
+ path: filename,
101
+ line: lineNumber,
102
+ scope: meta?.S,
105
103
  context: undefined,
106
104
  };
107
105
 
108
- if (meta) {
109
- parts.path = getRelativeFilename(meta.F);
110
- parts.line = meta.L;
111
- parts.scope = meta.S;
112
- }
113
-
114
106
  const context = getContextFromEntry(entry);
115
107
  if (context) {
116
108
  // Remove undefined fields.