diff --git a/.changeset/angry-spies-tickle.md b/.changeset/angry-spies-tickle.md new file mode 100644 index 0000000000..a42bfaf5bb --- /dev/null +++ b/.changeset/angry-spies-tickle.md @@ -0,0 +1,5 @@ +--- +'graphql-modules': minor +--- + +Tracing of internals diff --git a/packages/graphql-modules/src/application/application.ts b/packages/graphql-modules/src/application/application.ts index f5d7fb25be..8b855724e9 100644 --- a/packages/graphql-modules/src/application/application.ts +++ b/packages/graphql-modules/src/application/application.ts @@ -24,6 +24,7 @@ import { subscriptionCreator } from './subscription'; import { apolloSchemaCreator, apolloExecutorCreator } from './apollo'; import { operationControllerCreator } from './operation-controller'; import { Module } from '../module/types'; +import { emptyTracing } from '../shared/tracing'; export type ModulesMap = Map; @@ -62,6 +63,7 @@ export function createApplication( applicationConfig: ApplicationConfig ): Application { function applicationFactory(cfg?: ApplicationConfig): Application { + const tracing = applicationConfig.tracing ?? emptyTracing; const config = cfg || applicationConfig; const providers = config.providers && typeof config.providers === 'function' @@ -125,6 +127,7 @@ export function createApplication( modulesMap: modulesMap, singletonGlobalProvidersMap, operationGlobalProvidersMap, + tracing, }); const createOperationController = operationControllerCreator({ diff --git a/packages/graphql-modules/src/application/context.ts b/packages/graphql-modules/src/application/context.ts index 97c6e3f72b..ab4721617e 100644 --- a/packages/graphql-modules/src/application/context.ts +++ b/packages/graphql-modules/src/application/context.ts @@ -6,6 +6,7 @@ import type { InternalAppContext, ModulesMap } from './application'; import { attachGlobalProvidersMap } from './di'; import { CONTEXT } from './tokens'; import { executionContext, ExecutionContextPicker } from './execution-context'; +import type { Tracing } from '../shared/tracing'; export type ExecutionContextBuilder< TContext extends { @@ -20,6 +21,7 @@ export type ExecutionContextBuilder< }; export function createContextBuilder({ + tracing, appInjector, modulesMap, appLevelOperationProviders, @@ -35,6 +37,7 @@ export function createContextBuilder({ [key: string]: string; }; modulesMap: ModulesMap; + tracing: Tracing; }) { // This is very critical. It creates an execution context. // It has to run on every operation. @@ -42,6 +45,10 @@ export function createContextBuilder({ const contextBuilder: ExecutionContextBuilder = ( context ) => { + const tracer = tracing.create({ session: tracing.session(context) }); + const tracerContextEnd = tracer.onContext({ + id: 'application', + }); // Cache for context per module let contextCache: Record = {}; // A list of providers with OnDestroy hooks @@ -59,6 +66,11 @@ export function createContextBuilder({ }); } + const operationAppInjectorName = 'App (Operation Scope)'; + const tracerInjectorEnd = tracer.onInjector({ + name: operationAppInjectorName, + }); + let appContext: GraphQLModules.AppContext; attachGlobalProvidersMap({ @@ -99,7 +111,7 @@ export function createContextBuilder({ // Application level // Operation scoped - means it's created and destroyed on every GraphQL Operation const operationAppInjector = ReflectiveInjector.createFromResolved({ - name: 'App (Operation Scope)', + name: operationAppInjectorName, providers: appLevelOperationProviders.concat( ReflectiveInjector.resolve([ { @@ -110,27 +122,33 @@ export function createContextBuilder({ ), parent: appInjector, }); + // Track Providers with OnDestroy hooks + registerProvidersToDestroy(operationAppInjector); // Create a context for application-level ExecutionContext appContext = merge(context, { injector: operationAppInjector, }); - // Track Providers with OnDestroy hooks - registerProvidersToDestroy(operationAppInjector); - function getModuleContext( moduleId: string, ctx: GraphQLModules.GlobalContext ): GraphQLModules.ModuleContext { // Reuse a context or create if not available if (!contextCache[moduleId]) { + const operationModuleInjectorName = `Module "${moduleId}" (Operation Scope)`; + const tracerModContextEnd = tracer.onContext({ + id: moduleId, + }); + const tracerModInjectorEnd = tracer.onInjector({ + name: operationModuleInjectorName, + }); // We're interested in operation-scoped providers only const providers = modulesMap.get(moduleId)?.operationProviders!; // Create module-level Operation-scoped Injector const operationModuleInjector = ReflectiveInjector.createFromResolved({ - name: `Module "${moduleId}" (Operation Scope)`, + name: operationModuleInjectorName, providers: providers.concat( ReflectiveInjector.resolve([ { @@ -149,11 +167,13 @@ export function createContextBuilder({ // Same as on application level, we need to collect providers with OnDestroy hooks registerProvidersToDestroy(operationModuleInjector); + tracerModInjectorEnd(); contextCache[moduleId] = merge(ctx, { injector: operationModuleInjector, moduleId, }); + tracerModContextEnd(); } return contextCache[moduleId]; @@ -177,8 +197,12 @@ export function createContextBuilder({ }, }); + tracerInjectorEnd(); + tracerContextEnd(); + return { ɵdestroy: once(() => { + const tracerOnDestroyEnd = tracer.onDestroy(); providersToDestroy.forEach(([injector, keyId]) => { // If provider was instantiated if (injector._isObjectDefinedByKeyId(keyId)) { @@ -187,6 +211,7 @@ export function createContextBuilder({ } }); contextCache = {}; + tracerOnDestroyEnd(); }), ɵinjector: operationAppInjector, context: sharedContext, diff --git a/packages/graphql-modules/src/application/types.ts b/packages/graphql-modules/src/application/types.ts index 98a5871749..54416dee04 100644 --- a/packages/graphql-modules/src/application/types.ts +++ b/packages/graphql-modules/src/application/types.ts @@ -11,6 +11,7 @@ import type { MiddlewareMap } from '../shared/middleware'; import type { ApolloRequestContext } from './apollo'; import type { Single, ValueOrPromise } from '../shared/types'; import type { InternalAppContext } from './application'; +import type { Tracing } from '../shared/tracing'; type Execution = typeof execute; type Subscription = typeof subscribe; @@ -139,4 +140,5 @@ export interface ApplicationConfig { typeDefs: DocumentNode[]; resolvers: Record[]; }): GraphQLSchema; + tracing?: Tracing; } diff --git a/packages/graphql-modules/src/shared/tracing.ts b/packages/graphql-modules/src/shared/tracing.ts new file mode 100644 index 0000000000..b7804ddecd --- /dev/null +++ b/packages/graphql-modules/src/shared/tracing.ts @@ -0,0 +1,33 @@ +type EndCallback = () => void; + +export interface Tracer { + onContext(info: { id: string }): EndCallback; + onDestroy(): EndCallback; + onInjector(info: { name: string }): EndCallback; +} + +export interface Tracing { + session(context: GraphQLModules.GlobalContext): string; + create(info: { session: string }): Tracer; +} + +function noop() {} + +export const emptyTracing: Tracing = { + session() { + return ''; + }, + create() { + return { + onContext() { + return noop; + }, + onDestroy() { + return noop; + }, + onInjector() { + return noop; + }, + }; + }, +};