From 96af44e41a9816b52ca7777f80c218f0f5470ba3 Mon Sep 17 00:00:00 2001 From: Evans Hauser Date: Mon, 17 Sep 2018 22:45:34 -0700 Subject: [PATCH] Provide ability to specify client info in traces (#1631) * Provide ability to specify client info in traces Adds the createClientInfo to apollo-engine-reporting, which enables the differentiation of clients based on the request, operation, and variables. This could be extended to include the response. However for the first release. It doesn't quite make sense. * Use extensions and context in createClientInfo * Remove support for clientAddress The frontend will not support it in the near future * create -> generate and make default generator createClientInfo -> generateClientInfo * Clarify default values --- CHANGELOG.md | 1 + docs/source/api/apollo-server.md | 10 ++++++++ packages/apollo-engine-reporting/src/agent.ts | 13 ++++++++++ .../apollo-engine-reporting/src/extension.ts | 25 ++++++++++++++++++- .../apollo-server-core/src/runHttpQuery.ts | 1 + packages/apollo-server-core/src/runQuery.ts | 2 ++ packages/graphql-extensions/src/index.ts | 1 + 7 files changed, 52 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5afd3b5352..b3a7cca6552 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All of the packages in the `apollo-server` repo are released with the same versi ### vNEXT - Update link inside Authentication Docs [PR #1682](https://github.com/apollographql/apollo-server/pull/1682) +- Provide ability to specify client info in traces [#1631](https://github.com/apollographql/apollo-server/pull/1631) ### v2.0.8 diff --git a/docs/source/api/apollo-server.md b/docs/source/api/apollo-server.md index b0db2db34aa..40103674e79 100644 --- a/docs/source/api/apollo-server.md +++ b/docs/source/api/apollo-server.md @@ -350,3 +350,13 @@ addMockFunctionsToSchema({ * `maskErrorDetails`: boolean Set to true to remove error details from the traces sent to Apollo's servers. Defaults to false. + +* generateClientInfo?: (o: { context: any, extensions?: Record}) => ClientInfo; + + Creates the client information that is attached to the traces sent to the + Apollo backend. The context field is the execution context passed to the + resolvers and the extensions field corresponds to the same value in the POST + body or GET parameters. `ClientInfo` contains fields for `clientName` and + `clientVersion`, which are both optional. The default generation copies the + respective fields from `extensions.clientInfo`. If `clientName` or + `clientVersion` is not present, the values are set to the empty string. diff --git a/packages/apollo-engine-reporting/src/agent.ts b/packages/apollo-engine-reporting/src/agent.ts index 379ab085597..ea5b9b17a3d 100644 --- a/packages/apollo-engine-reporting/src/agent.ts +++ b/packages/apollo-engine-reporting/src/agent.ts @@ -34,6 +34,11 @@ Traces.encode = function(message, originalWriter) { return writer; }; +export interface ClientInfo { + clientName?: string; + clientVersion?: string; +} + export interface EngineReportingOptions { // API key for the service. Get this from // [Engine](https://engine.apollographql.com) by logging in and creating @@ -83,6 +88,14 @@ export interface EngineReportingOptions { sendReportsImmediately?: boolean; // To remove the error message from traces, set this to true. Defaults to false maskErrorDetails?: boolean; + // Creates the client information attached to the traces sent to the Apollo + // backend + generateClientInfo?: ( + o: { + context: any; + extensions?: Record; + }, + ) => ClientInfo; // XXX Provide a way to set client_name, client_version, client_address, // service, and service_version fields. They are currently not revealed in the diff --git a/packages/apollo-engine-reporting/src/extension.ts b/packages/apollo-engine-reporting/src/extension.ts index 624985dab0c..bf9b992d569 100644 --- a/packages/apollo-engine-reporting/src/extension.ts +++ b/packages/apollo-engine-reporting/src/extension.ts @@ -15,7 +15,7 @@ import { } from 'graphql-extensions'; import { Trace, google } from 'apollo-engine-reporting-protobuf'; -import { EngineReportingOptions } from './agent'; +import { EngineReportingOptions, ClientInfo } from './agent'; import { defaultSignature } from './signature'; // EngineReportingExtension is the per-request GraphQLExtension which creates a @@ -38,6 +38,12 @@ export class EngineReportingExtension operationName: string, trace: Trace, ) => void; + private generateClientInfo: ( + o: { + context: any; + extensions?: Record; + }, + ) => ClientInfo; public constructor( options: EngineReportingOptions, @@ -51,6 +57,11 @@ export class EngineReportingExtension const root = new Trace.Node(); this.trace.root = root; this.nodes.set(responsePathAsString(undefined), root); + this.generateClientInfo = + options.generateClientInfo || + // Default to using the clientInfo field of the request's extensions, when + // the ClientInfo fields are undefined, we send the empty string + (({ extensions }) => (extensions && extensions.clientInfo) || {}); } public requestDidStart(o: { @@ -60,6 +71,8 @@ export class EngineReportingExtension variables?: Record; persistedQueryHit?: boolean; persistedQueryRegister?: boolean; + context: any; + extensions?: Record; }): EndHandler { this.trace.startTime = dateToTimestamp(new Date()); this.startHrTime = process.hrtime(); @@ -154,6 +167,16 @@ export class EngineReportingExtension }); } + // While clientAddress could be a part of the protobuf, we'll ignore it for + // now, since the backend does not group by it and Engine frontend will not + // support it in the short term + const { clientName, clientVersion } = this.generateClientInfo({ + context: o.context, + extensions: o.extensions, + }); + this.trace.clientName = clientName || ''; + this.trace.clientVersion = clientVersion || ''; + return () => { this.trace.durationNs = durationHrTimeToNanos( process.hrtime(this.startHrTime), diff --git a/packages/apollo-server-core/src/runHttpQuery.ts b/packages/apollo-server-core/src/runHttpQuery.ts index 08964abd6aa..e49d1a7b7ad 100644 --- a/packages/apollo-server-core/src/runHttpQuery.ts +++ b/packages/apollo-server-core/src/runHttpQuery.ts @@ -402,6 +402,7 @@ export async function runHttpQuery( : false, request: request.request, extensions: optionsObject.extensions, + queryExtensions: extensions, persistedQueryHit, persistedQueryRegister, }; diff --git a/packages/apollo-server-core/src/runQuery.ts b/packages/apollo-server-core/src/runQuery.ts index 516750dfd78..4eabf053a36 100644 --- a/packages/apollo-server-core/src/runQuery.ts +++ b/packages/apollo-server-core/src/runQuery.ts @@ -66,6 +66,7 @@ export interface QueryOptions { cacheControl?: boolean | CacheControlExtensionOptions; request: Pick; extensions?: Array<() => GraphQLExtension>; + queryExtensions?: Record; persistedQueryHit?: boolean; persistedQueryRegister?: boolean; } @@ -136,6 +137,7 @@ function doRunQuery(options: QueryOptions): Promise { persistedQueryHit: options.persistedQueryHit, persistedQueryRegister: options.persistedQueryRegister, context, + extensions: options.queryExtensions, }); return Promise.resolve() .then( diff --git a/packages/graphql-extensions/src/index.ts b/packages/graphql-extensions/src/index.ts index b7f358f6da8..b4455109da6 100644 --- a/packages/graphql-extensions/src/index.ts +++ b/packages/graphql-extensions/src/index.ts @@ -81,6 +81,7 @@ export class GraphQLExtensionStack { persistedQueryHit?: boolean; persistedQueryRegister?: boolean; context: TContext; + extensions?: Record; }): EndHandler { return this.handleDidStart( ext => ext.requestDidStart && ext.requestDidStart(o),