Skip to content

Commit

Permalink
Merge pull request #224 from datalust/dev
Browse files Browse the repository at this point in the history
8.0.0 Release
  • Loading branch information
nblumhardt authored Jun 4, 2024
2 parents 803b4ab + 0b40637 commit fbc68c1
Show file tree
Hide file tree
Showing 14 changed files with 373 additions and 45 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ A Serilog sink that writes events to the [Seq](https://datalust.co/seq) structur

[<img alt="Package Logo" src="https://datalust.co/images/seq-nuget.png" width="128px">](https://nuget.org/packages/serilog.sinks.seq)

> [!TIP]
> If you would like to see timing and dependency information in Seq, [SerilogTracing](https://github.com/serilog-tracing/serilog-tracing) is a Serilog extension that supports both logs and traces.

### Getting started

Install _Serilog.Sinks.Seq_ into your .NET project:
Expand Down
4 changes: 2 additions & 2 deletions sample/BlazorWasm/BlazorWasm.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.4" PrivateAssets="all" />
<PackageReference Include="serilog.extensions.logging" Version="8.0.0" />
<PackageReference Include="serilog.sinks.browserconsole" Version="2.0.0" />
</ItemGroup>
Expand Down
39 changes: 18 additions & 21 deletions src/Serilog.Sinks.Seq/SeqLoggerConfigurationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
using Serilog.Sinks.Seq;
using System.Net.Http;
using Serilog.Formatting;
using Serilog.Sinks.PeriodicBatching;
using Serilog.Sinks.Seq.Batched;
using Serilog.Sinks.Seq.Audit;
using Serilog.Sinks.Seq.Http;
Expand Down Expand Up @@ -98,8 +97,6 @@ public static LoggerConfiguration Seq(
var formatter = payloadFormatter ?? CreateDefaultFormatter();
var ingestionApi = new SeqIngestionApiClient(serverUrl, apiKey, messageHandler);

ILogEventSink sink;

if (bufferBaseFilename == null)
{
var batchedSink = new BatchedSeqSink(
Expand All @@ -108,29 +105,29 @@ public static LoggerConfiguration Seq(
eventBodyLimitBytes,
controlledSwitch);

var options = new PeriodicBatchingSinkOptions
var options = new BatchingOptions
{
BatchSizeLimit = batchPostingLimit,
Period = defaultedPeriod,
BufferingTimeLimit = defaultedPeriod,
QueueLimit = queueSizeLimit
};

sink = new PeriodicBatchingSink(batchedSink, options);
}
else
{
sink = new DurableSeqSink(
ingestionApi,
formatter,
bufferBaseFilename,
batchPostingLimit,
defaultedPeriod,
bufferSizeLimitBytes,
eventBodyLimitBytes,
controlledSwitch,
retainedInvalidPayloadsLimitBytes);

return loggerSinkConfiguration.Conditional(
controlledSwitch.IsIncluded,
wt => wt.Sink(batchedSink, options, restrictedToMinimumLevel, levelSwitch: null));
}


var sink = new DurableSeqSink(
ingestionApi,
formatter,
bufferBaseFilename,
batchPostingLimit,
defaultedPeriod,
bufferSizeLimitBytes,
eventBodyLimitBytes,
controlledSwitch,
retainedInvalidPayloadsLimitBytes);

return loggerSinkConfiguration.Conditional(
controlledSwitch.IsIncluded,
wt => wt.Sink(sink, restrictedToMinimumLevel, levelSwitch: null));
Expand Down
7 changes: 3 additions & 4 deletions src/Serilog.Sinks.Seq/Serilog.Sinks.Seq.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<Description>A Serilog sink that writes events to Seq using newline-delimited JSON and HTTP/HTTPS.</Description>
<VersionPrefix>7.0.1</VersionPrefix>
<VersionPrefix>8.0.0</VersionPrefix>
<Authors>Serilog Contributors;Serilog.Sinks.Seq Contributors;Datalust Pty Ltd</Authors>
<Copyright>Copyright © Serilog Contributors, Serilog.Sinks.Seq Contributors, Datalust Pty Ltd.</Copyright>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
Expand All @@ -19,7 +19,7 @@
<RepositoryUrl>https://github.com/datalust/serilog-sinks-seq</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<GenerateAssemblyVersionAttribute>true</GenerateAssemblyVersionAttribute>
<LangVersion>10</LangVersion>
<LangVersion>12</LangVersion>
<Nullable>enable</Nullable>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>
Expand All @@ -29,8 +29,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Serilog" Version="3.1.1" />
<PackageReference Include="Serilog.Sinks.PeriodicBatching" Version="4.1.0" />
<PackageReference Include="Serilog" Version="4.0.0" />
<PackageReference Include="Nullable" Version="1.3.1" PrivateAssets="All" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
</ItemGroup>
Expand Down
9 changes: 4 additions & 5 deletions src/Serilog.Sinks.Seq/Sinks/Seq/Batched/BatchedSeqSink.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,16 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Serilog.Core;
using Serilog.Events;
using Serilog.Formatting;
using Serilog.Sinks.PeriodicBatching;
using Serilog.Sinks.Seq.Http;

namespace Serilog.Sinks.Seq.Batched;

/// <summary>
/// The default Seq sink, for use in combination with <see cref="PeriodicBatchingSink"/>.
/// The default Seq sink.
/// </summary>
sealed class BatchedSeqSink : IBatchedLogEventSink, IDisposable
{
Expand Down Expand Up @@ -60,11 +59,11 @@ public async Task OnEmptyBatchAsync()
if (_controlledSwitch.IsActive &&
_nextRequiredLevelCheckUtc < DateTime.UtcNow)
{
await EmitBatchAsync(Enumerable.Empty<LogEvent>());
await EmitBatchAsync([]);
}
}

public async Task EmitBatchAsync(IEnumerable<LogEvent> events)
public async Task EmitBatchAsync(IReadOnlyCollection<LogEvent> events)
{
_nextRequiredLevelCheckUtc = DateTime.UtcNow.Add(RequiredLevelCheckInterval);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright © 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.Collections.Generic;
using Serilog.Events;

namespace Serilog.Sinks.Seq.Conventions;

/// <summary>
/// Maintains verbatim processing of property names. A property named <c>"a.b"</c> will be transmitted to Seq as a
/// scalar value with name <c>"a.b"</c>.
/// </summary>
class PreserveDottedPropertyNames: IDottedPropertyNameConvention
{
/// <inheritdoc />
public IReadOnlyDictionary<string, LogEventPropertyValue> ProcessDottedPropertyNames(IReadOnlyDictionary<string, LogEventPropertyValue> maybeDotted)
{
return maybeDotted;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Copyright © 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 System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Serilog.Events;

namespace Serilog.Sinks.Seq.Conventions;

/// <summary>
/// Experimental. Unflatten property names. A property with name <c>"a.b"</c> will be transmitted to Seq as
/// a structure with name <c>"a"</c>, and one member <c>"b"</c>.
/// </summary>
/// <remarks>This behavior is enabled when the <c>Serilog.Parsing.MessageTemplateParser.AcceptDottedPropertyNames</c>
/// <see cref="AppContext"/> switch is set to value <c langword="true"/>.</remarks>
class UnflattenDottedPropertyNames: IDottedPropertyNameConvention
{
const int MaxDepth = 10;

/// <inheritdoc />
public IReadOnlyDictionary<string, LogEventPropertyValue> ProcessDottedPropertyNames(IReadOnlyDictionary<string, LogEventPropertyValue> maybeDotted)
{
return DottedToNestedRecursive(maybeDotted, 0);
}

static IReadOnlyDictionary<string, LogEventPropertyValue> DottedToNestedRecursive(IReadOnlyDictionary<string, LogEventPropertyValue> maybeDotted, int depth)
{
if (depth == MaxDepth)
return maybeDotted;

// Assume that the majority of entries will be bare or have unique prefixes.
var result = new Dictionary<string, LogEventPropertyValue>(maybeDotted.Count);

// Sorted for determinism.
var dotted = new SortedDictionary<string, LogEventPropertyValue>(StringComparer.Ordinal);

// First - give priority to bare names, since these would otherwise be claimed by the parents of further nested
// layers and we'd have nowhere to put them when resolving conflicts. (Dotted entries that conflict can keep their dotted keys).

foreach (var kv in maybeDotted)
{
if (IsDottedIdentifier(kv.Key))
{
// Stash for processing in the next stage.
dotted.Add(kv.Key, kv.Value);
}
else
{
result.Add(kv.Key, kv.Value);
}
}

// Then - for dotted keys with a prefix not already present in the result, convert to structured data and add to
// the result. Any set of dotted names that collide with a preexisting key will be left as-is.

string? prefix = null;
Dictionary<string, LogEventPropertyValue>? nested = null;
foreach (var kv in dotted)
{
var (newPrefix, rem) = TakeFirstIdentifier(kv.Key);

if (prefix != null && prefix != newPrefix)
{
result.Add(prefix, MakeStructureValue(DottedToNestedRecursive(nested!, depth + 1)));
prefix = null;
nested = null;
}

if (nested != null && !nested.ContainsKey(rem))
{
prefix = newPrefix;
nested.Add(rem, kv.Value);
}
else if (nested == null && !result.ContainsKey(newPrefix))
{
prefix = newPrefix;
nested = new () { { rem, kv.Value } };
}
else
{
result.Add(kv.Key, kv.Value);
}
}

if (prefix != null)
{
result[prefix] = MakeStructureValue(DottedToNestedRecursive(nested!, depth + 1));
}

return result;
}

static LogEventPropertyValue MakeStructureValue(IReadOnlyDictionary<string,LogEventPropertyValue> properties)
{
return new StructureValue(properties.Select(kv => new LogEventProperty(kv.Key, kv.Value)), typeTag: null);
}

internal static bool IsDottedIdentifier(string key) =>
key.Contains('.') &&
!key.StartsWith(".", StringComparison.Ordinal) &&
!key.EndsWith(".", StringComparison.Ordinal) &&
key.Split('.').All(IsIdentifier);

static bool IsIdentifier(string s) => s.Length != 0 &&
!char.IsDigit(s[0]) &&
s.All(ch => char.IsLetter(ch) || char.IsDigit(ch) || ch == '_');

static (string, string) TakeFirstIdentifier(string dottedIdentifier)
{
// We can do this simplistically because keys in `dotted` conform to `IsDottedName`.
Debug.Assert(IsDottedIdentifier(dottedIdentifier));

var firstDot = dottedIdentifier.IndexOf('.');
var prefix = dottedIdentifier.Substring(0, firstDot);
var rem = dottedIdentifier.Substring(firstDot + 1);
return (prefix, rem);
}
}
2 changes: 1 addition & 1 deletion src/Serilog.Sinks.Seq/Sinks/Seq/Durable/BookmarkFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public FileSetPosition TryReadBookmark()

if (current != null)
{
var parts = current.Split(new[] { ":::" }, StringSplitOptions.RemoveEmptyEntries);
var parts = current.Split([":::"], StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 2)
{
return new FileSetPosition(long.Parse(parts[0]), parts[1]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
namespace Serilog.Sinks.Seq.Durable;

/// <summary>
/// Based on the BatchedConnectionStatus class from <see cref="Serilog.Sinks.PeriodicBatching.PeriodicBatchingSink"/>.
/// Based on the <c>BatchedConnectionStatus</c> class from <c>Serilog.Sinks.PeriodicBatching</c>.
/// </summary>
sealed class ExponentialBackoffConnectionSchedule
{
Expand All @@ -30,7 +30,7 @@ sealed class ExponentialBackoffConnectionSchedule

public ExponentialBackoffConnectionSchedule(TimeSpan period)
{
if (period < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(period), "The connection retry period must be a positive timespan");
if (period < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(period), "The connection retry period must be a positive timespan.");

_period = period;
}
Expand All @@ -55,7 +55,7 @@ public TimeSpan NextInterval
// Second failure, start ramping up the interval - first 2x, then 4x, ...
var backoffFactor = Math.Pow(2, (_failuresSinceSuccessfulConnection - 1));

// If the period is ridiculously short, give it a boost so we get some
// If the period is ridiculously short, give it a boost so that we get some
// visible backoff.
var backoffPeriod = Math.Max(_period.Ticks, MinimumBackoffPeriod.Ticks);

Expand Down
33 changes: 33 additions & 0 deletions src/Serilog.Sinks.Seq/Sinks/Seq/IDottedPropertyNameConvention.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright © 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.Collections.Generic;
using Serilog.Events;

namespace Serilog.Sinks.Seq;

/// <summary>
/// Enables switching between the experimental "unflattening" behavior applied to dotted property names, and the
/// regular verbatim property name handling.
/// </summary>
interface IDottedPropertyNameConvention
{
/// <summary>
/// Convert the properties in <paramref name="maybeDotted"/> into the form specified by the current property
/// name processing convention.
/// </summary>
/// <param name="maybeDotted">The properties associated with a log event.</param>
/// <returns>The processed properties.</returns>
IReadOnlyDictionary<string, LogEventPropertyValue> ProcessDottedPropertyNames(IReadOnlyDictionary<string, LogEventPropertyValue> maybeDotted);
}
Loading

0 comments on commit fbc68c1

Please sign in to comment.