SuperAPI AI Directory

logging

Coding & development

View repository

Updated 10-15 21:37

About this skill

Logging

We use a centralized, request-scoped logging pattern where loggers are created by middleware and passed through the request/function chain.

API Route Logging (Primary Pattern)

Use middleware wrappers that automatically create loggers with request context:

import { withError, withAuth, withEmailAccount, withEmailProvider } from "@/utils/middleware";

// Basic route with error handling and logging
export const POST = withError("my-route", async (request) => {
  const logger = request.logger;
  logger.info("Processing request");
  // ...
});

// Authenticated route - logger includes userId export const GET = withAuth("my-route", async (request) => { request.logger.info("User action"); // Already has userId context // ... });

// Email account route - logger includes emailAccountId, email export const POST = withEmailAccount("my-route", async (request) => { request.logger.info("Email action"); // Has userId, emailAccountId, email // ... });

// Email provider route - same as email account, plus provides emailProvider export const GET = withEmailProvider("my-route", async (request) => { request.logger.info("Provider action"); const emails = await request.emailProvider.getMessages(); // ... });


The middleware automatically adds:
- `requestId` - Unique ID for request tracing
- `url` - Request URL
- `userId` - For authenticated routes
- `emailAccountId`, `email` - For email account routes

### Enriching Logger Context

Add additional context within your route handler:

```typescript
export const POST = withEmailAccount("digest", async (request) => {
  let logger = request.logger;
  
  const body = await request.json();
  logger = logger.with({ messageId: body.messageId });
  
  logger.info("Processing message");
  // ...
});

Helper Function Logging

Helper functions called from routes should receive the logger as a parameter instead of creating their own:

import type { Logger } from "@/utils/logger";

export async function processEmail(
  emailId: string,
  logger: Logger,
) {
  logger = logger.with({ emailId });
  logger.info("Processing email");
  // ...
}

Then call from your route:

export const POST = withEmailAccount("process", async (request) => {
  await processEmail(body.emailId, request.logger);
});

Server Action Logging

Server actions using actionClient receive the logger through context, similar to route middleware:

import { actionClient } from "@/utils/actions/safe-action";

export const createRuleAction = actionClient
  .metadata({ name: "createRule" })
  .inputSchema(createRuleBody)
  .action(
    async ({
      ctx: { emailAccountId, logger, provider },
      parsedInput: { name, actions },
    }) => {
      logger.info("Creating rule", { name });
      // ...
    },
  );

The actionClient context provides:

  • logger - Scoped logger with request context
  • emailAccountId - Current email account
  • provider - Email provider type

When to Use createScopedLogger

Use createScopedLogger only for code that doesn't run within a middleware chain (route or action):

import { createScopedLogger } from "@/utils/logger";

// Standalone scripts
const logger = createScopedLogger("script/migrate");

// Tests
const logger = createScopedLogger("test");

Don't use .with() for a global/file-level logger. Only use within a specific function.