@outputai/core 0.6.0 → 0.6.1-dev.aab2335.0

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 (51) hide show
  1. package/package.json +2 -2
  2. package/src/activity_integration/context.d.ts +5 -9
  3. package/src/activity_integration/context.js +5 -4
  4. package/src/activity_integration/context.spec.js +10 -15
  5. package/src/activity_integration/events.d.ts +2 -4
  6. package/src/activity_integration/events.js +8 -3
  7. package/src/activity_integration/events.spec.js +58 -29
  8. package/src/bus.js +18 -9
  9. package/src/bus.spec.js +30 -0
  10. package/src/hooks/index.d.ts +112 -58
  11. package/src/hooks/index.js +15 -12
  12. package/src/hooks/index.spec.js +60 -32
  13. package/src/interface/workflow.js +19 -35
  14. package/src/interface/workflow.spec.js +104 -15
  15. package/src/internal_activities/index.js +3 -3
  16. package/src/internal_activities/index.spec.js +31 -1
  17. package/src/internal_utils/temporal_context.js +12 -0
  18. package/src/internal_utils/temporal_context.spec.ts +83 -0
  19. package/src/internal_utils/trace_info.js +21 -0
  20. package/src/internal_utils/trace_info.spec.js +47 -0
  21. package/src/internal_utils/workflow_context.js +29 -0
  22. package/src/internal_utils/workflow_context.spec.js +46 -0
  23. package/src/logger/development.js +61 -0
  24. package/src/logger/development.spec.js +70 -0
  25. package/src/logger/index.js +14 -0
  26. package/src/logger/index.spec.js +27 -0
  27. package/src/logger/production.js +15 -0
  28. package/src/logger/production.spec.js +52 -0
  29. package/src/tracing/internal_interface.js +4 -4
  30. package/src/tracing/processors/local/index.js +21 -26
  31. package/src/tracing/processors/local/index.spec.js +39 -45
  32. package/src/tracing/processors/s3/index.js +13 -23
  33. package/src/tracing/processors/s3/index.spec.js +33 -26
  34. package/src/tracing/trace_attribute.js +0 -1
  35. package/src/tracing/trace_engine.js +8 -12
  36. package/src/tracing/trace_engine.spec.js +31 -27
  37. package/src/worker/configs.js +2 -0
  38. package/src/worker/configs.spec.js +25 -0
  39. package/src/worker/index.js +4 -1
  40. package/src/worker/index.spec.js +4 -0
  41. package/src/worker/interceptors/activity.js +31 -29
  42. package/src/worker/interceptors/activity.spec.js +58 -26
  43. package/src/worker/interceptors/workflow.js +7 -2
  44. package/src/worker/interceptors/workflow.spec.js +42 -6
  45. package/src/worker/log_hooks.js +35 -46
  46. package/src/worker/log_hooks.spec.js +43 -46
  47. package/src/worker/setup_telemetry.js +19 -0
  48. package/src/worker/setup_telemetry.spec.js +80 -0
  49. package/src/worker/sinks.js +24 -24
  50. package/src/interface/workflow_context.js +0 -33
  51. package/src/logger.js +0 -73
@@ -1,7 +1,7 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import {
3
+ ACTIVITY_GET_TRACE_DESTINATIONS,
3
4
  BusEventType,
4
- ComponentType,
5
5
  LifecycleEvent,
6
6
  WORKFLOW_CATALOG
7
7
  } from '#consts';
@@ -30,12 +30,15 @@ describe( 'log_hooks', () => {
30
30
  } );
31
31
 
