Skip to content

Commit

Permalink
Updates for Serilog 4
Browse files Browse the repository at this point in the history
  • Loading branch information
nblumhardt committed Jul 10, 2024
1 parent cb31a08 commit 9dcd59f
Show file tree
Hide file tree
Showing 11 changed files with 633 additions and 652 deletions.
33 changes: 22 additions & 11 deletions Build.ps1
Original file line number Diff line number Diff line change
@@ -1,31 +1,42 @@
Write-Output "build: Build started"

Push-Location $PSScriptRoot

if(Test-Path .\artifacts) { Remove-Item .\artifacts -Force -Recurse }
if(Test-Path .\artifacts) {
Write-Output "build: Cleaning ./artifacts"
Remove-Item ./artifacts -Force -Recurse
}

& dotnet restore --no-cache

$branch = @{ $true = $env:APPVEYOR_REPO_BRANCH; $false = $(git symbolic-ref --short -q HEAD) }[$env:APPVEYOR_REPO_BRANCH -ne $NULL];
$revision = @{ $true = "{0:00000}" -f [convert]::ToInt32("0" + $env:APPVEYOR_BUILD_NUMBER, 10); $false = "local" }[$env:APPVEYOR_BUILD_NUMBER -ne $NULL];
$suffix = @{ $true = ""; $false = "$branch-$revision"}[$branch -eq "master" -and $revision -ne "local"]
$branch = @{ $true = $env:APPVEYOR_REPO_BRANCH; $false = $(git symbolic-ref --short -q HEAD) }[$NULL -ne $env:APPVEYOR_REPO_BRANCH];
$revision = @{ $true = "{0:00000}" -f [convert]::ToInt32("0" + $env:APPVEYOR_BUILD_NUMBER, 10); $false = "local" }[$NULL -ne $env:APPVEYOR_BUILD_NUMBER];
$suffix = @{ $true = ""; $false = "$($branch.Substring(0, [math]::Min(10,$branch.Length)))-$revision"}[$branch -eq "main" -and $revision -ne "local"]

Write-Output "build: Package version suffix is $suffix"

