Skip to content

[Bug] opentelemetry traceState is handled properly in makeWorkflowExporter #1738

@safareli

Description

@safareli

What are you really trying to do?

When using otel if workflow has tracestate header like this:

Image

exporting span from worker fails

Describe the bug

getting this errors:

TypeError: _a.serialize is not a function
    at sdkSpanToOtlpSpan (/app/node_modules/.pnpm/@[email protected]_@[email protected]/node_modules/@opentelemetry/otlp-transformer/src/trace/internal.ts:44:33)
    at <anonymous> (/app/node_modules/.pnpm/@[email protected]_@[email protected]/node_modules/@opentelemetry/otlp-transformer/src/trace/internal.ts:159:11)
    at Array.map (<anonymous>)
    at spanRecordsToResourceSpans (/app/node_modules/.pnpm/@[email protected]_@[email protected]/node_modules/@opentelemetry/otlp-transformer/src/trace/internal.ts:158:34)
    at createExportTraceServiceRequest (/app/node_modules/.pnpm/@[email protected]_@[email protected]/node_modules/@opentelemetry/otlp-transformer/src/trace/internal.ts:110:20)
    at Object.serializeRequest (/app/node_modules/.pnpm/@[email protected]_@[email protected]/node_modules/@opentelemetry/otlp-transformer/src/trace/json/trace.ts:26:52)
    at OTLPExportDelegate.export (/app/node_modules/.pnpm/@[email protected]_@[email protected]/node_modules/@opentelemetry/otlp-exporter-base/src/otlp-export-delegate.ts:69:48)
    at OTLPTraceExporter.export (/app/node_modules/.pnpm/@[email protected]_@[email protected]/node_modules/@opentelemetry/otlp-exporter-base/src/OTLPExporterBase.ts:32:26)
    at Object.fn (/app/node_modules/.pnpm/@[email protected]_@[email protected]_@temporalio+c_49fd2775686fed0552daf86155185cd7/node_modules/@temporalio/interceptors-opentelemetry/src/worker/index.ts:86:24)
    at <anonymous> (/app/node_modules/.pnpm/@[email protected]/node_modules/@temporalio/worker/src/worker.ts:1364:23)
    at Array.map (<anonymous>)
    at Worker.processSinkCalls (/app/node_modules/.pnpm/@[email protected]/node_modules/@temporalio/worker/src/worker.ts:1362:21)
    at Worker.handleActivation (/app/node_modules/.pnpm/@[email protected]/node_modules/@temporalio/worker/src/worker.ts:1208:22)

Minimal Reproduction

reproducing this locally was bit hacky.
had to add this so tracestate was passed to temporal when invoking a workflow via js client.

Image

then once the trace state is passed to workflow in the makeWorkflowExporter that's passed to Worker.create we get traceState that is instance of Object and not an instance of expected TraceState. via debugger you can see this:

Image

because of this then at some point when span is converted to an request to otel collector as there is traceState .serialize is called on it but this object has no methods only that _internalState which is private prop of the TraceState see:

Image

I suppose the trace state is moved across different JS contexts (from workflow code to host) and information that this object is instance of TraceState is lost and on the other end we just get Object.

Environment/Versions

  • OS and processor: M1 Mac
  • Temporal SDK Version: 1.11.7
  • Are you using Docker or Kubernetes or building Temporal from source? no

hacky fix on my end was to do this:

const fixTemporalBug = (traceState: TraceState): TraceState => {
  const res = new TraceState();

  (res as any)._internalState = (traceState as any)._internalState;

  return res;
};

// inlined version of makeWorkflowExporter that fixes traceState bug in Temporal
const makeWorkflowExporterFixed = (
  exporter: SpanExporter,
  resource: Resource,
): InjectedSink<OpenTelemetryWorkflowExporter> => {
  return {
    export: {
      fn: (info: WorkflowInfo, spanData: SerializableSpan[]): void => {
        const spans = spanData.map((serialized) => {
          Object.assign(serialized.attributes, info);
          // Spans are copied over from the isolate and are converted to ReadableSpan instances
          const {
            spanContext: { traceState, ...spanContext },
            ...rest
          } = serialized;

          return {
            spanContext: (): SpanContext => ({
              ...spanContext,
              traceState: traceState == null ? traceState : fixTemporalBug(traceState as any),
            }),
            resource,
            ...rest,
          };
        });

        // Ignore the export result for simplicity
        exporter.export(spans, () => undefined);
      },
    },
  };
};

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions