Skip to content

3.1.0 Release #192

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 34 commits into from
Nov 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
015d4f7
Dev version bump [skip ci]
nblumhardt Aug 21, 2019
54ff29a
small fixes
sungam3r Sep 3, 2019
c5bad0c
Merge pull request #152 from sungam3r/small-refactor
nblumhardt Sep 3, 2019
d104db2
Fixes #154 - changes now tracked via /releases.
nblumhardt Sep 20, 2019
b43f68e
Replace deprecated PackageLicenseUrl
andrewlock Oct 3, 2019
7e827a6
Replace deprecated PackageIcon
andrewlock Oct 3, 2019
9f5a6a1
Merge pull request #156 from andrewlock/fix-deprecated-properties
nblumhardt Oct 4, 2019
6982c96
added stringification support
bicj-leica Nov 28, 2019
b201aba
pass null instead of text "null"
bicj-leica Nov 29, 2019
73671f8
remove $ from key in logger scope to support "force stringify"
bicj-leica Dec 5, 2019
8428510
fixed stringify support in logger scope
bicj-leica Dec 11, 2019
6152c32
Merge pull request #160 from jonnybi/dev
nblumhardt Dec 11, 2019
09aa8ed
Pin .NET Core SDK version to latest feature band
ralphhendriks Feb 24, 2020
56418c3
Merge pull request #165 from ralphhendriks/relax-gloabl-json-sdk-cons…
nblumhardt Feb 24, 2020
ef999bd
Use caching message template parser
nblumhardt Feb 25, 2020
cf6caad
Resolves #164 - suppress exceptions from FormattedLogValues
nblumhardt Feb 25, 2020
c695457
Merge pull request #167 from nblumhardt/suppress-write-exceptions
nblumhardt Mar 2, 2020
0507eb3
Merge branch 'dev' into formatter-exceptions
May 13, 2020
430dde1
Use `latestFeature` in global.json
May 13, 2020
29d4101
Merge pull request #173 from serilog/global-json-latest-feature
nblumhardt May 13, 2020
a2e70ee
Merge pull request #166 from nblumhardt/formatter-exceptions
nblumhardt May 14, 2020
282cefb
Update NuGet.org publishing key
nblumhardt May 14, 2020
babc23b
Another attempt at a working publishing key
nblumhardt May 14, 2020
c756037
Update README to point to Serilog.Extensions.Hosting - fixes #180
nblumhardt Aug 2, 2020
7fa8d7c
Tiny README fix [skip ci]
nblumhardt Aug 2, 2020
a7c57d1
Added notes about Log Scope usage to README
Rabadash8820 Nov 9, 2020
792e723
Tweaked some wording in the Log Scope notes, per review
Rabadash8820 Nov 9, 2020
3930717
Merge pull request #182 from Rabadash8820/patch-1
nblumhardt Jan 8, 2021
5dbeb54
Bump Serilog version dependency
bdovaz May 5, 2021
50b7228
Merge pull request #188 from bdovaz/unity-net-standard-2-0-compatibility
nblumhardt May 6, 2021
a3139b8
Added EventId hydration if it was defined
david-driscoll May 17, 2021
09ab33c
Merge pull request #189 from david-driscoll/feature/event-id-fix
nblumhardt May 17, 2021
8c503a4
Update publishing key
nblumhardt May 17, 2021
2a3afbb
Bump minor version - dependency and feature updates
nblumhardt Nov 1, 2021
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
12 changes: 0 additions & 12 deletions CHANGES.md

This file was deleted.

72 changes: 69 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
# Serilog.Extensions.Logging [![Build status](https://ci.appveyor.com/api/projects/status/865nohxfiq1rnby0/branch/master?svg=true)](https://ci.appveyor.com/project/serilog/serilog-framework-logging/history) [![NuGet Version](http://img.shields.io/nuget/v/Serilog.Extensions.Logging.svg?style=flat)](https://www.nuget.org/packages/Serilog.Extensions.Logging/)


