如何编写Apollo Server插件记录请求及耗时(TypeScript实现)
Got it, let's walk through building exactly what you need— a TypeScript plugin for Apollo Server that logs request details including operation type, processing time, and payload size. Since apollo-log doesn't support tracking request latency, a custom plugin is the way to go, and we'll use Apollo's recommended pattern for maintaining state across request lifecycle callbacks.
Core Approach: State Persistence via Closures
The key to sharing state between requestDidStart and willSendResponse is leveraging closures. Each time a request comes in, requestDidStart runs and returns an object with lifecycle callbacks (like willSendResponse) that have access to variables defined in the requestDidStart scope. This ensures each request's data stays isolated and doesn't leak across requests.
Full TypeScript Implementation
import { ApolloServerPlugin, BaseContext, Request } from '@apollo/server'; /** * Custom Apollo Server plugin to log request details: * - Operation name (query/mutation) * - Processing time in milliseconds * - Request payload size in bytes */ export const serverLoggingPlugin: ApolloServerPlugin<BaseContext> = { async requestDidStart(requestContext) { // Capture start time for latency calculation const startTime = Date.now(); // Calculate payload size by stringifying the request body const payloadSize = getPayloadSize(requestContext.request); // Extract the operation name (handle single and batch requests) const operationName = extractOperationName(requestContext.request); // Return lifecycle callbacks that have access to the above variables via closure return { async willSendResponse() { const processingTime = Date.now() - startTime; // Fallback to 'unknown-operation' if no name is found (e.g., introspection) const logPath = operationName || 'unknown-operation'; // Log in the format you requested console.log(`path="${logPath}" service=${processingTime}ms bytes=${payloadSize}`); }, }; }, }; /** * Helper to calculate payload size for single or batch requests */ function getPayloadSize(request: Request): number { if (request.body.kind === 'single') { return JSON.stringify(request.body).length; } else if (request.body.kind === 'batch') { // For batch requests, this returns the total size of all requests in the batch return JSON.stringify(request.body.requests).length; } return 0; } /** * Helper to extract operation name from single or batch requests */ function extractOperationName(request: Request): string | undefined { if (request.body.kind === 'single') { return request.body.operationName; } else if (request.body.kind === 'batch') { // For batch requests, we'll take the first operation's name here // Adjust this if you want to log each operation individually return request.body.requests[0]?.operationName; } return undefined; }
How to Use the Plugin
Add the plugin to your Apollo Server initialization:
import { ApolloServer } from '@apollo/server'; import { startStandaloneServer } from '@apollo/server/standalone'; import { serverLoggingPlugin } from './server-logging-plugin'; // Your schema and resolvers here const typeDefs = `#graphql type Query { getProfile(id: ID!): Profile } type Mutation { createAccount(input: AccountInput!): Account } input AccountInput { email: String! password: String! } type Profile { id: ID! name: String! } type Account { id: ID! email: String! } `; const resolvers = { Query: { getProfile: () => ({ id: '1', name: 'John Doe' }), }, Mutation: { createAccount: () => ({ id: '2', email: 'user@example.com' }), }, }; async function startServer() { const server = new ApolloServer({ typeDefs, resolvers, plugins: [serverLoggingPlugin], // Register the logging plugin }); const { url } = await startStandaloneServer(server, { listen: { port: 4000 }, }); console.log(`🚀 Server running at ${url}`); } startServer();
Optional Enhancements
- Precise Timing: For more accurate latency measurements, use
process.hrtime()instead ofDate.now():const start = process.hrtime(); // Later in willSendResponse: const [seconds, nanoseconds] = process.hrtime(start); const processingTime = seconds * 1000 + nanoseconds / 1e6; // Convert to ms - Structured Logging: Replace
console.logwith a logging library like Pino or Winston for structured logs easier to parse:import pino from 'pino'; const logger = pino(); // In willSendResponse: logger.info({ path: logPath, service: processingTime, bytes: payloadSize }); - Batch Request Per-Operation Logging: If you want to log each operation in a batch separately, loop through
request.body.requestsinwillSendResponseand calculate timing for each (you'll need to track start times per operation, which requires a bit more state management).
As you noted, apollo-log doesn't support tracking processing time natively, so this custom plugin gives you full control over exactly what data you log and how it's formatted.
内容的提问来源于stack exchange,提问作者Jonathan

