Extended Traces (OpenTelemetry)

Inngest supports OpenTelemetry for distributed tracing and observability across your functions. The extendedTracesMiddleware exports OpenTelemetry spans from your functions to Inngest Traces, giving you deep insights into function execution, step timing, and performance.

If you want Inngest to set up OpenTelemetry for you, use @inngest/otel for a Node.js OpenTelemetry provider and common instrumentation. If your app already configures OpenTelemetry, keep that setup and add the Extended Traces middleware so spans are exported to Inngest.

Basic Usage

Use this path when your application does not already configure OpenTelemetry. Install and use @inngest/otel to set up a Node.js OpenTelemetry provider and common instrumentations before your application code starts, and extendedTracesMiddleware() attaches to that provider.

Preload it before your application:

node --import @inngest/otel/node ./app.js

If --import is not available, use a small bootstrap file as your process entrypoint. Because ESM static imports are evaluated before the importing module runs, keep the dynamic await import("./app.js"); a static import "./app.js" in the same file can load app modules before instrumentation is registered.

import "@inngest/otel/node";

await import("./app.js");

Then configure the Inngest client to use the Extended Traces middleware:

import { Inngest } from "inngest";
import { extendedTracesMiddleware } from "inngest/experimental";

const inngest = new Inngest({
  id: "my-app",
  middleware: [extendedTracesMiddleware()],
});

Because @inngest/otel is preloaded, the middleware's default behavior extends the existing provider.

Load @inngest/otel/node before importing code that uses libraries you want OpenTelemetry to instrument. Loading it later can miss instrumentation patches for modules that were already imported.

Advanced Usage

Serverless

If you're using serverless, the entrypoint of your app will likely be the file for a particular endpoint, for example /api/inngest.

If your platform supports Node.js flags, prefer the --import @inngest/otel/node preload shown above. If not, import @inngest/otel/node before your Inngest client and functions in the route module.

// Import instrumentation first
import "@inngest/otel/node";

// Then import your Inngest client and functions
import { inngest } from "@/inngest";
import { serve } from "inngest/next";
import { myFn } from "@/inngest/functions";

export const { GET, POST, PUT } = serve({
  client: inngest,
  functions: [myFn],
});

Extending existing providers

A JavaScript process can only have a single OpenTelemetry Provider. Some libraries such as Sentry also create their own provider.

If your application already creates an OpenTelemetry provider, keep that setup. By default, extendedTracesMiddleware() extends the existing provider when it can.

extendedTracesMiddleware();

In the case of Sentry, extendedTracesMiddleware() will extend Sentry's provider as long as it's run after Sentry.init().

Set behaviour: "extendProvider" if you want the middleware to fail when no existing provider is available instead of falling back to provider creation:

extendedTracesMiddleware({
  behaviour: "extendProvider",
});

The options are:

  • "auto" (default): Attempt to extend a provider if one exists. For backward compatibility, this may create a provider if none exists, but provider creation is deprecated in favor of @inngest/otel.
  • "extendProvider": Only attempt to extend a provider and fails if none exists
  • "createProvider" (deprecated): Only attempt to create a provider. Use @inngest/otel instead.
  • "off": Do nothing

If you only use extendedTracesMiddleware() to extend an existing provider, you do not need to call it before other application code. The provider and instrumentation setup remains owned by your existing OpenTelemetry configuration.

Manually extend

If you're already manually creating your own trace provider and import ordering is an issue, you may want to manually add Inngest's InngestSpanProcessor to your existing setup.

Add an InngestSpanProcessor to your provider:

// Create your client the same as you would normally
import { Inngest } from "inngest";
import { extendedTracesMiddleware } from "inngest/experimental";

export const inngest = new Inngest({
  id: "my-app",
  middleware: [
    extendedTracesMiddleware({
      // The provider below adds InngestSpanProcessor, so keep the middleware from creating or extending another provider.
      behaviour: "off",
    }),
  ],
});

// Then when you create your provider, pass the client to it
import { inngest } from "@/inngest";
import { BasicTracerProvider } from "@opentelemetry/sdk-trace-base";
import { InngestSpanProcessor } from "inngest/experimental";

const provider = new BasicTracerProvider({
  // Add the span processor when creating your provider
  spanProcessors: [new InngestSpanProcessor(inngest)],
});

// Register the provider globally
provider.register();

Instrumentation

@inngest/otel automatically instruments common Node.js libraries when it is loaded before your application code. It instruments common Node packages, OpenAI, Anthropic, and Google Generative AI.

If you configure OpenTelemetry yourself, continue to manage your own instrumentation list. Check the @opentelemetry/auto-instrumentations-node docs for the current supported instrumentation list and default-disabled entries. See the OpenTelemetry setup guide for general setup, AI metadata extraction, and CommonJS and ESM preload examples.

Custom instrumentation

If you need instrumentations that are not covered by @inngest/otel, configure them yourself and add InngestSpanProcessor to your provider.

For example, here's an example of adding Prisma OpenTelemetry:

import { NodeSDK } from "@opentelemetry/sdk-node";
import { PrismaInstrumentation } from "@prisma/instrumentation";
import { Inngest } from "inngest";
import { InngestSpanProcessor, extendedTracesMiddleware } from "inngest/experimental";

const inngest = new Inngest({
  id: "my-app",
  middleware: [extendedTracesMiddleware({ behaviour: "off" })],
});

const sdk = new NodeSDK({
  spanProcessors: [new InngestSpanProcessor(inngest)],
  instrumentations: [new PrismaInstrumentation()],
});

sdk.start();