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
This commit is contained in:
Evans Hauser 2018-09-17 22:45:34 -07:00 committed by Jesse Rosenberger
parent 261994ca7c
commit 81c46421a9
No known key found for this signature in database
GPG key ID: C0CCCF81AA6C08D8
7 changed files with 52 additions and 2 deletions

View file

@ -3,7 +3,6 @@
### vNEXT
- FIXME(@abernix): Allow context to be passed to all GraphQLExtension methods.
- FIXME(@abernix): client info in traces.
- Allow an optional function to resolve the `rootValue`, passing the `DocumentNode` AST to determine the value. [PR #1555](https://github.com/apollographql/apollo-server/pull/1555)
- Follow-up on the work in [PR #1516](https://github.com/apollographql/apollo-server/pull/1516) to also fix missing insertion cursor/caret when a custom GraphQL configuration is specified which doesn't specify its own `cursorShape` property. [PR #1607](https://github.com/apollographql/apollo-server/pull/1607)

View file

@ -365,3 +365,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<string, any>}) => 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.

View file

@ -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<string, any>;
},
) => 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

View file

@ -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<TContext = any>
operationName: string,
trace: Trace,
) => void;
private generateClientInfo: (
o: {
context: any;
extensions?: Record<string, any>;
},
) => ClientInfo;
public constructor(
options: EngineReportingOptions,
@ -51,6 +57,11 @@ export class EngineReportingExtension<TContext = any>
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<TContext = any>
variables: Record<string, any>;
persistedQueryHit?: boolean;
persistedQueryRegister?: boolean;
context: any;
extensions?: Record<string, any>;
}): EndHandler {
this.trace.startTime = dateToTimestamp(new Date());
this.startHrTime = process.hrtime();
@ -154,6 +167,16 @@ export class EngineReportingExtension<TContext = any>
});
}
// 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),

View file

@ -55,6 +55,7 @@ export interface GraphQLRequestOptions<TContext = any> {
debug?: boolean;
extensions?: Array<() => GraphQLExtension>;
queryExtensions?: Record<string, any>;
tracing?: boolean;
persistedQueries?: PersistedQueryOptions;
cacheControl?: CacheControlExtensionOptions;
@ -185,6 +186,7 @@ export class GraphQLRequestProcessor {
queryString: request.query,
operationName: request.operationName,
variables: request.variables,
extensions: request.extensions,
persistedQueryHit,
persistedQueryRegister,
});

View file

@ -302,6 +302,8 @@ export async function runHttpQuery(
formatResponse: optionsObject.formatResponse,
debug: optionsObject.debug,
queryExtensions: extensions,
});
// GET operations should only be queries (not mutations). We want to throw

View file

@ -78,6 +78,7 @@ export class GraphQLExtensionStack<TContext = any> {
variables?: { [key: string]: any };
persistedQueryHit?: boolean;
persistedQueryRegister?: boolean;
extensions?: Record<string, any>;
}): EndHandler {
return this.handleDidStart(
ext => ext.requestDidStart && ext.requestDidStart(o),