Skip to content

Improvements - #14

Merged
tjidde-nl merged 36 commits into
mainfrom
improvements
Oct 4, 2026
Merged

tjidde-nl merged 36 commits into
mainfrom
improvements

Conversation

@tjidde-nl

Copy link
Copy Markdown
Owner

No description provided.

tjidde-nl and others added 30 commits October 2, 2026 17:56
Inventory of the public surface per namespace, proposed 2.0 layout
(registration extensions in Microsoft.Extensions.Logging, core types in
Tjidde.Logging), migration path and user migration guide.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds assets/icon.svg (source) and a 128x128 assets/icon.png, and packs
icon.png as the NuGet PackageIcon.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The test project only targeted net10.0, so the library builds for
net7.0, net8.0 and net9.0 (each with its own Microsoft.Extensions.*
major) were never tested.

- Multi-target the test project net7.0;net8.0;net9.0;net10.0 with
  per-TFM Microsoft.Extensions.* versions matching the library.
- Install the 7.0, 8.0, 9.0 and 10.0 runtimes in ci.yml and publish.yml
  so dotnet test runs every TFM.
- ci.yml: write a .trx per TFM and upload them as the test-results
  artifact (also when tests fail).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- .github/dependabot.yml: weekly NuGet updates (grouped:
  Microsoft.Extensions.* and test packages) and weekly GitHub Actions
  updates. Semver-major updates of Microsoft.Extensions.* are ignored
  because each TFM deliberately uses its own major.
- Test packages: xunit 2.6.6 -> 2.9.3 (last v2). net8.0+:
  Microsoft.NET.Test.Sdk 17.8.0 -> 18.10.1 and
  xunit.runner.visualstudio 2.5.6 -> 4.0.0. net7.0 stays on the last
  versions that support it: Microsoft.NET.Test.Sdk 17.13.0 and
  xunit.runner.visualstudio 3.0.2; Dependabot ignores the ranges that
  would drop net7.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PackageReleaseNotes used to contain the whole CHANGELOG.md. A target that
runs before GenerateNuspec now extracts only the "## [<PackageVersion>]"
section (up to the next "## [" heading) and appends a link to the full
changelog. If the section is missing, only the link is used and the build
does not fail. Works with -p:Version=... as passed by CI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LogMetrics used the custom level (LogLevel)10, which also reached other
providers; the Microsoft console formatters throw ArgumentOutOfRangeException
for it, so LogMetrics crashed as soon as AddConsole() was registered.

LogMetrics now logs at LogLevel.Information with MetricsEventId
(Id 10000, Name "Metrics"). TjiddeLogger shows entries with that event name,
and the old (LogLevel)10, as METRICS in magenta and never drops them through
its own minimum level. MetricsLoggerExtensions.Metrics is marked [Obsolete].

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TjiddeLogger.Log could throw from JSON serialization (cycles, unsupported
types, throwing getters), from ToString() or enumeration of logged values,
from a custom IExceptionFormatter, from the formatter delegate and from the
OpenTelemetry export.

- Serialize each JSON property value separately with IgnoreCycles and
  MaxDepth 32; on failure fall back to ToString() or
  "[unserializable: TypeName]".
- Fall back to the message template when rendering the message throws, and
  make MessageTemplateRenderer.FormatValue safe.
- Fall back to exception type and message when the exception formatter fails.
- Skip scopes that throw and ignore OpenTelemetry export failures.
- As a last resort, write a minimal line with level, category, message
  and a note that rendering failed (without unmasked values).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The provider now subscribes to IOptionsMonitor.OnChange and keeps options,
masker, exception formatter and fallback ActivitySource in one immutable
TjiddeLoggerConfiguration that is swapped atomically. Loggers read the
current configuration once per call, so they pick up changes right away
and never see a mixed set. The old ActivitySource is disposed on change,
and the change registration on provider dispose.

