Skip to content

9.0.0 Release #256

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

Merged
merged 57 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
322c5f2
SerilogLoggerScope add support for ValueTuple<string, object?> state.…
jimbojim1997 Nov 8, 2023
d8637c2
Update readme with exaple of BeginScope with a ValueTuple.
jimbojim1997 Nov 12, 2023
85172ad
Changed copyright header from .NET Foundation to Serilog Contributors.
jimbojim1997 Nov 14, 2023
14c2965
Update Serilog.Extensions.Logging.csproj
nblumhardt Nov 14, 2023
7a2fa1d
Minor optimisations
jods4 Feb 12, 2024
76a05ee
Revert String.StartsWith, not present in old .net fx
jods4 Feb 12, 2024
8d20ade
Merge pull request #242 from jods4/patch-1
nblumhardt Feb 14, 2024
099a66a
Document {SourceContext} for MEL log category
DavidHopkinsFbr Feb 21, 2024
7dc58c3
Merge pull request #244 from DavidHopkinsFbr/mention-sourcecontext-in…
nblumhardt Feb 22, 2024
3e5c53c
Add experiement project demonstrating that tags and baggage do not ap…
Mar 13, 2024
2395852
Implement ISupportExternalScope in SerilogLoggerProvider
Mar 13, 2024
e5d72ac
Add test of external scope provider support
Mar 13, 2024
fe4a9b9
Rename proof of concept sample app
Mar 13, 2024
65eabbf
Use all ActivityTrackingOptions in sample app
Mar 13, 2024
49fa3e9
Rename sample to SampleWithExternalScope
Mar 14, 2024
3b56cb4
Merge pull request #246 from david-obee/support-external-scopes
nblumhardt Mar 14, 2024
1e1479e
Merge pull request #232 from jimbojim1997/beginscope-valuetuple
nblumhardt Mar 14, 2024
6fd2e72
Publishing key update
nblumhardt Mar 14, 2024
62b775b
Move AddProperty() out to avoid a closure allocation
nblumhardt Mar 15, 2024
f845237
It's not necessary to handle {OriginalFormat} when TState is Dictiona…
nblumhardt Mar 15, 2024
5864a5d
Use LogEvent.UnstableAssembleFromParts() to avoid a dictionary alloc
nblumhardt Mar 15, 2024
1c77935
Update BenchmarkDotNet
nblumhardt Mar 15, 2024
03fcf95
"Improve LogEventBenchmark benchmarks":
nblumhardt Mar 15, 2024
bcc5021
Ordinal string comparisons
nblumhardt Mar 18, 2024
ccae6ff
Remove some accidental usages of throwing Dictionary<,>.Add()
nblumhardt Mar 18, 2024
7bdddec
Roll back EventId comparison change; the overridden != operator does …
nblumhardt Mar 18, 2024
adf275f
Tighter level mapping, though for invalid cases only
nblumhardt Mar 18, 2024
504fc5c
Support for ITuple
sungam3r Mar 25, 2024
3c32997
Merge pull request #249 from sungam3r/tuple
nblumhardt Apr 2, 2024
849cb02
Make the enrichment from scope state static
pavel-faltynek May 22, 2024
8bcc366
For external scope provider use enrichment with no side effects
pavel-faltynek May 22, 2024
1e9f655
Merge pull request #252 from pavel-faltynek/remove-logger-scope-side-…
nblumhardt May 22, 2024
3dd50b2
Optimization: used StartsWith(char) instead of StartsWith(string) whe…
epeshk Jul 17, 2024
51770bd
Merge branch 'dev' into serilog-4x
nblumhardt Jul 23, 2024
7fc50b0
Merge pull request #255 from epeshk/startswith-char
nblumhardt Jul 23, 2024
10afe06
Merge branch 'dev' into serilog-4x
nblumhardt Jul 23, 2024
d0dbbb1
Extend LogEventBenchmark with ILogger.Log that includes EventId.
andrii-babanin-sym Oct 31, 2024
0e5fe4c
Implement EventId to LogEventProperty cache.
andrii-babanin-sym Oct 31, 2024
4eb8b3c
Use EventIdPropertyCache in SerilogLogger; Create EventIdPropertyCach…
andrii-babanin-sym Oct 31, 2024
919b919
Compare both Id and Name for equity.
andrii-babanin-sym Oct 31, 2024
b68c884
Remove comment.
andrii-babanin-sym Oct 31, 2024
3731bcb
Define EventKey as readonly record struct.
andrii-babanin-sym Nov 1, 2024
59165d5
Change copyright text.
andrii-babanin-sym Nov 1, 2024
cb77647
Shared static EventIdPropertyCache cache; Use ConcurrentDictionary as…
andrii-babanin-sym Nov 1, 2024
29b94f3
Revert sample code changes.
andrii-babanin-sym Nov 1, 2024
4f3305a
Add functional tests of AddSerilog() extension method.
agehrke Nov 15, 2024
d5eb130
Make SerilogLoggerProvider implement IAsyncDisposable on .NET 6 or la…
agehrke Nov 15, 2024
2bc0ea5
Make EventIdPropertyCache non-static; Create unit tests.
andrii-babanin-sym Nov 15, 2024
5e1527d
Merge pull request #260 from AndreReise/improve-event-id-cache
nblumhardt Nov 15, 2024
03a235a
Address naming
agehrke Nov 16, 2024
ac351f2
Define FEATURE_ASYNCDISPOSABLE and use
agehrke Nov 16, 2024
0584a5b
Merge pull request #262 from agehrke/asyncdispose
nblumhardt Nov 16, 2024
a35b35b
Update to .NET 9, Actions build, revise targets, seal public types no…
nblumhardt Nov 24, 2024
44d0ec9
Ignore .DS_Store
nblumhardt Nov 24, 2024
1549fe5
Merge pull request #264 from nblumhardt/dotnet-9
nblumhardt Nov 26, 2024
e47a264
Merge branch 'dev' of https://github.com/serilog/serilog-extensions-l…
nblumhardt Dec 6, 2024
92751dd
Merge pull request #247 from nblumhardt/serilog-4x
nblumhardt Dec 6, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 64 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,41 +28,41 @@ using Serilog;

