|
4 | 4 |
|
5 | 5 | using System; |
6 | 6 | using System.Collections.Generic; |
| 7 | +using System.Diagnostics; |
7 | 8 | using System.Linq; |
8 | 9 | using System.Runtime.CompilerServices; |
9 | 10 | using System.Text.Json; |
@@ -165,11 +166,11 @@ private ValueTask<TResponse> DoRequestCoreAsync<TRequest, TResponse, TRequestPar |
165 | 166 | ValueTask<TResponse> SendRequest() |
166 | 167 | { |
167 | 168 | var (endpointPath, resolvedRouteValues, postData) = PrepareRequest<TRequest, TRequestParameters>(request); |
168 | | - var openTelemetryData = PrepareOpenTelemetryData<TRequest, TRequestParameters>(request, resolvedRouteValues); |
| 169 | + var openTelemetryDataMutator = GetOpenTelemetryDataMutator<TRequest, TRequestParameters>(request, resolvedRouteValues); |
169 | 170 |
|
170 | 171 | return isAsync |
171 | | - ? new ValueTask<TResponse>(_transport.RequestAsync<TResponse>(endpointPath, postData, in openTelemetryData, request.RequestConfig, cancellationToken)) |
172 | | - : new ValueTask<TResponse>(_transport.Request<TResponse>(endpointPath, postData, in openTelemetryData, request.RequestConfig)); |
| 172 | + ? new ValueTask<TResponse>(_transport.RequestAsync<TResponse>(endpointPath, postData, openTelemetryDataMutator, request.RequestConfig, cancellationToken)) |
| 173 | + : new ValueTask<TResponse>(_transport.Request<TResponse>(endpointPath, postData, openTelemetryDataMutator, request.RequestConfig)); |
173 | 174 | } |
174 | 175 |
|
175 | 176 | async ValueTask<TResponse> SendRequestWithProductCheck() |
@@ -211,19 +212,19 @@ async ValueTask<TResponse> SendRequestWithProductCheckCore() |
211 | 212 | // Send request |
212 | 213 |
|
213 | 214 | var (endpointPath, resolvedRouteValues, postData) = PrepareRequest<TRequest, TRequestParameters>(request); |
214 | | - var openTelemetryData = PrepareOpenTelemetryData<TRequest, TRequestParameters>(request, resolvedRouteValues); |
| 215 | + var openTelemetryDataMutator = GetOpenTelemetryDataMutator<TRequest, TRequestParameters>(request, resolvedRouteValues); |
215 | 216 |
|
216 | 217 | TResponse response; |
217 | 218 |
|
218 | 219 | if (isAsync) |
219 | 220 | { |
220 | 221 | response = await _transport |
221 | | - .RequestAsync<TResponse>(endpointPath, postData, in openTelemetryData, requestConfig, cancellationToken) |
| 222 | + .RequestAsync<TResponse>(endpointPath, postData, openTelemetryDataMutator, requestConfig, cancellationToken) |
222 | 223 | .ConfigureAwait(false); |
223 | 224 | } |
224 | 225 | else |
225 | 226 | { |
226 | | - response = _transport.Request<TResponse>(endpointPath, postData, in openTelemetryData, requestConfig); |
| 227 | + response = _transport.Request<TResponse>(endpointPath, postData, openTelemetryDataMutator, requestConfig); |
227 | 228 | } |
228 | 229 |
|
229 | 230 | // Evaluate product check result |
@@ -252,39 +253,41 @@ async ValueTask<TResponse> SendRequestWithProductCheckCore() |
252 | 253 | } |
253 | 254 | } |
254 | 255 |
|
255 | | - private static OpenTelemetryData PrepareOpenTelemetryData<TRequest, TRequestParameters>(TRequest request, Dictionary<string, string> resolvedRouteValues) |
| 256 | + private static Action<Activity>? GetOpenTelemetryDataMutator<TRequest, TRequestParameters>(TRequest request, Dictionary<string, string>? resolvedRouteValues) |
256 | 257 | where TRequest : Request<TRequestParameters> |
257 | 258 | where TRequestParameters : RequestParameters, new() |
258 | 259 | { |
259 | 260 | // If there are no subscribed listeners, we avoid some work and allocations |
260 | 261 | if (!Elastic.Transport.Diagnostics.OpenTelemetry.ElasticTransportActivitySourceHasListeners) |
261 | | - return default; |
| 262 | + return null; |
262 | 263 |
|
263 | | - // We fall back to a general operation name in cases where the derived request fails to override the property |
264 | | - var operationName = !string.IsNullOrEmpty(request.OperationName) ? request.OperationName : request.HttpMethod.GetStringValue(); |
| 264 | + return OpenTelemetryDataMutator; |
265 | 265 |
|
266 | | - // TODO: Optimisation: We should consider caching these, either for cases where resolvedRouteValues is null, or |
267 | | - // caching per combination of route values. |
268 | | - // We should benchmark this first to assess the impact for common workloads. |
269 | | - // The former is likely going to save some short-lived allocations, but only for requests to endpoints without required path parts. |
270 | | - // The latter may bloat the cache as some combinations of path parts may rarely re-occur. |
271 | | - var attributes = new Dictionary<string, object> |
| 266 | + void OpenTelemetryDataMutator(Activity activity) |
272 | 267 | { |
273 | | - [OpenTelemetry.SemanticConventions.DbOperation] = !string.IsNullOrEmpty(request.OperationName) ? request.OperationName : "unknown", |
274 | | - [$"{OpenTelemetrySpanAttributePrefix}schema_url"] = OpenTelemetrySchemaVersion |
275 | | - }; |
| 268 | + // We fall back to a general operation name in cases where the derived request fails to override the property |
| 269 | + var operationName = !string.IsNullOrEmpty(request.OperationName) ? request.OperationName : request.HttpMethod.GetStringValue(); |
| 270 | + |
| 271 | + // TODO: Optimisation: We should consider caching these, either for cases where resolvedRouteValues is null, or |
| 272 | + // caching per combination of route values. |
| 273 | + // We should benchmark this first to assess the impact for common workloads. |
| 274 | + // The former is likely going to save some short-lived allocations, but only for requests to endpoints without required path parts. |
| 275 | + // The latter may bloat the cache as some combinations of path parts may rarely re-occur. |
| 276 | + |
| 277 | + activity.DisplayName = operationName; |
| 278 | + |
| 279 | + activity.SetTag(OpenTelemetry.SemanticConventions.DbOperation, !string.IsNullOrEmpty(request.OperationName) ? request.OperationName : "unknown"); |
| 280 | + activity.SetTag($"{OpenTelemetrySpanAttributePrefix}schema_url", OpenTelemetrySchemaVersion); |
| 281 | + |
| 282 | + if (resolvedRouteValues is null) |
| 283 | + return; |
276 | 284 |
|
277 | | - if (resolvedRouteValues is not null) |
278 | | - { |
279 | 285 | foreach (var value in resolvedRouteValues) |
280 | 286 | { |
281 | 287 | if (!string.IsNullOrEmpty(value.Key) && !string.IsNullOrEmpty(value.Value)) |
282 | | - attributes.Add($"{OpenTelemetrySpanAttributePrefix}path_parts.{value.Key}", value.Value); |
| 288 | + activity.SetTag($"{OpenTelemetrySpanAttributePrefix}path_parts.{value.Key}", value.Value); |
283 | 289 | } |
284 | 290 | } |
285 | | - |
286 | | - var openTelemetryData = new OpenTelemetryData { SpanName = operationName, SpanAttributes = attributes }; |
287 | | - return openTelemetryData; |
288 | 291 | } |
289 | 292 |
|
290 | 293 | private (EndpointPath endpointPath, Dictionary<string, string>? resolvedRouteValues, PostData data) PrepareRequest<TRequest, TRequestParameters>(TRequest request) |
|
0 commit comments