Created
July 12, 2022 18:29
-
-
Save CodeBlanch/b967467718a5bf560405b853b0f7c295 to your computer and use it in GitHub Desktop.
OpenTelemetry TraceId/SpanId to DataDog dd.trace_id/dd.span_id log scope middleware
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// <auto-generated> <- Turns off style cop in this file | |
#nullable enable | |
using System.Buffers.Binary; | |
using System.Collections; | |
using System.Diagnostics; | |
using System.Globalization; | |
using System.Runtime.CompilerServices; | |
using System.Text; | |
namespace DataDog | |
{ | |
internal sealed class LogEnrichmentMiddleware | |
{ | |
private static readonly object s_DataDogScopeHttpContextKey = new(); | |
public static DataDogTraceState? GetDataDogTraceState(HttpContext context) | |
=> context.Items[s_DataDogScopeHttpContextKey] as DataDogTraceState; | |
private readonly ILogger<LogEnrichmentMiddleware> _Log; | |
private readonly RequestDelegate _Next; | |
public LogEnrichmentMiddleware( | |
ILogger<LogEnrichmentMiddleware> logger, | |
RequestDelegate next) | |
{ | |
_Log = logger ?? throw new ArgumentNullException(nameof(logger)); | |
_Next = next ?? throw new ArgumentNullException(nameof(next)); | |
} | |
public async Task InvokeAsync(HttpContext context) | |
{ | |
IDisposable? ddScope = null; | |
Activity? current = Activity.Current; | |
if (current != null) | |
{ | |
DataDogTraceState state = new(current); | |
context.Items[s_DataDogScopeHttpContextKey] = state; | |
ddScope = _Log.BeginScope(state); | |
} | |
try | |
{ | |
await _Next(context).ConfigureAwait(false); | |
} | |
finally | |
{ | |
ddScope?.Dispose(); | |
} | |
} | |
} | |
internal sealed class DataDogTraceState : IDataDogTraceContext, IReadOnlyList<KeyValuePair<string, object>> | |
{ | |
public static string GenerateDataDogTraceId(ActivityTraceId traceId) | |
{ | |
#if NET5_0_OR_GREATER | |
Span<byte> bytes = stackalloc byte[8]; | |
traceId.ToHexString().AsSpan().Slice(16, 16).ToBytes(bytes, out _); | |
ulong value = BinaryPrimitives.ReadUInt64BigEndian(bytes); | |
Span<char> chars = stackalloc char[20]; | |
value.TryFormat(chars, out int numberOfChars, format: "G", CultureInfo.InvariantCulture); | |
return new string(chars[..numberOfChars]); | |
#else | |
byte[] bytes = new byte[8]; | |
traceId.ToHexString().AsSpan().Slice(16, 16).ToBytes(bytes, out _); | |
ulong value = BinaryPrimitives.ReadUInt64BigEndian(bytes); | |
return value.ToString("G", CultureInfo.InvariantCulture); | |
#endif | |
} | |
public static string GenerateDataDogSpanId(ActivitySpanId spanId) | |
{ | |
#if NET5_0_OR_GREATER | |
Span<byte> bytes = stackalloc byte[8]; | |
spanId.ToHexString().AsSpan()[..16].ToBytes(bytes, out _); | |
ulong value = BinaryPrimitives.ReadUInt64BigEndian(bytes); | |
Span<char> chars = stackalloc char[20]; | |
value.TryFormat(chars, out int numberOfChars, format: "G", CultureInfo.InvariantCulture); | |
return new string(chars[..numberOfChars]); | |
#else | |
byte[] bytes = new byte[8]; | |
spanId.ToHexString().AsSpan()[..16].ToBytes(bytes, out _); | |
ulong value = BinaryPrimitives.ReadUInt64BigEndian(bytes); | |
return value.ToString("G", CultureInfo.InvariantCulture); | |
#endif | |
} | |
public string DataDogTraceId { get; } | |
public string DataDogSpanId { get; } | |
public DataDogTraceState(Activity activity) | |
{ | |
DataDogTraceId = GenerateDataDogTraceId(activity.TraceId); | |
DataDogSpanId = GenerateDataDogSpanId(activity.SpanId); | |
} | |
public KeyValuePair<string, object> this[int index] | |
=> index == 0 | |
? new KeyValuePair<string, object>("dd.trace_id", DataDogTraceId) | |
: index == 1 | |
? new KeyValuePair<string, object>("dd.span_id", DataDogSpanId) | |
: throw new ArgumentOutOfRangeException(nameof(index)); | |
public int Count => 2; | |
public override string ToString() | |
{ | |
StringBuilder sb = new(128); | |
sb.Append("dd.trace_id:"); | |
sb.Append(DataDogTraceId); | |
sb.Append(", dd.span_id:"); | |
sb.Append(DataDogSpanId); | |
return sb.ToString(); | |
} | |
public IEnumerator<KeyValuePair<string, object>> GetEnumerator() | |
{ | |
yield return this[0]; | |
yield return this[1]; | |
} | |
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); | |
} | |
/// <summary> | |
/// An interface for retrieving Data Dog trace details. | |
/// </summary> | |
public interface IDataDogTraceContext | |
{ | |
/// <summary> | |
/// Gets the datadog trace identifier. | |
/// </summary> | |
string DataDogTraceId { get; } | |
/// <summary> | |
/// Gets the datadog span identifier. | |
/// </summary> | |
string DataDogSpanId { get; } | |
} | |
public static class ConversionExtensions | |
{ | |
/// <summary> | |
/// Writes a span of hex characters into a destination span of bytes. | |
/// </summary> | |
/// <param name="source">Source characters.</param> | |
/// <param name="destination">Destination bytes.</param> | |
/// <param name="bytesWrittenToDestination">The number of bytes written.</param> | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static void ToBytes(this ReadOnlySpan<char> source, Span<byte> destination, out int bytesWrittenToDestination) | |
{ | |
bytesWrittenToDestination = 0; | |
for (int i = 0; i < source.Length - 1 && bytesWrittenToDestination < destination.Length; i += 2) | |
{ | |
destination[bytesWrittenToDestination++] = GetByteValueFromHexChars(source[i], source[i + 1]); | |
} | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
private static byte GetByteValueFromHexChars(char hi, char low) | |
=> (byte)((GetByteValueFromHexChar(hi) << 4) | GetByteValueFromHexChar(low)); | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
private static int GetByteValueFromHexChar(char c) | |
{ | |
int val = c - (c < 58 ? 48 : (c < 97 ? 55 : 87)); | |
return val > 15 || val < 0 | |
? throw new ArgumentOutOfRangeException($"Character [{c}] is not a valid Hex value.") | |
: val; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment