Skip to content

feat(logs): add log4net integration#5172

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

feat(logs): add log4net integration#5172
jamescrosswell merged 18 commits into
mainfrom
feat/logs-log4net

Conversation

@Flash0ver

@Flash0ver Flash0ver commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

closes #4731

Changes

Add log4net integration to Sentry Structured Logging.

Docs

See also

Implementation Notes

Sentry's log4net support is a log4net appender (SentryAppender : AppenderSkeleton). Every log call flows through Append(LoggingEvent).

Previously each event became either a SentryEvent (error) or a breadcrumb. Now, when options.EnableLogs is on, Append also calls CaptureStructuredLog to emit a Sentry structured log.

log4net-specific stuff

1. Level mapping. log4net doesn't have a clean enum — it has ~18 named Level objects (All, Finest, Verbose, Finer, Trace, Fine, Debug, Info, Notice, Warn, Error, Severe, Critical, Alert, Fatal, Emergency, …) plus user-defined custom levels with arbitrary integer values. ToSentryLogLevel collapses all that into Sentry's 6 levels using >= threshold comparisons on the numeric value, so custom levels land in the nearest bucket and Level.Off drops the log (returns null).

2. Message: rendered only. log4net pre-renders the final string into LoggingEvent.RenderedMessage, which becomes the log Message.

3. No template, no parameters. log4net can't give us the original template of parameters:

const string? template = null;   // cannot get format-string from log4net.Util.SystemStringFormat
var parameters = ImmutableArray<...>.Empty; // cannot get arguments either

log4net's message object is typically a SystemStringFormat that keeps the composite format string ("{0} {1}") and the args array as private fields with no public accessor. This is a limitation of log4net rather than Sentry's integration.

4. Properties → property.* attributes, with log4net-specific filtering. log4net merges event-level properties, ThreadContext.Properties, GlobalContext.Properties, and its own metadata into one bag via GetProperties(). The loop (Structured.cs:20-29) maps each DictionaryEntry to a property.<key> attribute, skipping:

  • empty keys,
  • keys starting with log4net: (log4net's internal entries — HostName, Identity, etc.),
  • GUID-shaped keys (log4net's transient/internal correlation entries).

This property.<key> convention matches Serilog, not MEL. (MEL doesn't have an ambient property bag — it emits parameters plus category.name/event.id/event.name.)

@Flash0ver Flash0ver self-assigned this Apr 29, 2026
@codecov

codecov Bot commented May 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.82353% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.61%. Comparing base (b006e95) to head (4236d5f).
⚠️ Report is 9 commits behind head on main.

Files with missing lines Patch % Lines
src/Sentry.Log4Net/SentryAppender.cs 80.76% 3 Missing and 2 partials ⚠️
src/Sentry.Log4Net/LevelMapping.cs 75.00% 1 Missing and 2 partials ⚠️
src/Sentry.Log4Net/SentryAppender.Structured.cs 86.36% 1 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5172      +/-   ##
==========================================
+ Coverage   74.23%   74.61%   +0.37%     
==========================================
  Files         509      512       +3     
  Lines       18435    18540     +105     
  Branches     3610     3617       +7     
==========================================
+ Hits        13685    13833     +148     
+ Misses       3875     3834      -41     
+ Partials      875      873       -2     

☔ 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:19
@Flash0ver
Flash0ver requested a review from jamescrosswell as a code owner May 18, 2026 11:19
Comment thread test/Sentry.Log4Net.Tests/SentryAppenderTests.Structured.cs Outdated
Comment thread src/Sentry.Log4Net/SentryAppender.Structured.cs Outdated
Comment thread test/Sentry.Log4Net.Tests/SentryAppenderTests.Structured.cs
@jamescrosswell

Copy link
Copy Markdown
Collaborator

@Flash0ver I put this back in draft state until CI passes. Looks like there is some warden feedback worth checking as well.

@jamescrosswell
jamescrosswell marked this pull request as draft June 23, 2026 01:24
jamescrosswell and others added 3 commits July 15, 2026 19:05
…n tests

- Fall back to string.Empty when RenderedMessage is null, matching the
  existing non-structured path (review r3258513795).
- Clear ThreadContext.Properties in test teardown so thread-static
  entries don't leak into subsequent tests (review r3258530109).
- Adapt CaptureStructuredLog to the SetDefaultAttributes(options, scope, sdk)
  signature introduced on main, passing the active scope for enrichment.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… extensions

Use the SentryAttributes.ShouldContain extensions added in #4936 instead
of the verbose TryGetAttribute/Should().Be pairs (review r3258494145).
Grants Sentry.Log4Net.Tests access to Sentry.Testing internals. The
collection assertion keeps BeEquivalentTo since the extension compares
by value equality.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jamescrosswell
jamescrosswell marked this pull request as ready for review July 15, 2026 22:55
@github-actions github-actions Bot added the risk: medium PR risk score: medium label Jul 15, 2026
Comment thread src/Sentry.Log4Net/LevelMapping.cs
Comment thread samples/Sentry.Samples.Log4Net/Program.cs Outdated
jamescrosswell and others added 2 commits July 16, 2026 13:36
Brings the log4net structured-log path in line with the MEL integration,
which sets category.name from the logger/category name. Previously the
log4net logger name was only surfaced on the legacy SentryEvent.Logger.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The sample only targets net481, so the #if NETFRAMEWORK conditional is
always true. Remove it and keep the SetPrincipalPolicy call unconditionally.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread src/Sentry/SentryLog.Factory.cs Outdated
… file

Move the Create factory method into SentryLog.cs, drop the now-unneeded
partial modifier, and remove the SentryLog.Factory.cs DependentUpon entry
from the csproj. A single method doesn't warrant its own file.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread src/Sentry.Log4Net/SentryAppender.Structured.cs Outdated
Invert the level check into a pattern-matched guard clause that returns
early, removing a level of nesting and folding the null check into the
assignment.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread src/Sentry.Log4Net/SentryAppender.Structured.cs
Matches the sibling GetLoggingEventProperties helper so structured logging
doesn't rely on log4net's GetProperties() never returning null.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread src/Sentry.Log4Net/SentryAppender.cs

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 13260a4. Configure here.

Comment thread src/Sentry.Log4Net/SentryAppender.cs
…umEventLevel

Two structured-logging fixes:

- Capture structured logs for every event again. The Append refactor in
  13260a4 moved the EnableLogs block after the breadcrumb early-return, so
  events below MinimumEventLevel no longer produced structured logs. Move the
  capture ahead of the breadcrumb/event branching, restoring the original
  behavior (covered by DoAppend_StructuredLoggingWithoutException_LeavesBreadcrumb).

- Apply the appender's Environment and SendIdentity settings to structured
  logs, overriding the scope/options defaults, to match the SentryEvent path.
  These are per-appender, opt-in settings, so SendIdentity mirrors the event
  path and is not additionally gated on SendDefaultPii.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
jamescrosswell added a commit to getsentry/sentry-docs that referenced this pull request Jul 16, 2026
… logs

The SentryAppender's Environment and SendIdentity settings now apply to
structured logs as well as events (getsentry/sentry-dotnet#5172).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread src/Sentry.Log4Net/SentryAppender.cs Outdated
@jamescrosswell
jamescrosswell merged commit ea85ec5 into main Jul 16, 2026
34 of 36 checks passed
@jamescrosswell
jamescrosswell deleted the feat/logs-log4net branch July 16, 2026 07:54
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 Log4Net

3 participants