Skip to content

feat(logs): add NLog integration - #5176

Merged
jamescrosswell merged 18 commits into
mainfrom
feat/logs-nlog
Jul 16, 2026
Merged

feat(logs): add NLog integration#5176
jamescrosswell merged 18 commits into
mainfrom
feat/logs-nlog

Conversation

@Flash0ver

@Flash0ver Flash0ver commented May 1, 2026

Copy link
Copy Markdown
Contributor

closes #5167

Changes

Add NLog integration to Sentry Structured Logging.

Docs

See also

Implementation notes

SentryTarget is an NLog target. Every log NLog routes to it lands in InnerWrite (SentryTarget.cs:331), which now fans out to two independent pipelines:

  1. Classic path (pre-existing): logEvent → Sentry event (if Level >= MinimumEventLevel) and/or breadcrumb.
  2. Structured-logs path (this PR): logEvent → Sentry structured log, gated on EnableLogs.

The two are additive and independently gated — enabling logs doesn't change event/breadcrumb behavior.

The dispatch gate

While the classic path continues to be gated by MinimumEventLevel, a new EnableLogs property has been added to determine whether structured logs are created from NLog logs:

var sentryOptions = hub.GetSentryOptions();
if (sentryOptions?.EnableLogs is true)
    CaptureStructuredLog(hub, sentryOptions, logEvent);

NLog-specifics

CaptureStructuredLog (SentryTarget.Structured.cs) translates NLog's LogEventInfo into a SentryLog.

The NLog-specific concerns:

NLog concept Sentry structured-log field Handling
logEvent.Level (NLog.LogLevel) level ToSentryLogLevel() maps Trace/Debug/Info/Warn/Error/Fatal; Off → null → drop (LogLevelExtensions.cs:33).
logEvent.FormattedMessage (rendered) body (message) The message with arguments already substituted.
logEvent.Message (raw template) template e.g. "User {UserId} logged in".
logEvent.MessageTemplateParameters parameters (→ sentry.message.parameter.*) e.g. UserId . Note that NLog also allows unnamed parammeters to be passed like {}. These have an empty name so GetParameters falls back to the positional index as the name, to avoid collisions.
logEvent.Properties property.* attributes NLog's structured/context properties. GetStructuredLoggingParametersAndAttributes filters out null/blank keys and any key already used as a message parameter (!parameterNames.Contains(key)) so a value isn't emitted twice.
logEvent.LoggerName category.name attribute The source logger identifier.
logEvent.TimeStamp (DateTime) timestamp Wrapped into a DateTimeOffset.

Everything else — Environment, Release, SDK name/version, server.address, user.* — comes from SetDefaultAttributes(options, scope, Sdk), shared with all integrations, plus SetOrigin("auto.log.nlog").

@Flash0ver Flash0ver self-assigned this May 1, 2026
@codecov

codecov Bot commented May 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.00000% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.31%. Comparing base (b006e95) to head (842e133).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
src/Sentry.NLog/LogLevelExtensions.cs 72.72% 2 Missing and 1 partial ⚠️
src/Sentry.NLog/SentryTarget.Structured.cs 94.87% 1 Missing and 1 partial ⚠️
src/Sentry.NLog/SentryTarget.cs 94.11% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5176      +/-   ##
==========================================
+ Coverage   74.23%   77.31%   +3.08%     
==========================================
  Files         509      421      -88     
  Lines       18435    15691    -2744     
  Branches     3610     3125     -485     
==========================================
- Hits        13685    12132    -1553     
+ Misses       3875     2826    -1049     
+ Partials      875      733     -142     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Flash0ver
Flash0ver marked this pull request as ready for review May 18, 2026 11:23
@Flash0ver
Flash0ver requested a review from jamescrosswell as a code owner May 18, 2026 11:23
Comment thread test/Sentry.NLog.Tests/SentryTargetTests.Structured.cs Outdated
Comment thread src/Sentry.NLog/SentryTarget.cs
Comment thread src/Sentry.NLog/SentryTarget.Structured.cs Outdated
Comment thread src/Sentry/SentryLog.Factory.cs Outdated

@jamescrosswell jamescrosswell left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Overall looks pretty good. No blocking comments from me - mostly stylistic.

The main one that needs to be dealt with is the one that the Sentry bot called out since that would be a bug for certain initialisation paths.

The NLog target checked its own SentryNLogOptions.EnableLogs and passed
those options to CaptureStructuredLog. When the SDK is initialized
elsewhere (e.g. ASP.NET Core) with InitializeSdk = false, the target's
own EnableLogs stays at its default, so structured logs were silently
dropped even when the user enabled them on the actual SDK options.

