Example middleware v2.0.0+
The following examples show how you might use middleware in some real-world scenarios.
Sentry error reporting and tracing
This example uses Sentry to:
- Capture exceptions for reporting
- Add tracing to each function run
- Include useful context for each exception and trace like function ID and event names
import * as Sentry from "@sentry/node";
const sentryMiddleware = new InngestMiddleware({
name: "Sentry Middleware",
init({ client }) {
// Initialize Sentry as soon as possible, creating a hub
Sentry.init({ dsn: "..." });
// Set up some tags that will be applied to all events
Sentry.setTag("inngest.client.name", client.name);
return {
onFunctionRun({ ctx, fn }) {
// Add specific context for the given function run
Sentry.setTags({
"inngest.function.id": fn.id(client.name),
"inngest.function.name": fn.name,
"inngest.event": ctx.event.name,
"inngest.run.id": ctx.runId,
});
// Start a transaction for this run
const transaction = Sentry.startTransaction({
name: "Inngest Function Run",
op: "run",
data: ctx.event,
});
let memoSpan: Sentry.Span;
let execSpan: Sentry.Span;
return {
transformInput() {
return {
ctx: {
// Add the Sentry client to the input arg so our
// functions can use it directly too
sentry: Sentry.getCurrentHub(),
},
};
},
beforeMemoization() {
// Track different spans for memoization and execution
memoSpan = transaction.startChild({ op: "memoization" });
},
afterMemoization() {
memoSpan.finish();
},
beforeExecution() {
execSpan = transaction.startChild({ op: "execution" });
},
afterExecution() {
execSpan.finish();
},
transformOutput({ result, step }) {
// Capture step output and log errors
if (step) {
Sentry.setTags({
"inngest.step.name": step.name,
"inngest.step.op": step.op,
});
if (result.error) {
Sentry.captureException(result.error);
}
}
},
async beforeResponse() {
// Finish the transaction and flush data to Sentry before the
// request closes
transaction.finish();
await Sentry.flush();
},
};
},
};
},
});
Prisma in function context
The following is an example of adding a Prisma client to all Inngest functions, allowing them immediate access without needing to create the client themselves.
While this example uses Prisma, it serves as a good example of using the onFunctionRun -> input hook to mutate function input to perform crucial setup for your functions and keep them to just business logic.
💡 Types are inferred from middleware outputs, so your Inngest functions will see an appropriately-typed prisma
property in their input.
import { PrismaClient } from "@prisma/client";
const prismaMiddleware = new InngestMiddleware({
name: "Prisma Middleware",
init() {
const prisma = new PrismaClient();
return {
onFunctionRun(ctx) {
return {
transformInput(ctx) {
return {
// Anything passed via `ctx` will be merged with the function's arguments
ctx: {
prisma,
},
};
},
};
},
};
},
});
// When defining a function...
inngest.createFunction(
{ name: "Example" },
{ event: "app/user.loggedin" },
async ({ prisma }) => {
await prisma.auditTrail.create(/* ... */);
}
);
Logging
The following shows you how you can create a logger middleware and customize it to your needs.
It is based on the built-in logger middleware in the SDK, and hope it gives you an idea of what you can do if the built-in logger doesn't meet your needs.
new InngestMiddleware({
name: "Inngest: Logger",
init({ client }) {
return {
onFunctionRun(arg) {
const { ctx } = arg;
const metadata = {
runID: ctx.runId,
eventName: ctx.event.name,
functionName: arg.fn.name,
};
let providedLogger: Logger = client["logger"];
// create a child logger if the provided logger has child logger implementation
try {
if ("child" in providedLogger) {
type ChildLoggerFn = (
metadata: Record<string, unknown>
) => Logger;
providedLogger = (providedLogger.child as ChildLoggerFn)(metadata)
}
} catch (err) {
console.error('failed to create "childLogger" with error: ', err);
// no-op
}
const logger = new ProxyLogger(providedLogger);
return {
transformInput() {
return {
ctx: {
/**
* The passed in logger from the user.
* Defaults to a console logger if not provided.
*/
logger,
},
};
},
beforeExecution() {
logger.enable();
},
transformOutput({ result: { error } }) {
if (error) {
logger.error(error);
}
},
async beforeResponse() {
await logger.flush();
},
};
},
};
},
})