AddTjiddeLogger(IConfiguration/IConfigurationSection) registers an
IOptionsChangeTokenSource so a configuration reload rebinds the options.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Loggers already get their masker from the shared configuration object, so
all categories use one SensitiveDataMasker and one set of regexes. Make
that masker safe for concurrent use while MaskedKeysContext changes: the
runtime key set is built from a copy, compared by content unless the
accessor hands out immutable snapshots, and published with
CompareExchange so a concurrent rebuild never overwrites a newer set.
This also fixes custom accessors that return one mutable collection.

IsEnabled now caches the resolved minimum level per logger together with
the configuration it came from, so it is recomputed only after an
options change.

Tests cover concurrent logging while keys are added and removed, the
shared masker across loggers and options changes, and the mutable
accessor case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TjiddeLogger used DateTime.Now. Timestamps now come from System.TimeProvider:
the one registered in DI, or TimeProvider.System. TjiddeLoggerProvider gets a
constructor overload that takes a TimeProvider; the existing constructor keeps
working. AddTjiddeLogger registers the provider through a factory, so the
container never has to choose between the constructors. net7.0 references
Microsoft.Bcl.TimeProvider.

New option UseUtcTimestamp (default false) writes UTC instead of local time;
JSON @timestamp is ISO-8601 with the offset. A throwing TimeProvider falls
back to the system clock, so logging still never throws.

Tests use FakeTimeProvider (Microsoft.Extensions.TimeProvider.Testing, 8.x for
net7.0/net8.0).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Tjidde:LogLevel

TjiddeLoggerOptions.MinimumLevel and CategoryMinimumLevels duplicate the
standard Microsoft.Extensions.Logging filters (Logging:LogLevel and, through
[ProviderAlias("Tjidde")], Logging:Tjidde:LogLevel). Mark them [Obsolete]
with a pointer to Logging:Tjidde:LogLevel; they will be removed in 2.0. They
keep working and keep binding from configuration; the library suppresses
CS0618 only where it still applies them.

New tests pin down how both filters combine: the framework filters first,
then Tjidde's own filter, so the stricter level wins (also for metrics,
which only the framework filter can drop). Existing tests of the legacy
options suppress CS0618 locally.

README and the wikis recommend Logging:Tjidde:LogLevel with an
appsettings.json example. AI-INTEGRATION.md no longer claims that options
changes need a restart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaskedKeysStore is a non-static, thread-safe IMaskedKeysAccessor that
hands out immutable snapshots, so the masker detects changes by
reference. UseIsolatedMaskedKeys() registers one store per host as the
accessor. MaskedKeysContext stays the static default and now wraps a
MaskedKeysStore. Docs recommend accessors and DI for testable code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Entries can now go to extra destinations next to the console. ILogSink
receives an immutable, masked TjiddeLogEntry (timestamp, level, event id,
category, class, method, customer, message, formatted exception, rendered
line, output format, IsMetrics). Sinks are registered with AddTjiddeSink,
AddTjiddeInMemorySink or plain DI, called synchronously, and isolated: a
throwing sink never breaks logging, the console or other sinks, and
entries logged from inside a sink are not dispatched again.
TjiddeLoggerOptions.WriteToConsole (default true) turns the console off,
also through an options reload.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Blazor, Avalonia and terminal samples drop their own in-memory logger
providers and read the built-in InMemoryLogSink. Metrics entries are
logged with LogMetrics and recognized by their event id instead of the
obsolete Metrics level. The terminal sample turns the console off so
console output no longer draws over its UI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main's squash commit fe98912 has the same tree as improvements@dd7dcb0;
conflicts were resolved by taking the improvements version, so the
merged tree is identical to improvements@325a711.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
benchmarks/Tjidde.Logging.Benchmarks (net10.0, not packable, in the
solution) measures one log call through TjiddeLogger with the console
off: a simple message, structured properties, a sensitive property,
active scopes, a no-op sink and the IsEnabled == false path, each for
Text and Json output. benchmarks/README.md explains how to run them and
records the baseline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaskMessage ran two regular expressions per sensitive key over every
message, property and scope value. Every match contains its key, so a
key that does not occur in the text (OrdinalIgnoreCase) is skipped; on
.NET 9+ one SearchValues<string> scan rules out all keys at once. The
check is only used for ASCII text and ASCII keys, where it agrees with
the expressions' IgnoreCase matching; anything else still runs them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Enable package validation against the published 1.0.1 package
  (PackageValidationBaselineVersion 1.0.1). dotnet pack reports no
  breaking changes; strict mode shows only additions, identical for
  net7.0, net8.0, net9.0 and net10.0.
