Skip to content

Commit 9f55277

Browse files
authored
Add sdkComponent and taskQueue metadata on log messages (#1401)
1 parent e41596b commit 9f55277

File tree

19 files changed

+324
-160
lines changed

19 files changed

+324
-160
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
matrix:
1818
reuse-v8-context: [true]
1919
node: [14, 16, 18, 20]
20-
os: [ubuntu-latest, macos-latest, windows-latest]
20+
os: [ubuntu-latest, macos-12, windows-latest]
2121
include:
2222
- os: ubuntu-latest
2323
node: 20
@@ -148,9 +148,9 @@ jobs:
148148
- runsOn: buildjet-2vcpu-ubuntu-1804
149149
target: aarch64-unknown-linux-gnu
150150
rustflags: '-C linker=aarch64-linux-gnu-gcc'
151-
- runsOn: macos-latest
151+
- runsOn: macos-12
152152
target: x86_64-apple-darwin
153-
- runsOn: macos-latest
153+
- runsOn: macos-12
154154
target: aarch64-apple-darwin
155155
- runsOn: windows-latest
156156
target: x86_64-pc-windows-msvc
@@ -216,7 +216,7 @@ jobs:
216216
node: 20
217217
server: local
218218
sample: fetch-esm
219-
- os: macos-latest
219+
- os: macos-12
220220
target: x86_64-apple-darwin
221221
node: 20
222222
server: cloud

packages/activity/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,8 +254,8 @@ export class Context {
254254
* The logger for this Activity.
255255
*
256256
* This defaults to the `Runtime`'s Logger (see {@link Runtime.logger}). Attributes from the current Activity context
257-
* will automatically be included as metadata on every log entries, and some key events of the Activity's lifecycle
258-
* will automatically be logged (at 'DEBUG' level for most messages; 'WARN' for failures).
257+
* are automatically included as metadata on every log entries. An extra `sdkComponent` metadata attribute is also
258+
* added, with value `activity`; this can be used for fine-grained filtering of log entries further downstream.
259259
*
260260
* To customize log attributes, register a {@link ActivityOutboundCallsInterceptor} that intercepts the
261261
* `getLogAttributes()` method.

packages/common/src/logger.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,43 @@ export interface Logger {
1313
warn(message: string, meta?: LogMetadata): any;
1414
error(message: string, meta?: LogMetadata): any;
1515
}
16+
17+
/**
18+
* Possible values of the `sdkComponent` meta attributes on log messages. This
19+
* attribute indicates which subsystem emitted the log message; this may for
20+
* example be used to implement fine-grained filtering of log messages.
21+
*
22+
* Note that there is no guarantee that this list will remain stable in the
23+
* future; values may be added or removed, and messages that are currently
24+
* emitted with some `sdkComponent` value may use a different value in the future.
25+
*/
26+
export enum SdkComponent {
27+
/**
28+
* Component name for messages emited from Workflow code, using the {@link Workflow context logger|workflow.log}.
29+
* The SDK itself never publishes messages with this component name.
30+
*/
31+
workflow = 'workflow',
32+
33+
/**
34+
* Component name for messages emited from an activity, using the {@link activity context logger|Context.log}.
35+
* The SDK itself never publishes messages with this component name.
36+
*/
37+
activity = 'activity',
38+
39+
/**
40+
* Component name for messages emited from a Temporal Worker instance.
41+
*
42+
* This notably includes:
43+
* - Issues with Worker or runtime configuration, or the JS execution environment;
44+
* - Worker's, Activity's, and Workflow's lifecycle events;
45+
* - Workflow Activation and Activity Task processing events;
46+
* - Workflow bundling messages;
47+
* - Sink processing issues.
48+
*/
49+
worker = 'worker',
50+
51+
/**
52+
* Component name for all messages emitted by the Rust Core SDK library.
53+
*/
54+
core = 'core',
55+
}

packages/test/src/mock-native-worker.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
11
/* eslint-disable @typescript-eslint/no-non-null-assertion */
22
import { lastValueFrom } from 'rxjs';
3-
import { defaultPayloadConverter, fromPayloadsAtIndex } from '@temporalio/common';
3+
import { SdkComponent, defaultPayloadConverter, fromPayloadsAtIndex } from '@temporalio/common';
44
import { msToTs } from '@temporalio/common/lib/time';
55
import { coresdk } from '@temporalio/proto';
66
import { DefaultLogger, Runtime, ShutdownError } from '@temporalio/worker';
77
import { byteArrayToBuffer } from '@temporalio/worker/lib/utils';
88
import { NativeReplayHandle, NativeWorkerLike, Worker as RealWorker } from '@temporalio/worker/lib/worker';
9-
import {
10-
addDefaultWorkerOptions,
11-
CompiledWorkerOptions,
12-
compileWorkerOptions,
13-
WorkerOptions,
14-
} from '@temporalio/worker/lib/worker-options';
9+
import { withMetadata } from '@temporalio/worker/lib/logger';
10+
import { CompiledWorkerOptions, compileWorkerOptions, WorkerOptions } from '@temporalio/worker/lib/worker-options';
1511
import type { WorkflowCreator } from '@temporalio/worker/lib/workflow/interface';
1612
import * as activities from './activities';
1713

@@ -163,11 +159,12 @@ export class Worker extends RealWorker {
163159
}
164160

165161
public constructor(workflowCreator: WorkflowCreator, opts: CompiledWorkerOptions) {
166-
// Worker.create() accesses Runtime.instance(), which has some side effects that would not happen (or that would
167-
// happen too late) when creating a MockWorker. Force the singleton to be created now, if it doesn't already exist.
168-
Runtime.instance();
162+
const logger = withMetadata(Runtime.instance().logger, {
163+
sdkComponent: SdkComponent.worker,
164+
taskQueue: opts.taskQueue,
165+
});
169166
const nativeWorker = new MockNativeWorker();
170-
super(nativeWorker, workflowCreator, opts);
167+
super(nativeWorker, workflowCreator, opts, logger);
171168
}
172169

173170
public runWorkflows(...args: Parameters<Worker['workflow$']>): Promise<void> {
@@ -183,6 +180,10 @@ export const defaultOptions: WorkerOptions = {
183180
};
184181

185182
export function isolateFreeWorker(options: WorkerOptions = defaultOptions): Worker {
183+
const logger = withMetadata(Runtime.instance().logger, {
184+
sdkComponent: SdkComponent.worker,
185+
taskQueue: options.taskQueue ?? 'default',
186+
});
186187
return new Worker(
187188
{
188189
async createWorkflow() {
@@ -192,6 +193,6 @@ export function isolateFreeWorker(options: WorkerOptions = defaultOptions): Work
192193
/* Nothing to destroy */
193194
},
194195
},
195-
compileWorkerOptions(addDefaultWorkerOptions(options))
196+
compileWorkerOptions(options, logger)
196197
);
197198
}

packages/test/src/test-activity-log-interceptor.ts

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,47 +5,52 @@ import { MockActivityEnvironment, defaultActivityInfo } from '@temporalio/testin
55
import { isCancellation } from '@temporalio/workflow';
66
import { isAbortError } from '@temporalio/common/lib/type-helpers';
77
import * as activity from '@temporalio/activity';
8+
import { SdkComponent } from '@temporalio/common';
89
import { withZeroesHTTPServer } from './zeroes-http-server';
910
import { cancellableFetch } from './activities';
1011

1112
interface MyTestActivityContext extends activity.Context {
1213
logs: Array<LogEntry>;
1314
}
1415

15-
test.before(() => {
16-
const mockLogger = new DefaultLogger('DEBUG', (entry) => {
16+
const mockLogger = new DefaultLogger('DEBUG', (entry) => {
17+
try {
1718
(activity.Context.current() as MyTestActivityContext).logs ??= [];
1819
(activity.Context.current() as MyTestActivityContext).logs.push(entry);
19-
});
20-
Runtime.install({
21-
logger: mockLogger,
22-
});
20+
} catch (e) {
21+
// Ignore messages produced from non activity context
22+
if ((e as Error).message !== 'Activity context not initialized') throw e;
23+
}
24+
});
25+
Runtime.install({
26+
logger: mockLogger,
2327
});
2428

25-
test("Activity Context logger defaults to Runtime's Logger", async (t) => {
26-
const env = new MockActivityEnvironment({});
29+
test('Activity Context logger funnel through the parent Logger', async (t) => {
30+
const env = new MockActivityEnvironment({}, { logger: mockLogger });
2731
await env.run(async () => {
2832
activity.log.debug('log message from activity');
2933
});
3034
const logs = (env.context as MyTestActivityContext).logs;
3135
const entry = logs.find((x) => x.level === 'DEBUG' && x.message === 'log message from activity');
3236
t.not(entry, undefined);
37+
t.deepEqual(entry?.meta, { ...activityLogAttributes(defaultActivityInfo), sdkComponent: SdkComponent.activity });
3338
});
3439

3540
test('Activity Worker logs when activity starts', async (t) => {
36-
const env = new MockActivityEnvironment({});
41+
const env = new MockActivityEnvironment({}, { logger: mockLogger });
3742
await env.run(async () => {
3843
activity.log.debug('log message from activity');
3944
});
4045
const logs = (env.context as MyTestActivityContext).logs;
4146
const entry = logs.find((x) => x.level === 'DEBUG' && x.message === 'Activity started');
4247
t.not(entry, undefined);
43-
t.deepEqual(entry?.meta, activityLogAttributes(defaultActivityInfo));
48+
t.deepEqual(entry?.meta, { ...activityLogAttributes(defaultActivityInfo), sdkComponent: SdkComponent.worker });
4449
});
4550

4651
test('Activity Worker logs warning when activity fails', async (t) => {
4752
const err = new Error('Failed for test');
48-
const env = new MockActivityEnvironment({});
53+
const env = new MockActivityEnvironment({}, { logger: mockLogger });
4954
try {
5055
await env.run(async () => {
5156
throw err;
@@ -60,11 +65,11 @@ test('Activity Worker logs warning when activity fails', async (t) => {
6065
const { durationMs, error, ...rest } = entry?.meta ?? {};
6166
t.true(Number.isInteger(durationMs));
6267
t.is(err, error);
63-
t.deepEqual(rest, activityLogAttributes(defaultActivityInfo));
68+
t.deepEqual(rest, { ...activityLogAttributes(defaultActivityInfo), sdkComponent: SdkComponent.worker });
6469
});
6570

6671
test('Activity Worker logs when activity completes async', async (t) => {
67-
const env = new MockActivityEnvironment({});
72+
const env = new MockActivityEnvironment({}, { logger: mockLogger });
6873
try {
6974
await env.run(async () => {
7075
throw new activity.CompleteAsyncError();
@@ -77,11 +82,11 @@ test('Activity Worker logs when activity completes async', async (t) => {
7782
t.not(entry, undefined);
7883
const { durationMs, ...rest } = entry?.meta ?? {};
7984
t.true(Number.isInteger(durationMs));
80-
t.deepEqual(rest, activityLogAttributes(defaultActivityInfo));
85+
t.deepEqual(rest, { ...activityLogAttributes(defaultActivityInfo), sdkComponent: SdkComponent.worker });
8186
});
8287

8388
test('Activity Worker logs when activity is cancelled with promise', async (t) => {
84-
const env = new MockActivityEnvironment({});
89+
const env = new MockActivityEnvironment({}, { logger: mockLogger });
8590
env.on('heartbeat', () => env.cancel());
8691
try {
8792
await env.run(async () => {
@@ -96,11 +101,11 @@ test('Activity Worker logs when activity is cancelled with promise', async (t) =
96101
t.not(entry, undefined);
97102
const { durationMs, ...rest } = entry?.meta ?? {};
98103
t.true(Number.isInteger(durationMs));
99-
t.deepEqual(rest, activityLogAttributes(defaultActivityInfo));
104+
t.deepEqual(rest, { ...activityLogAttributes(defaultActivityInfo), sdkComponent: SdkComponent.worker });
100105
});
101106

102107
test('Activity Worker logs when activity is cancelled with signal', async (t) => {
103-
const env = new MockActivityEnvironment({});
108+
const env = new MockActivityEnvironment({}, { logger: mockLogger });
104109
env.on('heartbeat', () => env.cancel());
105110
try {
106111
await env.run(async () => {
@@ -116,7 +121,7 @@ test('Activity Worker logs when activity is cancelled with signal', async (t) =>
116121
t.not(entry, undefined);
117122
const { durationMs, ...rest } = entry?.meta ?? {};
118123
t.true(Number.isInteger(durationMs));
119-
t.deepEqual(rest, activityLogAttributes(defaultActivityInfo));
124+
t.deepEqual(rest, { ...activityLogAttributes(defaultActivityInfo), sdkComponent: SdkComponent.worker });
120125
});
121126

122127
test('(Legacy) ActivityInboundLogInterceptor does not override Context.log by default', async (t) => {
@@ -125,6 +130,7 @@ test('(Legacy) ActivityInboundLogInterceptor does not override Context.log by de
125130
{
126131
// eslint-disable-next-line deprecation/deprecation
127132
interceptors: [(ctx) => ({ inbound: new ActivityInboundLogInterceptor(ctx) })],
133+
logger: mockLogger,
128134
}
129135
);
130136
await env.run(async () => {
@@ -146,15 +152,14 @@ test('(Legacy) ActivityInboundLogInterceptor overrides Context.log if a logger i
146152
{
147153
// eslint-disable-next-line deprecation/deprecation
148154
interceptors: [(ctx) => ({ inbound: new ActivityInboundLogInterceptor(ctx, logger) })],
155+
logger: mockLogger,
149156
}
150157
);
151158
await env.run(async () => {
152159
activity.log.debug('log message from activity');
153160
});
154-
const entry1 = logs.find((x) => x.level === 'DEBUG' && x.message === 'Activity started');
155-
t.not(entry1, undefined);
156-
const entry2 = logs.find((x) => x.level === 'DEBUG' && x.message === 'log message from activity');
157-
t.not(entry2, undefined);
161+
const entry = logs.find((x) => x.level === 'DEBUG' && x.message === 'log message from activity');
162+
t.not(entry, undefined);
158163
});
159164

160165
test('(Legacy) ActivityInboundLogInterceptor overrides Context.log if class is extended', async (t) => {
@@ -172,6 +177,7 @@ test('(Legacy) ActivityInboundLogInterceptor overrides Context.log if class is e
172177
{},
173178
{
174179
interceptors: [(ctx) => ({ inbound: new CustomActivityInboundLogInterceptor(ctx) })],
180+
logger: mockLogger,
175181
}
176182
);
177183
await env.run(async () => {

packages/test/src/test-mockactivityenv.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
import test from 'ava';
22
import { MockActivityEnvironment } from '@temporalio/testing';
3-
import { CancelledFailure, Context } from '@temporalio/activity';
3+
import * as activity from '@temporalio/activity';
4+
import { Runtime } from '@temporalio/worker';
5+
6+
test("MockActivityEnvironment doesn't implicitly instantiate Runtime", async (t) => {
7+
t.is(Runtime._instance, undefined);
8+
const env = new MockActivityEnvironment();
9+
await env.run(async (): Promise<void> => {
10+
activity.log.info('log message from activity');
11+
});
12+
t.is(Runtime._instance, undefined);
13+
});
414

515
test('MockActivityEnvironment can run a single activity', async (t) => {
616
const env = new MockActivityEnvironment();
@@ -19,12 +29,12 @@ test('MockActivityEnvironment emits heartbeat events and can be cancelled', asyn
1929
});
2030
await t.throwsAsync(
2131
env.run(async (x: number): Promise<number> => {
22-
Context.current().heartbeat(6);
23-
await Context.current().sleep(100);
32+
activity.heartbeat(6);
33+
await activity.sleep(100);
2434
return x + 1;
2535
}, 3),
2636
{
27-
instanceOf: CancelledFailure,
37+
instanceOf: activity.CancelledFailure,
2838
message: 'test',
2939
}
3040
);
@@ -33,7 +43,7 @@ test('MockActivityEnvironment emits heartbeat events and can be cancelled', asyn
3343
test('MockActivityEnvironment injects provided info', async (t) => {
3444
const env = new MockActivityEnvironment({ attempt: 3 });
3545
const res = await env.run(async (x: number): Promise<number> => {
36-
return x + Context.current().info.attempt;
46+
return x + activity.activityInfo().attempt;
3747
}, 1);
3848
t.is(res, 4);
3949
});

packages/test/src/test-runtime.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ if (RUN_INTEGRATION_TESTS) {
112112
t.is(typeof failingWftEntry.meta?.['failure'], 'string');
113113
t.is(typeof failingWftEntry.meta?.['runId'], 'string');
114114
t.is(typeof failingWftEntry.meta?.['workflowId'], 'string');
115-
t.is(typeof failingWftEntry.meta?.['subsystem'], 'string');
115+
t.is(typeof failingWftEntry.meta?.['sdkComponent'], 'string');
116116
} finally {
117117
await Runtime.instance().shutdown();
118118
}

packages/test/src/test-sinks.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
import { LoggerSinksInternal as DefaultLoggerSinks } from '@temporalio/workflow/lib/logs';
1414
import { SearchAttributes, WorkflowInfo } from '@temporalio/workflow';
1515
import { UnsafeWorkflowInfo } from '@temporalio/workflow/src/interfaces';
16+
import { SdkComponent } from '@temporalio/common';
1617
import { RUN_INTEGRATION_TESTS, Worker, registerDefaultCustomSearchAttributes } from './helpers';
1718
import { defaultOptions } from './mock-native-worker';
1819
import * as workflows from './workflows';
@@ -166,6 +167,10 @@ if (RUN_INTEGRATION_TESTS) {
166167
meta: {
167168
...x.meta,
168169
workflowInfo: fixWorkflowInfoDates(x.meta?.workflowInfo),
170+
namespace: info.namespace,
171+
runId: info.runId,
172+
workflowId: info.workflowId,
173+
workflowType: info.workflowType,
169174
},
170175
timestampNanos: undefined,
171176
})),
@@ -177,6 +182,12 @@ if (RUN_INTEGRATION_TESTS) {
177182
ifaceName: error.ifaceName,
178183
fnName: error.fnName,
179184
workflowInfo: info,
185+
sdkComponent: SdkComponent.worker,
186+
taskQueue,
187+
namespace: info.namespace,
188+
runId: info.runId,
189+
workflowId: info.workflowId,
190+
workflowType: info.workflowType,
180191
},
181192
timestampNanos: undefined,
182193
}))

packages/test/src/test-workflows.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
ApplicationFailure,
99
defaultFailureConverter,
1010
defaultPayloadConverter,
11+
SdkComponent,
1112
Payload,
1213
RetryState,
1314
toPayloads,
@@ -1578,6 +1579,7 @@ test('logAndTimeout', async (t) => {
15781579
taskQueue: 'test',
15791580
workflowId: 'test-workflowId',
15801581
workflowType: 'logAndTimeout',
1582+
sdkComponent: SdkComponent.worker,
15811583
},
15821584
],
15831585
},
@@ -1592,6 +1594,7 @@ test('logAndTimeout', async (t) => {
15921594
taskQueue: 'test',
15931595
workflowId: 'test-workflowId',
15941596
workflowType: 'logAndTimeout',
1597+
sdkComponent: SdkComponent.workflow,
15951598
},
15961599
],
15971600
},

0 commit comments

Comments
 (0)