Logging request and response data is useful for debugging API issues, monitoring
traffic patterns, and auditing API usage. This guide shows how to create custom
policies that log various parts of requests and responses while redacting
sensitive information.
Logging Request Headers
Create an inbound policy to log headers from incoming requests. This policy
redacts sensitive headers like Authorization and Cookie to prevent exposing
credentials in logs.
modules/log-request-headers.ts
import type { ZuploContext, ZuploRequest } from "@zuplo/runtime";export default async function policy( request: ZuploRequest, context: ZuploContext,) { const headers: Record<string, string> = {}; for (const [key, value] of request.headers.entries()) { const k = key.toLowerCase(); headers[key] = k === "authorization" || k === "cookie" || k === "set-cookie" || k === "x-api-key" ? "[REDACTED]" : value; } context.log.info({ headers }, "Incoming request headers"); return request;}
Logging Query Parameters
Log query parameters from the request URL:
modules/log-query-params.ts
import type { ZuploContext, ZuploRequest } from "@zuplo/runtime";export default async function policy( request: ZuploRequest, context: ZuploContext,) { const url = new URL(request.url); const queryParams = Object.fromEntries(url.searchParams); context.log.info( { path: url.pathname, query: queryParams, }, "Request query parameters", ); return request;}
Logging Request Body
Log the request body for POST, PUT, or PATCH requests. Clone the request first
to avoid consuming the body stream.
modules/log-request-body.ts
import type { ZuploContext, ZuploRequest } from "@zuplo/runtime";export default async function policy( request: ZuploRequest, context: ZuploContext,) { if (["POST", "PUT", "PATCH"].includes(request.method)) { const clone = request.clone(); const body = await clone.text(); // Parse JSON if applicable let parsedBody: unknown; try { parsedBody = JSON.parse(body); } catch { parsedBody = body; } context.log.info({ body: parsedBody }, "Request body"); } return request;}
Logging request bodies can expose sensitive data like passwords, tokens, or
personal information. Always sanitize or redact sensitive fields before logging.
Logging Response Headers and Status
Create an outbound policy to log response information:
modules/log-response-headers.ts
import type { ZuploContext, ZuploRequest, ZuploResponse } from "@zuplo/runtime";export default async function outboundPolicy( response: ZuploResponse, request: ZuploRequest, context: ZuploContext,) { const headers: Record<string, string> = {}; for (const [key, value] of response.headers.entries()) { const k = key.toLowerCase(); headers[key] = k === "set-cookie" ? "[REDACTED]" : value; } context.log.info( { status: response.status, headers }, "Outgoing response headers", ); return response;}
Logging Response Body
Log the response body from your backend. Clone the response first to avoid
consuming the body stream.
Always redact sensitive data before logging. Credentials, tokens, passwords, and
personal information should never appear in logs.
Redact sensitive data - Always redact Authorization, Cookie,
Set-Cookie, API keys, passwords, and any fields containing secrets or
personal data.
Use structured logging - Pass objects to context.log instead of string
concatenation. This enables better log searching and filtering.
Clone before reading - Always clone requests and responses before reading
their body to avoid consuming the stream.
Consider log volume - Logging bodies can generate significant log volume
and storage costs. Consider enabling body logging only for specific routes or
in development environments.
Use appropriate log levels - Use debug for verbose development logging
and info for production audit trails.
Limit body size - Consider truncating large bodies to avoid excessive log
storage: