可观测性:日志、追踪与监控


可观测性:日志、追踪与监控

GraphQL可观测性的特殊性

GraphQL的可观测性比REST复杂。REST的监控很简单:每个端点是一个URL,日志按URL聚合,延迟按URL统计。但GraphQL只有一个端点 /graphql,所有查询都发到这里。你不能用URL来区分不同的操作。

GraphQL的可观测性需要在请求级别添加上下文:操作名(operation name)、查询变量、解析器级别的耗时。

操作名:可观测性的锚点

操作名是GraphQL请求最基本的标识。每个查询和Mutation都应该有明确的操作名:

query GetUserProfile { ... } # 操作名: GetUserProfile mutation CreateNewPost { ... } # 操作名: CreateNewPost subscription OnCommentAdded { ... } # 操作名: OnCommentAdded ​

没有操作名的查询在生产环境中是不可接受的——你无法在日志和监控中区分不同的请求。

解析器级别的追踪

GraphQL的核心价值在于解析器模型,但这也意味着性能瓶颈可能隐藏在任何解析器中。解析器级别的追踪是找到性能问题的关键手段。

Apollo Server内置了追踪支持,可以通过插件获取每个解析器的执行时间:

import { ApolloServerPluginLandingPageLocalDefault } from '@apollo/server/plugin/landingPage/local'; import { ApolloServerPluginTracing } from '@apollo/server/plugin/tracing'; const server = new ApolloServer({ typeDefs, resolvers, plugins: [ ApolloServerPluginLandingPageLocalDefault(), // 每个解析器的执行时间记录在extensions.tracing中 { async requestDidStart(requestContext) { const start = Date.now(); const resolverTimings: Record<string, number[]> = {}; return { async executionDidStart() { return { async willResolveField({ info }) { const fieldStart = Date.now(); return (error) => { const duration = Date.now() - fieldStart; const key = `${info.parentType.name}.${info.fieldName}`; if (!resolverTimings[key]) resolverTimings[key] = []; resolverTimings[key].push(duration); }; } }; }, async didEncounterErrors(errors) { // 错误日志 }, async willSendResponse() { const totalDuration = Date.now() - start; console.log(JSON.stringify({ operation: requestContext.request.operationName, duration: totalDuration, resolvers: resolverTimings, variables: requestContext.request.variables, })); } }; } } ] }); ​

这段代码追踪了每个解析器的执行时间,在请求结束时输出一份详细的性能报告。

日志策略

GraphQL的日志需要记录以下信息:

  • 请求级:操作名、变量(脱敏后)、总耗时、HTTP状态码
  • 解析器级:字段路径、执行耗时、调用的数据源
  • 错误级:错误消息、错误路径、错误码
// 结构化日志示例 const logRequest = (context) => { logger.info('graphql_request', { operation: context.operationName, variables: sanitize(context.request.variables), // 脱敏:隐藏token等敏感信息 duration: context.totalDuration, status: context.response.statusCode, queryHash: hashQuery(context.request.query), // 用于聚合统计 }); }; ​

脱敏是关键。变量中可能包含密码、token、个人信息。日志中不应该记录这些敏感数据。

Apollo Studio的监控面板

如果你使用Apollo Cloud,Apollo Studio提供了开箱即用的监控面板:

  • 操作面板:展示每个操作名的请求量、成功率、P95延迟
  • 字段面板:展示每个字段的延迟分布,快速定位慢字段
  • 查询分析:展示最热门的查询和最慢的查询
  • 缓存命中率:展示客户端缓存的命中情况

这些面板的数据来源是客户端上报的 report-protocol——Apollo Client自动收集每个查询的执行信息(操作名、变量摘要、缓存命中情况、错误信息),定期上报到Apollo Studio。

// Apollo Client上报配置 const client = new ApolloClient({ link: authLink.concat(httpLink), cache: new InMemoryCache(), // Apollo Studio上报 connectToDevTools: process.env.NODE_ENV === 'development', defaultOptions: { watchQuery: { // 在上报中包含错误详情 fetchPolicy: 'cache-and-network', } } }); ​

Prometheus指标

对于自建的监控系统,可以用Prometheus格式的指标:

import { Counter, Histogram } from 'prom-client'; const graphqlRequestTotal = new Counter({ name: 'graphql_request_total', labelNames: ['operation', 'status'], help: 'GraphQL请求总数', }); const graphqlRequestDuration = new Histogram({ name: 'graphql_request_duration_seconds', labelNames: ['operation'], buckets: [0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5], help: 'GraphQL请求延迟', }); const graphqlResolverDuration = new Histogram({ name: 'graphql_resolver_duration_seconds', labelNames: ['type', 'field'], buckets: [0.001, 0.005, 0.01, 0.05, 0.1, 0.5], help: 'GraphQL解析器延迟', }); ​

然后在插件中收集这些指标,暴露Prometheus endpoint,用Grafana可视化。

分布式追踪

在微服务架构中,GraphQL服务可能调用多个后端服务。分布式追踪(如OpenTelemetry、Jaeger)让你看到一个GraphQL请求经过了哪些服务、每个服务的耗时。

import { ApolloServerPluginLandingPageDisabled } from '@apollo/server/plugin/disabled'; import { ApolloServerPluginOpenTelemetry } from '@apollo/server/plugin/openTelemetry'; const server = new ApolloServer({ typeDefs, resolvers, plugins: [ ApolloServerPluginOpenTelemetry({ // OpenTelemetry自动为每个解析器创建span }), ] }); ​

这样在Jaeger中,你可以看到一个GraphQL请求的追踪链:Gateway → 用户服务(查用户)→ 帖子服务(查帖子)→ 评论服务(查评论),每个环节的耗时一目了然。

可观测性的目标是:在出问题时,能在几分钟内定位到是哪个解析器、调用了哪个数据源、耗时多少、错误是什么。没有可观测性的GraphQL服务,在生产环境中就是黑箱。

图:GraphQL可观测性三层架构

图:GraphQL可观测性三层架构

作者与出处
原作者:灏天文库
来源:平台策划编纂
整理人: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U