44package clue .otel4s
55
66import cats .Applicative
7+ import cats .effect .Concurrent
78import cats .effect .MonadCancelThrow
89import cats .effect .Resource
910import cats .syntax .all .*
@@ -12,12 +13,14 @@ import clue.FetchClientWithPars
1213import clue .PersistentClientStatus
1314import clue .PersistentStreamingClient
1415import clue .StreamingClient
16+ import clue .TraceHeaderInjector
1517import clue .model .GraphQLQuery
1618import clue .model .GraphQLResponse
1719import io .circe .Decoder
1820import io .circe .Encoder
1921import io .circe .JsonObject
2022import org .typelevel .otel4s .Attribute
23+ import org .typelevel .otel4s .context .propagation .TextMapUpdater
2124import org .typelevel .otel4s .semconv .attributes .HttpAttributes
2225import org .typelevel .otel4s .semconv .experimental .attributes .GraphqlExperimentalAttributes
2326import org .typelevel .otel4s .trace .Span
@@ -45,40 +48,40 @@ object Otel4sMiddleware:
4548 (_, _) => List .empty[Attribute [? ]].pure[F ]
4649
4750 // Fetch client
48- def apply [F [_]: Tracer : MonadCancelThrow , P , S ](
51+ def apply [F [_]: Tracer : MonadCancelThrow , P : TraceHeaderInjector , S ](
4952 client : FetchClientWithPars [F , P , S ],
5053 spanBuilderMod : SpanBuilderMod [F ],
5154 additionalAttributesF : (GraphQLQuery , Option [JsonObject ]) => F [List [Attribute [? ]]]
5255 ): FetchClientWithPars [F , P , S ] =
5356 Otel4sFetchClient [F , P , S ](client, spanBuilderMod, additionalAttributesF)
5457
55- def apply [F [_]: Tracer : MonadCancelThrow , P , S ](
58+ def apply [F [_]: Tracer : MonadCancelThrow , P : TraceHeaderInjector , S ](
5659 client : FetchClientWithPars [F , P , S ]
5760 ): FetchClientWithPars [F , P , S ] =
5861 apply(client, identityMod[F ], emptyAttrs[F ])
5962
6063 // Streaming client
61- def apply [F [_]: Tracer : MonadCancelThrow , S ](
64+ def apply [F [_]: Tracer : Concurrent , S ](
6265 client : StreamingClient [F , S ],
6366 spanBuilderMod : SpanBuilderMod [F ],
6467 additionalAttributesF : (GraphQLQuery , Option [JsonObject ]) => F [List [Attribute [? ]]]
6568 ): StreamingClient [F , S ] =
6669 Otel4sStreamingClient (client, spanBuilderMod, additionalAttributesF)
6770
68- def apply [F [_]: Tracer : MonadCancelThrow , S ](
71+ def apply [F [_]: Tracer : Concurrent , S ](
6972 client : StreamingClient [F , S ]
7073 ): StreamingClient [F , S ] =
7174 apply(client, identityMod[F ], emptyAttrs[F ])
7275
7376 // Persistent streaming client
74- def apply [F [_]: Tracer : MonadCancelThrow , S , CP , CE ](
77+ def apply [F [_]: Tracer : Concurrent , S , CP , CE ](
7578 client : PersistentStreamingClient [F , S , CP , CE ],
7679 spanBuilderMod : SpanBuilderMod [F ],
7780 additionalAttributesF : (GraphQLQuery , Option [JsonObject ]) => F [List [Attribute [? ]]]
7881 ): PersistentStreamingClient [F , S , CP , CE ] =
7982 Otel4sPersistentStreamingClient (client, spanBuilderMod, additionalAttributesF)
8083
81- def apply [F [_]: Tracer : MonadCancelThrow , S , CP , CE ](
84+ def apply [F [_]: Tracer : Concurrent , S , CP , CE ](
8285 client : PersistentStreamingClient [F , S , CP , CE ]
8386 ): PersistentStreamingClient [F , S , CP , CE ] =
8487 apply(client, identityMod[F ], emptyAttrs[F ])
@@ -113,7 +116,7 @@ object Otel4sMiddleware:
113116 Attribute (" clue.response.errors" , errorsStr)
114117 ) *> span.setStatus(StatusCode .Error , " GraphQL request returned errors" )
115118
116- class Otel4sFetchClient [F [_]: Tracer : MonadCancelThrow , P , S ](
119+ class Otel4sFetchClient [F [_]: Tracer : MonadCancelThrow , P : TraceHeaderInjector , S ](
117120 wrapped : FetchClientWithPars [F , P , S ],
118121 spanBuilderMod : Otel4sMiddleware .SpanBuilderMod [F ],
119122 additionalAttributesF : (GraphQLQuery , Option [JsonObject ]) => F [List [Attribute [? ]]]
@@ -132,13 +135,16 @@ class Otel4sFetchClient[F[_]: Tracer: MonadCancelThrow, P, S](
132135 .addAttributes(Otel4sMiddleware .commonAttributes(document, operationName)* )
133136 ).build.use: span =>
134137 for
135- additional <- additionalAttributesF(document, variables)
136- _ <- span.addAttributes(additional* )
137- result <- poll(wrapped.requestInternal[D ](document, operationName, variables, modParams))
138- _ <- Otel4sMiddleware .recordResponseAttributes(span, result)
138+ additional <- additionalAttributesF(document, variables)
139+ _ <- span.addAttributes(additional* )
140+ traceHeaders <- Tracer [F ].propagate(Map .empty[String , String ])
141+ modWithTrace = modParams.andThen(p => TraceHeaderInjector [P ].addHeaders(p, traceHeaders))
142+ result <-
143+ poll(wrapped.requestInternal[D ](document, operationName, variables, modWithTrace))
144+ _ <- Otel4sMiddleware .recordResponseAttributes(span, result)
139145 yield result
140146
141- class Otel4sStreamingClient [F [_]: Tracer : MonadCancelThrow , S ](
147+ class Otel4sStreamingClient [F [_]: Tracer : Concurrent , S ](
142148 wrapped : StreamingClient [F , S ],
143149 spanBuilderMod : Otel4sMiddleware .SpanBuilderMod [F ],
144150 additionalAttributesF : (GraphQLQuery , Option [JsonObject ]) => F [List [Attribute [? ]]]
@@ -147,29 +153,63 @@ class Otel4sStreamingClient[F[_]: Tracer: MonadCancelThrow, S](
147153 protected [clue] def subscribeInternal [D : Decoder ](
148154 document : GraphQLQuery ,
149155 operationName : Option [String ] = none,
150- variables : Option [JsonObject ] = none
156+ variables : Option [JsonObject ] = none,
157+ extensions : Option [JsonObject ] = none
151158 ): Resource [F , fs2.Stream [F , GraphQLResponse [D ]]] =
152159 for
153- res <- spanBuilderMod(
154- Tracer [F ]
155- .spanBuilder(s " clue-client-subscribe- ${document.querySummary}" )
156- .withSpanKind(SpanKind .Client )
157- .addAttributes(Otel4sMiddleware .commonAttributes(document, operationName)* )
158- ).build.resource
159- span = res.span
160- _ <- Resource .eval:
161- additionalAttributesF(document, variables).flatMap: attrs =>
162- span.addAttributes(attrs* )
163- stream <- wrapped.subscribeInternal[D ](document, operationName, variables)
160+ res <- spanBuilderMod(
161+ Tracer [F ]
162+ .spanBuilder(s " clue-client-subscribe- ${document.querySummary}" )
163+ .withSpanKind(SpanKind .Client )
164+ .addAttributes(Otel4sMiddleware .commonAttributes(document, operationName)* )
165+ ).build.resource
166+ span = res.span
167+ _ <- Resource .eval:
168+ additionalAttributesF(document, variables).flatMap: attrs =>
169+ span.addAttributes(attrs* )
170+ traceHeaders <- Resource .eval(Tracer [F ].propagate(Map .empty[String , String ]))
171+ traceExt = JsonObject .fromMap(traceHeaders.map((k, v) => k -> io.circe.Json .fromString(v)))
172+ mergedExt = extensions
173+ .map(ext => JsonObject .fromIterable(ext.toIterable ++ traceExt.toIterable))
174+ .orElse(Some (traceExt))
175+ .filterNot(_.isEmpty)
176+ stream <- wrapped.subscribeInternal[D ](document, operationName, variables, mergedExt)
164177 yield stream.onFinalizeCase: exitCase =>
165178 span.addAttribute(Attribute (" clue.exitCase" , exitCase.toOutcome.toString)) *>
166179 (exitCase match
167180 case Resource .ExitCase .Errored (e) =>
168181 span.setStatus(StatusCode .Error , Option (e.getMessage).getOrElse(e.toString))
169- case _ => Applicative [F ].unit
170- )
182+ case _ => Applicative [F ].unit)
171183
172- class Otel4sPersistentStreamingClient [F [_]: Tracer : MonadCancelThrow , S , CP , CE ](
184+ override protected [clue] def requestInternal [D : Decoder ](
185+ document : GraphQLQuery ,
186+ operationName : Option [String ],
187+ variables : Option [JsonObject ],
188+ modParams : Unit => Unit
189+ ): F [GraphQLResponse [D ]] =
190+ MonadCancelThrow [F ].uncancelable: poll =>
191+ spanBuilderMod(
192+ Tracer [F ]
193+ .spanBuilder(s " clue-client-request- ${document.querySummary}" )
194+ .withSpanKind(SpanKind .Client )
195+ .addAttributes(Otel4sMiddleware .commonAttributes(document, operationName)* )
196+ ).build.use: span =>
197+ for
198+ additional <- additionalAttributesF(document, variables)
199+ _ <- span.addAttributes(additional* )
200+ traceHeaders <- Tracer [F ].propagate(Map .empty[String , String ])
201+ traceExt =
202+ JsonObject .fromMap(traceHeaders.map((k, v) => k -> io.circe.Json .fromString(v)))
203+ mergedExt = Some (traceExt).filterNot(_.isEmpty)
204+ result <- poll(
205+ wrapped
206+ .subscribeInternal[D ](document, operationName, variables, mergedExt)
207+ .use(_.head.compile.onlyOrError)
208+ )
209+ _ <- Otel4sMiddleware .recordResponseAttributes(span, result)
210+ yield result
211+
212+ class Otel4sPersistentStreamingClient [F [_]: Tracer : Concurrent , S , CP , CE ](
173213 wrapped : PersistentStreamingClient [F , S , CP , CE ],
174214 spanBuilderMod : Otel4sMiddleware .SpanBuilderMod [F ],
175215 additionalAttributesF : (GraphQLQuery , Option [JsonObject ]) => F [List [Attribute [? ]]]
0 commit comments