Skip to content

Instantly share code, notes, and snippets.

@ngbrown
Created March 29, 2019 04:00
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ngbrown/b626c589b76d0f6ef6f9801a2faf6458 to your computer and use it in GitHub Desktop.
Save ngbrown/b626c589b76d0f6ef6f9801a2faf6458 to your computer and use it in GitHub Desktop.
Custom logging in Apollo Server 2 using the extension API
import {GraphQLExtension, GraphQLResponse} from 'graphql-extensions';
import {formatApolloErrors} from 'apollo-server-errors';
import {GraphQLError, GraphQLFormattedError} from 'graphql';
import Logger from 'bunyan';
import icepick from 'icepick';
const filterOutErrorPaths = [
['extensions', 'exception', 'options', 'auth', 'bearer'],
[
'extensions',
'exception',
'response',
'request',
'headers',
'authorization',
],
];
function errSerializer(error: GraphQLError) {
if (error == null) {
return error;
}
let err = icepick.dissoc(error, 'nodes');
for (let i = 0; i < filterOutErrorPaths.length; i++) {
if (icepick.getIn(err, filterOutErrorPaths[i])) {
err = icepick.dissocIn(err, filterOutErrorPaths[i]);
}
}
return err;
}
export function logGraphQLError(
log: Logger,
error: GraphQLError
): GraphQLFormattedError {
if (error.extensions && error.extensions.logged === true) {
return error;
}
if (!error.extensions || error.extensions.logged !== true) {
const {message: Message = 'An unknown error occurred.'} = error;
const err = errSerializer(error);
const Code =
(error.extensions && error.extensions.code) ||
'INTERNAL_SERVER_ERROR_NOT_ENRICHED';
log
.child({SourceContext: 'graphql'}, true)
.error({err, Message, Code}, '{Code} in GraphQL Server: {Message}');
if (error.extensions) {
icepick.setIn(error, ['extensions', 'logged'], true);
}
}
return error;
}
export class ApolloLoggingExtension<
TContext extends {log: Logger} = any
> extends GraphQLExtension<TContext> {
private readonly debug: boolean;
constructor(debug: boolean = false) {
super();
this.debug = debug;
}
public willSendResponse(o: {
graphqlResponse: GraphQLResponse;
context: TContext;
}): void | {graphqlResponse: GraphQLResponse; context: TContext} {
if (o.graphqlResponse.errors) {
const errors = formatApolloErrors(o.graphqlResponse.errors, {
formatter: logGraphQLError.bind(null, o.context.log),
debug: this.debug,
});
return {
...o,
graphqlResponse: {
...o.graphqlResponse,
errors,
},
};
}
}
}
import {ApolloServer} from 'apollo-server-express';
import bunyan from 'bunyan';
import {
ApolloLoggingExtension,
logGraphQLError,
} from './ApolloLoggingExtension';
// TODO: setup bunyan streams
const streams = [];
const rootLog = bunyan.createLogger({
streams: streams,
serializers: bunyan.stdSerializers,
});
export const graphqlServer = new ApolloServer({
// ...other setup
formatError: logGraphQLError.bind(null, rootLog),
debug: true,
extensions: [() => new ApolloLoggingExtension()],
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment