Skip to content
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

Updates for Serilog 4 #34

Merged
merged 4 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
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 -o ../../artifacts --version-suffix=$suffix
} else {
& dotnet pack -c Release -o ..\..\.\artifacts --include-source
& dotnet pack -c Release /p:ContinuousIntegrationBuild=True -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>
Numpsy marked this conversation as resolved.
Show resolved Hide resolved
<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