public class Startup
{
public Startup(IHostingEnvironment env)
{
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.WriteTo.Console()
.CreateLogger();

// Other startup code
public Startup(IHostingEnvironment env)
{
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.WriteTo.Console()
.CreateLogger();

// Other startup code
```

**Finally, for .NET Core 2.0+**, in your `Startup` class's `Configure()` method, remove the existing logger configuration entries and
call `AddSerilog()` on the provided `loggingBuilder`.

```csharp
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging(loggingBuilder =>
loggingBuilder.AddSerilog(dispose: true));
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging(loggingBuilder =>
loggingBuilder.AddSerilog(dispose: true));

// Other services ...
}
// Other services ...
}
```

**For .NET Core 1.0 or 1.1**, in your `Startup` class's `Configure()` method, remove the existing logger configuration entries and call `AddSerilog()` on the provided `loggerFactory`.

```
public void Configure(IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerfactory,
IApplicationLifetime appLifetime)
{
loggerfactory.AddSerilog();

// Ensure any buffered events are sent at shutdown
appLifetime.ApplicationStopped.Register(Log.CloseAndFlush);
public void Configure(IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerfactory,
IApplicationLifetime appLifetime)
{
loggerfactory.AddSerilog();

// Ensure any buffered events are sent at shutdown
appLifetime.ApplicationStopped.Register(Log.CloseAndFlush);
```

That's it! With the level bumped up a little you should see log output like:
Expand All @@ -80,6 +80,19 @@ That's it! With the level bumped up a little you should see log output like:
[22:14:45.741 DBG] Handled. Status code: 304 File: /css/site.css
```

### Including the log category in text-format sink output
All _Microsoft.Extensions.Logging.ILogger_ implementations are created with a specified [_log category_](https://learn.microsoft.com/en-us/dotnet/core/extensions/logging?tabs=command-line#log-category) string, which is then attached as structured data to each log message created by that `ILogger` instance. Typically, the log category is the fully-qualified name of the class generating the log messages. This convention is implemented by the [`ILogger<TCategoryName>`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.ilogger-1) interface, which is commonly used as an injected dependency in frameworks that use _Microsoft.Extensions.Logging_.

_Serilog.Extensions.Logging_ captures the `ILogger`'s log category, but it's not included in the default output templates for text-based sinks, such as [Console](https://github.com/serilog/serilog-sinks-console), [File](https://github.com/serilog/serilog-sinks-file) and [Debug](https://github.com/serilog/serilog-sinks-debug).

To include the log category in the final written messages, add the `{SourceContext}` named hole to a customised `outputTemplate` parameter value when configuring the relevant sink(s). For example:
```csharp
.WriteTo.Console(
outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {SourceContext}: {Message:lj}{NewLine}{Exception}")
.WriteTo.File("log.txt",
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {SourceContext}: {Message:lj}{NewLine}{Exception}")
```

### Notes on Log Scopes

_Microsoft.Extensions.Logging_ provides the `BeginScope` API, which can be used to add arbitrary properties to log events within a certain region of code. The API comes in two forms:
Expand All @@ -90,7 +103,8 @@ _Microsoft.Extensions.Logging_ provides the `BeginScope` API, which can be used
Using the extension method will add a `Scope` property to your log events. This is most useful for adding simple "scope strings" to your events, as in the following code:

```csharp
using (_logger.BeginScope("Transaction")) {
using (_logger.BeginScope("Transaction"))
{
_logger.LogInformation("Beginning...");
_logger.LogInformation("Completed in {DurationMs}ms...", 30);
}
Expand All @@ -102,8 +116,9 @@ using (_logger.BeginScope("Transaction")) {
If you simply want to add a "bag" of additional properties to your log events, however, this extension method approach can be overly verbose. For example, to add `TransactionId` and `ResponseJson` properties to your log events, you would have to do something like the following:

```csharp
// WRONG! Prefer the dictionary approach below instead
using (_logger.BeginScope("TransactionId: {TransactionId}, ResponseJson: {ResponseJson}", 12345, jsonString)) {
// WRONG! Prefer the dictionary or value tuple approach below instead
using (_logger.BeginScope("TransactionId: {TransactionId}, ResponseJson: {ResponseJson}", 12345, jsonString))
{
_logger.LogInformation("Completed in {DurationMs}ms...", 30);
}
// Example JSON output:
Expand All @@ -125,11 +140,13 @@ Moreover, the template string within `BeginScope` is rather arbitrary when all y
A far better alternative is to use the `BeginScope<TState>(TState state)` method. If you provide any `IEnumerable<KeyValuePair<string, object>>` to this method, then Serilog will output the key/value pairs as structured properties _without_ the `Scope` property, as in this example:

```csharp
var scopeProps = new Dictionary<string, object> {
var scopeProps = new Dictionary<string, object>
{
{ "TransactionId", 12345 },
{ "ResponseJson", jsonString },
};
using (_logger.BeginScope(scopeProps) {
using (_logger.BeginScope(scopeProps)
{
_logger.LogInformation("Transaction completed in {DurationMs}ms...", 30);
}
// Example JSON output:
Expand All @@ -144,6 +161,25 @@ using (_logger.BeginScope(scopeProps) {
// }
```

Alternatively provide a `ValueTuple<string, object?>` to this method, where `Item1` is the property name and `Item2` is the property value.
Note that `T2` _must_ be `object?` if your target platform is net462 or netstandard2.0.

```csharp
using (_logger.BeginScope(("TransactionId", 12345))
{
_logger.LogInformation("Transaction completed in {DurationMs}ms...", 30);
}
// Example JSON output:
// {
// "@t":"2020-10-29T19:05:56.4176816Z",
// "@m":"Completed in 30ms...",
// "@i":"51812baa",
// "DurationMs":30,
// "SourceContext":"SomeNamespace.SomeService",
// "TransactionId": 12345
// }
```

### Versioning

This package tracks the versioning and target framework support of its [_Microsoft.Extensions.Logging_](https://nuget.org/packages/Microsoft.Extensions.Logging) dependency.
Expand Down
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ artifacts:
deploy:
- provider: NuGet
api_key:
secure: EN9f+XXE3fW+ebL4wxrIbafdtbNvRfddBN8UUixvctYh4qMBHzr1JdnM83QsM1zo
secure: H96ajkMxwIafhF2vrr+UAUS10bFcAL/1wc3iphidRiYi9WoTc2i8shTLtF+75ODb
skip_symbols: true
on:
branch: /^(main|dev)$/
Expand Down
56 changes: 56 additions & 0 deletions samples/SampleWithExternalScope/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System.Diagnostics;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Serilog;
using Serilog.Formatting.Json;

// Configure a JsonFormatter to log out scope to the console
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console(new JsonFormatter())
.CreateLogger();

// Setup Serilog with M.E.L, and configure the appropriate ActivityTrackingOptions
var services = new ServiceCollection();

services.AddLogging(l => l
.AddSerilog()
.Configure(options =>
{
options.ActivityTrackingOptions =
ActivityTrackingOptions.SpanId
| ActivityTrackingOptions.TraceId
| ActivityTrackingOptions.ParentId
| ActivityTrackingOptions.TraceState
| ActivityTrackingOptions.TraceFlags
| ActivityTrackingOptions.Tags
| ActivityTrackingOptions.Baggage;
}));

// Add an ActivityListener (required, otherwise Activities don't actually get created if nothing is listening to them)
ActivitySource.AddActivityListener(new ActivityListener
{
ShouldListenTo = source => true,
Sample = (ref ActivityCreationOptions<ActivityContext> options) => ActivitySamplingResult.AllDataAndRecorded
});

// Run our test
var activitySource = new ActivitySource("SomeActivitySource");

var serviceProvider = services.BuildServiceProvider();
var logger = serviceProvider.GetRequiredService<ILogger<Program>>();

using var activity = activitySource.StartActivity();

activity?.SetTag("tag.domain.id", 1234);
activity?.SetBaggage("baggage.environment", "uat");

using var scope = logger.BeginScope(new
{
User = "Hugh Mann",
Time = DateTimeOffset.UtcNow
});

logger.LogInformation("Hello world!");

serviceProvider.Dispose();
21 changes: 21 additions & 0 deletions samples/SampleWithExternalScope/SampleWithExternalScope.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<OutputType>Exe</OutputType>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Serilog.Extensions.Logging\Serilog.Extensions.Logging.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.0" />
</ItemGroup>

</Project>
7 changes: 7 additions & 0 deletions serilog-extensions-logging.sln
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{9C21B9
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Extensions.Logging.Benchmarks", "test\Serilog.Extensions.Logging.Benchmarks\Serilog.Extensions.Logging.Benchmarks.csproj", "{6D5986FF-EECD-4E75-8BC6-A5F78AB549B2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleWithExternalScope", "samples\SampleWithExternalScope\SampleWithExternalScope.csproj", "{653092A8-CBAD-40AA-A4CE-F8B19D6492C2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -53,6 +55,10 @@ Global
{6D5986FF-EECD-4E75-8BC6-A5F78AB549B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6D5986FF-EECD-4E75-8BC6-A5F78AB549B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6D5986FF-EECD-4E75-8BC6-A5F78AB549B2}.Release|Any CPU.Build.0 = Release|Any CPU
{653092A8-CBAD-40AA-A4CE-F8B19D6492C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{653092A8-CBAD-40AA-A4CE-F8B19D6492C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{653092A8-CBAD-40AA-A4CE-F8B19D6492C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{653092A8-CBAD-40AA-A4CE-F8B19D6492C2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -62,6 +68,7 @@ Global
{37EADF84-5E41-4224-A194-1E3299DCD0B8} = {E30F638E-BBBE-4AD1-93CE-48CC69CFEFE1}
{65357FBC-9BC4-466D-B621-1C3A19BC2A78} = {F2407211-6043-439C-8E06-3641634332E7}
{6D5986FF-EECD-4E75-8BC6-A5F78AB549B2} = {E30F638E-BBBE-4AD1-93CE-48CC69CFEFE1}
{653092A8-CBAD-40AA-A4CE-F8B19D6492C2} = {F2407211-6043-439C-8E06-3641634332E7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {811E61C5-3871-4633-AFAE-B35B619C8A10}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright (c) Serilog Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Serilog.Extensions.Logging;

using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
using Serilog.Events;

class EventIdPropertyCache
{
readonly int _maxCachedProperties;
readonly ConcurrentDictionary<EventKey, LogEventProperty> _propertyCache = new();

int _count;

public EventIdPropertyCache(int maxCachedProperties = 1024)
{
_maxCachedProperties = maxCachedProperties;
}

public LogEventProperty GetOrCreateProperty(in EventId eventId)
{
var eventKey = new EventKey(eventId);

LogEventProperty? property;

if (_count >= _maxCachedProperties)
{
if (!_propertyCache.TryGetValue(eventKey, out property))
{
property = CreateProperty(in eventKey);
}
}
else
{
if (!_propertyCache.TryGetValue(eventKey, out property))
{
// GetOrAdd is moved to a separate method to prevent closure allocation
property = GetOrAddCore(in eventKey);
}
}

return property;
}

static LogEventProperty CreateProperty(in EventKey eventKey)
{
var properties = new List<LogEventProperty>(2);

if (eventKey.Id != 0)
{
properties.Add(new LogEventProperty("Id", new ScalarValue(eventKey.Id)));
}

if (eventKey.Name != null)
{
properties.Add(new LogEventProperty("Name", new ScalarValue(eventKey.Name)));
}

return new LogEventProperty("EventId", new StructureValue(properties));
}

LogEventProperty GetOrAddCore(in EventKey eventKey) =>
_propertyCache.GetOrAdd(
eventKey,
key =>
{
Interlocked.Increment(ref _count);

return CreateProperty(in key);
});

readonly record struct EventKey
{
public EventKey(EventId eventId)
{
Id = eventId.Id;
Name = eventId.Name;
}

public int Id { get; }

public string? Name { get; }
}
}
Loading