diff --git a/NLog.Targets.OpenTelemetryProtocol.Test/Program.cs b/NLog.Targets.OpenTelemetryProtocol.Test/Program.cs index 64b72af..653ab6b 100644 --- a/NLog.Targets.OpenTelemetryProtocol.Test/Program.cs +++ b/NLog.Targets.OpenTelemetryProtocol.Test/Program.cs @@ -1,4 +1,7 @@ -using System; +using NLog.Targets.Wrappers; +using System; +using System.Diagnostics; +using System.Linq; using System.Threading; namespace NLog.Targets.OpenTelemetryProtocol.Test @@ -13,6 +16,7 @@ public static void Main() using var currentActivity = new System.Diagnostics.Activity("Hello World").Start(); + logger.Fatal("message: {messageField}", message); logger.Fatal("message: {messageField}", message); Thread.Sleep(10000); diff --git a/NLog.Targets.OpenTelemetryProtocol.Test/nlog.config b/NLog.Targets.OpenTelemetryProtocol.Test/nlog.config index b78e62d..c26d395 100644 --- a/NLog.Targets.OpenTelemetryProtocol.Test/nlog.config +++ b/NLog.Targets.OpenTelemetryProtocol.Test/nlog.config @@ -29,7 +29,6 @@ - \ No newline at end of file diff --git a/NLog.Targets.OpenTelemetryProtocol/ActivityExtensions.cs b/NLog.Targets.OpenTelemetryProtocol/ActivityExtensions.cs new file mode 100644 index 0000000..f26e6d1 --- /dev/null +++ b/NLog.Targets.OpenTelemetryProtocol/ActivityExtensions.cs @@ -0,0 +1,52 @@ +using System.Diagnostics; + +namespace NLog.Targets.OpenTelemetryProtocol +{ + /// + /// Formats elements of for inclusion in log events. Non-W3C-format activities are + /// ignored (Seq does not support the older Microsoft-proprietary hierarchical activity id format). + /// + internal static class ActivityExtensions + { + private static readonly System.Diagnostics.ActivitySpanId EmptySpanId = default(System.Diagnostics.ActivitySpanId); + private static readonly System.Diagnostics.ActivityTraceId EmptyTraceId = default(System.Diagnostics.ActivityTraceId); + + public static string GetSpanId(this Activity activity) + { + return activity.IdFormat == ActivityIdFormat.W3C ? + SpanIdToHexString(activity.SpanId) : + string.Empty; + } + + public static string GetTraceId(this Activity activity) + { + return activity.IdFormat == ActivityIdFormat.W3C ? + TraceIdToHexString(activity.TraceId) : + string.Empty; + } + + private static string SpanIdToHexString(ActivitySpanId spanId) + { + if (EmptySpanId.Equals(spanId)) + return string.Empty; + + var spanHexString = spanId.ToHexString(); + if (ReferenceEquals(spanHexString, EmptySpanId.ToHexString())) + return string.Empty; + + return spanHexString; + } + + private static string TraceIdToHexString(ActivityTraceId traceId) + { + if (EmptyTraceId.Equals(traceId)) + return string.Empty; + + var traceHexString = traceId.ToHexString(); + if (ReferenceEquals(traceHexString, EmptyTraceId.ToHexString())) + return string.Empty; + + return traceHexString; + } + } +} \ No newline at end of file diff --git a/NLog.Targets.OpenTelemetryProtocol/OtlpTarget.cs b/NLog.Targets.OpenTelemetryProtocol/OtlpTarget.cs index b20282f..4b4e345 100644 --- a/NLog.Targets.OpenTelemetryProtocol/OtlpTarget.cs +++ b/NLog.Targets.OpenTelemetryProtocol/OtlpTarget.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics.Tracing; using System.Globalization; +using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -11,6 +12,7 @@ using NLog.Layouts; using NLog.Targets.OpenTelemetryProtocol; using NLog.Targets.OpenTelemetryProtocol.Exceptions; +using NLog.Targets.Wrappers; using OpenTelemetry; using OpenTelemetry.Exporter; using OpenTelemetry.Logs; @@ -29,6 +31,7 @@ public class OtlpTarget : TargetWithContext private readonly ConcurrentDictionary _loggers = new(StringComparer.Ordinal); private readonly object _sync = new object(); + private readonly object _sync2 = new object(); private const string OriginalFormatName = "{OriginalFormat}"; @@ -67,6 +70,30 @@ public class OtlpTarget : TargetWithContext public bool DisableEventListener { get; set; } + public Layout TraceId { get; set; } = Layout.FromMethod(evt => System.Diagnostics.Activity.Current?.GetTraceId()); + + public Layout SpanId { get; set; } = Layout.FromMethod(evt => System.Diagnostics.Activity.Current?.GetSpanId()); + + private bool _checked = false; + private bool _isWrapped; + + public bool IsWrapped + { + get + { + lock (_sync2) + { + if (!_checked) + { + _checked = true; + _isWrapped = LogManager.Configuration.AllTargets.Where(t => t is AsyncTargetWrapper).ToArray().Cast().Any(x => x.WrappedTarget == this); + } + + return _isWrapped; + } + } + } + public OtlpTarget() { Layout = "${message}"; @@ -246,6 +273,16 @@ protected override void Write(LogEventInfo logEvent) Timestamp = logEvent.TimeStamp, }; + if (IsWrapped) + { + var spanId = RenderLogEvent(SpanId, logEvent); + if (!string.IsNullOrEmpty(spanId)) + data.SpanId = System.Diagnostics.ActivitySpanId.CreateFromString(spanId.AsSpan()); + var traceId = RenderLogEvent(TraceId, logEvent); + if (!string.IsNullOrEmpty(traceId)) + data.TraceId = System.Diagnostics.ActivityTraceId.CreateFromString(traceId.AsSpan()); + } + if (IncludeFormattedMessage && (logEvent.Parameters?.Length > 0 || logEvent.HasProperties)) { var formattedMessage = RenderLogEvent(Layout, logEvent); diff --git a/UnitTests/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj b/UnitTests/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj index e1ecbfa..ab99f54 100644 --- a/UnitTests/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj +++ b/UnitTests/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj @@ -60,6 +60,9 @@ + + Always + Always diff --git a/UnitTests/TargetTests.cs b/UnitTests/TargetTests.cs index 18af744..06d0475 100644 --- a/UnitTests/TargetTests.cs +++ b/UnitTests/TargetTests.cs @@ -503,6 +503,7 @@ public void ActivityContextIsPopulated() using var currentActivity = new System.Diagnostics.Activity("Hello World").Start(); logger.Info(message); + Assert.False(target.IsWrapped); OtlpLogs.LogRecord? otlpLogRecord = ToOtlpLogs(DefaultSdkLimitOptions, new ExperimentalOptions(), target.LogRecords[0]); @@ -510,6 +511,31 @@ public void ActivityContextIsPopulated() Assert.Equal(currentActivity.SpanId.ToString(), ByteStringToHexString(otlpLogRecord.SpanId)); } + [Fact] + public void ActivityContextIsPopulatedIfAsync() + { + LogManager.LoadConfiguration("nlog2.config"); + var logger = LogManager.GetCurrentClassLogger(); + + OtlpTarget target = (OtlpTarget)LogManager.Configuration.AllTargets.First(x => x is OtlpTarget); + LogManager.ReconfigExistingLoggers(); + + var message = "message"; + + using var currentActivity = new System.Diagnostics.Activity("Hello World").Start(); + + logger.Info(message); + + Thread.Sleep(10000); + + Assert.True(target.IsWrapped); + Assert.Single(target.LogRecords); + OtlpLogs.LogRecord? otlpLogRecord = ToOtlpLogs(DefaultSdkLimitOptions, new ExperimentalOptions(), target.LogRecords[0]); + + Assert.Equal(currentActivity.TraceId.ToString(), ByteStringToHexString(otlpLogRecord.TraceId)); + Assert.Equal(currentActivity.SpanId.ToString(), ByteStringToHexString(otlpLogRecord.SpanId)); + } + private string ByteStringToHexString(Google.Protobuf.ByteString str) { return BitConverter.ToString(str.ToByteArray()).Replace("-", "").ToLower(); diff --git a/UnitTests/nlog2.config b/UnitTests/nlog2.config new file mode 100644 index 0000000..b20d968 --- /dev/null +++ b/UnitTests/nlog2.config @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file