@effect-gql/opentelemetry 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,154 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.withTracedRouter = exports.makeTracedGraphQLRouter = void 0;
4
+ const platform_1 = require("@effect/platform");
5
+ const effect_1 = require("effect");
6
+ const server_1 = require("@effect-gql/core/server");
7
+ const context_propagation_1 = require("./context-propagation");
8
+ /**
9
+ * Create an Effect span options object from trace context
10
+ */
11
+ const createSpanOptions = (traceContext, request, config) => {
12
+ const attributes = {
13
+ "http.method": request.method,
14
+ "http.url": request.url,
15
+ "http.target": request.url,
16
+ ...config.rootSpanAttributes,
17
+ };
18
+ if (traceContext && config.propagateContext !== false) {
19
+ return {
20
+ attributes,
21
+ parent: effect_1.Tracer.externalSpan({
22
+ traceId: traceContext.traceId,
23
+ spanId: traceContext.parentSpanId,
24
+ sampled: (traceContext.traceFlags & 0x01) === 0x01,
25
+ }),
26
+ };
27
+ }
28
+ return { attributes };
29
+ };
30
+ /**
31
+ * Creates a GraphQL router with OpenTelemetry tracing at the HTTP level.
32
+ *
33
+ * This wraps the standard makeGraphQLRouter to:
34
+ * 1. Extract trace context from incoming HTTP headers (W3C Trace Context)
35
+ * 2. Create a root span for the entire HTTP request
36
+ * 3. Propagate trace context to child spans created by extensions/middleware
37
+ *
38
+ * **Span Hierarchy:**
39
+ * ```
40
+ * graphql.http (created by this router)
41
+ * ├── graphql.parse (from tracing extension)
42
+ * ├── graphql.validate (from tracing extension)
43
+ * └── graphql.resolve Query.* (from tracing middleware)
44
+ * ```
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * import { makeTracedGraphQLRouter } from "@effect-gql/opentelemetry"
49
+ * import { NodeSdk } from "@effect/opentelemetry"
50
+ *
51
+ * const router = makeTracedGraphQLRouter(schema, serviceLayer, {
52
+ * path: "/graphql",
53
+ * graphiql: { path: "/graphiql" },
54
+ * rootSpanName: "graphql.http",
55
+ * rootSpanAttributes: {
56
+ * "service.name": "my-api"
57
+ * }
58
+ * })
59
+ *
60
+ * // Provide OpenTelemetry layer when serving
61
+ * const TracingLayer = NodeSdk.layer(() => ({
62
+ * resource: { serviceName: "my-graphql-api" },
63
+ * spanProcessor: new BatchSpanProcessor(new OTLPTraceExporter())
64
+ * }))
65
+ * ```
66
+ *
67
+ * @param schema - The GraphQL schema
68
+ * @param layer - Effect layer providing services required by resolvers
69
+ * @param options - Router and tracing configuration
70
+ * @returns An HttpRouter with tracing enabled
71
+ */
72
+ const makeTracedGraphQLRouter = (schema, layer, options = {}) => {
73
+ const rootSpanName = options.rootSpanName ?? "graphql.http";
74
+ // Create the base router (handles GraphQL logic)
75
+ const baseRouter = (0, server_1.makeGraphQLRouter)(schema, layer, options);
76
+ // Convert base router to an HttpApp for Effect-based handling
77
+ const baseApp = platform_1.HttpRouter.toHttpApp(baseRouter);
78
+ // Wrap with tracing
79
+ return platform_1.HttpRouter.empty.pipe(platform_1.HttpRouter.all("*", effect_1.Effect.gen(function* () {
80
+ const request = yield* platform_1.HttpServerRequest.HttpServerRequest;
81
+ // Extract trace context from headers (if enabled)
82
+ const traceContext = options.propagateContext !== false
83
+ ? yield* context_propagation_1.extractTraceContext.pipe(effect_1.Effect.catchAll(() => effect_1.Effect.succeed(null)))
84
+ : null;
85
+ // Create span options with parent context if available
86
+ const spanOptions = createSpanOptions(traceContext, request, options);
87
+ // Execute the request inside a root span
88
+ return yield* effect_1.Effect.withSpan(rootSpanName, spanOptions)(effect_1.Effect.gen(function* () {
89
+ // Delegate to the base app (which handles the request from context)
90
+ const app = yield* baseApp;
91
+ const response = yield* app.pipe(effect_1.Effect.catchTag("RouteNotFound", () => platform_1.HttpServerResponse.text(JSON.stringify({ errors: [{ message: "Not Found" }] }), {
92
+ status: 404,
93
+ headers: { "content-type": "application/json" },
94
+ })));
95
+ // Annotate span with response info
96
+ yield* effect_1.Effect.annotateCurrentSpan("http.status_code", response.status);
97
+ return response;
98
+ }));
99
+ })));
100
+ };
101
+ exports.makeTracedGraphQLRouter = makeTracedGraphQLRouter;
102
+ /**
103
+ * Wrap an existing HttpRouter with OpenTelemetry tracing.
104
+ *
105
+ * This is useful when you already have a router and want to add
106
+ * request-level tracing without recreating it.
107
+ *
108
+ * @example
109
+ * ```typescript
110
+ * import { toRouter } from "@effect-gql/core/server"
111
+ * import { withTracedRouter } from "@effect-gql/opentelemetry"
112
+ *
113
+ * const baseRouter = toRouter(builder, serviceLayer)
114
+ * const tracedRouter = withTracedRouter(baseRouter, {
115
+ * rootSpanName: "graphql.http"
116
+ * })
117
+ * ```
118
+ */
119
+ const withTracedRouter = (router, options = {}) => {
120
+ const rootSpanName = options.rootSpanName ?? "graphql.http";
121
+ // Convert router to an HttpApp for Effect-based handling
122
+ const baseApp = platform_1.HttpRouter.toHttpApp(router);
123
+ return platform_1.HttpRouter.empty.pipe(platform_1.HttpRouter.all("*", effect_1.Effect.gen(function* () {
124
+ const request = yield* platform_1.HttpServerRequest.HttpServerRequest;
125
+ // Extract trace context from headers
126
+ const traceContext = options.propagateContext !== false
127
+ ? yield* context_propagation_1.extractTraceContext.pipe(effect_1.Effect.catchAll(() => effect_1.Effect.succeed(null)))
128
+ : null;
129
+ const spanOptions = {
130
+ attributes: {
131
+ "http.method": request.method,
132
+ "http.url": request.url,
133
+ ...options.rootSpanAttributes,
134
+ },
135
+ };
136
+ if (traceContext && options.propagateContext !== false) {
137
+ spanOptions.parent = effect_1.Tracer.externalSpan({
138
+ traceId: traceContext.traceId,
139
+ spanId: traceContext.parentSpanId,
140
+ sampled: (traceContext.traceFlags & 0x01) === 0x01,
141
+ });
142
+ }
143
+ return yield* effect_1.Effect.withSpan(rootSpanName, spanOptions)(effect_1.Effect.gen(function* () {
144
+ // Delegate to the base app (which handles the request from context)
145
+ const app = yield* baseApp;
146
+ const response = yield* app;
147
+ // Annotate span with response info
148
+ yield* effect_1.Effect.annotateCurrentSpan("http.status_code", response.status);
149
+ return response;
150
+ }));
151
+ })));
152
+ };
153
+ exports.withTracedRouter = withTracedRouter;
154
+ //# sourceMappingURL=traced-router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"traced-router.js","sourceRoot":"","sources":["../src/traced-router.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ import type { GraphQLExtension } from "@effect-gql/core";
2
+ import { ExtensionsService } from "@effect-gql/core";
3
+ /**
4
+ * Configuration for the GraphQL tracing extension
5
+ */
6
+ export interface TracingExtensionConfig {
7
+ /**
8
+ * Include the query source in span attributes.
9
+ * Default: false (for security - queries may contain sensitive data)
10
+ */
11
+ readonly includeQuery?: boolean;
12
+ /**
13
+ * Include variables in span attributes.
14
+ * Default: false (for security - variables may contain sensitive data)
15
+ */
16
+ readonly includeVariables?: boolean;
17
+ /**
18
+ * Add trace ID and span ID to the GraphQL response extensions.
19
+ * Useful for correlating client requests with backend traces.
20
+ * Default: false
21
+ */
22
+ readonly exposeTraceIdInResponse?: boolean;
23
+ /**
24
+ * Custom attributes to add to all spans.
25
+ */
26
+ readonly customAttributes?: Record<string, string | number | boolean>;
27
+ }
28
+ /**
29
+ * Creates a GraphQL extension that adds OpenTelemetry tracing to all execution phases.
30
+ *
31
+ * This extension:
32
+ * - Creates spans for parse, validate phases
33
+ * - Annotates the current span with operation metadata during execution
34
+ * - Optionally exposes trace ID in response extensions
35
+ *
36
+ * Requires an OpenTelemetry tracer to be provided via Effect's tracing layer
37
+ * (e.g., `@effect/opentelemetry` NodeSdk.layer or OtlpTracer.layer).
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * import { tracingExtension } from "@effect-gql/opentelemetry"
42
+ *
43
+ * const builder = GraphQLSchemaBuilder.empty.pipe(
44
+ * extension(tracingExtension({
45
+ * exposeTraceIdInResponse: true
46
+ * })),
47
+ * query("hello", { ... })
48
+ * )
49
+ * ```
50
+ */
51
+ export declare const tracingExtension: (config?: TracingExtensionConfig) => GraphQLExtension<ExtensionsService>;
52
+ //# sourceMappingURL=tracing-extension.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracing-extension.d.ts","sourceRoot":"","sources":["../src/tracing-extension.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAiB,MAAM,kBAAkB,CAAA;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAEpD;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAA;IAE/B;;;OAGG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAEnC;;;;OAIG;IACH,QAAQ,CAAC,uBAAuB,CAAC,EAAE,OAAO,CAAA;IAE1C;;OAEG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAA;CACtE;AA0BD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,gBAAgB,GAC3B,SAAS,sBAAsB,KAC9B,gBAAgB,CAAC,iBAAiB,CA2FnC,CAAA"}
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.tracingExtension = void 0;
4
+ const effect_1 = require("effect");
5
+ const core_1 = require("@effect-gql/core");
6
+ /**
7
+ * Extract the operation name from a parsed GraphQL document
8
+ */
9
+ const getOperationName = (document) => {
10
+ for (const definition of document.definitions) {
11
+ if (definition.kind === "OperationDefinition") {
12
+ return definition.name?.value;
13
+ }
14
+ }
15
+ return undefined;
16
+ };
17
+ /**
18
+ * Extract the operation type (query, mutation, subscription) from a parsed document
19
+ */
20
+ const getOperationType = (document) => {
21
+ for (const definition of document.definitions) {
22
+ if (definition.kind === "OperationDefinition") {
23
+ return definition.operation;
24
+ }
25
+ }
26
+ return "unknown";
27
+ };
28
+ /**
29
+ * Creates a GraphQL extension that adds OpenTelemetry tracing to all execution phases.
30
+ *
31
+ * This extension:
32
+ * - Creates spans for parse, validate phases
33
+ * - Annotates the current span with operation metadata during execution
34
+ * - Optionally exposes trace ID in response extensions
35
+ *
36
+ * Requires an OpenTelemetry tracer to be provided via Effect's tracing layer
37
+ * (e.g., `@effect/opentelemetry` NodeSdk.layer or OtlpTracer.layer).
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * import { tracingExtension } from "@effect-gql/opentelemetry"
42
+ *
43
+ * const builder = GraphQLSchemaBuilder.empty.pipe(
44
+ * extension(tracingExtension({
45
+ * exposeTraceIdInResponse: true
46
+ * })),
47
+ * query("hello", { ... })
48
+ * )
49
+ * ```
50
+ */
51
+ const tracingExtension = (config) => ({
52
+ name: "opentelemetry-tracing",
53
+ description: "Adds OpenTelemetry tracing to GraphQL execution phases",
54
+ onParse: (source, document) => effect_1.Effect.withSpan("graphql.parse")(effect_1.Effect.gen(function* () {
55
+ const operationName = getOperationName(document) ?? "anonymous";
56
+ yield* effect_1.Effect.annotateCurrentSpan("graphql.document.name", operationName);
57
+ yield* effect_1.Effect.annotateCurrentSpan("graphql.document.operation_count", document.definitions.filter((d) => d.kind === "OperationDefinition").length);
58
+ if (config?.includeQuery) {
59
+ yield* effect_1.Effect.annotateCurrentSpan("graphql.source", source);
60
+ }
61
+ // Add custom attributes if provided
62
+ if (config?.customAttributes) {
63
+ for (const [key, value] of Object.entries(config.customAttributes)) {
64
+ yield* effect_1.Effect.annotateCurrentSpan(key, value);
65
+ }
66
+ }
67
+ })),
68
+ onValidate: (document, errors) => effect_1.Effect.withSpan("graphql.validate")(effect_1.Effect.gen(function* () {
69
+ yield* effect_1.Effect.annotateCurrentSpan("graphql.validation.error_count", errors.length);
70
+ if (errors.length > 0) {
71
+ yield* effect_1.Effect.annotateCurrentSpan("graphql.validation.errors", JSON.stringify(errors.map((e) => e.message)));
72
+ yield* effect_1.Effect.annotateCurrentSpan("error", true);
73
+ }
74
+ })),
75
+ onExecuteStart: (args) => effect_1.Effect.gen(function* () {
76
+ const operationName = args.operationName ?? getOperationName(args.document) ?? "anonymous";
77
+ const operationType = getOperationType(args.document);
78
+ yield* effect_1.Effect.annotateCurrentSpan("graphql.operation.name", operationName);
79
+ yield* effect_1.Effect.annotateCurrentSpan("graphql.operation.type", operationType);
80
+ if (config?.includeVariables && args.variableValues) {
81
+ yield* effect_1.Effect.annotateCurrentSpan("graphql.variables", JSON.stringify(args.variableValues));
82
+ }
83
+ // Expose trace ID in response extensions if configured
84
+ if (config?.exposeTraceIdInResponse) {
85
+ const currentSpanOption = yield* effect_1.Effect.option(effect_1.Effect.currentSpan);
86
+ if (effect_1.Option.isSome(currentSpanOption)) {
87
+ const span = currentSpanOption.value;
88
+ const ext = yield* core_1.ExtensionsService;
89
+ yield* ext.set("tracing", {
90
+ traceId: span.traceId,
91
+ spanId: span.spanId,
92
+ });
93
+ }
94
+ }
95
+ }),
96
+ onExecuteEnd: (result) => effect_1.Effect.gen(function* () {
97
+ const hasErrors = result.errors !== undefined && result.errors.length > 0;
98
+ yield* effect_1.Effect.annotateCurrentSpan("graphql.response.has_errors", hasErrors);
99
+ yield* effect_1.Effect.annotateCurrentSpan("graphql.response.has_data", result.data !== null && result.data !== undefined);
100
+ if (hasErrors) {
101
+ yield* effect_1.Effect.annotateCurrentSpan("error", true);
102
+ yield* effect_1.Effect.annotateCurrentSpan("graphql.errors", JSON.stringify(result.errors.map((e) => ({
103
+ message: e.message,
104
+ path: e.path,
105
+ }))));
106
+ }
107
+ }),
108
+ });
109
+ exports.tracingExtension = tracingExtension;
110
+ //# sourceMappingURL=tracing-extension.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracing-extension.js","sourceRoot":"","sources":["../src/tracing-extension.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ import type { GraphQLResolveInfo } from "graphql";
2
+ import type { MiddlewareRegistration } from "@effect-gql/core";
3
+ /**
4
+ * Configuration for resolver tracing middleware
5
+ */
6
+ export interface ResolverTracingConfig {
7
+ /**
8
+ * Minimum field depth to trace.
9
+ * Depth 0 = root fields (Query.*, Mutation.*).
10
+ * Default: 0 (trace all fields)
11
+ */
12
+ readonly minDepth?: number;
13
+ /**
14
+ * Maximum field depth to trace.
15
+ * Default: Infinity (no limit)
16
+ */
17
+ readonly maxDepth?: number;
18
+ /**
19
+ * Field patterns to exclude from tracing.
20
+ * Patterns are matched against "TypeName.fieldName".
21
+ *
22
+ * @example
23
+ * // Exclude introspection and internal fields
24
+ * excludePatterns: [/^Query\.__/, /\.id$/]
25
+ */
26
+ readonly excludePatterns?: readonly RegExp[];
27
+ /**
28
+ * Whether to include field arguments in span attributes.
29
+ * Default: false (for security - args may contain sensitive data)
30
+ */
31
+ readonly includeArgs?: boolean;
32
+ /**
33
+ * Whether to include parent type in span attributes.
34
+ * Default: true
35
+ */
36
+ readonly includeParentType?: boolean;
37
+ /**
38
+ * Whether to trace introspection fields (__schema, __type, etc.).
39
+ * Default: false
40
+ */
41
+ readonly traceIntrospection?: boolean;
42
+ /**
43
+ * Custom span name generator.
44
+ * Default: "graphql.resolve TypeName.fieldName"
45
+ */
46
+ readonly spanNameGenerator?: (info: GraphQLResolveInfo) => string;
47
+ }
48
+ /**
49
+ * Creates middleware that wraps each resolver in an OpenTelemetry span.
50
+ *
51
+ * Each resolver execution creates a child span with GraphQL-specific attributes:
52
+ * - `graphql.field.name`: The field being resolved
53
+ * - `graphql.field.path`: Full path to the field (e.g., "Query.users.0.posts")
54
+ * - `graphql.field.type`: The return type of the field
55
+ * - `graphql.parent.type`: The parent type name
56
+ * - `graphql.operation.name`: The operation name (if available)
57
+ * - `error`: Set to true if the resolver fails
58
+ * - `error.type`: Error type/class name
59
+ * - `error.message`: Error message
60
+ *
61
+ * Requires an OpenTelemetry tracer to be provided via Effect's tracing layer.
62
+ *
63
+ * @example
64
+ * ```typescript
65
+ * import { resolverTracingMiddleware } from "@effect-gql/opentelemetry"
66
+ *
67
+ * const builder = GraphQLSchemaBuilder.empty.pipe(
68
+ * middleware(resolverTracingMiddleware({
69
+ * minDepth: 0,
70
+ * excludePatterns: [/^Query\.__/],
71
+ * includeArgs: false
72
+ * })),
73
+ * query("users", { ... })
74
+ * )
75
+ * ```
76
+ */
77
+ export declare const resolverTracingMiddleware: (config?: ResolverTracingConfig) => MiddlewareRegistration<never>;
78
+ //# sourceMappingURL=tracing-middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracing-middleware.d.ts","sourceRoot":"","sources":["../src/tracing-middleware.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAA;AACjD,OAAO,KAAK,EAAE,sBAAsB,EAAqB,MAAM,kBAAkB,CAAA;AAGjF;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAE1B;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAE1B;;;;;;;OAOG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAE5C;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAA;IAE9B;;;OAGG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAEpC;;;OAGG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAErC;;;OAGG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,MAAM,CAAA;CAClE;AAkCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,yBAAyB,GACpC,SAAS,qBAAqB,KAC7B,sBAAsB,CAAC,KAAK,CAwD7B,CAAA"}
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolverTracingMiddleware = void 0;
4
+ const effect_1 = require("effect");
5
+ const utils_1 = require("./utils");
6
+ /**
7
+ * Check if a field should be traced based on configuration
8
+ */
9
+ const shouldTraceField = (info, config) => {
10
+ // Skip introspection fields unless explicitly enabled
11
+ if (!config?.traceIntrospection && (0, utils_1.isIntrospectionField)(info)) {
12
+ return false;
13
+ }
14
+ const depth = (0, utils_1.getFieldDepth)(info);
15
+ // Check depth bounds
16
+ if (config?.minDepth !== undefined && depth < config.minDepth) {
17
+ return false;
18
+ }
19
+ if (config?.maxDepth !== undefined && depth > config.maxDepth) {
20
+ return false;
21
+ }
22
+ // Check exclude patterns
23
+ if (config?.excludePatterns) {
24
+ const fieldPath = `${info.parentType.name}.${info.fieldName}`;
25
+ for (const pattern of config.excludePatterns) {
26
+ if (pattern.test(fieldPath)) {
27
+ return false;
28
+ }
29
+ }
30
+ }
31
+ return true;
32
+ };
33
+ /**
34
+ * Creates middleware that wraps each resolver in an OpenTelemetry span.
35
+ *
36
+ * Each resolver execution creates a child span with GraphQL-specific attributes:
37
+ * - `graphql.field.name`: The field being resolved
38
+ * - `graphql.field.path`: Full path to the field (e.g., "Query.users.0.posts")
39
+ * - `graphql.field.type`: The return type of the field
40
+ * - `graphql.parent.type`: The parent type name
41
+ * - `graphql.operation.name`: The operation name (if available)
42
+ * - `error`: Set to true if the resolver fails
43
+ * - `error.type`: Error type/class name
44
+ * - `error.message`: Error message
45
+ *
46
+ * Requires an OpenTelemetry tracer to be provided via Effect's tracing layer.
47
+ *
48
+ * @example
49
+ * ```typescript
50
+ * import { resolverTracingMiddleware } from "@effect-gql/opentelemetry"
51
+ *
52
+ * const builder = GraphQLSchemaBuilder.empty.pipe(
53
+ * middleware(resolverTracingMiddleware({
54
+ * minDepth: 0,
55
+ * excludePatterns: [/^Query\.__/],
56
+ * includeArgs: false
57
+ * })),
58
+ * query("users", { ... })
59
+ * )
60
+ * ```
61
+ */
62
+ const resolverTracingMiddleware = (config) => ({
63
+ name: "opentelemetry-resolver-tracing",
64
+ description: "Wraps resolvers in OpenTelemetry spans",
65
+ match: (info) => shouldTraceField(info, config),
66
+ apply: (effect, context) => {
67
+ const { info } = context;
68
+ const spanName = config?.spanNameGenerator
69
+ ? config.spanNameGenerator(info)
70
+ : `graphql.resolve ${info.parentType.name}.${info.fieldName}`;
71
+ return effect_1.Effect.withSpan(spanName)(effect_1.Effect.gen(function* () {
72
+ // Add standard attributes
73
+ yield* effect_1.Effect.annotateCurrentSpan("graphql.field.name", info.fieldName);
74
+ yield* effect_1.Effect.annotateCurrentSpan("graphql.field.path", (0, utils_1.pathToString)(info.path));
75
+ yield* effect_1.Effect.annotateCurrentSpan("graphql.field.type", String(info.returnType));
76
+ if (config?.includeParentType !== false) {
77
+ yield* effect_1.Effect.annotateCurrentSpan("graphql.parent.type", info.parentType.name);
78
+ }
79
+ if (info.operation?.name?.value) {
80
+ yield* effect_1.Effect.annotateCurrentSpan("graphql.operation.name", info.operation.name.value);
81
+ }
82
+ if (config?.includeArgs && context.args && Object.keys(context.args).length > 0) {
83
+ yield* effect_1.Effect.annotateCurrentSpan("graphql.field.args", JSON.stringify(context.args));
84
+ }
85
+ // Execute resolver and handle errors
86
+ const result = yield* effect.pipe(effect_1.Effect.tapError((error) => effect_1.Effect.gen(function* () {
87
+ yield* effect_1.Effect.annotateCurrentSpan("error", true);
88
+ yield* effect_1.Effect.annotateCurrentSpan("error.type", error instanceof Error ? error.constructor.name : "Error");
89
+ yield* effect_1.Effect.annotateCurrentSpan("error.message", error instanceof Error ? error.message : String(error));
90
+ })));
91
+ return result;
92
+ }));
93
+ },
94
+ });
95
+ exports.resolverTracingMiddleware = resolverTracingMiddleware;
96
+ //# sourceMappingURL=tracing-middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracing-middleware.js","sourceRoot":"","sources":["../src/tracing-middleware.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { GraphQLResolveInfo, ResponsePath } from "graphql";
2
+ /**
3
+ * Convert a GraphQL response path to a string representation.
4
+ *
5
+ * @example
6
+ * // For path: Query -> users -> 0 -> posts -> 1 -> title
7
+ * // Returns: "Query.users.0.posts.1.title"
8
+ */
9
+ export declare const pathToString: (path: ResponsePath | undefined) => string;
10
+ /**
11
+ * Get the depth of a field in the query tree.
12
+ * Root fields (Query.*, Mutation.*) have depth 0.
13
+ */
14
+ export declare const getFieldDepth: (info: GraphQLResolveInfo) => number;
15
+ /**
16
+ * Check if a field is an introspection field (__schema, __type, etc.)
17
+ */
18
+ export declare const isIntrospectionField: (info: GraphQLResolveInfo) => boolean;
19
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAE/D;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,GAAI,MAAM,YAAY,GAAG,SAAS,KAAG,MAY7D,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,MAAM,kBAAkB,KAAG,MAaxD,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,oBAAoB,GAAI,MAAM,kBAAkB,KAAG,OAE/D,CAAA"}
package/dist/utils.js ADDED
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isIntrospectionField = exports.getFieldDepth = exports.pathToString = void 0;
4
+ /**
5
+ * Convert a GraphQL response path to a string representation.
6
+ *
7
+ * @example
8
+ * // For path: Query -> users -> 0 -> posts -> 1 -> title
9
+ * // Returns: "Query.users.0.posts.1.title"
10
+ */
11
+ const pathToString = (path) => {
12
+ if (!path)
13
+ return "";
14
+ const segments = [];
15
+ let current = path;
16
+ while (current) {
17
+ segments.unshift(current.key);
18
+ current = current.prev;
19
+ }
20
+ return segments.join(".");
21
+ };
22
+ exports.pathToString = pathToString;
23
+ /**
24
+ * Get the depth of a field in the query tree.
25
+ * Root fields (Query.*, Mutation.*) have depth 0.
26
+ */
27
+ const getFieldDepth = (info) => {
28
+ let depth = 0;
29
+ let current = info.path;
30
+ while (current?.prev) {
31
+ // Skip array indices in depth calculation
32
+ if (typeof current.key === "string") {
33
+ depth++;
34
+ }
35
+ current = current.prev;
36
+ }
37
+ return depth;
38
+ };
39
+ exports.getFieldDepth = getFieldDepth;
40
+ /**
41
+ * Check if a field is an introspection field (__schema, __type, etc.)
42
+ */
43
+ const isIntrospectionField = (info) => {
44
+ return info.fieldName.startsWith("__");
45
+ };
46
+ exports.isIntrospectionField = isIntrospectionField;
47
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";;;AAEA;;;;;;GAMG;AACI,MAAM,YAAY,GAAG,CAAC,IAA8B,EAAU,EAAE;IACrE,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAA;IAEpB,MAAM,QAAQ,GAAwB,EAAE,CAAA;IACxC,IAAI,OAAO,GAA6B,IAAI,CAAA;IAE5C,OAAO,OAAO,EAAE,CAAC;QACf,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QAC7B,OAAO,GAAG,OAAO,CAAC,IAAI,CAAA;IACxB,CAAC;IAED,OAAO,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAC3B,CAAC,CAAA;AAZY,QAAA,YAAY,gBAYxB;AAED;;;GAGG;AACI,MAAM,aAAa,GAAG,CAAC,IAAwB,EAAU,EAAE;IAChE,IAAI,KAAK,GAAG,CAAC,CAAA;IACb,IAAI,OAAO,GAA6B,IAAI,CAAC,IAAI,CAAA;IAEjD,OAAO,OAAO,EAAE,IAAI,EAAE,CAAC;QACrB,0CAA0C;QAC1C,IAAI,OAAO,OAAO,CAAC,GAAG,KAAK,QAAQ,EAAE,CAAC;YACpC,KAAK,EAAE,CAAA;QACT,CAAC;QACD,OAAO,GAAG,OAAO,CAAC,IAAI,CAAA;IACxB,CAAC;IAED,OAAO,KAAK,CAAA;AACd,CAAC,CAAA;AAbY,QAAA,aAAa,iBAazB;AAED;;GAEG;AACI,MAAM,oBAAoB,GAAG,CAAC,IAAwB,EAAW,EAAE;IACxE,OAAO,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;AACxC,CAAC,CAAA;AAFY,QAAA,oBAAoB,wBAEhC"}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@effect-gql/opentelemetry",
3
+ "version": "0.1.0",
4
+ "description": "OpenTelemetry tracing integration for @effect-gql/core",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ }
12
+ },
13
+ "files": [
14
+ "dist",
15
+ "src"
16
+ ],
17
+ "peerDependencies": {
18
+ "@effect-gql/core": "^0.1.0",
19
+ "@effect/opentelemetry": "^0.3.0",
20
+ "@effect/platform": "^0.94.0",
21
+ "effect": "^3.19.0",
22
+ "graphql": "^16.0.0"
23
+ },
24
+ "devDependencies": {
25
+ "@effect-gql/core": "*",
26
+ "@effect/opentelemetry": "^0.60.0",
27
+ "@effect/platform": "^0.94.0",
28
+ "@opentelemetry/api": "^1.9.0",
29
+ "@opentelemetry/sdk-trace-base": "^2.2.0",
30
+ "effect": "^3.19.13",
31
+ "graphql": "^16.0.0"
32
+ },
33
+ "keywords": [
34
+ "effect",
35
+ "graphql",
36
+ "opentelemetry",
37
+ "tracing",
38
+ "observability"
39
+ ],
40
+ "license": "MIT",
41
+ "scripts": {
42
+ "build": "tsc",
43
+ "dev": "tsc --watch",
44
+ "clean": "rm -rf dist",
45
+ "test": "vitest run",
46
+ "test:unit": "vitest run test/unit",
47
+ "test:integration": "vitest run test/integration",
48
+ "test:watch": "vitest"
49
+ }
50
+ }