@diia-inhouse/workflow 3.2.6 → 3.3.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.
@@ -0,0 +1,26 @@
1
+ import { activeSpanContext, spanContextLogAttributes } from "./spanContext.js";
2
+ //#region src/interceptors/activityTraceLogAttributes.ts
3
+ /**
4
+ * Injects trace and span ids into activity log attributes.
5
+ *
6
+ * The SDK logs the activity lifecycle after the OpenTelemetry span has ended, so the inbound hook
7
+ * remembers the span context while it is still active and `getLogAttributes` falls back to it.
8
+ * `Activity started` precedes the chain entirely and stays untraced.
9
+ */
10
+ var ActivityTraceLogAttributesInterceptor = class {
11
+ capturedSpanContext;
12
+ async execute(input, next) {
13
+ this.capturedSpanContext = activeSpanContext() ?? this.capturedSpanContext;
14
+ return await next(input);
15
+ }
16
+ getLogAttributes(input, next) {
17
+ const spanContext = activeSpanContext() ?? this.capturedSpanContext;
18
+ if (!spanContext) return next(input);
19
+ return next({
20
+ ...spanContextLogAttributes(spanContext),
21
+ ...input
22
+ });
23
+ }
24
+ };
25
+ //#endregion
26
+ export { ActivityTraceLogAttributesInterceptor };
@@ -0,0 +1,19 @@
1
+ import * as otel from "@opentelemetry/api";
2
+ //#region src/interceptors/spanContext.ts
3
+ /** Span context of the active span, if any. */
4
+ function activeSpanContext() {
5
+ const spanContext = otel.trace.getSpan(otel.context.active())?.spanContext();
6
+ return spanContext && otel.isSpanContextValid(spanContext) ? spanContext : void 0;
7
+ }
8
+ /** Trace log attributes, in both the Temporal SDK and the diia naming. */
9
+ function spanContextLogAttributes(spanContext) {
10
+ return {
11
+ trace_id: spanContext.traceId,
12
+ span_id: spanContext.spanId,
13
+ trace_flags: `0${spanContext.traceFlags.toString(16)}`,
14
+ traceId: spanContext.traceId,
15
+ spanId: spanContext.spanId
16
+ };
17
+ }
18
+ //#endregion
19
+ export { activeSpanContext, spanContextLogAttributes };
@@ -1,17 +1,27 @@
1
- import * as otel from "@opentelemetry/api";
1
+ import { activeSpanContext, spanContextLogAttributes } from "./spanContext.js";
2
2
  //#region src/interceptors/traceLogAttributes.ts
3
- /** Injects traceId and spanId from OpenTelemetry into workflow log attributes. */
3
+ /** Injects trace and span ids into workflow log attributes. */
4
4
  var TraceLogAttributesInterceptor = class {
5
+ capturedSpanContext;
6
+ async execute(input, next) {
7
+ this.capturedSpanContext = activeSpanContext() ?? this.capturedSpanContext;
8
+ return await next(input);
9
+ }
5
10
  getLogAttributes(input, next) {
6
- const attrs = next(input);
7
- const spanContext = otel.trace.getSpan(otel.context.active())?.spanContext();
8
- if (spanContext && otel.isSpanContextValid(spanContext)) {
9
- attrs.traceId = spanContext.traceId;
10
- attrs.spanId = spanContext.spanId;
11
- }
12
- return attrs;
11
+ const spanContext = activeSpanContext() ?? this.capturedSpanContext;
12
+ if (!spanContext) return next(input);
13
+ return next({
14
+ ...spanContextLogAttributes(spanContext),
15
+ ...input
16
+ });
13
17
  }
14
18
  };
15
- const interceptors = () => ({ outbound: [new TraceLogAttributesInterceptor()] });
19
+ const interceptors = () => {
20
+ const interceptor = new TraceLogAttributesInterceptor();
21
+ return {
22
+ inbound: [interceptor],
23
+ outbound: [interceptor]
24
+ };
25
+ };
16
26
  //#endregion