- Add Microsoft.CodeAnalysis.PublicApiAnalyzers 5.6.0 (PrivateAssets=all).
  PublicAPI.Shipped.txt holds exactly the 1.0.1 API (generated from the
  1.0.1 sources, which strict package validation confirms match the
  nuget.org package); PublicAPI.Unshipped.txt holds everything added
  since. The build has no RS0016/RS0017 warnings.
- Wikis (en/nl): after a release move Unshipped to Shipped and raise
  PackageValidationBaselineVersion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Create the masked-key list only when a property is masked, and size
  the property list from the state's count.
- Read the scopes in one pass for the MethodName scope and the scope
  texts, with a static callback and a per-thread reader instead of two
  closures and LINQ. A scope whose enumeration throws while reading the
  method name still fails the entry, as before.
- Write the text line straight into one reused StringBuilder per thread
  instead of building date, time, method and customer strings first.

The output is unchanged. A new test logs from a scope value's ToString
to check that a nested entry does not disturb the outer one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	CHANGELOG.md
#	docs/AI-INTEGRATION.md
#	docs/wiki-en.md
#	docs/wiki-nl.md
#	src/Tjidde.Logging/Extensions/TjiddeLoggingBuilderExtensions.cs
#	src/Tjidde.Logging/Logging/TjiddeLogger.cs
#	src/Tjidde.Logging/Logging/TjiddeLoggerProvider.cs
#	src/Tjidde.Logging/Masking/SensitiveDataMasker.cs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Updates Avalonia, Avalonia.Desktop, Avalonia.Themes.Fluent and
Avalonia.Fonts.Inter together. Avalonia 12 compiles bindings by default,
so MainWindow declares x:DataType="vm:MainViewModel"; the obsolete
TextBox.Watermark is replaced by PlaceholderText. The new Avalonia brings
Tmds.DBus.Protocol 0.94.1, which resolves the NU1903 warning for 0.21.2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
BuildJsonLogLine built a dictionary for the entry and one for the
properties and serialized them polymorphically. It now writes the same
fields with a Utf8JsonWriter into a reused per-thread buffer, with the
serializer's encoder and maximum depth. Repeated property names keep
their first position and last value, as the dictionary did. String
(without surrogates), int, long and bool values are written directly
instead of through SerializeToElement; other values still are. If the
writer throws, the previous serializer path runs, so the output and the
failure behavior stay the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hmarks

- samples and benchmarks (net10.0): 10.0.5 -> 10.0.12
- tests: net10.0 group 10.0.5 -> 10.0.12, net9.0 group 9.0.14 -> 9.0.20;
  net7.0 and net8.0 stay on the library's minimum versions

The library keeps its deliberately low per-TFM minimum versions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dependabot takes the highest Microsoft.Extensions.* version in a file as
the current one and rewrites every occurrence, which collapsed the per-TFM
ItemGroups in the library and tests. Microsoft.Extensions.* is now ignored
for /src/* and /tests/* (updated by hand per TFM); /samples/* and
/benchmarks/* get grouped microsoft-extensions and avalonia updates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tjidde-nl and others added 6 commits October 3, 2026 16:06
Co-Authored-By: Codex <codex@openai.com>
Co-Authored-By: Codex <codex@openai.com>
Co-Authored-By: Codex <codex@openai.com>
Co-Authored-By: Codex <codex@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tjidde-nl
tjidde-nl merged commit 45b6386 into main Oct 4, 2026
4 checks passed
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.

1 participant