A Serilog provider for [Microsoft.Extensions.Logging](https://www.nuget.org/packages/Microsoft.Extensions.Logging), the logging subsystem used by ASP.NET Core.

### ASP.NET Core 2.0+ Instructions
### ASP.NET Core Instructions

**ASP.NET Core** applications should prefer [Serilog.AspNetCore](https://github.com/serilog/serilog-aspnetcore) and `UseSerilog()` instead.

**ASP.NET Core 2.0** applications should prefer [Serilog.AspNetCore](https://github.com/serilog/serilog-aspnetcore) and `UseSerilog()` instead.
### Non-web .NET Core Instructions

**Non-web .NET Core** applications should prefer [Serilog.Extensions.Hosting](https://github.com/serilog/serilog-extensions-hosting) and `UseSerilog()` instead.

### .NET Core 1.0, 1.1 and Default Provider Integration

Expand Down Expand Up @@ -77,6 +80,69 @@ 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
```

### 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:

1. The method: `IDisposable BeginScope<TState>(TState state)`
2. The extension method: `IDisposable BeginScope(this ILogger logger, string messageFormat, params object[] args)`

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:

```cs
using (_logger.BeginScope("Transaction")) {
_logger.LogInformation("Beginning...");
_logger.LogInformation("Completed in {DurationMs}ms...", 30);
}
// Example JSON output:
// {"@t":"2020-10-29T19:05:56.4126822Z","@m":"Beginning...","@i":"f6a328e9","SourceContext":"SomeNamespace.SomeService","Scope":["Transaction"]}
// {"@t":"2020-10-29T19:05:56.4176816Z","@m":"Completed in 30ms...","@i":"51812baa","DurationMs":30,"SourceContext":"SomeNamespace.SomeService","Scope":["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:

```cs
// WRONG! Prefer the dictionary approach below instead
using (_logger.BeginScope("TransactionId: {TransactionId}, ResponseJson: {ResponseJson}", 12345, jsonString)) {
_logger.LogInformation("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,
// "ResponseJson": "{ \"Key1\": \"Value1\", \"Key2\": \"Value2\" }",
// "Scope":["TransactionId: 12345, ResponseJson: { \"Key1\": \"Value1\", \"Key2\": \"Value2\" }"]
// }
```

Not only does this add the unnecessary `Scope` property to your event, but it also duplicates serialized values between `Scope` and the intended properties, as you can see here with `ResponseJson`. If this were "real" JSON like an API response, then a potentially very large block of text would be duplicated within your log event! Moreover, the template string within `BeginScope` is rather arbitrary when all you want to do is add a bag of properties, and you start mixing enriching concerns with formatting concerns.

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:

```cs
var scopeProps = new Dictionary<string, object> {
{ "TransactionId", 12345 },
{ "ResponseJson", jsonString },
};
using (_logger.BeginScope(scopeProps) {
_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,
// "ResponseJson": "{ \"Key1\": \"Value1\", \"Key2\": \"Value2\" }"
// }
```

### Credits

This package evolved from an earlier package _Microsoft.Framework.Logging.Serilog_ [provided by the ASP.NET team](https://github.com/aspnet/Logging/pull/182).
7 changes: 1 addition & 6 deletions appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
version: '{build}'
skip_tags: true
image: Visual Studio 2019
configuration: Release
install:
- ps: mkdir -Force ".\build\" | Out-Null
#- ps: Invoke-WebRequest "https://raw.githubusercontent.com/dotnet/cli/release/2.0.0/scripts/obtain/dotnet-install.ps1" -OutFile ".\build\installcli.ps1"
#- ps: $env:DOTNET_INSTALL_DIR = "$pwd\.dotnetcli"
#- ps: '& .\build\installcli.ps1 -InstallDir "$env:DOTNET_INSTALL_DIR" -NoPath -Version 2.0.0-preview2-006497'
#- ps: $env:Path = "$env:DOTNET_INSTALL_DIR;$env:Path"
build_script:
- ps: ./Build.ps1
test: off
Expand All @@ -16,7 +11,7 @@ artifacts:
deploy:
- provider: NuGet
api_key:
secure: N59tiJECUYpip6tEn0xvdmDAEiP9SIzyLEFLpwiigm/8WhJvBNs13QxzT1/3/JW/
secure: kYR3BYzJm3wSFbFjPhgTzuDHHcE8ApoNUmHvJvunWZ39nyrqVk6J6srjzYVQ7/gX
skip_symbols: true
on:
branch: /^(master|dev)$/
Expand Down
Binary file added assets/serilog-extension-nuget.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
{
"sdk": {
"version": "2.2.103"
"allowPrerelease": false,
"version": "3.1.100",
"rollForward": "latestFeature"
}
}
3 changes: 2 additions & 1 deletion serilog-extensions-logging.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=Loggable/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Nonscalar/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Serilog/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=sobj/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/UserDictionary/Words/=sobj/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Stringification/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// 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.


using System;
using Serilog.Events;
using Serilog.Parsing;
using System.Collections;

namespace Serilog.Extensions.Logging
{
class CachingMessageTemplateParser
{
readonly MessageTemplateParser _innerParser = new MessageTemplateParser();

readonly object _templatesLock = new object();
readonly Hashtable _templates = new Hashtable();

const int MaxCacheItems = 1000;
const int MaxCachedTemplateLength = 1024;

public MessageTemplate Parse(string messageTemplate)
{
if (messageTemplate == null) throw new ArgumentNullException(nameof(messageTemplate));

if (messageTemplate.Length > MaxCachedTemplateLength)
return _innerParser.Parse(messageTemplate);

// ReSharper disable once InconsistentlySynchronizedField
// ignored warning because this is by design
var result = (MessageTemplate)_templates[messageTemplate];
if (result != null)
return result;

result = _innerParser.Parse(messageTemplate);

lock (_templatesLock)
{
// Exceeding MaxCacheItems is *not* the sunny day scenario; all we're doing here is preventing out-of-memory
// conditions when the library is used incorrectly. Correct use (templates, rather than
// direct message strings) should barely, if ever, overflow this cache.

// Changing workloads through the lifecycle of an app instance mean we can gain some ground by
// potentially dropping templates generated only in startup, or only during specific infrequent
// activities.

if (_templates.Count == MaxCacheItems)
_templates.Clear();

_templates[messageTemplate] = result;
}

return result;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ namespace Serilog.Extensions.Logging
/// </summary>
public class LoggerProviderCollection : IDisposable
{
volatile ILoggerProvider[] _providers = new ILoggerProvider[0];
volatile ILoggerProvider[] _providers = Array.Empty<ILoggerProvider>();

/// <summary>
/// Add <paramref name="provider"/> to the collection.
Expand All @@ -36,16 +36,16 @@ public void AddProvider(ILoggerProvider provider)
{
if (provider == null) throw new ArgumentNullException(nameof(provider));

var existing = _providers;
var added = existing.Concat(new[] {provider}).ToArray();
ILoggerProvider[] existing, added;

#pragma warning disable 420 // ref to a volatile field
while (Interlocked.CompareExchange(ref _providers, added, existing) != existing)
#pragma warning restore 420
do
{
existing = _providers;
added = existing.Concat(new[] { provider }).ToArray();
}
#pragma warning disable 420 // ref to a volatile field
while (Interlocked.CompareExchange(ref _providers, added, existing) != existing);
#pragma warning restore 420
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// limitations under the License.

using System;
using Microsoft.Extensions.Logging;
using Serilog.Core;
using Serilog.Events;

Expand All @@ -30,13 +31,26 @@ public LoggerProviderCollectionSink(LoggerProviderCollection providers)
public void Emit(LogEvent logEvent)
{
string categoryName = null;
EventId eventId = default;

if (logEvent.Properties.TryGetValue("SourceContext", out var sourceContextProperty) &&
sourceContextProperty is ScalarValue sourceContextValue &&
sourceContextValue.Value is string sourceContext)
{
categoryName = sourceContext;
}
if (logEvent.Properties.TryGetValue("EventId", out var eventIdPropertyValue) && eventIdPropertyValue is StructureValue structuredEventId)
{
string name = null;
var id = 0;
foreach (var item in structuredEventId.Properties)
{
if (item.Name == "Id" && item.Value is ScalarValue sv && sv.Value is int i) id = i;
if (item.Name == "Name" && item.Value is ScalarValue sv2 && sv2.Value is string s) name = s;
}

eventId = new EventId(id, name);
}

var level = LevelConvert.ToExtensionsLevel(logEvent.Level);
var slv = new SerilogLogValues(logEvent.MessageTemplate, logEvent.Properties);
Expand All @@ -47,7 +61,7 @@ sourceContextProperty is ScalarValue sourceContextValue &&

logger.Log(
level,
default,
eventId,
slv,
logEvent.Exception,
(s, e) => s.ToString());
Expand Down
23 changes: 20 additions & 3 deletions src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
using Serilog.Events;
using FrameworkLogger = Microsoft.Extensions.Logging.ILogger;
using System.Reflection;
using Serilog.Parsing;
using Serilog.Debugging;

namespace Serilog.Extensions.Logging
{
Expand All @@ -18,7 +18,7 @@ class SerilogLogger : FrameworkLogger
readonly SerilogLoggerProvider _provider;
readonly ILogger _logger;

static readonly MessageTemplateParser MessageTemplateParser = new MessageTemplateParser();
static readonly CachingMessageTemplateParser MessageTemplateParser = new CachingMessageTemplateParser();

// It's rare to see large event ids, as they are category-specific
static readonly LogEventProperty[] LowEventIdValues = Enumerable.Range(0, 48)
Expand Down Expand Up @@ -60,6 +60,18 @@ public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Except
return;
}

try
{
Write(level, eventId, state, exception, formatter);
}
catch (Exception ex)
{
SelfLog.WriteLine($"Failed to write event through {typeof(SerilogLogger).Name}: {ex}");
}
}

void Write<TState>(LogEventLevel level, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
var logger = _logger;
string messageTemplate = null;

Expand All @@ -78,6 +90,11 @@ public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Except
if (logger.BindProperty(property.Key.Substring(1), property.Value, true, out var destructured))
properties.Add(destructured);
}
else if (property.Key.StartsWith("$"))
{
if (logger.BindProperty(property.Key.Substring(1), property.Value?.ToString(), true, out var stringified))
properties.Add(stringified);
}
else
{
if (logger.BindProperty(property.Key, property.Value, false, out var bound))
Expand Down Expand Up @@ -154,4 +171,4 @@ internal static LogEventProperty CreateEventIdProperty(EventId eventId)
return new LogEventProperty("EventId", new StructureValue(properties));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,7 @@ public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
List<LogEventPropertyValue> scopeItems = null;
for (var scope = CurrentScope; scope != null; scope = scope.Parent)
{
LogEventPropertyValue scopeItem;
scope.EnrichAndCreateScopeItem(logEvent, propertyFactory, out scopeItem);
scope.EnrichAndCreateScopeItem(logEvent, propertyFactory, out LogEventPropertyValue scopeItem);

if (scopeItem != null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,7 @@ public void EnrichAndCreateScopeItem(LogEvent logEvent, ILogEventPropertyFactory
return;
}

var stateProperties = _state as IEnumerable<KeyValuePair<string, object>>;
if (stateProperties != null)
if (_state is IEnumerable<KeyValuePair<string, object>> stateProperties)
{
scopeItem = null; // Unless it's `FormattedLogValues`, these are treated as property bags rather than scope items.

Expand All @@ -72,14 +71,21 @@ public void EnrichAndCreateScopeItem(LogEvent logEvent, ILogEventPropertyFactory

var key = stateProperty.Key;
var destructureObject = false;
var value = stateProperty.Value;

if (key.StartsWith("@"))
{
key = key.Substring(1);
destructureObject = true;
}

var property = propertyFactory.CreateProperty(key, stateProperty.Value, destructureObject);

if (key.StartsWith("$"))
{
key = key.Substring(1);
value = value?.ToString();
}

var property = propertyFactory.CreateProperty(key, value, destructureObject);
logEvent.AddPropertyIfAbsent(property);
}
}
Expand Down
Loading