Skip to content

Instantly share code, notes, and snippets.

@allyjweir
Last active May 14, 2020 17:48
Show Gist options
  • Save allyjweir/ffc2cb36ed814d2be6ca78d0db54a1a2 to your computer and use it in GitHub Desktop.
Save allyjweir/ffc2cb36ed814d2be6ca78d0db54a1a2 to your computer and use it in GitHub Desktop.
import { GraphQLRequestContext } from 'apollo-server-core/dist/requestPipelineAPI';
import { Request } from 'apollo-server-env';
import beeline from 'honeycomb-beeline';
import {
DocumentNode,
GraphQLResolveInfo,
ResponsePath,
ExecutionArgs,
GraphQLOutputType,
GraphQLCompositeType,
} from 'graphql';
import { GraphQLExtension, EndHandler } from 'graphql-extensions';
import uuid from 'uuid';
import { responsePathAsString, parentResponsePathAsString } from './utils';
export default class HoneycombTracingExtension<TContext = any> implements GraphQLExtension<TContext> {
public spans: Map<string, any>;
public queryString;
public documentAST
public operationName;
public constructor() {
this.spans = new Map<string, any>();
}
public requestDidStart(o: {
request: Request;
queryString?: string;
parsedQuery?: DocumentNode;
variables?: Record<string, any>;
persistedQueryHit?: boolean;
persistedQueryRegister?: boolean;
context: TContext;
extensions?: Record<string, any>;
requestContext: GraphQLRequestContext<TContext>;
}): EndHandler {
// Generally, we'll get queryString here and not parsedQuery; we only get
// parsedQuery if you're using an OperationStore. In normal cases we'll get
// our documentAST in the execution callback after it is parsed.
this.queryString = o.queryString;
this.documentAST = o.parsedQuery;
if (beeline.traceActive()) {
const rootSpan = beeline.startSpan({ name: 'graphql_query' });
this.spans.set('', rootSpan);
} else {
beeline.startTrace();
}
return () => {
const rootSpanToFinish = this.spans.get('');
rootSpanToFinish['graphql.query_string'] = this.queryString;
beeline.finishSpan(rootSpanToFinish);
};
}
public executionDidStart(o: { executionArgs: ExecutionArgs }) {
// If the operationName is explicitly provided, save it. If there's just one
// named operation, the client doesn't have to provide it, but we still want
// to know the operation name so that the server can identify the query by
// it without having to parse a signature.
//
// Fortunately, in the non-error case, we can just pull this out of
// the first call to willResolveField's `info` argument. In an
// error case (eg, the operationName isn't found, or there are more
// than one operation and no specified operationName) it's OK to continue
// to file this trace under the empty operationName.
if (o.executionArgs.operationName) {
this.operationName = o.executionArgs.operationName;
}
this.documentAST = o.executionArgs.document;
}
public willResolveField(
_source: any,
_args: { [argName: string]: any },
_context: TContext,
info: GraphQLResolveInfo,
): ((error: Error | null, result: any) => void) | void {
if (this.operationName === undefined) {
this.operationName = (info.operation.name && info.operation.name.value) || '';
}
this.newSpan(info.path, info.returnType, info.parentType);
return () => {
const spanToFinish = this.spans.get(responsePathAsString(info.path));
spanToFinish['graphql.operation_name'] = this.operationName;
if (spanToFinish) {
beeline.finishSpan(spanToFinish);
}
};
}
private newSpan(path: ResponsePath, returnType: GraphQLOutputType, parentType: GraphQLCompositeType) {
const fieldResponsePath = responsePathAsString(path);
const context = {
name: 'graphql_field_resolver',
'graphql.type': returnType.toString(),
'graphql.parent_type': parentType.toString(),
'graphql.field_path': fieldResponsePath,
};
const id = path && path.key;
if (path && path.prev && typeof path.prev.key === 'number') {
context['graphql.field_name'] = `${path.prev.key}.${id}`;
} else {
context['graphql.field_name'] = id;
}
let parentSpanId;
if (path && path.prev) {
const parentSpan = this.spans.get(parentResponsePathAsString(path));
if (parentSpan) {
parentSpanId = parentSpan['trace.span_id'];
}
}
const span = beeline.startSpan(context, uuid(), parentSpanId);
this.spans.set(fieldResponsePath, span);
return span;
}
}
@allyjweir
Copy link
Author

allyjweir commented May 14, 2020 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment