Capturing stack traces from GraphQLErrors in Apollo Server

Kibana version: 7.5.0

Elasticsearch version: 7.5.0

APM Server version: 7.5.0

APM Agent language and version: Node.js Agent 3.3.0

Browser version: Google Chrome Version 79.0.3945.88 (Official Build) (64-bit)

Original install method (e.g. download page, yum, deb, from source, etc.) and version: Using Elastic Cloud; installed Node.js agent via yarn

Fresh install or upgraded from other version? Fresh install (no updates applied to cluster yet)

Is there anything special in your setup? Not in the Elastic Cloud services. For the agent, we've set the environment variable ELASTIC_APM_CAPTURE_BODY=all and left all other configuration options set to their defaults.

Description of the problem including expected versus actual behavior. Please include screenshots (if relevant):

The agent doesn't seem to extract stack traces from Apollo Server's GraphQLError objects, though it does extract error messages. Within Kibana's APM Module, the 'Errors' section shows information about each exception including exception messages and all fields in the 'Metadata' tab. However, the 'Culprit' field and 'Exception stacktrace' tabs are both blank as shown below. Since the GraphQLError object being thrown includes a stack trace, the expected behavior is for the stack trace to be visible within the APM UI.

Steps to reproduce:

Here's the code that initializes the APM agent in our server.ts file:

require('dotenv').config({ /* Load env vars */ })
const apm = require('elastic-apm-node').start();

import { ApolloServer } from 'apollo-server-express';
import { GraphQLError } from 'graphql';
import { ContextFactory } from './context';
/* ...import a bunch of other modules... */

async function startServer() {
  console.log(`Starting application in ENVIRONMENT: ${appEnvironment} `);

  const server = new ApolloServer({
    schema,
    context: async ({ req }) => {
      return ContextFactory.createContext(req);
    },
    formatError: (error: GraphQLError) => {
      apm.captureError(error);

      /* ...more specific error handling... */
    },
  });

  const app = express();
  /* ...a bunch of `app.use()` calls... */
  server.applyMiddleware({ app });

  app.listen({ port: process.env.PORT || 8000 }, () =>
    console.log(
      `Server ready at http://localhost:${process.env.PORT}${server.graphqlPath}`
    )
  );
}

startServer();

After starting the app, navigate to the graphQL endpoint (at http://localhost:8000/graphql when run in development mode) and issue any invalid query to trigger an error.

Note the apm.captureError(error) call in the formatError block. As shown in the screenshot above, this statement successfully transmits errors to the APM server. However, no stack trace is sent. My first guess about why is because the GraphQLError object's structure differs significantly from the generic Error class referenced in the APM agent's API docs. (See the Masking and Logging Errors section of the Apollo Server documentation to see the differences.) However, the agent's API doesn't seem to provide any way of manually specifying the location of a stack trace within an error object.

Questions:

  1. Since both apollo-server-express and graphql are listed as supported modules, should the Node.js APM agent be parsing these errors automatically? (In other words, did I break something?)
  2. If not, is there a way to tell the APM agent where to look within an error for a stack trace?

Other version info
apollo-server-express: 2.9.3
graphql: 14.5.6

Sample GraphQLError

[ValidationError: Cannot query field "programsssss" on type "RootQuery". Did you mean "programs" or "program"?] {
  message: 'Cannot query field "programsssss" on type "RootQuery". Did you mean "programs" or "program"?',
  locations: [ { line: 2, column: 3 } ],
  path: undefined,
  extensions: {
    code: 'GRAPHQL_VALIDATION_FAILED',
    exception: {
      stacktrace: [
        'GraphQLError: Cannot query field "programsssss" on type "RootQuery". Did you mean "programs" or "program"?',
        '    at Object.Field (/projects/platform-api/node_modules/graphql/validation/rules/FieldsOnCorrectType.js:53:31)',
        '    at Object.enter (/projects/platform-api/node_modules/graphql/language/visitor.js:324:29)',
        '    at Object.enter (/projects/platform-api/node_modules/graphql/language/visitor.js:375:25)',
        '    at visit (/projects/platform-api/node_modules/graphql/language/visitor.js:242:26)',
        '    at Object.validate (/projects/platform-api/node_modules/graphql/validation/validate.js:73:24)',
        '    at validate (/projects/platform-api/node_modules/apollo-server-core/src/requestPipeline.ts:434:22)',
        '    at Object.<anonymous> (/projects/platform-api/node_modules/apollo-server-core/src/requestPipeline.ts:252:32)',
        '    at Generator.next (<anonymous>)',
        '    at fulfilled (/projects/platform-api/node_modules/apollo-server-core/dist/requestPipeline.js:5:58)',
        '    at runMicrotasks (<anonymous>)',
        '    at processTicksAndRejections (internal/process/task_queues.js:93:5)'
      ]
    }
  }
}

This topic was automatically closed 20 days after the last reply. New replies are no longer allowed.

thanks for the feedback,
I opened an issue on this subject