@@ -9,24 +9,31 @@ import cats.effect.Resource
99import cats .syntax .all .*
1010import clue .BuildInfo
1111import clue .FetchClientWithPars
12+ import clue .PersistentClientStatus
13+ import clue .PersistentStreamingClient
1214import clue .StreamingClient
1315import clue .model .GraphQLQuery
1416import clue .model .GraphQLResponse
1517import io .circe .Decoder
18+ import io .circe .Encoder
1619import io .circe .JsonObject
1720import org .typelevel .otel4s .Attribute
21+ import org .typelevel .otel4s .semconv .attributes .HttpAttributes
22+ import org .typelevel .otel4s .semconv .experimental .attributes .GraphqlExperimentalAttributes
1823import org .typelevel .otel4s .trace .Span
1924import org .typelevel .otel4s .trace .SpanBuilder
2025import org .typelevel .otel4s .trace .SpanKind
2126import org .typelevel .otel4s .trace .StatusCode
2227import org .typelevel .otel4s .trace .Tracer
2328
2429object Otel4sMiddleware :
25- private [otel4s] def extractOperationType (query : String ): Option [String ] =
30+ import GraphqlExperimentalAttributes .GraphqlOperationTypeValue
31+
32+ private [otel4s] def extractOperationType (query : String ): Option [GraphqlOperationTypeValue ] =
2633 val trimmed = query.trim.toLowerCase
27- if trimmed.startsWith(" query" ) then Some (" query " )
28- else if trimmed.startsWith(" mutation" ) then Some (" mutation " )
29- else if trimmed.startsWith(" subscription" ) then Some (" subscription " )
34+ if trimmed.startsWith(" query" ) then Some (GraphqlOperationTypeValue . Query )
35+ else if trimmed.startsWith(" mutation" ) then Some (GraphqlOperationTypeValue . Mutation )
36+ else if trimmed.startsWith(" subscription" ) then Some (GraphqlOperationTypeValue . Subscription )
3037 else None
3138
3239 type SpanBuilderMod [F [_]] = SpanBuilder [F ] => SpanBuilder [F ]
@@ -37,80 +44,60 @@ object Otel4sMiddleware:
3744 : (GraphQLQuery , Option [JsonObject ]) => F [List [Attribute [? ]]] =
3845 (_, _) => List .empty[Attribute [? ]].pure[F ]
3946
40- // Fetch client overloads
47+ // Fetch client
4148 def apply [F [_]: Tracer : MonadCancelThrow , P , S ](
4249 client : FetchClientWithPars [F , P , S ],
4350 spanBuilderMod : SpanBuilderMod [F ],
4451 additionalAttributesF : (GraphQLQuery , Option [JsonObject ]) => F [List [Attribute [? ]]]
4552 ): FetchClientWithPars [F , P , S ] =
4653 Otel4sFetchClient [F , P , S ](client, spanBuilderMod, additionalAttributesF)
4754
48- def apply [F [_]: Tracer : MonadCancelThrow , P , S ](
49- client : FetchClientWithPars [F , P , S ],
50- additionalAttributesF : (GraphQLQuery , Option [JsonObject ]) => F [List [Attribute [? ]]]
51- ): FetchClientWithPars [F , P , S ] =
52- apply(client, identityMod[F ], additionalAttributesF)
53-
5455 def apply [F [_]: Tracer : MonadCancelThrow , P , S ](
5556 client : FetchClientWithPars [F , P , S ]
5657 ): FetchClientWithPars [F , P , S ] =
5758 apply(client, identityMod[F ], emptyAttrs[F ])
5859
59- def withAttributes [F [_]: Tracer : MonadCancelThrow , P , S ](
60- client : FetchClientWithPars [F , P , S ],
61- spanBuilderMod : SpanBuilderMod [F ]
62- )(additionalAttributes : Attribute [? ]* ): FetchClientWithPars [F , P , S ] =
63- apply(client, spanBuilderMod, (_, _) => additionalAttributes.toList.pure[F ])
64-
65- def withAttributes [F [_]: Tracer : MonadCancelThrow , P , S ](
66- client : FetchClientWithPars [F , P , S ]
67- )(additionalAttributes : Attribute [? ]* ): FetchClientWithPars [F , P , S ] =
68- apply(client, identityMod[F ], (_, _) => additionalAttributes.toList.pure[F ])
69-
70- // Streaming client overloads
71-
60+ // Streaming client
7261 def apply [F [_]: Tracer : MonadCancelThrow , S ](
7362 client : StreamingClient [F , S ],
7463 spanBuilderMod : SpanBuilderMod [F ],
7564 additionalAttributesF : (GraphQLQuery , Option [JsonObject ]) => F [List [Attribute [? ]]]
7665 ): StreamingClient [F , S ] =
7766 Otel4sStreamingClient (client, spanBuilderMod, additionalAttributesF)
7867
79- def apply [F [_]: Tracer : MonadCancelThrow , S ](
80- client : StreamingClient [F , S ],
81- additionalAttributesF : (GraphQLQuery , Option [JsonObject ]) => F [List [Attribute [? ]]]
82- ): StreamingClient [F , S ] =
83- apply(client, identityMod[F ], additionalAttributesF)
84-
8568 def apply [F [_]: Tracer : MonadCancelThrow , S ](
8669 client : StreamingClient [F , S ]
8770 ): StreamingClient [F , S ] =
8871 apply(client, identityMod[F ], emptyAttrs[F ])
8972
90- def withAttributes [F [_]: Tracer : MonadCancelThrow , S ](
91- client : StreamingClient [F , S ],
92- spanBuilderMod : SpanBuilderMod [F ]
93- )(additionalAttributes : Attribute [? ]* ): StreamingClient [F , S ] =
94- apply(client, spanBuilderMod, (_, _) => additionalAttributes.toList.pure[F ])
73+ // Persistent streaming client
74+ def apply [F [_]: Tracer : MonadCancelThrow , S , CP , CE ](
75+ client : PersistentStreamingClient [F , S , CP , CE ],
76+ spanBuilderMod : SpanBuilderMod [F ],
77+ additionalAttributesF : (GraphQLQuery , Option [JsonObject ]) => F [List [Attribute [? ]]]
78+ ): PersistentStreamingClient [F , S , CP , CE ] =
79+ Otel4sPersistentStreamingClient (client, spanBuilderMod, additionalAttributesF)
9580
96- def withAttributes [F [_]: Tracer : MonadCancelThrow , S ](
97- client : StreamingClient [F , S ]
98- )( additionalAttributes : Attribute [ ? ] * ) : StreamingClient [ F , S ] =
99- apply(client, identityMod[F ], (_, _) => additionalAttributes.toList.pure [F ])
81+ def apply [F [_]: Tracer : MonadCancelThrow , S , CP , CE ](
82+ client : PersistentStreamingClient [F , S , CP , CE ]
83+ ): PersistentStreamingClient [ F , S , CP , CE ] =
84+ apply(client, identityMod[F ], emptyAttrs [F ])
10085
10186 private [otel4s] def commonAttributes (
10287 document : GraphQLQuery ,
10388 operationName : Option [String ]
10489 ): List [Attribute [? ]] =
10590 val base = List [Attribute [? ]](
10691 Attribute (" clue.version" , BuildInfo .version),
107- Attribute ( " http.request.method " , " POST" ),
108- Attribute ( " graphql.document " , document.value)
92+ HttpAttributes . HttpRequestMethod ( " POST" ),
93+ GraphqlExperimentalAttributes . GraphqlDocument ( document.value)
10994 )
11095 val opType = extractOperationType(document.value)
111- .map(t => Attribute (" graphql.operation.type" , t))
96+ .map(t => GraphqlExperimentalAttributes .GraphqlOperationType (t.value))
97+ .toList
98+ val opName = operationName
99+ .map(n => GraphqlExperimentalAttributes .GraphqlOperationName (n))
112100 .toList
113- val opName = operationName.map(n => Attribute (" graphql.operation.name" , n)).toList
114101 base ++ opType ++ opName
115102
116103 private [otel4s] def recordResponseAttributes [F [_]: Applicative , D ](
@@ -126,51 +113,6 @@ object Otel4sMiddleware:
126113 Attribute (" clue.response.errors" , errorsStr)
127114 ) *> span.setStatus(StatusCode .Error , " GraphQL request returned errors" )
128115
129- object http4s :
130- import Otel4sMiddleware .SpanBuilderMod
131-
132- extension [F [_], P , S ](client : F [FetchClientWithPars [F , P , S ]]) {
133- @ scala.annotation.targetName(" tracedFetchClient" )
134- def traced (using
135- Tracer [F ],
136- MonadCancelThrow [F ],
137- cats.Functor [F ]
138- ): F [FetchClientWithPars [F , P , S ]] =
139- client.map(Otel4sMiddleware (_))
140-
141- @ scala.annotation.targetName(" tracedWithFetchClient" )
142- def tracedWith (
143- spanBuilderMod : SpanBuilderMod [F ],
144- additionalAttributesF : (GraphQLQuery , Option [JsonObject ]) => F [List [Attribute [? ]]]
145- )(using
146- Tracer [F ],
147- MonadCancelThrow [F ],
148- cats.Functor [F ]
149- ): F [FetchClientWithPars [F , P , S ]] =
150- client.map(Otel4sMiddleware (_, spanBuilderMod, additionalAttributesF))
151- }
152-
153- extension [F [_], S ](client : F [StreamingClient [F , S ]]) {
154- @ scala.annotation.targetName(" tracedStreamingClient" )
155- def traced (using
156- Tracer [F ],
157- MonadCancelThrow [F ],
158- cats.Functor [F ]
159- ): F [StreamingClient [F , S ]] =
160- client.map(Otel4sMiddleware (_))
161-
162- @ scala.annotation.targetName(" tracedWithStreamingClient" )
163- def tracedWith (
164- spanBuilderMod : SpanBuilderMod [F ],
165- additionalAttributesF : (GraphQLQuery , Option [JsonObject ]) => F [List [Attribute [? ]]]
166- )(using
167- Tracer [F ],
168- MonadCancelThrow [F ],
169- cats.Functor [F ]
170- ): F [StreamingClient [F , S ]] =
171- client.map(Otel4sMiddleware (_, spanBuilderMod, additionalAttributesF))
172- }
173-
174116class Otel4sFetchClient [F [_]: Tracer : MonadCancelThrow , P , S ](
175117 wrapped : FetchClientWithPars [F , P , S ],
176118 spanBuilderMod : Otel4sMiddleware .SpanBuilderMod [F ],
@@ -224,4 +166,18 @@ class Otel4sStreamingClient[F[_]: Tracer: MonadCancelThrow, S](
224166 (exitCase match
225167 case Resource .ExitCase .Errored (e) =>
226168 span.setStatus(StatusCode .Error , Option (e.getMessage).getOrElse(e.toString))
227- case _ => Applicative [F ].unit)
169+ case _ => Applicative [F ].unit
170+ )
171+
172+ class Otel4sPersistentStreamingClient [F [_]: Tracer : MonadCancelThrow , S , CP , CE ](
173+ wrapped : PersistentStreamingClient [F , S , CP , CE ],
174+ spanBuilderMod : Otel4sMiddleware .SpanBuilderMod [F ],
175+ additionalAttributesF : (GraphQLQuery , Option [JsonObject ]) => F [List [Attribute [? ]]]
176+ ) extends Otel4sStreamingClient [F , S ](wrapped, spanBuilderMod, additionalAttributesF)
177+ with PersistentStreamingClient [F , S , CP , CE ]:
178+ def status : F [PersistentClientStatus ] = wrapped.status
179+ def statusStream : fs2.Stream [F , PersistentClientStatus ] = wrapped.statusStream
180+ def connect [A : Encoder .AsObject ](payload : F [A ]): F [JsonObject ] = wrapped.connect(payload)
181+ def connect (): F [JsonObject ] = wrapped.connect()
182+ def disconnect (closeParameters : CP ): F [Unit ] = wrapped.disconnect(closeParameters)
183+ def disconnect (): F [Unit ] = wrapped.disconnect()
0 commit comments