From 79fbd424ae06afe9d12c0633aee3bf9f8a0a503e Mon Sep 17 00:00:00 2001 From: Tom Koufakis Date: Wed, 7 Apr 2021 14:18:26 -0700 Subject: [PATCH 1/2] Added ability to enable/disable segment generation at runtime --- .gitignore | 1 + package.json | 1 + src/traceResolvers.ts | 38 ++++--- test/helpers/schema.ts | 8 +- test/traceResolvers-contextMissing.test.ts | 6 +- test/traceResolvers-withOptions.test.ts | 114 +++++++++++++++++++++ test/traceResolvers.test.ts | 6 +- 7 files changed, 157 insertions(+), 17 deletions(-) create mode 100644 test/traceResolvers-withOptions.test.ts diff --git a/.gitignore b/.gitignore index ebd2d84..4358e8c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ node_modules/ .nyc_output/ +coverage/ src/**/*.js test/**/*.js diff --git a/package.json b/package.json index 4ca685a..2a6188d 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "posttest": "yarn lint && tsc --noEmit", "postinstall": "if [ -d test ]; then yarn types; fi", "coverage": "nyc report --reporter=text-lcov > ./.nyc_output/lcov.info", + "coverage_report": "nyc --reporter=lcov --reporter=text-summary ava", "prepublishOnly": "yarn tsc", "cleanTypes": "rm -rf test/__generated__" }, diff --git a/src/traceResolvers.ts b/src/traceResolvers.ts index 982d55b..c2f533f 100644 --- a/src/traceResolvers.ts +++ b/src/traceResolvers.ts @@ -14,20 +14,32 @@ function fieldPathFromInfo (info: GraphQLResolveInfo) { return segments.join('.'); } -export default (schema: GraphQLSchema): GraphQLSchemaWithFragmentReplacements => { +export interface TraceOptions { + enabled: boolean; +} + +const DefaultOptions: TraceOptions = { + enabled: true +}; + +export default (schema: GraphQLSchema, options: TraceOptions = DefaultOptions): GraphQLSchemaWithFragmentReplacements => { const tracer: IMiddlewareResolver = async (resolver, _parent, _args, _ctx, info) => { - const fieldPath = fieldPathFromInfo(info); - return AWSXRay.captureAsyncFunc(`GraphQL ${fieldPath}`, async (subsegment) => { - // When AWS_XRAY_CONTEXT_MISSING is set to LOG_MISSING and no context is found then subsegment is undefined. - try { - const result = await resolver(); - subsegment?.close(); - return result; - } catch (error: any) { - subsegment?.close(error); - throw error; - } - }); + if (options.enabled) { + const fieldPath = fieldPathFromInfo(info); + return AWSXRay.captureAsyncFunc(`GraphQL ${fieldPath}`, async (subsegment) => { + // When AWS_XRAY_CONTEXT_MISSING is set to LOG_MISSING and no context is found then subsegment is undefined. + try { + const result = await resolver(); + subsegment?.close(); + return result; + } catch (error: any) { + subsegment?.close(error); + throw error; + } + }); + } else { + return resolver(); + } }; return applyMiddlewareToDeclaredResolvers(schema, tracer); diff --git a/test/helpers/schema.ts b/test/helpers/schema.ts index 779bf7d..df3a06f 100644 --- a/test/helpers/schema.ts +++ b/test/helpers/schema.ts @@ -2,7 +2,7 @@ import { makeExecutableSchema } from 'graphql-tools'; import { v4 as uuid } from 'uuid'; import { loadFiles } from '@graphql-toolkit/file-loading'; import path from 'path'; -import traceResolvers from '../../src/traceResolvers'; +import traceResolvers, { TraceOptions } from '../../src/traceResolvers'; const blocked = new Map(); @@ -80,10 +80,14 @@ const resolvers = { } }; -export function traceSchema () { +export function traceSchema (options: TraceOptions|null) { const schema = makeExecutableSchema({ typeDefs: loadFiles(path.join(__dirname, '**/*.graphql')), resolvers }); + + if (options) { + return traceResolvers(schema, options); + } return traceResolvers(schema); } diff --git a/test/traceResolvers-contextMissing.test.ts b/test/traceResolvers-contextMissing.test.ts index 38997b9..01eb355 100644 --- a/test/traceResolvers-contextMissing.test.ts +++ b/test/traceResolvers-contextMissing.test.ts @@ -1,5 +1,6 @@ import { graphql, GraphQLError, Source } from 'graphql'; import { traceSchema } from './helpers/schema'; +import { TraceOptions } from '../src/traceResolvers'; import nock from 'nock'; import anyTest, { TestInterface } from 'ava'; import AWSXRay from 'aws-xray-sdk-core'; @@ -25,11 +26,14 @@ interface TestContext { } const test = anyTest as TestInterface; +const traceOptions: TraceOptions = { + enabled: true +}; test.beforeEach((t) => { nock.disableNetConnect(); nock.enableNetConnect('127.0.0.1'); - const schema = traceSchema(); + const schema = traceSchema(traceOptions); t.context.graphql = (query) => graphql(schema, query); }); diff --git a/test/traceResolvers-withOptions.test.ts b/test/traceResolvers-withOptions.test.ts new file mode 100644 index 0000000..5d0c31a --- /dev/null +++ b/test/traceResolvers-withOptions.test.ts @@ -0,0 +1,114 @@ +import { graphql } from 'graphql'; +import { traceSchema } from './helpers/schema'; +import { TraceOptions } from '../src/traceResolvers'; +import nock from 'nock'; +import anyTest, { TestInterface } from 'ava'; +import AWSXRay, { Segment } from 'aws-xray-sdk-core'; +import { ExecutionResult, ExecutionResultDataDefault } from 'graphql/execution/execute'; + +AWSXRay.capturePromise(); + +type GraphQlQuery = Parameters[1]; +type Namespace = ReturnType; + +interface TestContext { + ns: Namespace; + segment: Segment; + graphql: (query: GraphQlQuery) => Promise>; +} + +const test = anyTest as TestInterface; +const traceOptions: TraceOptions = { + enabled: true +}; + +test.beforeEach(function (test) { + const schema = traceSchema(traceOptions); + nock.disableNetConnect(); + nock.enableNetConnect('127.0.0.1'); + + const ns: Namespace = AWSXRay.getNamespace(); + test.context.ns = ns; + + const segment = new AWSXRay.Segment('parent'); + test.context.segment = segment; + + test.context.graphql = ns.bind(function (query: GraphQlQuery) { + AWSXRay.setSegment(segment); + try { + return graphql(schema, query); + } finally { + segment.close(); + } + }); +}); + +test.afterEach.always(() => { + nock.enableNetConnect(); + nock.cleanAll(); +}); + +test('Trace segments are not created for resolvers when enabled is not true', async function (test) { + try { + traceOptions.enabled = false; + const { segment, graphql } = test.context; + const result = await graphql('{ hello }'); + + if (result.errors) { + throw result.errors[0]; + } + test.deepEqual(result, { + data: { + hello: 'world' + } + }); + + test.is(segment.subsegments, undefined); + } finally { + traceOptions.enabled = true; + } +}); + +const pre = (test: any, options: TraceOptions|null) => { + const schema = traceSchema(options); + nock.disableNetConnect(); + nock.enableNetConnect('127.0.0.1'); + + const ns: Namespace = AWSXRay.getNamespace(); + test.context.ns = ns; + + const segment = new AWSXRay.Segment('parent'); + test.context.segment = segment; + + test.context.graphql = ns.bind(function (query: GraphQlQuery) { + AWSXRay.setSegment(segment); + try { + return graphql(schema, query); + } finally { + segment.close(); + } + }); +}; + +test('Trace segments are created for resolvers when defaults are used', async function (test) { + try { + pre(test, null); + + const { segment, graphql } = test.context; + const result = await graphql('{ hello }'); + + if (result.errors) { + throw result.errors[0]; + } + test.deepEqual(result, { + data: { + hello: 'world' + } + }); + + test.is(segment.subsegments?.length, 1); + } finally { + nock.enableNetConnect(); + nock.cleanAll(); + } +}); diff --git a/test/traceResolvers.test.ts b/test/traceResolvers.test.ts index e0660bd..d9236cf 100644 --- a/test/traceResolvers.test.ts +++ b/test/traceResolvers.test.ts @@ -1,5 +1,6 @@ import { graphql } from 'graphql'; import { traceSchema } from './helpers/schema'; +import { TraceOptions } from '../src/traceResolvers'; import nock from 'nock'; import anyTest, { ExecutionContext, TestInterface } from 'ava'; import AWSXRay, { Segment, Subsegment } from 'aws-xray-sdk-core'; @@ -19,9 +20,12 @@ interface TestContext { } const test = anyTest as TestInterface; +const traceOptions: TraceOptions = { + enabled: true +}; test.beforeEach(function (test) { - const schema = traceSchema(); + const schema = traceSchema(traceOptions); nock.disableNetConnect(); nock.enableNetConnect('127.0.0.1'); From 8ba0316c6909f8227c1fea894af0032614f647f9 Mon Sep 17 00:00:00 2001 From: Tom Koufakis Date: Thu, 8 Apr 2021 09:54:38 -0700 Subject: [PATCH 2/2] Added Readme instructions --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index cbc5615..4730e54 100644 --- a/README.md +++ b/README.md @@ -36,3 +36,20 @@ AWS has a 64K upload limit when submitting segements to AWS see [AWSXRay concept If you try and submit more than this limit you will see the following aws error message
**" [ERROR] Segment too large to send: {