foreach ($src in ls src/Serilog.*) {
foreach ($src in Get-ChildItem src/*) {
Push-Location $src

if($suffix) {
& dotnet pack -c Release -o ..\..\.\artifacts --version-suffix=$suffix --include-source
Write-Output "build: Packaging project in $src"

if ($suffix) {
& dotnet pack -c Release /p:ContinuousIntegrationBuild=True --include-source -o ../../artifacts --version-suffix=$suffix
} else {
& dotnet pack -c Release -o ..\..\.\artifacts --include-source
& dotnet pack -c Release /p:ContinuousIntegrationBuild=True --include-source -o ../../artifacts
}
if($LASTEXITCODE -ne 0) { exit 1 }
if($LASTEXITCODE -ne 0) { throw "Packaging failed" }

Pop-Location
}

foreach ($test in ls test/Serilog.*.Tests) {
foreach ($test in Get-ChildItem test/*.Tests) {
Push-Location $test

Write-Output "build: Testing project in $test"

& dotnet test -c Release
if($LASTEXITCODE -ne 0) { exit 2 }
if($LASTEXITCODE -ne 0) { throw "Testing failed" }

Pop-Location
}
Expand Down
34 changes: 18 additions & 16 deletions appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
version: '{build}'
skip_tags: true
image: Visual Studio 2019
image:
- Visual Studio 2022
build_script:
- ps: ./Build.ps1
- pwsh: ./Build.ps1
test: off
artifacts:
- path: artifacts/Serilog.*.nupkg
- path: artifacts/Serilog.*.nupkg
- path: artifacts/Serilog.*.snupkg
deploy:
- provider: NuGet
api_key:
secure: K3/810hkTO6rd2AEHVkUQAadjGmDREus9k96QHu6hxrA1/wRTuAJemHMKtVVgIvf
skip_symbols: true
on:
branch: /^(master|dev)$/
- provider: GitHub
auth_token:
secure: p4LpVhBKxGS5WqucHxFQ5c7C8cP74kbNB0Z8k9Oxx/PMaDQ1+ibmoexNqVU5ZlmX
artifact: /Serilog.*\.nupkg/
tag: v$(appveyor_build_version)
on:
branch: master
- provider: NuGet
api_key:
secure: sDnchSg4TZIOK7oIUI6BJwFPNENTOZrGNsroGO1hehLJSvlHpFmpTwiX8+bgPD+Q
on:
branch: /^(main|dev)$/
- provider: GitHub
auth_token:
secure: p4LpVhBKxGS5WqucHxFQ5c7C8cP74kbNB0Z8k9Oxx/PMaDQ1+ibmoexNqVU5ZlmX
artifact: /Serilog.*(\.|\.s)nupkg/
tag: v$(appveyor_build_version)
on:
branch: main
431 changes: 215 additions & 216 deletions src/Serilog.Sinks.Map/MapLoggerConfigurationExtensions.cs

Large diffs are not rendered by default.

65 changes: 38 additions & 27 deletions src/Serilog.Sinks.Map/Serilog.Sinks.Map.csproj
Original file line number Diff line number Diff line change
@@ -1,32 +1,43 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Description>A Serilog sink wrapper that dispatches events based on a property value.</Description>
<VersionPrefix>1.0.3</VersionPrefix>
<RootNamespace>Serilog</RootNamespace>
<Authors>Serilog Contributors</Authors>
<Copyright>Copyright © Serilog Contributors</Copyright>
<TargetFrameworks>netstandard1.0;netstandard2.0;net5.0</TargetFrameworks>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AssemblyOriginatorKeyFile>../../assets/Serilog.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly>
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
<PackageTags>serilog</PackageTags>
<PackageIcon>icon.png</PackageIcon>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/serilog/serilog-sinks-map</PackageProjectUrl>
<RepositoryUrl>https://github.com/serilog/serilog-sinks-map</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<GenerateAssemblyVersionAttribute>true</GenerateAssemblyVersionAttribute>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Serilog" Version="2.8.0" />
</ItemGroup>
<PropertyGroup>
<Description>A Serilog sink wrapper that dispatches events based on a property value.</Description>
<VersionPrefix>2.0.0</VersionPrefix>
<RootNamespace>Serilog</RootNamespace>
<Authors>Serilog Contributors</Authors>
<Copyright>Copyright © Serilog Contributors</Copyright>
<!-- .NET Framework version targeting is frozen at these two TFMs. -->
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT'">net471;net462</TargetFrameworks>
<!-- Policy is to trim TFM-specific builds to `netstandard2.0`, `net6.0`,
all active LTS versions, and optionally the latest RTM version, when releasing new
major Serilog versions. -->
<TargetFrameworks>$(TargetFrameworks);net8.0;net6.0;netstandard2.0</TargetFrameworks>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AssemblyOriginatorKeyFile>../../assets/Serilog.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly>
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
<PackageTags>serilog</PackageTags>
<PackageIcon>icon.png</PackageIcon>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/serilog/serilog-sinks-map</PackageProjectUrl>
<RepositoryUrl>https://github.com/serilog/serilog-sinks-map</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<GenerateAssemblyVersionAttribute>true</GenerateAssemblyVersionAttribute>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<PackageReadmeFile>README.md</PackageReadmeFile>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<None Include="..\..\assets\icon.png" Pack="true" Visible="false" PackagePath="" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Serilog" Version="4.0.0"/>
<PackageReference Include="Nullable" Version="1.3.1" PrivateAssets="all" />
</ItemGroup>

<ItemGroup>
<None Include="..\..\assets\icon.png" Pack="true" Visible="false" PackagePath=""/>
<None Include="..\..\README.md" Pack="true" Visible="false" PackagePath=""/>
</ItemGroup>

</Project>
177 changes: 80 additions & 97 deletions src/Serilog.Sinks.Map/Sinks/Map/MappedSink`1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,134 +12,117 @@
// See the License for the specific language governing permissions and
// limitations under the License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics.CodeAnalysis;
using Serilog.Configuration;
using Serilog.Core;
using Serilog.Events;

namespace Serilog.Sinks.Map
namespace Serilog.Sinks.Map;

/// <summary>
/// A function delegate to select a key value given a log event, if exists.
/// </summary>
/// <typeparam name="TKey">The type of the key value.</typeparam>
/// <param name="logEvent">The log event.</param>
/// <param name="key">The selected key.</param>
/// <returns>true, if a key can be selected, or false, otherwise.</returns>
public delegate bool KeySelector<TKey>(LogEvent logEvent, [MaybeNull] out TKey key);

class MappedSink<TKey> : ILogEventSink, IDisposable
{
/// <summary>
/// A function delegate to select a key value given a log event, if exists.
/// </summary>
/// <typeparam name="TKey">The type of the key value.</typeparam>
/// <param name="logEvent">The log event.</param>
/// <param name="key">The selected key.</param>
/// <returns>true, if a key can be selected, or false, otherwise.</returns>
public delegate bool KeySelector<TKey>(LogEvent logEvent, out TKey key);
readonly KeySelector<TKey> _keySelector;
readonly Action<TKey, LoggerSinkConfiguration> _configure;
readonly int? _sinkMapCountLimit;
readonly object _sync = new();
readonly Dictionary<KeyValuePair<TKey, bool>, ILogEventSink> _sinkMap = new();
bool _disposed;

public MappedSink(KeySelector<TKey> keySelector,
Action<TKey, LoggerSinkConfiguration> configure,
int? sinkMapCountLimit)
{
_keySelector = keySelector;
_configure = configure;
_sinkMapCountLimit = sinkMapCountLimit;
}

class MappedSink<TKey> : ILogEventSink, IDisposable
// All writes are synchronized, even though this could be avoided in a few cases; the reasoning behind this is that
// changes in synchronization behavior between writes and with different map count limits could lead to surprises
// when using the sink with log files, which are one of the main use cases. Since most sinks already synchronize
// writes or offload work to background threads, this is a reasonable trade-off.
public void Emit(LogEvent logEvent)
{
readonly KeySelector<TKey> _keySelector;
readonly Action<TKey, LoggerSinkConfiguration> _configure;
readonly int? _sinkMapCountLimit;
readonly object _sync = new object();
readonly Dictionary<KeyValuePair<TKey, bool>, ILogEventSink> _sinkMap = new Dictionary<KeyValuePair<TKey, bool>, ILogEventSink>();
bool _disposed;
if (!_keySelector(logEvent, out var keyValue))
return;

public MappedSink(KeySelector<TKey> keySelector,
Action<TKey, LoggerSinkConfiguration> configure,
int? sinkMapCountLimit)
{
_keySelector = keySelector;
_configure = configure;
_sinkMapCountLimit = sinkMapCountLimit;
}
var key = new KeyValuePair<TKey, bool>(keyValue!, false);

// All writes are synchronized, even though this could be avoided in a few cases; the reasoning behind this is that
// changes in synchronization behavior between writes and with different map count limits could lead to surprises
// when using the sink with log files, which are one of the main use cases. Since most sinks already synchronize
// writes or offload work to background threads, this is a reasonable trade-off.
public void Emit(LogEvent logEvent)
lock (_sync)
{
if (!_keySelector(logEvent, out var keyValue))
return;

var key = new KeyValuePair<TKey, bool>(keyValue, false);
if (_disposed)
throw new ObjectDisposedException(nameof(MappedSink<TKey>), "The mapped sink has been disposed.");

lock (_sync)
if (_sinkMap.TryGetValue(key, out var existing))
{
if (_disposed)
throw new ObjectDisposedException(nameof(MappedSink<TKey>), "The mapped sink has been disposed.");

if (_sinkMap.TryGetValue(key, out var existing))
{
existing.Emit(logEvent);
return;
}
existing.Emit(logEvent);
return;
}

var sink = CreateSink(key.Key);
var sink = LoggerSinkConfiguration.CreateSink(wt => _configure(key.Key, wt));

if (_sinkMapCountLimit == 0)
{
using (sink as IDisposable)
sink.Emit(logEvent);
}
else if (_sinkMapCountLimit == null || _sinkMapCountLimit > _sinkMap.Count)
if (_sinkMapCountLimit == 0)
{
using (sink as IDisposable)
sink.Emit(logEvent);
}
else if (_sinkMapCountLimit == null || _sinkMapCountLimit > _sinkMap.Count)
{
// This case is a little faster as no EH nor iteration is required
_sinkMap[key] = sink;
sink.Emit(logEvent);
}
else
{
_sinkMap[key] = sink;
try
{
// This case is a little faster as no EH nor iteration is required
_sinkMap[key] = sink;
sink.Emit(logEvent);
}
else
finally
{
_sinkMap[key] = sink;
try
{
sink.Emit(logEvent);
}
finally
while (_sinkMap.Count > _sinkMapCountLimit.Value)
{
while (_sinkMap.Count > _sinkMapCountLimit.Value)
foreach (var k in _sinkMap.Keys)
{
foreach (var k in _sinkMap.Keys)
{
if (key.Equals(k))
continue;
if (key.Equals(k))
continue;

var removed = _sinkMap[k];
_sinkMap.Remove(k);
(removed as IDisposable)?.Dispose();
break;
}
var removed = _sinkMap[k];
_sinkMap.Remove(k);
(removed as IDisposable)?.Dispose();
break;
}
}
}
}
}
}

ILogEventSink CreateSink(TKey key)
public void Dispose()
{
lock (_sync)
{
// Allocates a few delegates, but avoids a lot more allocation in the `LoggerConfiguration`/`Logger` machinery.
ILogEventSink sink = null;
LoggerSinkConfiguration.Wrap(
new LoggerConfiguration().WriteTo,
s => sink = s,
config => _configure(key, config),
LevelAlias.Minimum,
null);
if (_disposed)
return;

return sink;
}
_disposed = true;

public void Dispose()
{
lock (_sync)
var values = _sinkMap.Values.ToArray();
_sinkMap.Clear();
foreach (var sink in values)
{
if (_disposed)
return;

_disposed = true;

var values = _sinkMap.Values.ToArray();
_sinkMap.Clear();
foreach (var sink in values)
{
(sink as IDisposable)?.Dispose();
}
(sink as IDisposable)?.Dispose();
}
}
}
}
}
Loading

0 comments on commit 9dcd59f

Please sign in to comment.