17
27
  export { interceptors };
@@ -0,0 +1,36 @@
1
+ //#region src/services/worker/runtimeLogger.ts
2
+ const ERROR_META_KEY = "error";
3
+ const NORMALIZED_ERROR_META_KEY = "err";
4
+ function normalizeMeta(meta) {
5
+ if (!meta || !(ERROR_META_KEY in meta) || NORMALIZED_ERROR_META_KEY in meta) return meta;
6
+ const { [ERROR_META_KEY]: error, ...rest } = meta;
7
+ return {
8
+ ...rest,
9
+ [NORMALIZED_ERROR_META_KEY]: error
10
+ };
11
+ }
12
+ /**
13
+ * Adapts a diia `Logger` to the Temporal runtime `Logger`.
14
+ *
15
+ * Remaps failures to the `err` key the diia logger serializes, and implements the
16
+ * level-first `log()` signature the two interfaces disagree on.
17
+ */
18
+ function toTemporalRuntimeLogger(logger) {
19
+ const levels = {
20
+ TRACE: (message, meta) => logger.trace(message, meta),
21
+ DEBUG: (message, meta) => logger.debug(message, meta),
22
+ INFO: (message, meta) => logger.info(message, meta),
23
+ WARN: (message, meta) => logger.warn(message, meta),
24
+ ERROR: (message, meta) => logger.error(message, meta)
25
+ };
26
+ return {
27
+ log: (level, message, meta) => levels[level]?.(message, normalizeMeta(meta)),
28
+ trace: (message, meta) => levels.TRACE(message, normalizeMeta(meta)),
29
+ debug: (message, meta) => levels.DEBUG(message, normalizeMeta(meta)),
30
+ info: (message, meta) => levels.INFO(message, normalizeMeta(meta)),
31
+ warn: (message, meta) => levels.WARN(message, normalizeMeta(meta)),
32
+ error: (message, meta) => levels.ERROR(message, normalizeMeta(meta))
33
+ };
34
+ }
35
+ //#endregion
36
+ export { toTemporalRuntimeLogger };
@@ -4,7 +4,7 @@ import { WorkerHealthDetails, WorkerHealthService } from "./workerHealth.js";
4
4
  import { buildWorkerIdentity } from "./worker/identity.js";
5
5
  import { EnvService } from "@diia-inhouse/env";
6
6
  import { AlsData, Logger } from "@diia-inhouse/types";
7
- import { Worker, WorkerOptions } from "@temporalio/worker";
7
+ import { Worker, WorkerInterceptors, WorkerOptions } from "@temporalio/worker";
8
8
  import { AsyncLocalStorage } from "node:async_hooks";
9
9
 
10
10
  //#region src/services/worker.d.ts
@@ -31,6 +31,7 @@ declare function applyWorkerProcessConfig(config: AppConfig): void;
31
31
  * and returns a filesystem path suitable for Temporal's worker.
32
32
  */
33
33
  declare function toWorkflowsPath(input: string): string;
34
+ declare function buildWorkerInterceptors(tracingEnabled: boolean, asyncLocalStorage: AsyncLocalStorage<AlsData> | undefined, logger: Logger | undefined, workflowsPath: string | undefined): WorkerInterceptors | undefined;
34
35
  declare function instantiateActivities(app: App, workerActivities: Record<string, ActivityClass>): Record<string, (...args: unknown[]) => Promise<unknown>>;
