Skip to content

Commit 0d825d8

Browse files
authored
feat(workflow): Logger improvements (#1159)
- [Make log attributes optional for defaultWorkerLogger sink](8d3a3f1) - [Attach log attributes to every workflow log message](5938b70) The approach taken in this PR (compared to the alternative #1158) attaches the log attributes in workflow context where the user has more control.
1 parent dbcc9e8 commit 0d825d8

File tree

5 files changed

+46
-19
lines changed

5 files changed

+46
-19
lines changed

packages/worker/src/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,11 @@ export {
5050
WorkflowBundlePathWithSourceMap, // eslint-disable-line deprecation/deprecation
5151
} from './worker-options';
5252
export { ReplayError, ReplayHistoriesIterable, ReplayResult } from './replay';
53-
export { WorkflowInboundLogInterceptor, workflowLogAttributes } from './workflow-log-interceptor';
53+
export {
54+
WorkflowInboundLogInterceptor, // eslint-disable-line deprecation/deprecation
55+
WorkflowLogInterceptor,
56+
workflowLogAttributes,
57+
} from './workflow-log-interceptor';
5458
export {
5559
BundleOptions,
5660
bundleWorkflowCode,

packages/worker/src/workflow-log-interceptor.ts

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import {
33
Next,
44
WorkflowExecuteInput,
55
WorkflowInboundCallsInterceptor,
6+
WorkflowOutboundCallsInterceptor,
67
workflowInfo,
78
WorkflowInfo,
89
WorkflowInterceptorsFactory,
910
log,
1011
ContinueAsNew,
12+
GetLogAttributesInput,
1113
} from '@temporalio/workflow';
1214
import { untrackPromise } from '@temporalio/workflow/lib/stack-helpers';
1315

@@ -24,32 +26,35 @@ export function workflowLogAttributes(info: WorkflowInfo): Record<string, unknow
2426
};
2527
}
2628

27-
/** Logs Workflow execution starts and completions */
28-
export class WorkflowInboundLogInterceptor implements WorkflowInboundCallsInterceptor {
29-
protected logAttributes(): Record<string, unknown> {
30-
return workflowLogAttributes(workflowInfo());
29+
/** Logs workflow execution starts and completions, attaches log attributes to `workflow.log` calls */
30+
export class WorkflowLogInterceptor implements WorkflowInboundCallsInterceptor, WorkflowOutboundCallsInterceptor {
31+
getLogAttributes(
32+
input: GetLogAttributesInput,
33+
next: Next<WorkflowOutboundCallsInterceptor, 'getLogAttributes'>
34+
): Record<string, unknown> {
35+
return next({ ...input, ...workflowLogAttributes(workflowInfo()) });
3136
}
3237

3338
execute(input: WorkflowExecuteInput, next: Next<WorkflowInboundCallsInterceptor, 'execute'>): Promise<unknown> {
34-
log.debug('Workflow started', this.logAttributes());
39+
log.debug('Workflow started');
3540
const p = next(input).then(
3641
(res) => {
37-
log.debug('Workflow completed', this.logAttributes());
42+
log.debug('Workflow completed');
3843
return res;
3944
},
4045
(error) => {
4146
// Avoid using instanceof checks in case the modules they're defined in loaded more than once,
4247
// e.g. by jest or when multiple versions are installed.
4348
if (typeof error === 'object' && error != null) {
4449
if (isCancellation(error)) {
45-
log.debug('Workflow completed as cancelled', this.logAttributes());
50+
log.debug('Workflow completed as cancelled');
4651
throw error;
4752
} else if (ContinueAsNew.is(error)) {
48-
log.debug('Workflow continued as new', this.logAttributes());
53+
log.debug('Workflow continued as new');
4954
throw error;
5055
}
5156
}
52-
log.warn('Workflow failed', { error, ...this.logAttributes() });
57+
log.warn('Workflow failed', { error });
5358
throw error;
5459
}
5560
);
@@ -59,5 +64,11 @@ export class WorkflowInboundLogInterceptor implements WorkflowInboundCallsInterc
5964
}
6065
}
6166

67+
/** @deprecated use {@link WorkflowLogInterceptor} instead */
68+
export const WorkflowInboundLogInterceptor = WorkflowLogInterceptor;
69+
6270
// ts-prune-ignore-next
63-
export const interceptors: WorkflowInterceptorsFactory = () => ({ inbound: [new WorkflowInboundLogInterceptor()] });
71+
export const interceptors: WorkflowInterceptorsFactory = () => {
72+
const interceptor = new WorkflowLogInterceptor();
73+
return { inbound: [interceptor], outbound: [interceptor] };
74+
};

packages/workflow/src/interceptors.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ export interface SignalWorkflowInput {
118118
};
119119
}
120120

121+
/** Input for WorkflowOutboundCallsInterceptor.getLogAttributes */
122+
export type GetLogAttributesInput = Record<string, unknown>;
123+
121124
/**
122125
* Implement any of these methods to intercept Workflow code calls to the Temporal APIs, like scheduling an activity and starting a timer
123126
*/
@@ -161,6 +164,13 @@ export interface WorkflowOutboundCallsInterceptor {
161164
input: StartChildWorkflowExecutionInput,
162165
next: Next<this, 'startChildWorkflowExecution'>
163166
) => Promise<[Promise<string>, Promise<unknown>]>;
167+
168+
/**
169+
* Called on each invocation of the `workflow.log` methods.
170+
*
171+
* The attributes returned in this call are attached to every log message.
172+
*/
173+
getLogAttributes?: (input: GetLogAttributesInput, next: Next<this, 'getLogAttributes'>) => Record<string, unknown>;
164174
}
165175

166176
/** Input for WorkflowInternalsInterceptor.concludeActivation */

packages/workflow/src/sinks.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@ export interface SinkCall {
4545
*/
4646
export interface LoggerSinks extends Sinks {
4747
defaultWorkerLogger: {
48-
trace(message: string, attrs: Record<string, unknown>): void;
49-
debug(message: string, attrs: Record<string, unknown>): void;
50-
info(message: string, attrs: Record<string, unknown>): void;
51-
warn(message: string, attrs: Record<string, unknown>): void;
52-
error(message: string, attrs: Record<string, unknown>): void;
48+
trace(message: string, attrs?: Record<string, unknown>): void;
49+
debug(message: string, attrs?: Record<string, unknown>): void;
50+
info(message: string, attrs?: Record<string, unknown>): void;
51+
warn(message: string, attrs?: Record<string, unknown>): void;
52+
error(message: string, attrs?: Record<string, unknown>): void;
5353
};
5454
}

packages/workflow/src/workflow.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1308,11 +1308,13 @@ export const log: LoggerSinks['defaultWorkerLogger'] = Object.fromEntries(
13081308
(['trace', 'debug', 'info', 'warn', 'error'] as Array<keyof LoggerSinks['defaultWorkerLogger']>).map((level) => {
13091309
return [
13101310
level,
1311-
(message: string, attrs: Record<string, unknown>) => {
1312-
assertInWorkflowContext('Workflow.log(...) may only be used from a Workflow Execution.)');
1311+
(message: string, attrs?: Record<string, unknown>) => {
1312+
const activator = assertInWorkflowContext('Workflow.log(...) may only be used from a Workflow Execution.');
1313+
const getLogAttributes = composeInterceptors(activator.interceptors.outbound, 'getLogAttributes', (a) => a);
13131314
return loggerSinks.defaultWorkerLogger[level](message, {
13141315
// Inject the call time in nanosecond resolution as expected by the worker logger.
1315-
[LogTimestamp]: getActivator().getTimeOfDay(),
1316+
[LogTimestamp]: activator.getTimeOfDay(),
1317+
...getLogAttributes({}),
13161318
...attrs,
13171319
});
13181320
},

0 commit comments

Comments
 (0)