Skip to content
Open
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
12b3643
Add NLog instrumentation for OpenTelemetry .NET Auto-Instrumentation
danifitz Aug 6, 2025
dd0eee6
Fix: https://github.com/open-telemetry/opentelemetry-dotnet-instrumen…
danifitz Aug 11, 2025
1b5ee77
Fix https://github.com/open-telemetry/opentelemetry-dotnet-instrument…
danifitz Aug 11, 2025
489fa3b
Fix: https://github.com/open-telemetry/opentelemetry-dotnet-instrumen…
danifitz Aug 11, 2025
638a6aa
Test: https://github.com/open-telemetry/opentelemetry-dotnet-instrume…
danifitz Aug 11, 2025
206be87
feat: refactor NLog instrumentation to use standard NLog Target archi…
danifitz Aug 20, 2025
799ef71
refactor: remove unused NLog.Extensions.Logging from TestApplication.…
danifitz Aug 20, 2025
4401757
refactor: optimize NLog target for async compatibility and performance
danifitz Aug 21, 2025
0fcf644
feat: add hybrid typed layout support for NLog version compatibility
danifitz Aug 27, 2025
4ffa40a
feat: implement NLog v5.3.4+ typed layouts for OpenTelemetryTarget
danifitz Sep 10, 2025
f6cd7d5
fix test coverage
danifitz Sep 10, 2025
665fb09
fix: correct NLog bridge EmitLog method call to match OpenTelemetry SDK
danifitz Sep 10, 2025
3a79c85
refactor: simplify NLog target configuration to use environment varia…
danifitz Sep 11, 2025
f6957e6
Removed NLog from AssemblyInfo
danifitz Sep 24, 2025
b078f85
feat: add NLog instrumentation with duck typing and NLog 6.x support
danifitz Sep 24, 2025
4a0d5a3
Merge branch 'main' into feature/nlog-instrumentation
danifitz Sep 24, 2025
bf571f5
fix CHANGELOG
Kielek Sep 30, 2025
16b004e
Fix build errors
Kielek Sep 30, 2025
e650142
commit generated file
Kielek Sep 30, 2025
c41f73c
Merge branch 'main' into feature/nlog-instrumentation
Kielek Sep 30, 2025
e66da6a
Move NLog version to test folder
Kielek Sep 30, 2025
3d5aff8
cleanup solution
Kielek Sep 30, 2025
ad1c62f
add NLOG to dictionary
Kielek Sep 30, 2025
54dd6da
typo fixes
Kielek Sep 30, 2025
0602818
remove reference to NLog.Extensions.Logging
Kielek Sep 30, 2025
903ab69
Update tested versions
Kielek Sep 30, 2025
4702a68
Minimal assembly version set to 4.0.0
Kielek Sep 30, 2025
951c355
fix sln file
Kielek Sep 30, 2025
29d4251
remove reference to System.Private.Uri
Kielek Sep 30, 2025
29bde76
Fix compilation for tests app
Kielek Sep 30, 2025
911fbd8
Apply suggestions from code review
lachmatt Oct 1, 2025
60311e1
Merge branch 'main' into feature/nlog-instrumentation
Kielek Oct 1, 2025
d912c7e
Merge branch 'main' into feature/nlog-instrumentation
Kielek Oct 1, 2025
c6b7bde
Merge branch 'main' into feature/nlog-instrumentation
Kielek Oct 2, 2025
fad8ea4
Fix issue occurring in VS
Kielek Oct 2, 2025
e3d9274
Add missing settings test case
Kielek Oct 2, 2025
e95e972
remove redundant lines
Kielek Oct 2, 2025
62f933d
Sync implementation with available documentation
Kielek Oct 2, 2025
33546ee
user facing documentation
Kielek Oct 2, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .cspell/other.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ mycompanymyproductmylibrary
MYSQLCONNECTOR
MYSQLDATA
NETRUNTIME
NLOG
Npgsql
NSERVICEBUS
omnisharp
Expand All @@ -58,9 +59,9 @@ protos
RABBITMQ
Serilog
spdlog
srcs
SQLCLIENT
sqlserver
srcs
STACKEXCHANGEREDIS
TMPDIR
tracesexporter
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ This component adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.h

### Added

- Support for [`NLog`](https://www.nuget.org/packages/NLog/)
logs instrumentation for versions `5.*` and `6.*` on .NET using duck typing
for zero-config auto-injection.

### Changed

#### Dependency updates
Expand Down
19 changes: 19 additions & 0 deletions OpenTelemetry.AutoInstrumentation.sln
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SdkVersionAnalyzer", "tools
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestApplication.Log4NetBridge", "test\test-applications\integrations\TestApplication.Log4NetBridge\TestApplication.Log4NetBridge.csproj", "{926B7C03-42C2-4192-94A7-CD0B1C693279}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestApplication.NLogBridge", "test\test-applications\integrations\TestApplication.NLogBridge\TestApplication.NLogBridge.csproj", "{A7B8C9D0-1E2F-3A4B-5C6D-7E8F9A0B1C2D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestApplication.SelectiveSampler", "test\test-applications\integrations\TestApplication.SelectiveSampler\TestApplication.SelectiveSampler.csproj", "{FD1A1ABD-6A48-4E94-B5F7-2081AFCD1BBB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestApplication.ProfilerSpanStoppageHandling", "test\test-applications\integrations\TestApplication.ProfilerSpanStoppageHandling\TestApplication.ProfilerSpanStoppageHandling.csproj", "{665280EB-F428-4C04-A293-33228C73BF8A}"
Expand Down Expand Up @@ -1535,6 +1537,22 @@ Global
{926B7C03-42C2-4192-94A7-CD0B1C693279}.Release|x64.Build.0 = Release|Any CPU
{926B7C03-42C2-4192-94A7-CD0B1C693279}.Release|x86.ActiveCfg = Release|Any CPU
{926B7C03-42C2-4192-94A7-CD0B1C693279}.Release|x86.Build.0 = Release|Any CPU
{A7B8C9D0-1E2F-3A4B-5C6D-7E8F9A0B1C2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A7B8C9D0-1E2F-3A4B-5C6D-7E8F9A0B1C2D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A7B8C9D0-1E2F-3A4B-5C6D-7E8F9A0B1C2D}.Debug|ARM64.ActiveCfg = Debug|Any CPU
{A7B8C9D0-1E2F-3A4B-5C6D-7E8F9A0B1C2D}.Debug|ARM64.Build.0 = Debug|Any CPU
{A7B8C9D0-1E2F-3A4B-5C6D-7E8F9A0B1C2D}.Debug|x64.ActiveCfg = Debug|Any CPU
{A7B8C9D0-1E2F-3A4B-5C6D-7E8F9A0B1C2D}.Debug|x64.Build.0 = Debug|Any CPU
{A7B8C9D0-1E2F-3A4B-5C6D-7E8F9A0B1C2D}.Debug|x86.ActiveCfg = Debug|Any CPU
{A7B8C9D0-1E2F-3A4B-5C6D-7E8F9A0B1C2D}.Debug|x86.Build.0 = Debug|Any CPU
{A7B8C9D0-1E2F-3A4B-5C6D-7E8F9A0B1C2D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A7B8C9D0-1E2F-3A4B-5C6D-7E8F9A0B1C2D}.Release|Any CPU.Build.0 = Release|Any CPU
{A7B8C9D0-1E2F-3A4B-5C6D-7E8F9A0B1C2D}.Release|ARM64.ActiveCfg = Release|Any CPU
{A7B8C9D0-1E2F-3A4B-5C6D-7E8F9A0B1C2D}.Release|ARM64.Build.0 = Release|Any CPU
{A7B8C9D0-1E2F-3A4B-5C6D-7E8F9A0B1C2D}.Release|x64.ActiveCfg = Release|Any CPU
{A7B8C9D0-1E2F-3A4B-5C6D-7E8F9A0B1C2D}.Release|x64.Build.0 = Release|Any CPU
{A7B8C9D0-1E2F-3A4B-5C6D-7E8F9A0B1C2D}.Release|x86.ActiveCfg = Release|Any CPU
{A7B8C9D0-1E2F-3A4B-5C6D-7E8F9A0B1C2D}.Release|x86.Build.0 = Release|Any CPU
{FD1A1ABD-6A48-4E94-B5F7-2081AFCD1BBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FD1A1ABD-6A48-4E94-B5F7-2081AFCD1BBB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FD1A1ABD-6A48-4E94-B5F7-2081AFCD1BBB}.Debug|ARM64.ActiveCfg = Debug|Any CPU
Expand Down Expand Up @@ -1657,6 +1675,7 @@ Global
{AA3E0C5C-A4E2-46AB-BD18-2D30D3ABF692} = {E409ADD3-9574-465C-AB09-4324D205CC7C}
{C75FA076-D460-414B-97F7-6F8D0E85AE74} = {00F4C92D-6652-4BD8-A334-B35D3E711BE6}
{926B7C03-42C2-4192-94A7-CD0B1C693279} = {E409ADD3-9574-465C-AB09-4324D205CC7C}
{A7B8C9D0-1E2F-3A4B-5C6D-7E8F9A0B1C2D} = {E409ADD3-9574-465C-AB09-4324D205CC7C}
{FD1A1ABD-6A48-4E94-B5F7-2081AFCD1BBB} = {E409ADD3-9574-465C-AB09-4324D205CC7C}
{665280EB-F428-4C04-A293-33228C73BF8A} = {E409ADD3-9574-465C-AB09-4324D205CC7C}
EndGlobalSection
Expand Down
9 changes: 9 additions & 0 deletions build/LibraryVersions.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,15 @@ public static partial class LibraryVersion
new("3.2.0"),
]
},
{
"TestApplication.NLogBridge",
[
new("5.0.0"),
new("5.3.4"),
new("6.0.0"),
new("6.0.4"),
]
},
{
"TestApplication.MassTransit",
[
Expand Down
4 changes: 4 additions & 0 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ due to lack of stable semantic convention.
|-----------|---------------------------------------------------------------------------------------------------------------------------------|--------------------|------------------------|-----------------------------------------------------------------------------------------------------------------------------------|
| `ILOGGER` | [Microsoft.Extensions.Logging](https://www.nuget.org/packages/Microsoft.Extensions.Logging) **Not supported on .NET Framework** | ≥9.0.0 | bytecode or source \[1\] | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
| `LOG4NET` | [log4net](https://www.nuget.org/packages/log4net) \[2\] | ≥2.0.13 && < 4.0.0 | bytecode | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
| `NLOG` | [NLog](https://www.nuget.org/packages/NLog) \[3\] | ≥5.0.0 && < 7.0.0 | bytecode | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |

\[1\]: For ASP.NET Core applications, the `LoggingBuilder` instrumentation
can be enabled without using the .NET CLR Profiler by setting
Expand All @@ -216,6 +217,9 @@ the `ASPNETCORE_HOSTINGSTARTUPASSEMBLIES` environment variable to
\[2\]: Instrumentation provides both [trace context injection](./log-trace-correlation.md#log4net-trace-context-injection)
and [logs bridge](./log4net-bridge.md).

\[3\]: The NLog instrumentation uses duck typing for zero-config auto-injection.
Configuration is handled entirely through OpenTelemetry environment variables.

### Instrumentation options

| Environment variable | Description | Default value | Status |
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
OpenTelemetry.AutoInstrumentation.Instrumentations.NLog.Bridge.Integrations.LoggerIntegration
OpenTelemetry.AutoInstrumentation.Instrumentations.RabbitMqLegacy.Integrations.AsyncDefaultBasicConsumerIntegration
OpenTelemetry.AutoInstrumentation.Instrumentations.RabbitMqLegacy.Integrations.DefaultBasicConsumerIntegration
OpenTelemetry.AutoInstrumentation.Instrumentations.RabbitMqLegacy.Integrations.ModelBaseBasicGetIntegration
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
OpenTelemetry.AutoInstrumentation.Instrumentations.NLog.Bridge.Integrations.LoggerIntegration
OpenTelemetry.AutoInstrumentation.Instrumentations.RabbitMqLegacy.Integrations.AsyncDefaultBasicConsumerIntegration
OpenTelemetry.AutoInstrumentation.Instrumentations.RabbitMqLegacy.Integrations.DefaultBasicConsumerIntegration
OpenTelemetry.AutoInstrumentation.Instrumentations.RabbitMqLegacy.Integrations.ModelBaseBasicGetIntegration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,12 @@ public static class Logs
/// </summary>
public const string EnableLog4NetBridge = "OTEL_DOTNET_AUTO_LOGS_ENABLE_LOG4NET_BRIDGE";

/// <summary>
/// Configuration key for whether or not experimental NLog bridge
/// should be enabled.
/// </summary>
public const string EnableNLogBridge = "OTEL_DOTNET_AUTO_LOGS_ENABLE_NLOG_BRIDGE";

/// <summary>
/// Configuration key for disabling all log instrumentations.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,9 @@ internal enum LogInstrumentation
/// Log4Net instrumentation.
/// </summary>
Log4Net = 1,

/// <summary>
/// NLog instrumentation.
/// </summary>
NLog = 2,
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ internal class LogSettings : Settings
/// </summary>
public bool EnableLog4NetBridge { get; private set; }

/// <summary>
/// Gets a value indicating whether the experimental NLog bridge is enabled.
/// </summary>
public bool EnableNLogBridge { get; private set; }

/// <summary>
/// Gets the list of enabled instrumentations.
/// </summary>
Expand All @@ -54,6 +59,7 @@ protected override void OnLoadEnvVar(Configuration configuration)

IncludeFormattedMessage = configuration.GetBool(ConfigurationKeys.Logs.IncludeFormattedMessage) ?? false;
EnableLog4NetBridge = configuration.GetBool(ConfigurationKeys.Logs.EnableLog4NetBridge) ?? false;
EnableNLogBridge = configuration.GetBool(ConfigurationKeys.Logs.EnableNLogBridge) ?? false;

var instrumentationEnabledByDefault =
configuration.GetBool(ConfigurationKeys.Logs.LogsInstrumentationEnabled) ??
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ internal static partial class InstrumentationDefinitions
{
private static NativeCallTargetDefinition[] GetDefinitionsArray()
{
var nativeCallTargetDefinitions = new List<NativeCallTargetDefinition>(36);
var nativeCallTargetDefinitions = new List<NativeCallTargetDefinition>(37);
// Traces
var tracerSettings = Instrumentation.TracerSettings.Value;
if (tracerSettings.TracesEnabled)
Expand Down Expand Up @@ -101,6 +101,12 @@ private static NativeCallTargetDefinition[] GetDefinitionsArray()
nativeCallTargetDefinitions.Add(new("log4net", "log4net.Appender.AppenderCollection", "ToArray", ["log4net.Appender.IAppender[]"], 2, 0, 13, 3, 65535, 65535, AssemblyFullName, "OpenTelemetry.AutoInstrumentation.Instrumentations.Log4Net.Bridge.Integrations.AppenderCollectionIntegration"));
nativeCallTargetDefinitions.Add(new("log4net", "log4net.Util.AppenderAttachedImpl", "AppendLoopOnAppenders", ["System.Int32", "log4net.Core.LoggingEvent"], 2, 0, 13, 3, 65535, 65535, AssemblyFullName, "OpenTelemetry.AutoInstrumentation.Instrumentations.Log4Net.TraceContextInjection.Integrations.AppenderAttachedImplIntegration"));
}

// NLog
if (logSettings.EnabledInstrumentations.Contains(LogInstrumentation.NLog))
{
nativeCallTargetDefinitions.Add(new("NLog", "NLog.Logger", "Log", ["System.Void", "NLog.LogEventInfo"], 5, 0, 0, 6, 65535, 65535, AssemblyFullName, "OpenTelemetry.AutoInstrumentation.Instrumentations.NLog.Bridge.Integrations.LoggerIntegration"));
}
}

// Metrics
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ internal static partial class InstrumentationDefinitions
{
private static NativeCallTargetDefinition[] GetDefinitionsArray()
{
var nativeCallTargetDefinitions = new List<NativeCallTargetDefinition>(39);
var nativeCallTargetDefinitions = new List<NativeCallTargetDefinition>(40);
// Traces
var tracerSettings = Instrumentation.TracerSettings.Value;
if (tracerSettings.TracesEnabled)
Expand Down Expand Up @@ -104,6 +104,12 @@ private static NativeCallTargetDefinition[] GetDefinitionsArray()
{
nativeCallTargetDefinitions.Add(new("Microsoft.Extensions.Logging", "Microsoft.Extensions.Logging.LoggingBuilder", ".ctor", ["System.Void", "Microsoft.Extensions.DependencyInjection.IServiceCollection"], 9, 0, 0, 9, 65535, 65535, AssemblyFullName, "OpenTelemetry.AutoInstrumentation.Instrumentations.Logger.LoggingBuilderIntegration"));
}

// NLog
if (logSettings.EnabledInstrumentations.Contains(LogInstrumentation.NLog))
{
nativeCallTargetDefinitions.Add(new("NLog", "NLog.Logger", "Log", ["System.Void", "NLog.LogEventInfo"], 5, 0, 0, 6, 65535, 65535, AssemblyFullName, "OpenTelemetry.AutoInstrumentation.Instrumentations.NLog.Bridge.Integrations.LoggerIntegration"));
}
}

// Metrics
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

using System.Reflection;
using OpenTelemetry.AutoInstrumentation.DuckTyping;
using OpenTelemetry.AutoInstrumentation.Logging;

namespace OpenTelemetry.AutoInstrumentation.Instrumentations.NLog.AutoInjection;

internal static class NLogAutoInjector
{
private static readonly IOtelLogger Logger = OtelLogging.GetLogger();
private static int _attempted;

public static void EnsureConfigured()
{
if (Interlocked.Exchange(ref _attempted, 1) != 0)
{
return;
}

try
{
var nlogLogManager = Type.GetType("NLog.LogManager, NLog");
if (nlogLogManager is null)
{
return;
}

var configurationProperty = nlogLogManager.GetProperty("Configuration", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
if (configurationProperty is null)
{
return;
}

var configuration = configurationProperty.GetValue(null);
if (configuration is null)
{
var configurationType = Type.GetType("NLog.Config.LoggingConfiguration, NLog");
configuration = Activator.CreateInstance(configurationType!);
configurationProperty.SetValue(null, configuration);
}

// Create the OpenTelemetry target instance and wrap it in a duck proxy
var otelTarget = new OpenTelemetryTarget();
var targetType = Type.GetType("NLog.Targets.TargetWithContext, NLog", false);
if (targetType is null)
{
Logger.Warning("NLog auto-injection skipped: TargetWithContext type not found.");
return;
}

var targetProxy = otelTarget.DuckImplement(targetType);

// Add target to configuration
var addTargetMethod = configuration!.GetType().GetMethod("AddTarget", BindingFlags.Instance | BindingFlags.Public);
addTargetMethod?.Invoke(configuration, new object?[] { "otlp", targetProxy });

// Create rule: * -> otlp (minlevel: Trace)
var loggingRuleType = Type.GetType("NLog.Config.LoggingRule, NLog");
var logLevelType = Type.GetType("NLog.LogLevel, NLog");
var traceLevel = logLevelType?.GetProperty("Trace", BindingFlags.Static | BindingFlags.Public)?.GetValue(null);
var rule = Activator.CreateInstance(loggingRuleType!, new object?[] { "*", traceLevel, targetProxy });

var loggingRulesProp = configuration.GetType().GetProperty("LoggingRules", BindingFlags.Instance | BindingFlags.Public);
var rulesList = loggingRulesProp?.GetValue(configuration) as System.Collections.IList;
rulesList?.Add(rule);

// Apply configuration
var reconfigMethod = nlogLogManager.GetMethod("ReconfigExistingLoggers", BindingFlags.Static | BindingFlags.Public);
reconfigMethod?.Invoke(null, null);

Logger.Information("NLog OpenTelemetryTarget auto-injected.");
}
catch (Exception ex)
{
Logger.Warning(ex, "NLog OpenTelemetryTarget auto-injection failed.");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

using System.Diagnostics;
using System.Reflection;
using OpenTelemetry.AutoInstrumentation.CallTarget;
using OpenTelemetry.AutoInstrumentation.Instrumentations.NLog.AutoInjection;
using OpenTelemetry.AutoInstrumentation.Logging;
#if NET
using OpenTelemetry.AutoInstrumentation.Logger;
#endif

namespace OpenTelemetry.AutoInstrumentation.Instrumentations.NLog.Bridge.Integrations;

/// <summary>
/// NLog Logger integration that hooks into the actual logging process.
/// This integration intercepts NLog's Logger.Log method calls to automatically
/// capture log events and forward them to OpenTelemetry when the NLog bridge is enabled.
///
/// The integration targets NLog.Logger.Log method which is the core method called
/// for all logging operations, allowing us to capture events without modifying configuration.
/// </summary>
[InstrumentMethod(
assemblyName: "NLog",
typeName: "NLog.Logger",
methodName: "Log",
returnTypeName: ClrNames.Void,
parameterTypeNames: new[] { "NLog.LogEventInfo" },
Comment on lines +23 to +28
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that you need to revisit the way how you injecting bytecode.

Logger.log(LogEventInfo) is not covering all cases when you are trying to log.

Even when I have checked the testing application calling this method, it is failing.

minimumVersion: "5.0.0",
maximumVersion: "6.*.*",
integrationName: "NLog",
type: InstrumentationType.Log)]
public static class LoggerIntegration
{
#if NET
private static readonly IOtelLogger Logger = OtelLogging.GetLogger();
private static int _warningLogged;
#endif

/// <summary>
/// Intercepts NLog's Logger.Log method calls to capture log events.
/// This method is called before the original Log method executes,
/// allowing us to capture and forward log events to OpenTelemetry.
/// </summary>
/// <typeparam name="TTarget">The type of the logger instance.</typeparam>
/// <param name="instance">The NLog Logger instance.</param>
/// <param name="logEvent">The NLog LogEventInfo being logged.</param>
/// <returns>A CallTargetState (unused in this case).</returns>
internal static CallTargetState OnMethodBegin<TTarget>(TTarget instance, ILoggingEvent logEvent)
{
#if NET
// Check if ILogger bridge has been initialized and warn if so
// This prevents conflicts between different logging bridges
if (LoggerInitializer.IsInitializedAtLeastOnce)
{
if (Interlocked.Exchange(ref _warningLogged, 1) != default)
{
return CallTargetState.GetDefault();
}

Logger.Warning("Disabling NLog bridge due to ILogger bridge initialization.");
return CallTargetState.GetDefault();
}
#endif

// Only process the log event if the NLog bridge is enabled
if (Instrumentation.LogSettings.Value.EnableNLogBridge)
{
// Ensure the OpenTelemetry NLog target is configured (zero-config path)
NLogAutoInjector.EnsureConfigured();

// Inject trace context into NLog GlobalDiagnosticsContext for current destination outputs
TrySetTraceContext(Activity.Current);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this method should be called always, even if the bridge is not enabled.
Bridge means that we are sending NLog logs through OTel infrastructure. Typically OTLP.

You need to set context also when you are injecting in to the typical NLog sinks.

}

// Return default state - we don't need to track anything between begin/end
return CallTargetState.GetDefault();
}

private static void TrySetTraceContext(Activity? activity)
{
try
{
var gdcType = Type.GetType("NLog.GlobalDiagnosticsContext, NLog");
if (gdcType is null)
{
return;
}

var setMethod = gdcType.GetMethod("Set", BindingFlags.Public | BindingFlags.Static, null, [typeof(string), typeof(string)], null);
if (setMethod is null)
{
return;
}

string spanId = activity?.SpanId.ToString() ?? "(null)";
string traceId = activity?.TraceId.ToString() ?? "(null)";
string traceFlags = activity is null ? "(null)" : ((byte)activity.ActivityTraceFlags).ToString("x2");

setMethod.Invoke(null, new object[] { "span_id", spanId });
setMethod.Invoke(null, new object[] { "trace_id", traceId });
setMethod.Invoke(null, new object[] { "trace_flags", traceFlags });
}
catch
{
// best-effort only
}
}
}
Loading
Loading