From 0b783e2c1ae559890409cdb89fcbbf2a91e1e2fc Mon Sep 17 00:00:00 2001 From: Jay Herron Date: Wed, 14 Jan 2026 23:30:35 -0700 Subject: [PATCH] feat: Adds OTel-standard GraphQL span See https://opentelemetry.io/docs/specs/semconv/graphql/graphql-spans/ --- Benchmarks/Package.resolved | 18 +++++++++++++ Package.resolved | 18 +++++++++++++ Package.swift | 5 ++++ Sources/GraphQL/GraphQL.swift | 48 ++++++++++++++++++++++------------- 4 files changed, 71 insertions(+), 18 deletions(-) diff --git a/Benchmarks/Package.resolved b/Benchmarks/Package.resolved index 81906493..a5c132d6 100644 --- a/Benchmarks/Package.resolved +++ b/Benchmarks/Package.resolved @@ -54,6 +54,15 @@ "version" : "1.3.0" } }, + { + "identity" : "swift-distributed-tracing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-distributed-tracing", + "state" : { + "revision" : "baa932c1336f7894145cbaafcd34ce2dd0b77c97", + "version" : "1.3.1" + } + }, { "identity" : "swift-numerics", "kind" : "remoteSourceControl", @@ -63,6 +72,15 @@ "version" : "1.1.1" } }, + { + "identity" : "swift-service-context", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-service-context.git", + "state" : { + "revision" : "1983448fefc717a2bc2ebde5490fe99873c5b8a6", + "version" : "1.2.1" + } + }, { "identity" : "swift-system", "kind" : "remoteSourceControl", diff --git a/Package.resolved b/Package.resolved index a9ec17d4..6766d3af 100644 --- a/Package.resolved +++ b/Package.resolved @@ -8,6 +8,24 @@ "revision" : "671108c96644956dddcd89dd59c203dcdb36cec7", "version" : "1.1.4" } + }, + { + "identity" : "swift-distributed-tracing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-distributed-tracing", + "state" : { + "revision" : "baa932c1336f7894145cbaafcd34ce2dd0b77c97", + "version" : "1.3.1" + } + }, + { + "identity" : "swift-service-context", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-service-context.git", + "state" : { + "revision" : "1983448fefc717a2bc2ebde5490fe99873c5b8a6", + "version" : "1.2.1" + } } ], "version" : 2 diff --git a/Package.swift b/Package.swift index bcc14305..71af2db9 100644 --- a/Package.swift +++ b/Package.swift @@ -9,12 +9,17 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/apple/swift-collections", .upToNextMajor(from: "1.0.0")), + .package( + url: "https://github.com/apple/swift-distributed-tracing", + .upToNextMajor(from: "1.0.0") + ), ], targets: [ .target( name: "GraphQL", dependencies: [ .product(name: "OrderedCollections", package: "swift-collections"), + .product(name: "Tracing", package: "swift-distributed-tracing"), ] ), .testTarget( diff --git a/Sources/GraphQL/GraphQL.swift b/Sources/GraphQL/GraphQL.swift index cd66ba88..82522d40 100644 --- a/Sources/GraphQL/GraphQL.swift +++ b/Sources/GraphQL/GraphQL.swift @@ -1,3 +1,4 @@ +import Tracing public struct GraphQLResult: Equatable, Codable, Sendable, CustomStringConvertible { public var data: Map? @@ -98,25 +99,36 @@ public func graphql( let source = Source(body: request, name: "GraphQL request") let documentAST = try parse(source: source) - // Validate - let validationErrors = validate( - schema: schema, - ast: documentAST, - rules: validationRules - ) - guard validationErrors.isEmpty else { - return GraphQLResult(errors: validationErrors) - } + let operation = documentAST.definitions.first { + $0 is OperationDefinition + } as! OperationDefinition? - // Execute - return try await execute( - schema: schema, - documentAST: documentAST, - rootValue: rootValue, - context: context, - variableValues: variableValues, - operationName: operationName - ) + // See https://opentelemetry.io/docs/specs/semconv/graphql/graphql-spans/ + return try await withSpan(operation?.operation.rawValue ?? "GraphQL Operation") { span in + // `graphql.document` was omitted due to the possibility of very large sizes. + span.attributes["graphql.operation.name"] = operation?.name?.value + span.attributes["graphql.operation.type"] = operation?.operation.rawValue + + // Validate + let validationErrors = validate( + schema: schema, + ast: documentAST, + rules: validationRules + ) + guard validationErrors.isEmpty else { + return GraphQLResult(errors: validationErrors) + } + + // Execute + return try await execute( + schema: schema, + documentAST: documentAST, + rootValue: rootValue, + context: context, + variableValues: variableValues, + operationName: operationName + ) + } } /// This is the primary entry point function for fulfilling GraphQL operations