Skip to content

Instantly share code, notes, and snippets.

@CodeBlanch
Created July 12, 2022 18:29
Show Gist options
  • Save CodeBlanch/b967467718a5bf560405b853b0f7c295 to your computer and use it in GitHub Desktop.
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
// <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