35
36
  /**
36
37
  * Runs the Temporal worker in the **dedicated worker process**.
@@ -131,4 +132,4 @@ declare function initWorker({
131
132
  service?: string;
132
133
  }, envService: EnvService, logger?: Logger, nodeTracerProvider?: NodeTracerProviderLike, asyncLocalStorage?: AsyncLocalStorage<AlsData>): Promise<Worker>;
133
134
  //#endregion
134
- export { type ActivityClass, type App, type RunInProcessWorkerOptions, type RunStandaloneWorkerOptions, type WorkerBootstrapOptions, type WorkerHealthDetails, WorkerHealthService, type WorkerRunOptions, applyServiceProcessConfig, applyWorkerProcessConfig, bootstrapWorker, buildWorkerIdentity, initWorker, instantiateActivities, runInProcessWorker, runStandaloneWorker, toWorkflowsPath };
135
+ export { type ActivityClass, type App, type RunInProcessWorkerOptions, type RunStandaloneWorkerOptions, type WorkerBootstrapOptions, type WorkerHealthDetails, WorkerHealthService, type WorkerRunOptions, applyServiceProcessConfig, applyWorkerProcessConfig, bootstrapWorker, buildWorkerIdentity, buildWorkerInterceptors, initWorker, instantiateActivities, runInProcessWorker, runStandaloneWorker, toWorkflowsPath };
@@ -1,9 +1,11 @@
1
1
  import { getDataConverter } from "../encryption/dataConverter.js";
2
2
  import "../encryption/index.js";
3
3
  import { traceExporter } from "../instrumentation.js";
4
+ import { ActivityTraceLogAttributesInterceptor } from "../interceptors/activityTraceLogAttributes.js";
4
5
  import { AsyncLocalStorageBridgeInterceptor } from "../interceptors/asyncLocalStorageBridge.js";
5
6
  import { buildWorkerIdentity } from "./worker/identity.js";
6
7
  import { deriveWorkflowTypes, registerWorkerInfo } from "./worker/info.js";
8
+ import { toTemporalRuntimeLogger } from "./worker/runtimeLogger.js";
7
9
  import { WorkerHealthService } from "./workerHealth.js";
8
10
  import { EnvService } from "@diia-inhouse/env";
9
11
  import { NativeConnection, Runtime, Worker } from "@temporalio/worker";
@@ -58,17 +60,30 @@ function toWorkflowsPath(input) {
58
60
  /**
59
61
  * Builds worker interceptors with OpenTelemetry and AsyncLocalStorage support.
60
62
  * OpenTelemetry creates span first, then AsyncLocalStorage bridge extracts traceId.
63
+ *
64
+ * Order matters: the first factory is the outermost, so the ones after it see an open span.
61
65
  */
62
66
  const traceLogAttributesModulePath = path.resolve(import.meta.dirname, "../interceptors/traceLogAttributes");
67
+ function traceLogAttributesInterceptor() {
68
+ const interceptor = new ActivityTraceLogAttributesInterceptor();
69
+ return {
70
+ inbound: interceptor,
71
+ outbound: interceptor
72
+ };
73
+ }
63
74
  function buildWorkerInterceptors(tracingEnabled, asyncLocalStorage, logger, workflowsPath) {
64
75
  if (tracingEnabled) {
65
76
  const workflowModules = [traceLogAttributesModulePath];
66
77
  if (workflowsPath) workflowModules.unshift(workflowsPath);
67
78
  return {
68
- activity: [(ctx) => ({
69
- inbound: new OpenTelemetryActivityInboundInterceptor(ctx),
70
- outbound: new OpenTelemetryActivityOutboundInterceptor(ctx)
71
- }), ...asyncLocalStorage && logger ? [(ctx) => ({ inbound: new AsyncLocalStorageBridgeInterceptor(ctx, asyncLocalStorage, logger) })] : []],
79
+ activity: [
80
+ (ctx) => ({
81
+ inbound: new OpenTelemetryActivityInboundInterceptor(ctx),
82
+ outbound: new OpenTelemetryActivityOutboundInterceptor(ctx)
83
+ }),
84
+ traceLogAttributesInterceptor,
85
+ ...asyncLocalStorage && logger ? [(ctx) => ({ inbound: new AsyncLocalStorageBridgeInterceptor(ctx, asyncLocalStorage, logger) })] : []
86
+ ],
72
87
  workflowModules
73
88
  };
74
89
  }
@@ -76,7 +91,7 @@ function buildWorkerInterceptors(tracingEnabled, asyncLocalStorage, logger, work
76
91
  activity: [(ctx) => ({
77
92
  inbound: new AsyncLocalStorageBridgeInterceptor(ctx, asyncLocalStorage, logger),
78
93
  outbound: new OpenTelemetryActivityOutboundInterceptor(ctx)
79
- })],
94
+ }), traceLogAttributesInterceptor],
80
95
  workflowModules: workflowsPath ? [workflowsPath] : []
81
96
  };
82
97
  }
@@ -277,7 +292,7 @@ function tryResolve(container, key) {
277
292
  async function initWorker({ temporal: temporalConfig, metrics: { custom: metricsConfig } }, options, envService, logger, nodeTracerProvider, asyncLocalStorage) {
278
293
  const { encryptionEnabled, encryptionKeyId, encryptionKeyRefreshInterval, namespace = "default", address, taskQueue } = temporalConfig;
279
294
  const runtimeParams = {};
280
- if (logger) runtimeParams.logger = logger.child({ taskQueue });
295
+ if (logger) runtimeParams.logger = toTemporalRuntimeLogger(logger.child({ taskQueue }));
281
296
  const temporalMetrics = metricsConfig.scrapers?.find((s) => s.name === "temporal");
282
297
  if (temporalMetrics && !temporalMetrics.disabled) runtimeParams.telemetryOptions = { metrics: { prometheus: {
283
298
  bindAddress: `0.0.0.0:${temporalMetrics.port}`,
@@ -321,4 +336,4 @@ async function initWorker({ temporal: temporalConfig, metrics: { custom: metrics
321
336
  }
322
337
  }
323
338
  //#endregion
324
- export { WorkerHealthService, applyServiceProcessConfig, applyWorkerProcessConfig, bootstrapWorker, buildWorkerIdentity, initWorker, instantiateActivities, runInProcessWorker, runStandaloneWorker, toWorkflowsPath };
339
+ export { WorkerHealthService, applyServiceProcessConfig, applyWorkerProcessConfig, bootstrapWorker, buildWorkerIdentity, buildWorkerInterceptors, initWorker, instantiateActivities, runInProcessWorker, runStandaloneWorker, toWorkflowsPath };
package/dist/worker.d.ts CHANGED
@@ -2,7 +2,7 @@ import { workflowInterceptors } from "./interceptors.js";
2
2
  import { ActivityClass, App, RunInProcessWorkerOptions, RunStandaloneWorkerOptions, WorkerBootstrapOptions, WorkerRunOptions } from "./interfaces/services/worker.js";
3
3
  import { WorkerHealthDetails, WorkerHealthService } from "./services/workerHealth.js";
4
4
  import { buildWorkerIdentity } from "./services/worker/identity.js";
5
- import { applyServiceProcessConfig, applyWorkerProcessConfig, bootstrapWorker, initWorker, instantiateActivities, runInProcessWorker, runStandaloneWorker, toWorkflowsPath } from "./services/worker.js";
5
+ import { applyServiceProcessConfig, applyWorkerProcessConfig, bootstrapWorker, buildWorkerInterceptors, initWorker, instantiateActivities, runInProcessWorker, runStandaloneWorker, toWorkflowsPath } from "./services/worker.js";
6
6
  import { RegisterWorkerInfoParams, WORKER_INFO_METRIC, WorkerInfoLabels, deriveWorkflowTypes, registerWorkerInfo, taskQueueToService } from "./services/worker/info.js";
7
7
  import { NativeConnection, NativeConnectionPlugin, Runtime, State, Worker, WorkerDeploymentOptions, WorkerInterceptors, WorkerOptions, WorkerPlugin, WorkerStatus, bundleWorkflowCode } from "@temporalio/worker";
8
- export { type ActivityClass, type App, NativeConnection, type NativeConnectionPlugin, RegisterWorkerInfoParams, type RunInProcessWorkerOptions, type RunStandaloneWorkerOptions, Runtime, type State, WORKER_INFO_METRIC, Worker, type WorkerBootstrapOptions, type WorkerDeploymentOptions, type WorkerHealthDetails, WorkerHealthService, WorkerInfoLabels, type WorkerInterceptors, type WorkerOptions, type WorkerPlugin, type WorkerRunOptions, type WorkerStatus, applyServiceProcessConfig, applyWorkerProcessConfig, bootstrapWorker, buildWorkerIdentity, bundleWorkflowCode, deriveWorkflowTypes, initWorker, instantiateActivities, registerWorkerInfo, runInProcessWorker, runStandaloneWorker, taskQueueToService, toWorkflowsPath, workflowInterceptors };
8
+ export { type ActivityClass, type App, NativeConnection, type NativeConnectionPlugin, RegisterWorkerInfoParams, type RunInProcessWorkerOptions, type RunStandaloneWorkerOptions, Runtime, type State, WORKER_INFO_METRIC, Worker, type WorkerBootstrapOptions, type WorkerDeploymentOptions, type WorkerHealthDetails, WorkerHealthService, WorkerInfoLabels, type WorkerInterceptors, type WorkerOptions, type WorkerPlugin, type WorkerRunOptions, type WorkerStatus, applyServiceProcessConfig, applyWorkerProcessConfig, bootstrapWorker, buildWorkerIdentity, buildWorkerInterceptors, bundleWorkflowCode, deriveWorkflowTypes, initWorker, instantiateActivities, registerWorkerInfo, runInProcessWorker, runStandaloneWorker, taskQueueToService, toWorkflowsPath, workflowInterceptors };
package/dist/worker.js CHANGED
@@ -2,6 +2,6 @@ import { workflowInterceptors } from "./interceptors.js";
2
2
  import { buildWorkerIdentity } from "./services/worker/identity.js";
3
3
  import { WORKER_INFO_METRIC, deriveWorkflowTypes, registerWorkerInfo, taskQueueToService } from "./services/worker/info.js";
4
4
  import { WorkerHealthService } from "./services/workerHealth.js";
5
- import { applyServiceProcessConfig, applyWorkerProcessConfig, bootstrapWorker, initWorker, instantiateActivities, runInProcessWorker, runStandaloneWorker, toWorkflowsPath } from "./services/worker.js";
5
+ import { applyServiceProcessConfig, applyWorkerProcessConfig, bootstrapWorker, buildWorkerInterceptors, initWorker, instantiateActivities, runInProcessWorker, runStandaloneWorker, toWorkflowsPath } from "./services/worker.js";
6
6
  import { NativeConnection, Runtime, Worker, bundleWorkflowCode } from "@temporalio/worker";
7
- export { NativeConnection, Runtime, WORKER_INFO_METRIC, Worker, WorkerHealthService, applyServiceProcessConfig, applyWorkerProcessConfig, bootstrapWorker, buildWorkerIdentity, bundleWorkflowCode, deriveWorkflowTypes, initWorker, instantiateActivities, registerWorkerInfo, runInProcessWorker, runStandaloneWorker, taskQueueToService, toWorkflowsPath, workflowInterceptors };
7
+ export { NativeConnection, Runtime, WORKER_INFO_METRIC, Worker, WorkerHealthService, applyServiceProcessConfig, applyWorkerProcessConfig, bootstrapWorker, buildWorkerIdentity, buildWorkerInterceptors, bundleWorkflowCode, deriveWorkflowTypes, initWorker, instantiateActivities, registerWorkerInfo, runInProcessWorker, runStandaloneWorker, taskQueueToService, toWorkflowsPath, workflowInterceptors };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diia-inhouse/workflow",
3
- "version": "3.2.6",
3
+ "version": "3.3.0",
4
4
  "description": "Workflow",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",