-
Notifications
You must be signed in to change notification settings - Fork 123
Add NLog instrumentation for OpenTelemetry .NET Auto-Instrumentation #4371
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 16 commits
12b3643
dd0eee6
1b5ee77
489fa3b
638a6aa
206be87
799ef71
4401757
0fcf644
4ffa40a
f6cd7d5
665fb09
3a79c85
f6957e6
b078f85
4a0d5a3
bf571f5
16b004e
e650142
c41f73c
e66da6a
3d5aff8
ad1c62f
54dd6da
0602818
903ab69
4702a68
951c355
29d4251
29bde76
911fbd8
60311e1
d912c7e
c6b7bde
fad8ea4
e3d9274
e95e972
62f933d
33546ee
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -246,6 +246,9 @@ EndProject | |
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SdkVersionAnalyzer", "tools\SdkVersionAnalyzer\SdkVersionAnalyzer.csproj", "{C75FA076-D460-414B-97F7-6F8D0E85AE74}" | ||
| EndProject | ||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestApplication.Log4NetBridge", "test\test-applications\integrations\TestApplication.Log4NetBridge\TestApplication.Log4NetBridge.csproj", "{926B7C03-42C2-4192-94A7-CD0B1C693279}" | ||
| 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}") = "OpenTelemetry.AutoInstrumentation.NLogTarget", "src\OpenTelemetry.AutoInstrumentation.NLogTarget\OpenTelemetry.AutoInstrumentation.NLogTarget.csproj", "{3C7A3F7B-77E5-4C55-9B2D-1A4A9E7B1D33}" | ||
|
||
| EndProject | ||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestApplication.SelectiveSampler", "test\test-applications\integrations\TestApplication.SelectiveSampler\TestApplication.SelectiveSampler.csproj", "{FD1A1ABD-6A48-4E94-B5F7-2081AFCD1BBB}" | ||
| EndProject | ||
|
|
@@ -1535,6 +1538,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 | ||
|
|
@@ -1657,6 +1676,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 | ||
|
|
||
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Even when I have checked the testing application calling this method, it is failing. |
||
| minimumVersion: "4.0.0", | ||
| maximumVersion: "6.*.*", | ||
| integrationName: "NLog", | ||
| type: InstrumentationType.Log)] | ||
| public static class LoggerIntegration | ||
Kielek marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| #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); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. 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, new Type[] { typeof(string), typeof(string) }); | ||
| 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 | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.