Read the options from the Hub instead, mirroring the Serilog sink.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: James Crosswell <james.crosswell@gmail.com>
@github-actions github-actions Bot added the risk: medium PR risk score: medium label Jul 15, 2026
Comment thread src/Sentry.NLog/SentryTarget.Structured.cs Outdated
jamescrosswell and others added 3 commits July 15, 2026 19:04
Move the Create factory method into SentryLog.cs and drop the separate
SentryLog.Factory.cs file (and its csproj DependentUpon entry). The
method name makes it clear it's a factory, so a dedicated file isn't
warranted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: James Crosswell <james.crosswell@gmail.com>
Use the ShouldContain/ShouldNotContain SentryAttributes extensions
from Sentry.Testing (added in #4936) in place of the verbose
TryGetAttribute(...).Should().BeTrue() pairs. Grant Sentry.NLog.Tests
InternalsVisibleTo access to Sentry.Testing so the internal extensions
are usable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: James Crosswell <james.crosswell@gmail.com>
CaptureStructuredLog never set the logger name on the structured log,
so NLog structured logs lost the main source identifier used to filter
and correlate in Sentry. Set the 'category.name' attribute from
logEvent.LoggerName, mirroring the Microsoft.Extensions.Logging
integration (and the NLog event path which sets Logger from the same
value).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: James Crosswell <james.crosswell@gmail.com>
Comment thread src/Sentry.NLog/SentryTarget.Structured.cs
jamescrosswell and others added 2 commits July 15, 2026 19:23
Unnamed message-template holes (e.g. `{}`) have an empty parameter name.
Stored as-is, the first hole serializes to `sentry.message.parameter.`
(empty trailing segment), and multiple genuinely-empty names collide on
that same attribute key so only the last value survives. Fall back to the
positional index when the name is empty, matching how other integrations
key unnamed parameters.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: James Crosswell <james.crosswell@gmail.com>
Guard only the two TFMs that lack the HashSet<T>(int capacity)
constructor (netstandard2.0 and net462) instead of enumerating every
framework that has it. net462 also lacks the constructor (added in
net472), so a plain '!netstandard2.0' check would not compile there.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: James Crosswell <james.crosswell@gmail.com>
Comment thread src/Sentry.NLog/SentryTarget.cs
jamescrosswell and others added 2 commits July 15, 2026 20:03
main changed SentryLog.SetDefaultAttributes to (options, scope, sdk),
where scope populates user.* and server.address attributes. The NLog
structured path still called (options, sdk), binding SdkVersion into the
Scope? parameter after merging main. Acquire the scope via hub.GetScope()
and pass it explicitly, matching the Serilog and Extensions.Logging
integrations.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: James Crosswell <james.crosswell@gmail.com>
Comment thread src/Sentry.NLog/SentryTarget.Structured.cs Outdated
evgenygunko pushed a commit to evgenygunko/CopyWordsDA that referenced this pull request Jul 27, 2026
> ℹ️ **Note**
> 
> This PR body was truncated due to platform limits.

This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [Sentry.Maui](https://sentry.io/) ([source](https://github.com/getsentry/sentry-dotnet)) | `6.7.0` → `6.8.0` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Sentry.Maui/6.8.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Sentry.Maui/6.7.0/6.8.0?slim=true) |

---

### Release Notes

<details>
<summary>getsentry/sentry-dotnet (Sentry.Maui)</summary>

### [`v6.8.0`](https://github.com/getsentry/sentry-dotnet/blob/HEAD/CHANGELOG.md#680)

[Compare Source](getsentry/sentry-dotnet@6.7.0...6.8.0)

##### Features ✨

##### Logs

- feat(logs): add `log4net` integration by [@&#8203;Flash0ver](https://github.com/Flash0ver) in [#&#8203;5172](getsentry/sentry-dotnet#5172)
- feat(logs): add `NLog` integration by [@&#8203;Flash0ver](https://github.com/Flash0ver) in [#&#8203;5176](getsentry/sentry-dotnet#5176)

##### Other

- feat(serilog): support restrictedToMinimumLevel when configuring Serilog in code by [@&#8203;jamescrosswell](https://github.com/jamescrosswell) in [#&#8203;5181](getsentry/sentry-dotnet#5181)
- Attachments can now be sent with transactions by setting `AddToTransactions` on `SentryAttachment` [#&#8203;5182](getsentry/sentry-dotnet#5182) by [@&#8203;jamescrosswell](https://github.com/jamescrosswell) in [#&#8203;5182](getsentry/sentry-dotnet#5182)
- Added `SentrySdk.RecordTransaction` to record already-completed transactions and spans (e.g. replayed through a proxy) [#&#8203;5333](getsentry/sentry-dotnet#5333) by [@&#8203;jamescrosswell](https://github.com/jamescrosswell) in [#&#8203;5333](getsentry/sentry-dotnet#5333)
- The `Environment` set on the `Scope` now gets synchronized to the native layers (`sentry-cocoa` and `sentry-native`) by [@&#8203;bitsandfoxes](https://github.com/bitsandfoxes) in [#&#8203;5365](getsentry/sentry-dotnet#5365)

##### Fixes 🐛

- fix: `SentrySpanProcessor` no longer leaks spans whose Activity never ends (e.g. aborted requests); the Activity is now held via a `WeakReference` so orphaned spans are pruned once it is garbage-collected. by [@&#8203;Ermabo](https://github.com/Ermabo) in [#&#8203;5393](getsentry/sentry-dotnet#5393)
- The SDK was incorrectly ignoring server rate limits for errors, check-ins, and logs by [@&#8203;jamescrosswell](https://github.com/jamescrosswell) in [#&#8203;5412](getsentry/sentry-dotnet#5412)
- fix: BackpressureMonitor.Dispose() no longer deadlocks on single-threaded targets by [@&#8203;jamescrosswell](https://git...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk: medium PR risk score: medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Sentry Logs support to NLog

2 participants