SigNoz Cloud - This page is relevant for SigNoz Cloud editions.
Self-Host - This page is relevant for self-hosted SigNoz editions.

Instrumenting a Temporal TypeScript application with OpenTelemetry

Learn how to instrument a Temporal TypeScript application with OpenTelemetry and send traces, metrics, and logs to SigNoz.

What is Temporal?

Temporal is a durable workflow execution platform that helps you build reliable distributed systems. Unlike traditional REST APIs that handle single requests, Temporal manages long-running workflows that can span hours, days, or even months - coordinating multiple steps, retries, and state management automatically.

Temporal Architecture

Temporal separates applications into Clients (which start workflows) and Workers (which execute the business logic). This separation provides fault tolerance, allowing workers to resume seamlessly if they crash mid-workflow.

Prerequisites

Before starting, ensure you have:

Sample Application

We've published a complete example application at GitHub. Follow along with the code as you work through this guide.

Architecture Overview

Here's how OpenTelemetry integrates with your Temporal application:

Architecture Diagram
Architecture Diagram

Each component (client, worker, workflows, activities) gets instrumented to capture traces, metrics, and logs.

Setup

Step 1: Add Dependencies

Add OpenTelemetry and Temporal packages to your package.json:

package.json
{
  "dependencies": {
    "@opentelemetry/auto-instrumentations-node": "^0.55.0",
    "@opentelemetry/core": "^1.30.0",
    "@opentelemetry/exporter-metrics-otlp-grpc": "^0.57.0",
    "@opentelemetry/exporter-trace-otlp-grpc": "^0.57.0",
    "@opentelemetry/resources": "^1.30.0",
    "@opentelemetry/sdk-metrics": "^1.30.0",
    "@opentelemetry/sdk-node": "^0.57.0",
    "@opentelemetry/sdk-trace-node": "^1.30.0",
    "@opentelemetry/semantic-conventions": "^1.28.0",
    "@opentelemetry/winston-transport": "^0.11.0",
    "@temporalio/activity": "^1.13.2",
    "@temporalio/client": "^1.13.2",
    "@temporalio/interceptors-opentelemetry": "^1.13.2",
    "@temporalio/worker": "^1.13.2",
    "@temporalio/workflow": "^1.13.2"
  }
}

Install the packages:

npm install

Step 2: Configure OpenTelemetry SDK

Create src/instrumentation.ts to set up the OpenTelemetry SDK. This file initializes tracing, metrics, and logging for your entire application.

📝 Note

Key concept: This configuration must be loaded before any other code to ensure automatic instrumentation works correctly.

src/instrumentation.ts
import { NodeSDK } from '@opentelemetry/sdk-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-grpc';
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { Resource } from '@opentelemetry/resources';
import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';

// Parse authentication headers from environment
function parseHeaders(headersString: string | undefined): Record<string, string> {
  if (!headersString) return {};
  const headers: Record<string, string> = {};
  headersString.split(',').forEach((pair) => {
    const [key, value] = pair.split('=');
    if (key && value) headers[key.trim()] = value.trim();
  });
  return headers;
}

export const otlpHeaders = parseHeaders(process.env.OTEL_EXPORTER_OTLP_HEADERS);

// Set up resource with service name
const serviceName = process.env.OTEL_SERVICE_NAME || 'temporal-typescript-app';
export const resource = new Resource({
  [ATTR_SERVICE_NAME]: serviceName,
});

// Configure trace exporter (sends traces to SigNoz)
export const traceExporter = new OTLPTraceExporter({
  headers: otlpHeaders,
  timeoutMillis: 10000,
});

// Configure metric reader (sends metrics to SigNoz)
const metricReader = new PeriodicExportingMetricReader({
  exporter: new OTLPMetricExporter({
    headers: otlpHeaders,
    timeoutMillis: 10000,
  }),
});

// Initialize OpenTelemetry SDK
export const otelSdk = new NodeSDK({
  resource,
  traceExporter,
  metricReader,
  instrumentations: [getNodeAutoInstrumentations()],
});

otelSdk.start();
console.log('[OpenTelemetry] SDK initialized successfully');
What this does
  • Reads your SigNoz endpoint and ingestion key from environment variables (OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS)
  • Sets up trace and metric exporters to send data to SigNoz using OTLP/gRPC
  • Enables automatic instrumentation for Node.js libraries (HTTP, DNS, etc.)
  • Must be imported first in your client and worker files

Step 3: Instrument the Temporal Client

The Temporal Client starts workflows and queries their status. Add OpenTelemetry instrumentation to trace these operations.

src/client.ts
import { otelSdk } from './instrumentation'; // MUST be first import
import { Client } from '@temporalio/client';
import { OpenTelemetryWorkflowClientInterceptor } from '@temporalio/interceptors-opentelemetry';

