.claude/skills/logging/SKILL.md
How to do backend logging
npx skillsauth add elie222/inbox-zero loggingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
We use a centralized, request-scoped logging pattern where loggers are created by middleware and passed through the request/function chain.
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 tracingurl - Request URLuserId - For authenticated routesemailAccountId, email - For email account routesAdd additional context within your route handler:
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 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 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 contextemailAccountId - Current email accountprovider - Email provider typeUse 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.
tools
Use the Inbox Zero API CLI to inspect the live API schema, list and manage automation rules, and read inbox analytics through the public API. Use this when a task involves Inbox Zero rules, stats, or API-driven automation and can be solved through the CLI instead of browser interaction.
tools
Write focused unit tests for backend and utility logic
testing
Pause execution for a user-specified duration
testing
Update workspace packages while respecting the repo's pinned package list in .ncurc.cjs. Use when the user asks to update dependencies or refresh package versions.