UNPKG

@temporalio/workflow

Version:
128 lines (119 loc) 5.46 kB
import { composeInterceptors } from '@temporalio/common/lib/interceptors'; import { SdkComponent } from '@temporalio/common'; import { untrackPromise } from './stack-helpers'; import { type Sink, type Sinks, proxySinks } from './sinks'; import { isCancellation } from './errors'; import { WorkflowInfo, ContinueAsNew } from './interfaces'; import { assertInWorkflowContext } from './global-attributes'; export interface WorkflowLogger extends Sink { trace(message: string, attrs?: Record<string, unknown>): void; debug(message: string, attrs?: Record<string, unknown>): void; info(message: string, attrs?: Record<string, unknown>): void; warn(message: string, attrs?: Record<string, unknown>): void; error(message: string, attrs?: Record<string, unknown>): void; } /** * Sink interface for forwarding logs from the Workflow sandbox to the Worker * * @deprecated Do not use LoggerSinks directly. To log from Workflow code, use the `log` object * exported by the `@temporalio/workflow` package. To capture log messages emitted * by Workflow code, set the {@link Runtime.logger} property. */ export interface LoggerSinksDeprecated extends Sinks { /** * @deprecated Do not use LoggerSinks directly. To log from Workflow code, use the `log` object * exported by the `@temporalio/workflow` package. To capture log messages emitted * by Workflow code, set the {@link Runtime.logger} property. */ defaultWorkerLogger: WorkflowLogger; } /** * Sink interface for forwarding logs from the Workflow sandbox to the Worker */ export interface LoggerSinksInternal extends Sinks { __temporal_logger: WorkflowLogger; } const loggerSink = proxySinks<LoggerSinksInternal>().__temporal_logger; /** * Symbol used by the SDK logger to extract a timestamp from log attributes. * Also defined in `worker/logger.ts` - intentionally not shared. */ const LogTimestamp = Symbol.for('log_timestamp'); /** * Default workflow logger. * * This logger is replay-aware and will omit log messages on workflow replay. Messages emitted by this logger are * funnelled through a sink that forwards them to the logger registered on {@link Runtime.logger}. * * Attributes from the current Workflow Execution context are automatically included as metadata on every log * entries. An extra `sdkComponent` metadata attribute is also added, with value `workflow`; this can be used for * fine-grained filtering of log entries further downstream. * * To customize log attributes, register a {@link WorkflowOutboundCallsInterceptor} that intercepts the * `getLogAttributes()` method. * * Notice that since sinks are used to power this logger, any log attributes must be transferable via the * {@link https://nodejs.org/api/worker_threads.html#worker_threads_port_postmessage_value_transferlist | postMessage} * API. * * NOTE: Specifying a custom logger through {@link defaultSink} or by manually registering a sink named * `defaultWorkerLogger` has been deprecated. Please use {@link Runtime.logger} instead. */ export const log: WorkflowLogger = Object.fromEntries( (['trace', 'debug', 'info', 'warn', 'error'] as Array<keyof WorkflowLogger>).map((level) => { return [ level, (message: string, attrs?: Record<string, unknown>) => { const activator = assertInWorkflowContext('Workflow.log(...) may only be used from workflow context.'); const getLogAttributes = composeInterceptors(activator.interceptors.outbound, 'getLogAttributes', (a) => a); return loggerSink[level](message, { // Inject the call time in nanosecond resolution as expected by the worker logger. [LogTimestamp]: activator.getTimeOfDay(), sdkComponent: SdkComponent.workflow, ...getLogAttributes(workflowLogAttributes(activator.info)), ...attrs, }); }, ]; }) ) as any; export function executeWithLifecycleLogging(fn: () => Promise<unknown>): Promise<unknown> { log.debug('Workflow started', { sdkComponent: SdkComponent.worker }); const p = fn().then( (res) => { log.debug('Workflow completed', { sdkComponent: SdkComponent.worker }); return res; }, (error) => { // Avoid using instanceof checks in case the modules they're defined in loaded more than once, // e.g. by jest or when multiple versions are installed. if (typeof error === 'object' && error != null) { if (isCancellation(error)) { log.debug('Workflow completed as cancelled', { sdkComponent: SdkComponent.worker }); throw error; } else if (error instanceof ContinueAsNew) { log.debug('Workflow continued as new', { sdkComponent: SdkComponent.worker }); throw error; } } log.warn('Workflow failed', { error, sdkComponent: SdkComponent.worker }); throw error; } ); // Avoid showing this interceptor in stack trace query untrackPromise(p); return p; } /** * Returns a map of attributes to be set _by default_ on log messages for a given Workflow. * Note that this function may be called from outside of the Workflow context (eg. by the worker itself). */ export function workflowLogAttributes(info: WorkflowInfo): Record<string, unknown> { return { namespace: info.namespace, taskQueue: info.taskQueue, workflowId: info.workflowId, runId: info.runId, workflowType: info.workflowType, }; }