async function run() {
  try {
    const client = new Client({
      interceptors: {
        workflow: [new OpenTelemetryWorkflowClientInterceptor()],
      },
    });

    // Start a workflow
    const handle = await client.workflow.start('myWorkflow', {
      taskQueue: 'my-task-queue',
      workflowId: 'my-workflow-id',
    });

    console.log(`Started workflow: ${handle.workflowId}`);
  } finally {
    await otelSdk.shutdown(); // Clean shutdown
  }
}

run().catch(console.error);
What this does
  • The OpenTelemetryWorkflowClientInterceptor automatically creates spans for workflow start, signal, and query operations
  • Each workflow operation becomes a trace you can view in SigNoz
  • Always import instrumentation.ts first to enable auto-instrumentation

Step 4: Instrument the Temporal Worker

The Temporal Worker executes workflow and activity code. This is where most of your business logic runs, so instrumentation here is critical.

src/worker.ts
import { otelSdk, otlpHeaders, resource, traceExporter } from './instrumentation'; // MUST be first
import { 
  DefaultLogger, 
  makeTelemetryFilterString, 
  NativeConnection, 
  Runtime, 
  Worker 
} from '@temporalio/worker';
import {
  OpenTelemetryActivityInboundInterceptor,
  OpenTelemetryActivityOutboundInterceptor,
  makeWorkflowExporter,
} from '@temporalio/interceptors-opentelemetry/lib/worker';

// Configure Temporal's runtime telemetry
Runtime.install({
  telemetryOptions: {
    metrics: {
      otel: {
        url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
        headers: otlpHeaders,
        metricsExportInterval: '10s',
      },
    },
  },
});

async function run() {
  try {
    const worker = await Worker.create({
      workflowsPath: require.resolve('./workflows'),
      taskQueue: 'my-task-queue',
      
      // Export workflow traces
      sinks: traceExporter && {
        exporter: makeWorkflowExporter(traceExporter, resource),
      },
      
      // Instrument activities
      interceptors: {
        workflowModules: [require.resolve('./workflows')],
        activity: [
          (ctx) => ({
            inbound: new OpenTelemetryActivityInboundInterceptor(ctx),
            outbound: new OpenTelemetryActivityOutboundInterceptor(ctx),
          }),
        ],
      },
    });

    await worker.run();
  } finally {
    await otelSdk.shutdown();
  }
}

run().catch(console.error);
What this does
  • Runtime.install() sends Temporal's internal metrics (worker health, task queue lag) to SigNoz
  • makeWorkflowExporter() exports traces from workflow execution
  • Activity interceptors capture spans for each activity invocation
  • You'll see the entire workflow execution as a distributed trace in SigNoz

Step 5: Instrument Workflows

Workflows define your business process. Add interceptors to trace workflow execution, timers, and child workflows.

src/workflows.ts
import { WorkflowInterceptorsFactory } from '@temporalio/workflow';
import {
  OpenTelemetryInboundInterceptor,
  OpenTelemetryOutboundInterceptor,
  OpenTelemetryInternalsInterceptor,
} from '@temporalio/interceptors-opentelemetry/lib/workflow';

// Your workflow interceptors
export const interceptors: WorkflowInterceptorsFactory = () => ({
  inbound: [new OpenTelemetryInboundInterceptor()],
  outbound: [new OpenTelemetryOutboundInterceptor()],
  internals: [new OpenTelemetryInternalsInterceptor()],
});

// Example workflow
export async function myWorkflow(): Promise<string> {
  // Your workflow logic here
  return 'completed';
}
What this does
  • OpenTelemetryInboundInterceptor traces incoming signals and queries
  • OpenTelemetryOutboundInterceptor traces outgoing activity calls
  • OpenTelemetryInternalsInterceptor traces internal workflow operations like timers and child workflows

Step 6: Deploy and Run

Now that your application is instrumented, you need to configure the environment variables and run it.

1. Set environment variables:

On Mac/Linux, export the environment variables in your terminal:

export OTEL_SERVICE_NAME="temporal-worker"
export OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.<region>.signoz.cloud:443"
export OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=<your-ingestion-key>"
export OTEL_RESOURCE_ATTRIBUTES="deployment.environment=production"

2. Start the worker:

npm run start

3. Start the client (in another terminal):

Export the environment variables in the new terminal. You may want to set OTEL_SERVICE_NAME to temporal-client so it shows up distinctly in SigNoz:

export OTEL_SERVICE_NAME="temporal-client"
export OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.<region>.signoz.cloud:443"
export OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=<your-ingestion-key>"
export OTEL_RESOURCE_ATTRIBUTES="deployment.environment=production"

Then run:

npm run workflow

Validate in SigNoz

After running your application, verify that data is flowing to SigNoz:

  1. View Traces:

    • Go to Services → find temporal-worker and temporal-client
    • Click into a service to see traces
    • You'll see spans for workflow starts, activity executions, and signals
  2. View Metrics:

    • Go to Dashboards → Import the Temporal SDK Metrics dashboard
    • Click Import JSON and paste the dashboard JSON
    • View worker health, task queue metrics, and workflow/activity durations
  3. View Logs:

