Skip to content

Add FileAppend feature to the Logging module - #1

Merged
NikolayPianikov merged 1 commit into
DevTeam:devfrom
FreeAndNil:feature/logging-fair-comparison-and-scenarios
Aug 4, 2026
Merged

Add FileAppend feature to the Logging module#1
NikolayPianikov merged 1 commit into
DevTeam:devfrom
FreeAndNil:feature/logging-fair-comparison-and-scenarios

Conversation

@FreeAndNil

Copy link
Copy Markdown
Contributor

align the log4net arms with the work the other libraries are actually charged for

The log4net arms of two existing features were doing strictly more work than
their counterparts, so the measurements were not comparing like with like.

Log4NetFixture now sets Fix = FixFlags.Partial on the
BufferingForwardingAppender. log4net defaults to FixFlags.All, which
captures caller location (a stack walk) and the Windows identity (a local
security authority lookup) for every buffered event. The counterparts in this
feature - NLog's AsyncTargetWrapper and Serilog's WriteTo.Async - capture
neither. Measured in isolation on one machine, the two flags cost +6.9 us and
+81.6 us per event respectively, against ~325 ns for everything Partial
fixes; the flag choice, not the buffering pipeline, was almost the whole
result. Partial is the set log4net's own FixFlags documentation recommends
for performance and still fixes everything this feature validates: message,
level, exception and properties.

03_StructuredProperties now uses ThreadContext instead of
LogicalThreadContext. The other arms of that feature pass structured
parameters straight to the logger and use no ambient context at all, so the
async-flow-safe variant charged log4net for propagation nobody else paid for.
05_ScopeOrContext is where async-safe context belongs and deliberately keeps
LogicalThreadContext, matching Serilog's LogContext.

The new FileAppend feature covers the gap that no scenario in the module
touches a file. Each arm appends one event to an already open handle with
buffering enabled and no flush per event, through an equivalent plain-text
layout, and delivery is validated by reading the file back off disk rather
than from an in-memory sink. log4net, NLog, Serilog and ZLogger are supported;
Microsoft.Extensions.Logging is Unsupported because the core package defines
no file provider.

…rms with the

work the other libraries are actually charged for

The log4net arms of two existing features were doing strictly more work than
their counterparts, so the measurements were not comparing like with like.

`Log4NetFixture` now sets `Fix = FixFlags.Partial` on the
`BufferingForwardingAppender`. log4net defaults to `FixFlags.All`, which
captures caller location (a stack walk) and the Windows identity (a local
security authority lookup) for every buffered event. The counterparts in this
feature - NLog's `AsyncTargetWrapper` and Serilog's `WriteTo.Async` - capture
neither. Measured in isolation on one machine, the two flags cost +6.9 us and
+81.6 us per event respectively, against ~325 ns for everything `Partial`
fixes; the flag choice, not the buffering pipeline, was almost the whole
result. `Partial` is the set log4net's own `FixFlags` documentation recommends
for performance and still fixes everything te,
level, exception and properties.

`03_StructuredProperties` now uses `ThreadContext` instead of
`LogicalThreadContext`. The other arms of th
parameters straight to the logger and use no ambient context at all, so the
async-flow-safe variant charged log4net for for.
`05_ScopeOrContext` is where async-safe context belongs and deliberately keeps
`LogicalThreadContext`, matching Serilog's `

The new `FileAppend` feature covers the gap e
touches a file. Each arm appends one event to an already open handle with
buffering enabled and no flush per event, tht
layout, and delivery is validated by reading the file back off disk rather
than from an in-memory sink. log4net, NLog, rted;
Microsoft.Extensions.Logging is `Unsupported` because the core package defines
no file provider.
@NikolayPianikov
NikolayPianikov changed the base branch from master to dev August 4, 2026 06:02
@NikolayPianikov
NikolayPianikov merged commit ae8a9ef into DevTeam:dev Aug 4, 2026
@NikolayPianikov

Copy link
Copy Markdown
Member

@FreeAndNil thanks for the PR and especially for the detailed explanation!

The FixFlags.Partial change, switching to ThreadContext, equivalent layouts, and result validation were all well done.

The main issue was the measurement boundary: log4net, NLog, and Serilog formatted synchronously, while ZLogger only queued the event for background processing. Filesystem and OS caching also added noise without measuring an actual flush.

I preserved your commit and merged the PR, then locally replaced FileAppend with FormattedOutput. All four libraries now synchronously format into a bounded in-memory custom sink, with no disk access, background queue, or growing memory usage. I also removed Serilog.Sinks.File and updated validation.

Full validation and the smoke run of all 38 logging benchmarks pass. Thanks again — your fairness fixes remain and provided a great foundation for the new scenario.

@FreeAndNil

Copy link
Copy Markdown
Contributor Author

@NikolayPianikov Thanks.
I also created apache/logging-log4net#304 for further improvements on our side.
Those will land in log4net 3.4.0.

@FreeAndNil
FreeAndNil deleted the feature/logging-fair-comparison-and-scenarios branch August 4, 2026 06:44
@NikolayPianikov

Copy link
Copy Markdown
Member

The changes are already in version 1.0.2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants