You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何编写Apollo Server插件记录请求及耗时(TypeScript实现)

Custom Apollo Server Logging Plugin (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 of Date.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.log with 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.requests in willSendResponse and 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 12:24:08