Distributed trace from a Temporal application
Distributed trace showing workflow execution across client, worker, and activities
JSON logs from a Temporal application
Application logs from your Temporal worker in SigNoz
SDK metrics from a Temporal application
Temporal SDK metrics dashboard showing worker health and task queue metrics

Configure Logging (Optional)

Connect Winston logging to OpenTelemetry to send logs to SigNoz alongside traces and metrics.

src/logger.ts
import { otlpHeaders, resource } from './instrumentation';
import winston from 'winston';
import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-grpc';
import { logs } from '@opentelemetry/api-logs';
import { LoggerProvider, SimpleLogRecordProcessor } from '@opentelemetry/sdk-logs';
import { OpenTelemetryTransportV3 } from '@opentelemetry/winston-transport';

// Initialize log provider
const loggerProvider = new LoggerProvider({ resource });
const otlpExporter = new OTLPLogExporter({ headers: otlpHeaders });
loggerProvider.addLogRecordProcessor(new SimpleLogRecordProcessor(otlpExporter));
logs.setGlobalLoggerProvider(loggerProvider);

// Create Winston logger
export const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [
    new winston.transports.Console(),
    new OpenTelemetryTransportV3(),
  ],
});

Use this logger in your worker configuration by passing it to Runtime.install({ logger }).

Setup OpenTelemetry Collector (Optional)

What is the OpenTelemetry Collector?

Think of the OTel Collector as a middleman between your app and SigNoz. Instead of your application sending data directly to SigNoz, it sends everything to the Collector first, which then forwards it along.

Why use it?

  • Cleaning up data — Filter out noisy traces you don't care about, or remove sensitive info before it leaves your servers.
  • Keeping your app lightweight — Let the Collector handle batching, retries, and compression instead of your application code.
  • Adding context automatically — The Collector can tag your data with useful info like which Kubernetes pod or cloud region it came from.
  • Future flexibility — Want to send data to multiple backends later? The Collector makes that easy without changing your app.

See Switch from direct export to Collector for step-by-step instructions to convert your setup.

For more details, see Why use the OpenTelemetry Collector? and the Collector configuration guide.

Troubleshooting

Common Issues

No data appearing in SigNoz

Symptom: Worker and client run without errors, but no traces/metrics appear in SigNoz.

Causes and fixes:

  1. Wrong endpoint or region:

    • Verify your endpoint matches your SigNoz region: https://ingest.<region>.signoz.cloud:443
    • Check available regions at SigNoz Cloud endpoints
  2. Invalid ingestion key:

  3. Network/firewall issues:

    • Test connectivity: curl -v https://ingest.us.signoz.cloud:443
    • Ensure outbound HTTPS (port 443) is allowed
  4. SDK not initialized:

    • Verify instrumentation.ts is the first import in both client.ts and worker.ts
    • Check console for [OpenTelemetry] SDK initialized successfully

Error: "Failed to export traces"

Symptom: Console shows Failed to export traces or timeout errors.

Cause: Network issues or incorrect exporter configuration.

Fix:

  1. Increase timeout in instrumentation.ts:
    export const traceExporter = new OTLPTraceExporter({
      headers: otlpHeaders,
      timeoutMillis: 30000, // Increase from 10s to 30s
    });
    
  2. Check if your network requires a proxy and set HTTP_PROXY/HTTPS_PROXY environment variables

Traces appear but logs don't

Symptom: Traces and metrics work, but logs are missing.

Fix:

  1. Verify logger.ts is imported and used in your worker
  2. Check that OpenTelemetryTransportV3 is in Winston transports
  3. Set up log parsing pipeline in SigNoz (see Validate section above)

High cardinality warning

Symptom: SigNoz shows warnings about high cardinality metrics.

Cause: Temporal can generate many metrics with unique label combinations.

Fix: Configure metric filtering in Runtime.install():

Runtime.install({
  telemetryOptions: {
    metrics: {
      otel: {
        url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
        headers: otlpHeaders,
      },
      // Filter out noisy metrics
      filter: makeTelemetryFilterString({
        core: 'INFO',
        other: 'WARN',
      }),
    },
  },
});

Next Steps

Now that your Temporal application is instrumented:

  • Set up alerts: Create alerts for workflow failures, high activity latency, or worker health issues in SigNoz Alerts
  • Build dashboards: Customize the Temporal SDK dashboard or create your own
  • Explore traces: Use the Traces Explorer to debug failed workflows
  • Query logs: Learn how to search and filter logs in the Logs Explorer documentation
  • Monitor activities: Track activity execution times and error rates

Get Help

If you need help with the steps in this topic, please reach out to us on SigNoz Community Slack.

If you are a SigNoz Cloud user, please use in product chat support located at the bottom right corner of your SigNoz instance or contact us at cloud-support@signoz.io.

Last updated: March 18, 2026

Edit on GitHub