Skip to content

Commit

Permalink
Support for ITuple
Browse files Browse the repository at this point in the history
  • Loading branch information
sungam3r committed Mar 25, 2024
1 parent 6fd2e72 commit 504fc5c
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 34 deletions.
72 changes: 39 additions & 33 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 @@ -87,10 +87,10 @@ _Serilog.Extensions.Logging_ captures the `ILogger`'s log category, but it's not
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}")
.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
Expand All @@ -103,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 @@ -116,7 +117,8 @@ If you simply want to add a "bag" of additional properties to your log events, h

```csharp
// WRONG! Prefer the dictionary or value tuple approach below instead
using (_logger.BeginScope("TransactionId: {TransactionId}, ResponseJson: {ResponseJson}", 12345, jsonString)) {
using (_logger.BeginScope("TransactionId: {TransactionId}, ResponseJson: {ResponseJson}", 12345, jsonString))
{
_logger.LogInformation("Completed in {DurationMs}ms...", 30);
}
// Example JSON output:
Expand All @@ -138,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 @@ -157,10 +161,12 @@ 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?`.
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", (object?)12345)) {
using (_logger.BeginScope(("TransactionId", 12345))
{
_logger.LogInformation("Transaction completed in {DurationMs}ms...", 30);
}
// Example JSON output:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,17 @@ void AddProperty(string key, object? value)
AddProperty(stateProperty.Key, stateProperty.Value);
}
}
#if FEATURE_ITUPLE
else if (_state is System.Runtime.CompilerServices.ITuple tuple && tuple.Length == 2 && tuple[0] is string s)
{
scopeItem = null; // Unless it's `FormattedLogValues`, these are treated as property bags rather than scope items.

if (s == SerilogLoggerProvider.OriginalFormatPropertyName && tuple[1] is string)
scopeItem = new ScalarValue(_state.ToString());
else
AddProperty(s, tuple[1]);
}
#else
else if (_state is ValueTuple<string, object?> tuple)
{
scopeItem = null; // Unless it's `FormattedLogValues`, these are treated as property bags rather than scope items.
Expand All @@ -108,6 +119,7 @@ void AddProperty(string key, object? value)
else
AddProperty(tuple.Item1, tuple.Item2);
}
#endif
else
{
scopeItem = propertyFactory.CreateProperty(NoName, _state).Value;
Expand Down
15 changes: 15 additions & 0 deletions src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,19 @@
<PackageReference Include="Nullable" Version="1.3.1" PrivateAssets="All" />
</ItemGroup>

<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.1' ">
<DefineConstants>$(DefineConstants);FEATURE_ITUPLE</DefineConstants>
</PropertyGroup>

<PropertyGroup Condition=" '$(TargetFramework)' == 'net6.0' ">
<DefineConstants>$(DefineConstants);FEATURE_ITUPLE</DefineConstants>
</PropertyGroup>

<PropertyGroup Condition=" '$(TargetFramework)' == 'net7.0' ">
<DefineConstants>$(DefineConstants);FEATURE_ITUPLE</DefineConstants>
</PropertyGroup>

<PropertyGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
<DefineConstants>$(DefineConstants);FEATURE_ITUPLE</DefineConstants>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,11 @@ public void EnrichWithTupleStringObject()

var (loggerProvider, logEventPropertyFactory, logEvent) = SetUp();


#if NET48
var state = (propertyName, (object)expectedValue);
#else
var state = (propertyName, expectedValue);
#endif

var loggerScope = new SerilogLoggerScope(loggerProvider, state);

Expand Down

0 comments on commit 504fc5c

Please sign in to comment.