TypeScript SDK v4 is now available! See what's new

Logging in Inngest

Log handling can have some caveats when working with serverless runtimes.

One of the main problems is due to how serverless providers terminate after a function exits. There might not be enough time for a logger to finish flushing, which results in logs being lost.

Another (opposite) problem is due to how Inngest handles memoization and code execution via HTTP calls to the SDK. A log statement outside of step function could end up running multiple times, resulting in duplicated deliveries.

example-fn.ts
async ({ event, step }) => {
  console.log("something") // this can be run three times

  await step.run("fn", () => {
    console.log("something else") // this will always be run once
  })

  await step.run(...)
}

We provide a thin wrapper over existing logging tools, and export it to Inngest functions in order to mitigate these problems, so you, as the user, don't need to deal with them and things should work as you expect.

Usage

A logger object is available within all Inngest functions as a handler argument. You can use it with the logger of your choice, or if absent, logger will default to use console.

The SDK uses Pino-style object-first logging, where structured data is passed before the message string:

inngest.createFunction(
  { id: "my-awesome-function", triggers: { event: "func/awesome" } },
  async ({ event, step, logger }) => {
    logger.info({ eventId: event.data.id }, "Starting function");

    const val = await step.run("do-something", () => {
      if (somethingBadHappens) logger.warn("something bad happened");
    });

    return { success: true, event };
  }
);

We recommend using a structured logger like Pino that supports a child logger .child() implementation, which automatically adds function runtime metadata to your logs. Read more about enriched logs with function metadata for more details.

Using your preferred logger

While the default ConsoleLogger may be good enough for local development, structured logging libraries provide more features that are suitable for production use. Pass a logger to the logger option on the Inngest client to make it available as ctx.logger in all functions.

import pino from "pino";
import { Inngest } from "inngest";

const logger = pino({ level: "debug" });

export const inngest = new Inngest({
  id: "my-awesome-app",
  logger: logger,
});
inngest.createFunction(
  { id: 'my-fn', },
  ({ event, step, logger }) => {
    logger.info({ hello: "world" }, "this uses my pino logger");
  }
);

Object-first vs string-first loggers

The SDK expects object-first loggers (like Pino), where structured data comes before the message:

// Object-first (Pino style) - works out of the box
logger.info({ userId: "abc" }, "User created");

Some loggers like Winston use string-first conventions, where the message comes first. For these loggers, use wrapStringFirstLogger to adapt them:

import { wrapStringFirstLogger } from "inngest";

const logger = wrapStringFirstLogger(winstonLogger);

See the Logging reference for more details on logger configuration, the ConsoleLogger, and the internalLogger option.

Enriched logs with function metadata

If the logger library supports a child logger .child() implementation, the built-in middleware will utilize it to add function runtime metadata to your logs automatically:

  • Function name
  • Event name
  • Run ID
Example usage with Pino logger
await step.run("summarize-content", async ({ step, logger }) => {
  logger.info({ max_tokens: 1000 }, "Calling Claude");
});
Example log output
{"eventName":"inngest/function.invoked","functionName":"Summarize content via GPT-4",
"level":"info","max_tokens":1000,"message":"Calling Claude",
"runID":"01KB7YQXYNPEX3XB257A3RQDRX"}

Loggers supported

The following is a list of loggers we're aware of that work, but is not an exhaustive list:

Customizing the logger

The built-in logger is implemented using middleware. You can create your own middleware to customize the logger to your needs. See the logging middleware example for more details.

Further reading

  • Logging reference - Full details on logger configuration, ConsoleLogger, internalLogger, and wrapStringFirstLogger.
  • Traces - View detailed execution traces for your functions in the Inngest dashboard, including step-by-step breakdowns and timing information.