32
32
  describe( 'activity events', () => {
33
+ const activityInfo = {
34
+ activityId: 'act-1',
35
+ activityType: 'myWorkflow#myStep',
36
+ workflowExecution: { workflowId: 'wf-1', runId: 'run-1' },
37
+ workflowType: 'myWorkflow'
38
+ };
33
39
  const basePayload = {
34
- id: 'act-1',
35
- name: 'myWorkflow#myStep',
36
- kind: 'step',
37
- workflowId: 'wf-1',
38
- workflowName: 'myWorkflow'
40
+ activityInfo,
41
+ outputActivityKind: 'step'
39
42
  };
40
43
 
41
44
  it( 'ACTIVITY_START logs full message and second arg', () => {
@@ -47,18 +50,18 @@ describe( 'log_hooks', () => {
47
50
  {
48
51
  event: LifecycleEvent.START,
49
52
  activityId: 'act-1',
50
- activityName: 'myWorkflow#myStep',
51
- activityKind: 'step',
53
+ activityType: 'myWorkflow#myStep',
52
54
  workflowId: 'wf-1',
53
- workflowName: 'myWorkflow'
55
+ workflowType: 'myWorkflow',
56
+ runId: 'run-1'
54
57
  }
55
58
  );
56
59
  } );
57
60
 
58
- it( 'ACTIVITY_START does not log when kind is INTERNAL_STEP', () => {
61
+ it( 'ACTIVITY_START does not log trace destination activity', () => {
59
62
  onHandlers[BusEventType.ACTIVITY_START]( {
60
63
  ...basePayload,
61
- kind: ComponentType.INTERNAL_STEP
64
+ activityInfo: { ...activityInfo, activityType: ACTIVITY_GET_TRACE_DESTINATIONS }
62
65
  } );
63
66
 
64
67
  expect( activityLogMock.info ).not.toHaveBeenCalled();
@@ -73,20 +76,18 @@ describe( 'log_hooks', () => {
73
76
  {
74
77
  event: LifecycleEvent.END,
75
78
  activityId: 'act-1',
76
- activityName: 'myWorkflow#myStep',
77
- activityKind: 'step',
79
+ activityType: 'myWorkflow#myStep',
78
80
  workflowId: 'wf-1',
79
- workflowName: 'myWorkflow',
80
- durationMs: 42
81
+ workflowType: 'myWorkflow',
82
+ runId: 'run-1'
81
83
  }
82
84
  );
83
85
  } );
84
86
 
85
- it( 'ACTIVITY_END does not log when kind is INTERNAL_STEP', () => {
87
+ it( 'ACTIVITY_END does not log trace destination activity', () => {
86
88
  onHandlers[BusEventType.ACTIVITY_END]( {
87
89
  ...basePayload,
88
- kind: ComponentType.INTERNAL_STEP,
89
- duration: 10
90
+ activityInfo: { ...activityInfo, activityType: ACTIVITY_GET_TRACE_DESTINATIONS }
90
91
  } );
91
92
 
92
93
  expect( activityLogMock.info ).not.toHaveBeenCalled();
@@ -106,21 +107,19 @@ describe( 'log_hooks', () => {
106
107
  {
107
108
  event: LifecycleEvent.ERROR,
108
109
  activityId: 'act-1',
109
- activityName: 'myWorkflow#myStep',
110
- activityKind: 'step',
110
+ activityType: 'myWorkflow#myStep',
111
111
  workflowId: 'wf-1',
112
- workflowName: 'myWorkflow',
113
- durationMs: 100,
112
+ workflowType: 'myWorkflow',
113
+ runId: 'run-1',
114
114
  error: 'step failed'
115
115
  }
116
116
  );
117
117
  } );
118
118
 
119
- it( 'ACTIVITY_ERROR does not log when kind is INTERNAL_STEP', () => {
119
+ it( 'ACTIVITY_ERROR does not log trace destination activity', () => {
120
120
  onHandlers[BusEventType.ACTIVITY_ERROR]( {
121
121
  ...basePayload,
122
- kind: ComponentType.INTERNAL_STEP,
123
- duration: 5,
122
+ activityInfo: { ...activityInfo, activityType: ACTIVITY_GET_TRACE_DESTINATIONS },
124
123
  error: new Error( 'x' )
125
124
  } );
126
125
 
@@ -129,7 +128,12 @@ describe( 'log_hooks', () => {
129
128
  } );
130
129
 
131
130
  describe( 'workflow events', () => {
132
- const basePayload = { id: 'wf-1', name: 'myWorkflow' };
131
+ const workflowDetails = {
132
+ workflowId: 'wf-1',
133
+ workflowType: 'myWorkflow',
134
+ runId: 'run-1'
135
+ };
136
+ const basePayload = { workflowDetails };
133
137
 
134
138
  it( 'WORKFLOW_START logs full message and second arg', () => {
135
139
  onHandlers[BusEventType.WORKFLOW_START]( basePayload );
@@ -140,25 +144,22 @@ describe( 'log_hooks', () => {
140
144
  {
141
145
  event: LifecycleEvent.START,
142
146
  workflowId: 'wf-1',
143
- workflowName: 'myWorkflow'
147
+ workflowType: 'myWorkflow',
148
+ runId: 'run-1'
144
149
  }
145
150
  );
146
151
  } );
147
152
 
148
- it( 'WORKFLOW_START does not log when name is WORKFLOW_CATALOG', () => {
153
+ it( 'WORKFLOW_START does not log when workflowType is WORKFLOW_CATALOG', () => {
149
154
  onHandlers[BusEventType.WORKFLOW_START]( {
150
- id: 'cat-1',
151
- name: WORKFLOW_CATALOG
155
+ workflowDetails: { ...workflowDetails, workflowType: WORKFLOW_CATALOG }
152
156
  } );
153
157
 
154
158
  expect( workflowLogMock.info ).not.toHaveBeenCalled();
155
159
  } );
156
160
 
157
161
  it( 'WORKFLOW_END logs full message and second arg', () => {
158
- onHandlers[BusEventType.WORKFLOW_END]( {
159
- ...basePayload,
160
- duration: 200
161
- } );
162
+ onHandlers[BusEventType.WORKFLOW_END]( basePayload );
162
163
 
163
164
  expect( workflowLogMock.info ).toHaveBeenCalledTimes( 1 );
164
165
  expect( workflowLogMock.info ).toHaveBeenCalledWith(
@@ -166,17 +167,15 @@ describe( 'log_hooks', () => {
166
167
  {
167
168
  event: LifecycleEvent.END,
168
169
  workflowId: 'wf-1',
169
- workflowName: 'myWorkflow',
170
- durationMs: 200
170
+ workflowType: 'myWorkflow',
171
+ runId: 'run-1'
171
172
  }
172
173
  );
173
174
  } );
174
175
 
175
- it( 'WORKFLOW_END does not log when name is WORKFLOW_CATALOG', () => {
176
+ it( 'WORKFLOW_END does not log when workflowType is WORKFLOW_CATALOG', () => {
176
177
  onHandlers[BusEventType.WORKFLOW_END]( {
177
- id: 'cat-1',
178
- name: WORKFLOW_CATALOG,
179
- duration: 50
178
+ workflowDetails: { ...workflowDetails, workflowType: WORKFLOW_CATALOG }
180
179
  } );
181
180
 
182
181
  expect( workflowLogMock.info ).not.toHaveBeenCalled();
@@ -196,18 +195,16 @@ describe( 'log_hooks', () => {
196
195
  {
197
196
  event: LifecycleEvent.ERROR,
198
197
  workflowId: 'wf-1',
199
- workflowName: 'myWorkflow',
200
- durationMs: 150,
198
+ workflowType: 'myWorkflow',
199
+ runId: 'run-1',
201
200
  error: 'workflow boom'
202
201
  }
203
202
  );
204
203
  } );
205
204
 
206
- it( 'WORKFLOW_ERROR does not log when name is WORKFLOW_CATALOG', () => {
205
+ it( 'WORKFLOW_ERROR does not log when workflowType is WORKFLOW_CATALOG', () => {
207
206
  onHandlers[BusEventType.WORKFLOW_ERROR]( {
208
- id: 'cat-1',
209
- name: WORKFLOW_CATALOG,
210
- duration: 1,
207
+ workflowDetails: { ...workflowDetails, workflowType: WORKFLOW_CATALOG },
211
208
  error: new Error( 'x' )
212
209
  } );
213
210
 
@@ -0,0 +1,19 @@
1
+ import { createChildLogger } from '#logger';
2
+ import { workerTelemetryIntervalMs } from './configs.js';
3
+
4
+ const log = createChildLogger( 'Telemetry' );
5
+
6
+ export const setupTelemetry = ( { worker } ) => {
7
+ if ( workerTelemetryIntervalMs > 0 ) {
8
+ setInterval( () => {
9
+ log.info( 'Worker', {
10
+ status: worker.getStatus(),
11
+ memory: {
12
+ availableMemory: process.availableMemory(),
13
+ constrainedMemory: process.constrainedMemory(),
14
+ memoryUsage: process.memoryUsage()
15
+ }
16
+ } );
17
+ }, workerTelemetryIntervalMs ).unref();
18
+ }
19
+ };
@@ -0,0 +1,80 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ const configMock = vi.hoisted( () => ( { workerTelemetryIntervalMs: 0 } ) );
4
+ const logMock = vi.hoisted( () => ( { info: vi.fn() } ) );
5
+ const createChildLoggerMock = vi.hoisted( () => vi.fn( () => logMock ) );
6
+
7
+ vi.mock( './configs.js', () => ( {
8
+ get workerTelemetryIntervalMs() {
9
+ return configMock.workerTelemetryIntervalMs;
10
+ }
11
+ } ) );
12
+
13
+ vi.mock( '#logger', () => ( { createChildLogger: createChildLoggerMock } ) );
14
+
15
+ const loadSetupTelemetry = async () => {
16
+ vi.resetModules();
17
+ return import( './setup_telemetry.js' );
18
+ };
19
+
20
+ const mockSetInterval = unrefMock =>
21
+ vi.spyOn( globalThis, 'setInterval' ).mockReturnValue( { unref: unrefMock } );
22
+
23
+ describe( 'worker/setup_telemetry', () => {
24
+ const availableMemoryMock = vi.fn();
25
+ const constrainedMemoryMock = vi.fn();
26
+ const memoryUsageMock = vi.fn();
27
+ const unrefMock = vi.fn();
28
+
29
+ beforeEach( () => {
30
+ vi.clearAllMocks();
31
+ configMock.workerTelemetryIntervalMs = 0;
32
+
33
+ availableMemoryMock.mockReturnValue( 1_000 );
34
+ constrainedMemoryMock.mockReturnValue( 2_000 );
35
+ memoryUsageMock.mockReturnValue( { heapUsed: 300 } );
36
+
37
+ vi.spyOn( process, 'availableMemory' ).mockImplementation( availableMemoryMock );
38
+ vi.spyOn( process, 'constrainedMemory' ).mockImplementation( constrainedMemoryMock );
39
+ vi.spyOn( process, 'memoryUsage' ).mockImplementation( memoryUsageMock );
40
+ } );
41
+
42
+ afterEach( () => {
43
+ vi.restoreAllMocks();
44
+ } );
45
+
46
+ it( 'does not create an interval when telemetry interval is disabled', async () => {
47
+ const setIntervalMock = mockSetInterval( unrefMock );
48
+ const { setupTelemetry } = await loadSetupTelemetry();
49
+
50
+ setupTelemetry( { worker: { getStatus: vi.fn() } } );
51
+
52
+ expect( setIntervalMock ).not.toHaveBeenCalled();
53
+ expect( logMock.info ).not.toHaveBeenCalled();
54
+ } );
55
+
56
+ it( 'logs worker status and memory on the configured interval', async () => {
57
+ const setIntervalMock = mockSetInterval( unrefMock );
58
+ configMock.workerTelemetryIntervalMs = 5_000;
59
+ const worker = { getStatus: vi.fn().mockReturnValue( { runState: 'RUNNING' } ) };
60
+ const { setupTelemetry } = await loadSetupTelemetry();
61
+
62
+ setupTelemetry( { worker } );
63
+
64
+ expect( createChildLoggerMock ).toHaveBeenCalledWith( 'Telemetry' );
65
+ expect( setIntervalMock ).toHaveBeenCalledWith( expect.any( Function ), 5_000 );
66
+ expect( unrefMock ).toHaveBeenCalled();
67
+
68
+ const [ callback ] = setIntervalMock.mock.calls[0];
69
+ callback();
70
+
71
+ expect( logMock.info ).toHaveBeenCalledWith( 'Worker', {
72
+ status: { runState: 'RUNNING' },
73
+ memory: {
74
+ availableMemory: 1_000,
75
+ constrainedMemory: 2_000,
76
+ memoryUsage: { heapUsed: 300 }
77
+ }
78
+ } );
79
+ } );
80
+ } );
@@ -1,6 +1,7 @@
1
1
  import { BusEventType, ComponentType } from '#consts';
2
2
  import * as Tracing from '#tracing';
3
3
  import { messageBus } from '#bus';
4
+ import { createWorkflowDetails } from '#internal_utils/temporal_context';
4
5
 
5
6
  // This sink allow for sandbox Temporal environment to send trace logs back to the main thread.
6
7
  export const sinks = {
@@ -11,10 +12,17 @@ export const sinks = {
11
12
  workflow: {
12
13
  start: {
13
14
  fn: ( workflowInfo, input ) => {
14
- const { workflowId: id, runId, workflowType: name, memo: { parentId, executionContext } } = workflowInfo;
15
- messageBus.emit( BusEventType.WORKFLOW_START, { id, runId, name } );
16
- if ( executionContext ) { // filters out internal workflows
17
- Tracing.addEventStart( { id, kind: ComponentType.WORKFLOW, name, details: input, parentId, executionContext } );
15
+ const { runId, workflowType, memo: { traceInfo }, parent } = workflowInfo;
16
+ messageBus.emit( BusEventType.WORKFLOW_START, { workflowDetails: createWorkflowDetails( workflowInfo ) } );
17
+ if ( traceInfo ) { // internal workflows (catalog) do not have this info
18
+ Tracing.addEventStart( {
19
+ id: runId,
20
+ kind: ComponentType.WORKFLOW,
21
+ name: workflowType,
22
+ details: input,
23
+ parentId: parent?.runId,
24
+ traceInfo
25
+ } );
18
26
  }
19
27
  },
20
28
  callDuringReplay: false
@@ -22,10 +30,10 @@ export const sinks = {
22
30
 
23
31
  end: {
24
32
  fn: ( workflowInfo, output ) => {
25
- const { workflowId: id, runId, workflowType: name, startTime, memo: { executionContext } } = workflowInfo;
26
- messageBus.emit( BusEventType.WORKFLOW_END, { id, runId, name, duration: Date.now() - startTime.getTime() } );
27
- if ( executionContext ) { // filters out internal workflows
28
- Tracing.addEventEnd( { id, details: output, executionContext } );
33
+ const { runId, memo: { traceInfo } } = workflowInfo;
34
+ messageBus.emit( BusEventType.WORKFLOW_END, { workflowDetails: createWorkflowDetails( workflowInfo ) } );
35
+ if ( traceInfo ) { // internal workflows (catalog) do not have this info
36
+ Tracing.addEventEnd( { id: runId, details: output, traceInfo } );
29
37
  }
30
38
  },
31
39
  callDuringReplay: false
@@ -33,10 +41,10 @@ export const sinks = {
33
41
 
34
42
  error: {
35
43
  fn: ( workflowInfo, error ) => {
36
- const { workflowId: id, runId, workflowType: name, startTime, memo: { executionContext } } = workflowInfo;
37
- messageBus.emit( BusEventType.WORKFLOW_ERROR, { id, runId, name, error, duration: Date.now() - startTime.getTime() } );
38
- if ( executionContext ) { // filters out internal workflows
39
- Tracing.addEventError( { id, details: error, executionContext } );
44
+ const { runId, memo: { traceInfo } } = workflowInfo;
45
+ messageBus.emit( BusEventType.WORKFLOW_ERROR, { workflowDetails: createWorkflowDetails( workflowInfo ), error } );
46
+ if ( traceInfo ) { // internal workflows (catalog) do not have this info
47
+ Tracing.addEventError( { id: runId, details: error, traceInfo } );
40
48
  }
41
49
  },
42
50
  callDuringReplay: false
@@ -48,26 +56,18 @@ export const sinks = {
48
56
  */
49
57
  trace: {
50
58
  start: {
51
- fn: ( workflowInfo, { id, name, kind, details } ) => {
52
- const { memo: { executionContext, parentId } } = workflowInfo;
53
- Tracing.addEventStart( { id, kind, name, details, parentId, executionContext } );
54
- },
59
+ fn: ( workflowInfo, { id, name, kind, details } ) =>
60
+ Tracing.addEventStart( { id, kind, name, details, parentId: workflowInfo.parent?.runId, traceInfo: workflowInfo.memo.traceInfo } ),
55
61
  callDuringReplay: false
56
62
  },
57
63
 
58
64
  end: {
59
- fn: ( workflowInfo, { id, details } ) => {
60
- const { memo: { executionContext } } = workflowInfo;
61
- Tracing.addEventEnd( { id, details, executionContext } );
62
- },
65
+ fn: ( workflowInfo, { id, details } ) => Tracing.addEventEnd( { id, details, traceInfo: workflowInfo.memo.traceInfo } ),
63
66
  callDuringReplay: false
64
67
  },
65
68
 
66
69
  error: {
67
- fn: ( workflowInfo, { id, details } ) => {
68
- const { memo: { executionContext } } = workflowInfo;
69
- Tracing.addEventError( { id, details, executionContext } );
70
- },
70
+ fn: ( workflowInfo, { id, details } ) => Tracing.addEventError( { id, details, traceInfo: workflowInfo.memo.traceInfo } ),
71
71
  callDuringReplay: false
72
72
  }
73
73
  }
@@ -1,33 +0,0 @@
1
- /**
2
- * Context instance builder
3
- */
4
- export class Context {
5
-
6
- /**
7
- * Builds a new context instance
8
- * @param {object} options - Arguments to build a new context instance
9
- * @param {string} workflowId
10
- * @param {string} runId
11
- * @param {function} continueAsNew
12
- * @param {function} isContinueAsNewSuggested
13
- * @returns {object} context
14
- */
15
- static build( { workflowId, runId, continueAsNew, isContinueAsNewSuggested } ) {
16
- return {
17
- /**
18
- * Control namespace: This object adds functions to interact with Temporal flow mechanisms
19
- */
20
- control: {
21
- continueAsNew,
22
- isContinueAsNewSuggested
23
- },
24
- /**
25
- * Info namespace: abstracts workflowInfo()
26
- */
27
- info: {
28
- workflowId,
29
- runId
30
- }
31
- };
32
- }
33
- };
package/src/logger.js DELETED
@@ -1,73 +0,0 @@
1
- import winston from 'winston';
2
- import { shuffleArray } from '#utils';
3
-
4
- const isProduction = process.env.NODE_ENV === 'production';
5
-
6
- const levels = {
7
- error: 0,
8
- warn: 1,
9
- info: 2,
10
- http: 3,
11
- debug: 4
12
- };
13
-
14
- const colors = shuffleArray( [
15
- '033', // blue
16
- '030', // green
17
- '208', // orange
18
- '045', // turquoise
19
- '129', // purple
20
- '184' // yellow
21
- ] );
22
- const assignedColors = new Map();
23
-
24
- // Format metadata as friendly JSON: "{ name: "foo", count: 5 }"
25
- const formatMeta = obj => {
26
- const entries = Object.entries( obj );
27
- if ( !entries.length ) {
28
- return '';
29
- }
30
- return ' { ' + entries.map( ( [ k, v ] ) => `${k}: ${JSON.stringify( v )}` ).join( ', ' ) + ' }';
31
- };
32
- // Distribute the namespace in a map and assign it the next available color
33
- const getColor = v =>
34
- assignedColors.has( v ) ? assignedColors.get( v ) : assignedColors.set( v, colors[assignedColors.size % colors.length] ).get( v );
35
-
36
- // Colorize a text using the namespace string
37
- const colorizeByNamespace = ( namespace, text ) => `\x1b[38;5;${getColor( namespace )}m${text}\x1b[0m`;
38
-
39
- // Development format: colorized with namespace prefix
40
- const devFormat = winston.format.combine(
41
- winston.format.colorize(),
42
- winston.format.printf( ( { level, message, namespace, service: _, environment: __, ...rest } ) => {
43
- const ns = 'Core' + ( namespace ? `.${namespace}` : '' );
44
- const meta = formatMeta( rest );
45
- return `[${level}] ${colorizeByNamespace( ns, `${namespace}: ${message}` )}${meta}`;
46
- } )
47
- );
48
-
49
- // Production format: structured JSON
50
- const prodFormat = winston.format.combine(
51
- winston.format.timestamp( { format: 'YYYY-MM-DDTHH:mm:ss.SSSZ' } ),
52
- winston.format.errors( { stack: true } ),
53
- winston.format.json()
54
- );
55
-
56
- export const logger = winston.createLogger( {
57
- levels,
58
- level: isProduction ? 'info' : 'debug',
59
- format: isProduction ? prodFormat : devFormat,
60
- defaultMeta: {
61
- service: 'output-worker',
62
- environment: process.env.NODE_ENV || 'development'
63
- },
64
- transports: [ new winston.transports.Console() ]
65
- } );
66
-
67
- /**
68
- * Creates a child logger with a specific namespace
69
- *
70
- * @param {string} namespace - The namespace for this logger (e.g., 'Scanner', 'Tracing')
71
- * @returns {winston.Logger} Child logger instance with namespace metadata
72
- */
73
- export const createChildLogger = namespace => logger.child( { namespace } );