From c2a6313e775c7ae99eb085e950b0d2a083c6ffc5 Mon Sep 17 00:00:00 2001 From: evilpilaf Date: Tue, 9 Mar 2021 09:33:23 +0100 Subject: [PATCH 01/18] add otel values --- .vscode/tasks.json | 42 +++++++++++ HoneycombSerilogSink.sln | 5 ++ .../Formatters/RawJsonFormatter.cs | 27 ++++++- .../HoneycombSinkExtensions.cs | 7 +- .../HoneycombSerilogSinkTests.cs | 74 +++++++++++++++++++ 5 files changed, 148 insertions(+), 7 deletions(-) create mode 100644 .vscode/tasks.json diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..f92aa1f --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,42 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "${workspaceFolder}/src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/HoneycombSerilogSink.sln b/HoneycombSerilogSink.sln index 12b6491..9194bf6 100644 --- a/HoneycombSerilogSink.sln +++ b/HoneycombSerilogSink.sln @@ -24,6 +24,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{87AFA633-E EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Honeycomb.Serilog.Sink.Tests", "test\Honeycomb.Serilog.Sink.Tests\Honeycomb.Serilog.Sink.Tests.csproj", "{3153A916-94B4-418D-84BD-EB1649449CFF}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ci", "ci", "{F33664C2-6205-4DB6-B00F-0C18887E8272}" +ProjectSection(SolutionItems) = preProject + ci\templates\build-and-package.yml = ci\templates\build-and-package.yml +EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/src/Honeycomb.Serilog.Sink/Formatters/RawJsonFormatter.cs b/src/Honeycomb.Serilog.Sink/Formatters/RawJsonFormatter.cs index 19f6c8d..f7a354f 100644 --- a/src/Honeycomb.Serilog.Sink/Formatters/RawJsonFormatter.cs +++ b/src/Honeycomb.Serilog.Sink/Formatters/RawJsonFormatter.cs @@ -33,6 +33,7 @@ public static void FormatContent(LogEvent logEvent, TextWriter output) output.Write($"{{\"time\":\"{logEvent.Timestamp:O}\","); output.Write("\"data\":{"); output.Write($"\"level\":\"{logEvent.Level}\""); + output.Write(",\"meta.annotation_type\":\"span_event\""); output.Write(",\"messageTemplate\":"); JsonValueFormatter.WriteQuotedJsonString(logEvent.MessageTemplate.Text, output); if (logEvent.Exception != null) @@ -63,12 +64,30 @@ private static void WriteProperties(IReadOnlyDictionary - /// The name of the team to submit the events to + /// The name of the dataset where to send the events to /// The API key given in the Honeycomb ui /// The maximum number of events to include in a single batch. /// The time to wait between checking for event batches. + /// See the official Honeycomb documentation for more details. public static LoggerConfiguration HoneycombSink(this LoggerSinkConfiguration loggerConfiguration, - string teamId, + string dataset, string apiKey, int batchSizeLimit, TimeSpan period) @@ -25,7 +26,7 @@ public static LoggerConfiguration HoneycombSink(this LoggerSinkConfiguration log Period = period }; - return loggerConfiguration.HoneycombSink(teamId, apiKey, batchingOptions); + return loggerConfiguration.HoneycombSink(dataset, apiKey, batchingOptions); } public static LoggerConfiguration HoneycombSink(this LoggerSinkConfiguration loggerConfiguration, diff --git a/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkTests.cs b/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkTests.cs index e83d7d5..8dfee28 100644 --- a/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkTests.cs +++ b/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkTests.cs @@ -224,6 +224,80 @@ public async Task Emit_GivenAMessageWithEmptyPropertyValue_SkipsSendingProperty( } } + [Fact] + public async Task Emit_GivenAMessageWithTraceId_WritesItAsOTELStandard() + { + const string dataset = nameof(dataset); + const string apiKey = nameof(apiKey); + + HttpClientStub clientStub = A.HttpClient(); + + var sut = CreateSut(dataset, apiKey, clientStub); + + var level = LogEventLevel.Information; + + var property = 1; + const string spanId = nameof(spanId); + + var messageTemplateString = $"Testing message property {{{nameof(property)}}} {{TraceId}}"; + + var eventToSend = Some.LogEvent(level, messageTemplateString, property, spanId); + + + await sut.EmitTestable(eventToSend); + + var requestContent = clientStub.RequestContent; + using (var document = JsonDocument.Parse(requestContent)) + using (new AssertionScope()) + { + document.RootElement.ValueKind.Should().Be(JsonValueKind.Array); + document.RootElement.GetArrayLength().Should().Be(1); + JsonElement sentEvent = document.RootElement.EnumerateArray().Single(); + + sentEvent.GetProperty("time").GetDateTimeOffset().Should().Be(eventToSend.Timestamp); + sentEvent.GetProperty("data").ValueKind.Should().Be(JsonValueKind.Object); + sentEvent.GetProperty("data").GetProperty("trace.trace_id").ValueKind.Should().Be(JsonValueKind.String); + sentEvent.GetProperty("data").GetProperty("trace.trace_id").GetString().Should().Be(spanId); + } + } + + [Fact] + public async Task Emit_GivenAMessageWithParentId_WritesItAsOTELStandard() + { + const string dataset = nameof(dataset); + const string apiKey = nameof(apiKey); + + HttpClientStub clientStub = A.HttpClient(); + + var sut = CreateSut(dataset, apiKey, clientStub); + + var level = LogEventLevel.Information; + + var property = 1; + const string parentId = nameof(parentId); + + var messageTemplateString = $"Testing message property {{{nameof(property)}}} {{ParentId}}"; + + var eventToSend = Some.LogEvent(level, messageTemplateString, property, parentId); + + + await sut.EmitTestable(eventToSend); + + var requestContent = clientStub.RequestContent; + using (var document = JsonDocument.Parse(requestContent)) + using (new AssertionScope()) + { + document.RootElement.ValueKind.Should().Be(JsonValueKind.Array); + document.RootElement.GetArrayLength().Should().Be(1); + JsonElement sentEvent = document.RootElement.EnumerateArray().Single(); + + sentEvent.GetProperty("time").GetDateTimeOffset().Should().Be(eventToSend.Timestamp); + sentEvent.GetProperty("data").ValueKind.Should().Be(JsonValueKind.Object); + sentEvent.GetProperty("data").GetProperty("trace.parent_id").ValueKind.Should().Be(JsonValueKind.String); + sentEvent.GetProperty("data").GetProperty("trace.parent_id").GetString().Should().Be(parentId); + } + } + private HoneycombSerilogSinkStub CreateSut(string dataset, string apiKey, HttpClient client = null) { return new HoneycombSerilogSinkStub(client, dataset, apiKey); From 63ca8280297031b24fd7c83fcae59d8f72aabf70 Mon Sep 17 00:00:00 2001 From: evilpilaf Date: Tue, 9 Mar 2021 16:10:39 +0100 Subject: [PATCH 02/18] add span_id and library info to the event --- .../Formatters/RawJsonFormatter.cs | 10 +++++++++- src/Honeycomb.Serilog.Sink/HoneycombSerilogSink.cs | 10 ++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/Honeycomb.Serilog.Sink/Formatters/RawJsonFormatter.cs b/src/Honeycomb.Serilog.Sink/Formatters/RawJsonFormatter.cs index f7a354f..7e5b51a 100644 --- a/src/Honeycomb.Serilog.Sink/Formatters/RawJsonFormatter.cs +++ b/src/Honeycomb.Serilog.Sink/Formatters/RawJsonFormatter.cs @@ -11,7 +11,7 @@ namespace Honeycomb.Serilog.Sink.Formatters { internal class RawJsonFormatter : ITextFormatter { - private static readonly JsonValueFormatter ValueFormatter = new JsonValueFormatter(); + private static readonly JsonValueFormatter ValueFormatter = new(); public void Format(LogEvent logEvent, TextWriter output) { @@ -80,6 +80,14 @@ private static void WriteProperties(IReadOnlyDictionary BuildHttpClient(); #else - private static readonly Lazy _clientBuilder = new Lazy(BuildHttpClient); + private static readonly Lazy _clientBuilder = new(BuildHttpClient); protected virtual HttpClient Client => _clientBuilder.Value; #endif private readonly string _apiKey; private readonly string _teamId; - private static readonly Uri _honeycombApiUrl = new Uri(HoneycombBaseUri); + private static readonly Uri _honeycombApiUrl = new(HoneycombBaseUri); + + private static readonly string LibraryVersion = typeof(HoneycombSerilogSink).Assembly.GetName().Version.ToString(); + private static readonly string LibraryName = typeof(HoneycombSerilogSink).Assembly.GetName().Name; private const string JsonContentType = "application/json"; private const string HoneycombBaseUri = "https://api.honeycomb.io/"; @@ -111,6 +115,8 @@ private static void BuildLogEvent(IEnumerable logEvents, TextWriter pa var eventSeparator = ""; foreach (var evnt in logEvents) { + evnt.AddPropertyIfAbsent(new LogEventProperty("library.name", new ScalarValue(LibraryName))); + evnt.AddPropertyIfAbsent(new LogEventProperty("library.version", new ScalarValue(LibraryVersion))); payload.Write(eventSeparator); eventSeparator = ","; RawJsonFormatter.FormatContent(evnt, payload); From 3d00da9f725eebc8ccbb80ef5e8634ab00bb497b Mon Sep 17 00:00:00 2001 From: evilpilaf Date: Tue, 9 Mar 2021 16:24:22 +0100 Subject: [PATCH 03/18] feat: simplify the logic for getting the property name when a transform is desired --- .../Formatters/RawJsonFormatter.cs | 47 +++++++------------ 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/src/Honeycomb.Serilog.Sink/Formatters/RawJsonFormatter.cs b/src/Honeycomb.Serilog.Sink/Formatters/RawJsonFormatter.cs index 7e5b51a..fdf7b19 100644 --- a/src/Honeycomb.Serilog.Sink/Formatters/RawJsonFormatter.cs +++ b/src/Honeycomb.Serilog.Sink/Formatters/RawJsonFormatter.cs @@ -12,6 +12,11 @@ namespace Honeycomb.Serilog.Sink.Formatters internal class RawJsonFormatter : ITextFormatter { private static readonly JsonValueFormatter ValueFormatter = new(); + private static readonly IReadOnlyDictionary PropertyTransforms = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + {"ParentId", "trace.parent_id"}, + {"TraceId", "trace.trace_id"} + }; public void Format(LogEvent logEvent, TextWriter output) { @@ -64,39 +69,21 @@ private static void WriteProperties(IReadOnlyDictionary Date: Tue, 9 Mar 2021 17:22:01 +0100 Subject: [PATCH 04/18] asda --- src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj b/src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj index f94fa6e..60a5dcd 100644 --- a/src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj +++ b/src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj @@ -65,13 +65,16 @@ $(MinVerMajor).$(MinVerMinor).$(MinVerPatch)-pr.$(BUILD_PR) $(PackageVersion).$(MinVerPreRelease) $(PackageVersion)+$(MinVerBuildMetadata) + $(MinVerMajor).$(MinVerMinor).$(MinVerPatch).$(BUILD_ID) $(PackageVersion) - + 0 + $(PackageVersion) + $(MinVerMajor).0.0.0 $(MinVerMajor).$(MinVerMinor).$(MinVerPatch).$(BUILD_ID) From 137b964c7a987b0015bf861caed6b2ad2e9a4a9d Mon Sep 17 00:00:00 2001 From: evilpilaf Date: Thu, 11 Mar 2021 21:18:24 +0100 Subject: [PATCH 05/18] feat: map span id to the trace.parent_id --- src/Honeycomb.Serilog.Sink/Formatters/RawJsonFormatter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Honeycomb.Serilog.Sink/Formatters/RawJsonFormatter.cs b/src/Honeycomb.Serilog.Sink/Formatters/RawJsonFormatter.cs index fdf7b19..4cbafb3 100644 --- a/src/Honeycomb.Serilog.Sink/Formatters/RawJsonFormatter.cs +++ b/src/Honeycomb.Serilog.Sink/Formatters/RawJsonFormatter.cs @@ -14,7 +14,7 @@ internal class RawJsonFormatter : ITextFormatter private static readonly JsonValueFormatter ValueFormatter = new(); private static readonly IReadOnlyDictionary PropertyTransforms = new Dictionary(StringComparer.OrdinalIgnoreCase) { - {"ParentId", "trace.parent_id"}, + {"SpanId", "trace.parent_id"}, {"TraceId", "trace.trace_id"} }; From a3de11f760e15d1c31a5c8be43736ff9fb364032 Mon Sep 17 00:00:00 2001 From: evilpilaf Date: Thu, 11 Mar 2021 21:18:45 +0100 Subject: [PATCH 06/18] WiP: Enricher --- .vscode/launch.json | 27 ++++++++ .vscode/tasks.json | 6 +- Directory.Build.props | 30 +++++++++ HoneycombSerilogSink.sln | 13 +++- src/Directory.Build.props | 18 ++++++ .../Honeycomb.Serilog.Sink.csproj | 11 +--- .../ActivityEnricher.cs | 20 ++++++ .../ActivityExtensions.cs | 64 +++++++++++++++++++ ...LoggerEnrichmentConfigurationExtensions.cs | 21 ++++++ .../Serilog.Enricher.ActivityEnricher.csproj | 23 +++++++ test/Directory.Build.props | 10 +++ .../Honeycomb.Serilog.Sink.Tests.csproj | 7 +- .../HoneycombSerilogSinkTests.cs | 6 +- 13 files changed, 232 insertions(+), 24 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 Directory.Build.props create mode 100644 src/Directory.Build.props create mode 100644 src/Serilog.Enricher.ActivityEnricher/ActivityEnricher.cs create mode 100644 src/Serilog.Enricher.ActivityEnricher/ActivityExtensions.cs create mode 100644 src/Serilog.Enricher.ActivityEnricher/LoggerEnrichmentConfigurationExtensions.cs create mode 100644 src/Serilog.Enricher.ActivityEnricher/Serilog.Enricher.ActivityEnricher.csproj create mode 100644 test/Directory.Build.props diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..a749545 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + "version": "0.2.0", + "configurations": [ + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/test/Honeycomb.Serilog.Sink.Tests/bin/Debug/netcoreapp2.1/Honeycomb.Serilog.Sink.Tests.dll", + "args": [], + "cwd": "${workspaceFolder}/test/Honeycomb.Serilog.Sink.Tests", + // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach", + "processId": "${command:pickProcess}" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index f92aa1f..c10b177 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -7,7 +7,7 @@ "type": "process", "args": [ "build", - "${workspaceFolder}/src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj", + "${workspaceFolder}/test/Honeycomb.Serilog.Sink.Tests/Honeycomb.Serilog.Sink.Tests.csproj", "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], @@ -19,7 +19,7 @@ "type": "process", "args": [ "publish", - "${workspaceFolder}/src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj", + "${workspaceFolder}/test/Honeycomb.Serilog.Sink.Tests/Honeycomb.Serilog.Sink.Tests.csproj", "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], @@ -32,7 +32,7 @@ "args": [ "watch", "run", - "${workspaceFolder}/src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj", + "${workspaceFolder}/test/Honeycomb.Serilog.Sink.Tests/Honeycomb.Serilog.Sink.Tests.csproj", "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..8bf7659 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,30 @@ + + + true + latest + true + latest + enable + $(NoWarn),NETSDK1138,NETSDK1138 + true + true + + + + evilpilaf + https://github.com/evilpilaf/HoneycombSerilogSink/ + evilpilaf © $([System.DateTime]::Now.Year) + true + LICENSE.TXT + true + true + true + true + snupkg + + + + + + + diff --git a/HoneycombSerilogSink.sln b/HoneycombSerilogSink.sln index 9194bf6..cab6a1b 100644 --- a/HoneycombSerilogSink.sln +++ b/HoneycombSerilogSink.sln @@ -25,9 +25,11 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Honeycomb.Serilog.Sink.Tests", "test\Honeycomb.Serilog.Sink.Tests\Honeycomb.Serilog.Sink.Tests.csproj", "{3153A916-94B4-418D-84BD-EB1649449CFF}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ci", "ci", "{F33664C2-6205-4DB6-B00F-0C18887E8272}" -ProjectSection(SolutionItems) = preProject - ci\templates\build-and-package.yml = ci\templates\build-and-package.yml -EndProjectSection + ProjectSection(SolutionItems) = preProject + ci\templates\build-and-package.yml = ci\templates\build-and-package.yml + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serilog.Enricher.ActivityEnricher", "src\Serilog.Enricher.ActivityEnricher\Serilog.Enricher.ActivityEnricher.csproj", "{891082BA-F789-415C-BC8B-6A748E633E95}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -43,6 +45,10 @@ Global {3153A916-94B4-418D-84BD-EB1649449CFF}.Debug|Any CPU.Build.0 = Debug|Any CPU {3153A916-94B4-418D-84BD-EB1649449CFF}.Release|Any CPU.ActiveCfg = Release|Any CPU {3153A916-94B4-418D-84BD-EB1649449CFF}.Release|Any CPU.Build.0 = Release|Any CPU + {891082BA-F789-415C-BC8B-6A748E633E95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {891082BA-F789-415C-BC8B-6A748E633E95}.Debug|Any CPU.Build.0 = Debug|Any CPU + {891082BA-F789-415C-BC8B-6A748E633E95}.Release|Any CPU.ActiveCfg = Release|Any CPU + {891082BA-F789-415C-BC8B-6A748E633E95}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -50,6 +56,7 @@ Global GlobalSection(NestedProjects) = preSolution {053B3440-863F-4330-9FD1-56BC473A6C00} = {F5B2D9C6-6E1E-482F-A216-143C5DB44239} {3153A916-94B4-418D-84BD-EB1649449CFF} = {87AFA633-EBC9-4A0C-AE07-C6616BAFB602} + {891082BA-F789-415C-BC8B-6A748E633E95} = {F5B2D9C6-6E1E-482F-A216-143C5DB44239} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6D3B3C14-041E-4E42-99C4-0D3A19EAB1BA} diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 0000000..2b2ccd8 --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,18 @@ + + + + + netcoreapp2.1;netcoreapp3.1 + $(TargetFrameworks);net461 + + + + Honeycomb Serilog Sink Observability Logging Monitoring + A sink for pushing Serilog structured log events into a Honeycomb instance. + + + + + + + diff --git a/src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj b/src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj index 60a5dcd..a801334 100644 --- a/src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj +++ b/src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj @@ -1,19 +1,12 @@  - net461;netcoreapp2.1;netcoreapp3.1 + netcoreapp2.1;netcoreapp3.1 + $TargetFrameworks;net461 Honeycomb Serilog sink - evilpilaf Honeycomb Serilog Sink Observability Logging Monitoring https://github.com/evilpilaf/HoneycombSerilogSink/ A sink for pushing Serilog structured log events into a Honeycomb instance. - evilpilaf © $([System.DateTime]::Now.Year) - true - LICENSE.TXT - latest - enable - true - true diff --git a/src/Serilog.Enricher.ActivityEnricher/ActivityEnricher.cs b/src/Serilog.Enricher.ActivityEnricher/ActivityEnricher.cs new file mode 100644 index 0000000..f96773a --- /dev/null +++ b/src/Serilog.Enricher.ActivityEnricher/ActivityEnricher.cs @@ -0,0 +1,20 @@ +namespace Serilog.Enricher.ActivityEnricher +{ + using System.Diagnostics; + using Serilog.Core; + using Serilog.Events; + + public class ActivityEnricher : ILogEventEnricher + { + /// + public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) + { + var current = Activity.Current; + if (current is not null) + { + logEvent.AddPropertyIfAbsent(new LogEventProperty("trace.parent_id", new ScalarValue(current.GetSpanId()))); + logEvent.AddPropertyIfAbsent(new LogEventProperty("trace.trace_id", new ScalarValue(current.GetTraceId()))); + } + } + } +} diff --git a/src/Serilog.Enricher.ActivityEnricher/ActivityExtensions.cs b/src/Serilog.Enricher.ActivityEnricher/ActivityExtensions.cs new file mode 100644 index 0000000..578bc8a --- /dev/null +++ b/src/Serilog.Enricher.ActivityEnricher/ActivityExtensions.cs @@ -0,0 +1,64 @@ +namespace Serilog.Enricher.ActivityEnricher +{ + using System.Diagnostics; + + /// + /// extension methods. + /// + internal static class ActivityExtensions + { + /// + /// Gets the span unique identifier regardless of the activity identifier format. + /// + /// The activity. + /// The span unique identifier. + public static string GetSpanId(this Activity activity) + { + var spanId = activity.IdFormat switch + { + ActivityIdFormat.Hierarchical => activity.Id, + ActivityIdFormat.W3C => activity.SpanId.ToHexString(), + ActivityIdFormat.Unknown => null, + _ => null, + }; + + return spanId ?? string.Empty; + } + + /// + /// Gets the span trace unique identifier regardless of the activity identifier format. + /// + /// The activity. + /// The span trace unique identifier. + public static string GetTraceId(this Activity activity) + { + var traceId = activity.IdFormat switch + { + ActivityIdFormat.Hierarchical => activity.RootId, + ActivityIdFormat.W3C => activity.TraceId.ToHexString(), + ActivityIdFormat.Unknown => null, + _ => null, + }; + + return traceId ?? string.Empty; + } + + /// + /// Gets the span parent unique identifier regardless of the activity identifier format. + /// + /// The activity. + /// The span parent unique identifier. + public static string GetParentId(this Activity activity) + { + var parentId = activity.IdFormat switch + { + ActivityIdFormat.Hierarchical => activity.ParentId, + ActivityIdFormat.W3C => activity.ParentSpanId.ToHexString(), + ActivityIdFormat.Unknown => null, + _ => null, + }; + + return parentId ?? string.Empty; + } + } +} diff --git a/src/Serilog.Enricher.ActivityEnricher/LoggerEnrichmentConfigurationExtensions.cs b/src/Serilog.Enricher.ActivityEnricher/LoggerEnrichmentConfigurationExtensions.cs new file mode 100644 index 0000000..8960ba9 --- /dev/null +++ b/src/Serilog.Enricher.ActivityEnricher/LoggerEnrichmentConfigurationExtensions.cs @@ -0,0 +1,21 @@ +namespace Serilog.Enricher.ActivityEnricher +{ + using System; + using Serilog.Configuration; + + + /// + /// extension methods. + /// + public static class LoggerEnrichmentConfigurationExtensions + { + public static LoggerConfiguration WithActivity(this LoggerEnrichmentConfiguration self) + { + if (self is null) + { + throw new ArgumentNullException(nameof(self)); + } + return self.With(); + } + } +} diff --git a/src/Serilog.Enricher.ActivityEnricher/Serilog.Enricher.ActivityEnricher.csproj b/src/Serilog.Enricher.ActivityEnricher/Serilog.Enricher.ActivityEnricher.csproj new file mode 100644 index 0000000..d6c9688 --- /dev/null +++ b/src/Serilog.Enricher.ActivityEnricher/Serilog.Enricher.ActivityEnricher.csproj @@ -0,0 +1,23 @@ + + + + net5.0; netstandard2.0 + + + + Serilog.Enrichers.Activity + Enrich Serilog log events with properties from open telemetry spans. + Serilog;Span;Activity;Open Telemetry;Logging;Log;Trace;Telemetry + icon124x124.png + + + + + + + + + + + + diff --git a/test/Directory.Build.props b/test/Directory.Build.props new file mode 100644 index 0000000..dae7f0a --- /dev/null +++ b/test/Directory.Build.props @@ -0,0 +1,10 @@ + + + + + netcoreapp2.1;netcoreapp3.0;netcoreapp3.1;net5.0 + $(TargetFrameworks);net461;net462;net47;net471;net472;net48; + false + + + diff --git a/test/Honeycomb.Serilog.Sink.Tests/Honeycomb.Serilog.Sink.Tests.csproj b/test/Honeycomb.Serilog.Sink.Tests/Honeycomb.Serilog.Sink.Tests.csproj index 9cbf309..a01c716 100644 --- a/test/Honeycomb.Serilog.Sink.Tests/Honeycomb.Serilog.Sink.Tests.csproj +++ b/test/Honeycomb.Serilog.Sink.Tests/Honeycomb.Serilog.Sink.Tests.csproj @@ -1,10 +1,5 @@  - - net461;net462;net47;net471;net472;net48;netcoreapp2.1;netcoreapp3.0;netcoreapp3.1;net5.0 - false - - @@ -26,7 +21,7 @@ - + diff --git a/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkTests.cs b/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkTests.cs index 8dfee28..8a7357a 100644 --- a/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkTests.cs +++ b/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkTests.cs @@ -242,7 +242,7 @@ public async Task Emit_GivenAMessageWithTraceId_WritesItAsOTELStandard() var messageTemplateString = $"Testing message property {{{nameof(property)}}} {{TraceId}}"; var eventToSend = Some.LogEvent(level, messageTemplateString, property, spanId); - + await sut.EmitTestable(eventToSend); @@ -276,10 +276,10 @@ public async Task Emit_GivenAMessageWithParentId_WritesItAsOTELStandard() var property = 1; const string parentId = nameof(parentId); - var messageTemplateString = $"Testing message property {{{nameof(property)}}} {{ParentId}}"; + var messageTemplateString = $"Testing message property {{{nameof(property)}}} {{SpanId}}"; var eventToSend = Some.LogEvent(level, messageTemplateString, property, parentId); - + await sut.EmitTestable(eventToSend); From 2249115d3f47e2b9b50c4e943afd0155ba221b98 Mon Sep 17 00:00:00 2001 From: evilpilaf Date: Sat, 13 Mar 2021 16:02:54 +0100 Subject: [PATCH 07/18] move activity enricher into the same csproj --- HoneycombSerilogSink.sln | 7 ------ .../Enricher}/ActivityEnricher.cs | 10 ++++---- .../Enricher}/ActivityExtensions.cs | 2 +- ...LoggerEnrichmentConfigurationExtensions.cs | 10 ++++---- .../Formatters/RawJsonFormatter.cs | 6 ++++- .../Honeycomb.Serilog.Sink.csproj | 3 ++- .../HoneycombSinkExtensions.cs | 7 +++--- .../{ => Sink}/HoneycombSerilogSink.cs | 0 .../Serilog.Enricher.ActivityEnricher.csproj | 23 ------------------- 9 files changed, 21 insertions(+), 47 deletions(-) rename src/{Serilog.Enricher.ActivityEnricher => Honeycomb.Serilog.Sink/Enricher}/ActivityEnricher.cs (81%) rename src/{Serilog.Enricher.ActivityEnricher => Honeycomb.Serilog.Sink/Enricher}/ActivityExtensions.cs (98%) rename src/{Serilog.Enricher.ActivityEnricher => Honeycomb.Serilog.Sink/Enricher}/LoggerEnrichmentConfigurationExtensions.cs (82%) rename src/Honeycomb.Serilog.Sink/{ => Sink}/HoneycombSerilogSink.cs (100%) delete mode 100644 src/Serilog.Enricher.ActivityEnricher/Serilog.Enricher.ActivityEnricher.csproj diff --git a/HoneycombSerilogSink.sln b/HoneycombSerilogSink.sln index cab6a1b..dae52aa 100644 --- a/HoneycombSerilogSink.sln +++ b/HoneycombSerilogSink.sln @@ -29,8 +29,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ci", "ci", "{F33664C2-6205- ci\templates\build-and-package.yml = ci\templates\build-and-package.yml EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serilog.Enricher.ActivityEnricher", "src\Serilog.Enricher.ActivityEnricher\Serilog.Enricher.ActivityEnricher.csproj", "{891082BA-F789-415C-BC8B-6A748E633E95}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -45,10 +43,6 @@ Global {3153A916-94B4-418D-84BD-EB1649449CFF}.Debug|Any CPU.Build.0 = Debug|Any CPU {3153A916-94B4-418D-84BD-EB1649449CFF}.Release|Any CPU.ActiveCfg = Release|Any CPU {3153A916-94B4-418D-84BD-EB1649449CFF}.Release|Any CPU.Build.0 = Release|Any CPU - {891082BA-F789-415C-BC8B-6A748E633E95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {891082BA-F789-415C-BC8B-6A748E633E95}.Debug|Any CPU.Build.0 = Debug|Any CPU - {891082BA-F789-415C-BC8B-6A748E633E95}.Release|Any CPU.ActiveCfg = Release|Any CPU - {891082BA-F789-415C-BC8B-6A748E633E95}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -56,7 +50,6 @@ Global GlobalSection(NestedProjects) = preSolution {053B3440-863F-4330-9FD1-56BC473A6C00} = {F5B2D9C6-6E1E-482F-A216-143C5DB44239} {3153A916-94B4-418D-84BD-EB1649449CFF} = {87AFA633-EBC9-4A0C-AE07-C6616BAFB602} - {891082BA-F789-415C-BC8B-6A748E633E95} = {F5B2D9C6-6E1E-482F-A216-143C5DB44239} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6D3B3C14-041E-4E42-99C4-0D3A19EAB1BA} diff --git a/src/Serilog.Enricher.ActivityEnricher/ActivityEnricher.cs b/src/Honeycomb.Serilog.Sink/Enricher/ActivityEnricher.cs similarity index 81% rename from src/Serilog.Enricher.ActivityEnricher/ActivityEnricher.cs rename to src/Honeycomb.Serilog.Sink/Enricher/ActivityEnricher.cs index f96773a..6efb6fd 100644 --- a/src/Serilog.Enricher.ActivityEnricher/ActivityEnricher.cs +++ b/src/Honeycomb.Serilog.Sink/Enricher/ActivityEnricher.cs @@ -1,9 +1,9 @@ -namespace Serilog.Enricher.ActivityEnricher -{ - using System.Diagnostics; - using Serilog.Core; - using Serilog.Events; +using System.Diagnostics; +using Serilog.Core; +using Serilog.Events; +namespace Honeycomb.Serilog.Sink.Enricher +{ public class ActivityEnricher : ILogEventEnricher { /// diff --git a/src/Serilog.Enricher.ActivityEnricher/ActivityExtensions.cs b/src/Honeycomb.Serilog.Sink/Enricher/ActivityExtensions.cs similarity index 98% rename from src/Serilog.Enricher.ActivityEnricher/ActivityExtensions.cs rename to src/Honeycomb.Serilog.Sink/Enricher/ActivityExtensions.cs index 578bc8a..e6a4f8c 100644 --- a/src/Serilog.Enricher.ActivityEnricher/ActivityExtensions.cs +++ b/src/Honeycomb.Serilog.Sink/Enricher/ActivityExtensions.cs @@ -1,4 +1,4 @@ -namespace Serilog.Enricher.ActivityEnricher +namespace Honeycomb.Serilog.Sink.Enricher { using System.Diagnostics; diff --git a/src/Serilog.Enricher.ActivityEnricher/LoggerEnrichmentConfigurationExtensions.cs b/src/Honeycomb.Serilog.Sink/Enricher/LoggerEnrichmentConfigurationExtensions.cs similarity index 82% rename from src/Serilog.Enricher.ActivityEnricher/LoggerEnrichmentConfigurationExtensions.cs rename to src/Honeycomb.Serilog.Sink/Enricher/LoggerEnrichmentConfigurationExtensions.cs index 8960ba9..c43002b 100644 --- a/src/Serilog.Enricher.ActivityEnricher/LoggerEnrichmentConfigurationExtensions.cs +++ b/src/Honeycomb.Serilog.Sink/Enricher/LoggerEnrichmentConfigurationExtensions.cs @@ -1,9 +1,9 @@ -namespace Serilog.Enricher.ActivityEnricher -{ - using System; - using Serilog.Configuration; - +using System; +using Serilog; +using Serilog.Configuration; +namespace Honeycomb.Serilog.Sink.Enricher +{ /// /// extension methods. /// diff --git a/src/Honeycomb.Serilog.Sink/Formatters/RawJsonFormatter.cs b/src/Honeycomb.Serilog.Sink/Formatters/RawJsonFormatter.cs index 4cbafb3..8357f73 100644 --- a/src/Honeycomb.Serilog.Sink/Formatters/RawJsonFormatter.cs +++ b/src/Honeycomb.Serilog.Sink/Formatters/RawJsonFormatter.cs @@ -43,8 +43,12 @@ public static void FormatContent(LogEvent logEvent, TextWriter output) JsonValueFormatter.WriteQuotedJsonString(logEvent.MessageTemplate.Text, output); if (logEvent.Exception != null) { - output.Write(",\"exception\":"); + output.Write(",\"exception.type\":"); + JsonValueFormatter.WriteQuotedJsonString(logEvent.Exception.GetType().ToString(), output); + output.Write(",\"exception.message\":"); JsonValueFormatter.WriteQuotedJsonString(logEvent.Exception.ToString(), output); + output.Write(",\"exception.stacktrace\":"); + JsonValueFormatter.WriteQuotedJsonString(logEvent.Exception.StackTrace, output); } if (logEvent.Properties.Any()) diff --git a/src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj b/src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj index a801334..19da87e 100644 --- a/src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj +++ b/src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj @@ -24,7 +24,6 @@ - @@ -40,6 +39,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -47,6 +47,7 @@ + diff --git a/src/Honeycomb.Serilog.Sink/HoneycombSinkExtensions.cs b/src/Honeycomb.Serilog.Sink/HoneycombSinkExtensions.cs index d537e0a..56fd7af 100644 --- a/src/Honeycomb.Serilog.Sink/HoneycombSinkExtensions.cs +++ b/src/Honeycomb.Serilog.Sink/HoneycombSinkExtensions.cs @@ -1,5 +1,5 @@ using System; - +using Honeycomb.Serilog.Sink.Enricher; using Serilog; using Serilog.Configuration; using Serilog.Sinks.PeriodicBatching; @@ -25,8 +25,7 @@ public static LoggerConfiguration HoneycombSink(this LoggerSinkConfiguration log BatchSizeLimit = batchSizeLimit, Period = period }; - - return loggerConfiguration.HoneycombSink(dataset, apiKey, batchingOptions); + return loggerConfiguration.HoneycombSink(dataset, apiKey, batchingOptions).Enrich.WithActivity(); } public static LoggerConfiguration HoneycombSink(this LoggerSinkConfiguration loggerConfiguration, @@ -38,7 +37,7 @@ public static LoggerConfiguration HoneycombSink(this LoggerSinkConfiguration log var batchingSink = new PeriodicBatchingSink(honeycombSink, batchingOptions ?? new PeriodicBatchingSinkOptions()); - return loggerConfiguration.Sink(batchingSink); + return loggerConfiguration.Sink(batchingSink).Enrich.WithActivity(); } } } diff --git a/src/Honeycomb.Serilog.Sink/HoneycombSerilogSink.cs b/src/Honeycomb.Serilog.Sink/Sink/HoneycombSerilogSink.cs similarity index 100% rename from src/Honeycomb.Serilog.Sink/HoneycombSerilogSink.cs rename to src/Honeycomb.Serilog.Sink/Sink/HoneycombSerilogSink.cs diff --git a/src/Serilog.Enricher.ActivityEnricher/Serilog.Enricher.ActivityEnricher.csproj b/src/Serilog.Enricher.ActivityEnricher/Serilog.Enricher.ActivityEnricher.csproj deleted file mode 100644 index d6c9688..0000000 --- a/src/Serilog.Enricher.ActivityEnricher/Serilog.Enricher.ActivityEnricher.csproj +++ /dev/null @@ -1,23 +0,0 @@ - - - - net5.0; netstandard2.0 - - - - Serilog.Enrichers.Activity - Enrich Serilog log events with properties from open telemetry spans. - Serilog;Span;Activity;Open Telemetry;Logging;Log;Trace;Telemetry - icon124x124.png - - - - - - - - - - - - From d0c41717522255347ee0d4d339dc308a61741c9a Mon Sep 17 00:00:00 2001 From: evilpilaf Date: Sat, 13 Mar 2021 16:50:04 +0100 Subject: [PATCH 08/18] use demystified stack traces and use otel structure for exceptions --- .../Formatters/RawJsonFormatter.cs | 3 ++- .../HoneycombSerilogSinkStub.cs | 7 ++++--- .../HoneycombSerilogSinkTests.cs | 21 ++++++++++++++----- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/Honeycomb.Serilog.Sink/Formatters/RawJsonFormatter.cs b/src/Honeycomb.Serilog.Sink/Formatters/RawJsonFormatter.cs index 8357f73..8108ce2 100644 --- a/src/Honeycomb.Serilog.Sink/Formatters/RawJsonFormatter.cs +++ b/src/Honeycomb.Serilog.Sink/Formatters/RawJsonFormatter.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; @@ -46,7 +47,7 @@ public static void FormatContent(LogEvent logEvent, TextWriter output) output.Write(",\"exception.type\":"); JsonValueFormatter.WriteQuotedJsonString(logEvent.Exception.GetType().ToString(), output); output.Write(",\"exception.message\":"); - JsonValueFormatter.WriteQuotedJsonString(logEvent.Exception.ToString(), output); + JsonValueFormatter.WriteQuotedJsonString(logEvent.Exception.ToStringDemystified(), output); output.Write(",\"exception.stacktrace\":"); JsonValueFormatter.WriteQuotedJsonString(logEvent.Exception.StackTrace, output); } diff --git a/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkStub.cs b/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkStub.cs index 630a330..e3e05fc 100644 --- a/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkStub.cs +++ b/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkStub.cs @@ -1,4 +1,5 @@ -using System.Net.Http; +using System; +using System.Net.Http; using System.Threading.Tasks; using Serilog.Events; @@ -9,10 +10,10 @@ internal class HoneycombSerilogSinkStub : HoneycombSerilogSink { private readonly HttpClient _client; - public HoneycombSerilogSinkStub(HttpClient client, string dataset, string apiKey) + public HoneycombSerilogSinkStub(HttpClient? client, string dataset, string apiKey) : base(dataset, apiKey) { - _client = client; + _client = client ?? throw new ArgumentNullException(nameof(client)); } protected override HttpClient Client => _client; diff --git a/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkTests.cs b/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkTests.cs index 8a7357a..42cabb0 100644 --- a/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkTests.cs +++ b/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkTests.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using System.Net.Http; using System.Text.Json; @@ -124,7 +125,7 @@ public async Task Emit_GivenAnExceptionToLog_SerializesLogMessageAsJson_Includes var level = LogEventLevel.Fatal; var messageTempalteString = "Testing message {message}"; - var ex = new Exception("TestException"); + var ex = new TestException("TestException"); var eventToSend = Some.LogEvent(level, ex, messageTempalteString); @@ -143,8 +144,10 @@ public async Task Emit_GivenAnExceptionToLog_SerializesLogMessageAsJson_Includes JsonElement data = sentEvent.GetProperty("data"); data.GetProperty("level").GetString().Should().Be(level.ToString()); - data.GetProperty("messageTemplate").GetString().Should().Be(messageTempalteString); - data.GetProperty("exception").GetString().Should().Be(ex.ToString()); + data.GetProperty("exception.type").GetString().Should().Be(ex.GetType().ToString()); + data.GetProperty("exception.message").GetString().Should().Be(ex.ToStringDemystified()); + data.GetProperty("exception.stacktrace").GetString().Should().Be(ex.StackTrace); + } } @@ -280,7 +283,6 @@ public async Task Emit_GivenAMessageWithParentId_WritesItAsOTELStandard() var eventToSend = Some.LogEvent(level, messageTemplateString, property, parentId); - await sut.EmitTestable(eventToSend); var requestContent = clientStub.RequestContent; @@ -298,9 +300,18 @@ public async Task Emit_GivenAMessageWithParentId_WritesItAsOTELStandard() } } - private HoneycombSerilogSinkStub CreateSut(string dataset, string apiKey, HttpClient client = null) + private static HoneycombSerilogSinkStub CreateSut(string dataset, string apiKey, HttpClient? client = null) { return new HoneycombSerilogSinkStub(client, dataset, apiKey); } } + + internal class TestException : Exception + { + public TestException(string message) : base(message) + { + } + + public override string StackTrace => nameof(StackTrace); + } } From 7e350ccdcec51220222d616168ec5a601f78ffa8 Mon Sep 17 00:00:00 2001 From: evilpilaf Date: Mon, 15 Mar 2021 10:00:41 +0100 Subject: [PATCH 09/18] feat: allow setting custon uri for honeycomb --- .../Sink/HoneycombSerilogSink.cs | 61 +++++++++++++------ 1 file changed, 41 insertions(+), 20 deletions(-) diff --git a/src/Honeycomb.Serilog.Sink/Sink/HoneycombSerilogSink.cs b/src/Honeycomb.Serilog.Sink/Sink/HoneycombSerilogSink.cs index 5bd37ba..86a0061 100644 --- a/src/Honeycomb.Serilog.Sink/Sink/HoneycombSerilogSink.cs +++ b/src/Honeycomb.Serilog.Sink/Sink/HoneycombSerilogSink.cs @@ -17,24 +17,29 @@ namespace Honeycomb.Serilog.Sink internal class HoneycombSerilogSink : IBatchedLogEventSink, IDisposable { #if NETCOREAPP - private static SocketsHttpHandler? _socketsHttpHandler; - - private static SocketsHttpHandler SocketsHttpHandler - { - get - { - return _socketsHttpHandler ??= new SocketsHttpHandler { PooledConnectionLifetime = TimeSpan.FromMinutes(30) }; - } - } + private static Lazy _socketsHttpHandler + = new(() => new SocketsHttpHandler { PooledConnectionLifetime = TimeSpan.FromMinutes(30) }); protected virtual HttpClient Client => BuildHttpClient(); #else private static readonly Lazy _clientBuilder = new(BuildHttpClient); protected virtual HttpClient Client => _clientBuilder.Value; #endif + + private readonly Func _httpClientFactory = () => + { + HttpClient client; +#if NETCOREAPP + client = new HttpClient(_socketsHttpHandler.Value, disposeHandler: false); +#else + client = new HttpClient(); +#endif + return client; + }; + private readonly string _apiKey; private readonly string _teamId; - private static readonly Uri _honeycombApiUrl = new(HoneycombBaseUri); + private readonly Uri _honeycombApiUrl; private static readonly string LibraryVersion = typeof(HoneycombSerilogSink).Assembly.GetName().Version.ToString(); private static readonly string LibraryName = typeof(HoneycombSerilogSink).Assembly.GetName().Name; @@ -48,10 +53,30 @@ private static SocketsHttpHandler SocketsHttpHandler /// The name of the dataset where to send the events to /// The API key given in the Honeycomb ui - public HoneycombSerilogSink(string dataset, string apiKey) + /// The URL where to send the events. Default https://api.honeycomb.io + public HoneycombSerilogSink(string? dataset, string? apiKey, Func? httpClientFactory = null, string? honeycombUrl = HoneycombBaseUri) { - _teamId = string.IsNullOrWhiteSpace(dataset) ? throw new ArgumentNullException(nameof(dataset)) : dataset; - _apiKey = string.IsNullOrWhiteSpace(apiKey) ? throw new ArgumentNullException(nameof(apiKey)) : apiKey; + if (dataset is not null && !string.IsNullOrWhiteSpace(dataset)) + { + _teamId = dataset; + } + else + { + throw new ArgumentNullException(nameof(dataset)); + } + if (apiKey is not null && !string.IsNullOrWhiteSpace(apiKey)) + { + _apiKey = dataset; + } + else + { + throw new ArgumentNullException(nameof(apiKey)); + } + if (httpClientFactory is not null) + { + _httpClientFactory = httpClientFactory; + } + _honeycombApiUrl = new Uri(honeycombUrl); } public async Task EmitBatchAsync(IEnumerable events) @@ -124,14 +149,10 @@ private static void BuildLogEvent(IEnumerable logEvents, TextWriter pa payload.Write("]"); } - private static HttpClient BuildHttpClient() + private HttpClient BuildHttpClient() { - HttpClient client; -#if NETCOREAPP - client = new HttpClient(SocketsHttpHandler, disposeHandler: false); -#else - client = new HttpClient(); -#endif + var client = _httpClientFactory(); + client.BaseAddress = _honeycombApiUrl; return client; From 0ebafbb5ac96b583d3bc7fd567ae4eee3ed3a4bd Mon Sep 17 00:00:00 2001 From: evilpilaf Date: Mon, 15 Mar 2021 12:18:19 +0100 Subject: [PATCH 10/18] fix: add build.props to solution and fix targetframeworks --- HoneycombSerilogSink.sln | 7 +++++++ src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj | 2 -- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/HoneycombSerilogSink.sln b/HoneycombSerilogSink.sln index dae52aa..c42e487 100644 --- a/HoneycombSerilogSink.sln +++ b/HoneycombSerilogSink.sln @@ -6,6 +6,9 @@ MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Honeycomb.Serilog.Sink", "src\Honeycomb.Serilog.Sink\Honeycomb.Serilog.Sink.csproj", "{053B3440-863F-4330-9FD1-56BC473A6C00}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F5B2D9C6-6E1E-482F-A216-143C5DB44239}" + ProjectSection(SolutionItems) = preProject + src\Directory.Build.props = src\Directory.Build.props + EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E4F55B6D-46A4-49ED-B2B6-34CF950436F7}" ProjectSection(SolutionItems) = preProject @@ -14,6 +17,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .gitignore = .gitignore azure-pipelines.yml = azure-pipelines.yml CODE_OF_CONDUCT.md = CODE_OF_CONDUCT.md + Directory.Build.props = Directory.Build.props global.json = global.json LICENSE.TXT = LICENSE.TXT README.md = README.md @@ -21,6 +25,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{87AFA633-EBC9-4A0C-AE07-C6616BAFB602}" + ProjectSection(SolutionItems) = preProject + test\Directory.Build.props = test\Directory.Build.props + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Honeycomb.Serilog.Sink.Tests", "test\Honeycomb.Serilog.Sink.Tests\Honeycomb.Serilog.Sink.Tests.csproj", "{3153A916-94B4-418D-84BD-EB1649449CFF}" EndProject diff --git a/src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj b/src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj index 19da87e..2b48787 100644 --- a/src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj +++ b/src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj @@ -1,8 +1,6 @@  - netcoreapp2.1;netcoreapp3.1 - $TargetFrameworks;net461 Honeycomb Serilog sink Honeycomb Serilog Sink Observability Logging Monitoring https://github.com/evilpilaf/HoneycombSerilogSink/ From 8bd1a6aec8fbd442b2491cc6fc062af627ec0e9d Mon Sep 17 00:00:00 2001 From: evilpilaf Date: Mon, 15 Mar 2021 13:58:17 +0100 Subject: [PATCH 11/18] feat: rollback span and trace id in favor of inner activity enricher --- .../Formatters/RawJsonFormatter.cs | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/Honeycomb.Serilog.Sink/Formatters/RawJsonFormatter.cs b/src/Honeycomb.Serilog.Sink/Formatters/RawJsonFormatter.cs index 8108ce2..4a94392 100644 --- a/src/Honeycomb.Serilog.Sink/Formatters/RawJsonFormatter.cs +++ b/src/Honeycomb.Serilog.Sink/Formatters/RawJsonFormatter.cs @@ -13,11 +13,6 @@ namespace Honeycomb.Serilog.Sink.Formatters internal class RawJsonFormatter : ITextFormatter { private static readonly JsonValueFormatter ValueFormatter = new(); - private static readonly IReadOnlyDictionary PropertyTransforms = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - {"SpanId", "trace.parent_id"}, - {"TraceId", "trace.trace_id"} - }; public void Format(LogEvent logEvent, TextWriter output) { @@ -76,19 +71,10 @@ private static void WriteProperties(IReadOnlyDictionary Date: Mon, 15 Mar 2021 13:59:38 +0100 Subject: [PATCH 12/18] feat: namespace --- src/Honeycomb.Serilog.Sink/HoneycombSinkExtensions.cs | 1 + src/Honeycomb.Serilog.Sink/Sink/HoneycombSerilogSink.cs | 2 +- test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkStub.cs | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Honeycomb.Serilog.Sink/HoneycombSinkExtensions.cs b/src/Honeycomb.Serilog.Sink/HoneycombSinkExtensions.cs index 56fd7af..eb3a884 100644 --- a/src/Honeycomb.Serilog.Sink/HoneycombSinkExtensions.cs +++ b/src/Honeycomb.Serilog.Sink/HoneycombSinkExtensions.cs @@ -1,5 +1,6 @@ using System; using Honeycomb.Serilog.Sink.Enricher; +using Honeycomb.Serilog.Sink.Sink; using Serilog; using Serilog.Configuration; using Serilog.Sinks.PeriodicBatching; diff --git a/src/Honeycomb.Serilog.Sink/Sink/HoneycombSerilogSink.cs b/src/Honeycomb.Serilog.Sink/Sink/HoneycombSerilogSink.cs index 86a0061..9224e2c 100644 --- a/src/Honeycomb.Serilog.Sink/Sink/HoneycombSerilogSink.cs +++ b/src/Honeycomb.Serilog.Sink/Sink/HoneycombSerilogSink.cs @@ -12,7 +12,7 @@ using Serilog.Events; using Serilog.Sinks.PeriodicBatching; -namespace Honeycomb.Serilog.Sink +namespace Honeycomb.Serilog.Sink.Sink { internal class HoneycombSerilogSink : IBatchedLogEventSink, IDisposable { diff --git a/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkStub.cs b/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkStub.cs index e3e05fc..6af9ae0 100644 --- a/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkStub.cs +++ b/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkStub.cs @@ -2,6 +2,8 @@ using System.Net.Http; using System.Threading.Tasks; +using Honeycomb.Serilog.Sink.Sink; + using Serilog.Events; namespace Honeycomb.Serilog.Sink.Tests From ade766ffc81af9408e42b5249208e3b9af67aa21 Mon Sep 17 00:00:00 2001 From: evilpilaf Date: Mon, 15 Mar 2021 14:01:49 +0100 Subject: [PATCH 13/18] feat: allow override of sink url and simplify the logic --- .../Sink/HoneycombSerilogSink.cs | 76 +++------ .../Builders/HttpClientBuilder.cs | 7 +- .../Helpers/HttpMessageHandlerStub.cs | 13 +- .../Helpers/Some.cs | 10 +- .../HoneycombSerilogSinkStub.cs | 15 +- .../HoneycombSerilogSinkTests.cs | 153 +++++++----------- 6 files changed, 102 insertions(+), 172 deletions(-) diff --git a/src/Honeycomb.Serilog.Sink/Sink/HoneycombSerilogSink.cs b/src/Honeycomb.Serilog.Sink/Sink/HoneycombSerilogSink.cs index 9224e2c..8d1405a 100644 --- a/src/Honeycomb.Serilog.Sink/Sink/HoneycombSerilogSink.cs +++ b/src/Honeycomb.Serilog.Sink/Sink/HoneycombSerilogSink.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.IO; using System.Net.Http; -using System.Reflection; using System.Text; using System.Threading.Tasks; @@ -16,36 +15,26 @@ namespace Honeycomb.Serilog.Sink.Sink { internal class HoneycombSerilogSink : IBatchedLogEventSink, IDisposable { -#if NETCOREAPP - private static Lazy _socketsHttpHandler - = new(() => new SocketsHttpHandler { PooledConnectionLifetime = TimeSpan.FromMinutes(30) }); - - protected virtual HttpClient Client => BuildHttpClient(); + private static readonly Lazy _messageHandler = new(() => +#if NETCOREAPP2_0_OR_GREATER + new SocketsHttpHandler { PooledConnectionLifetime = TimeSpan.FromMinutes(30) } #else - private static readonly Lazy _clientBuilder = new(BuildHttpClient); - protected virtual HttpClient Client => _clientBuilder.Value; + new HttpClientHandler() #endif + ); - private readonly Func _httpClientFactory = () => - { - HttpClient client; -#if NETCOREAPP - client = new HttpClient(_socketsHttpHandler.Value, disposeHandler: false); -#else - client = new HttpClient(); -#endif - return client; - }; + private readonly Func _httpClientFactory + = () => new HttpClient(_messageHandler.Value, disposeHandler: false); private readonly string _apiKey; private readonly string _teamId; private readonly Uri _honeycombApiUrl; - private static readonly string LibraryVersion = typeof(HoneycombSerilogSink).Assembly.GetName().Version.ToString(); - private static readonly string LibraryName = typeof(HoneycombSerilogSink).Assembly.GetName().Name; + private static readonly string LibraryVersion = typeof(HoneycombSerilogSink).Assembly.GetName().Version?.ToString() ?? "1.0.0.0"; + private static readonly string LibraryName = typeof(HoneycombSerilogSink).Assembly.GetName().Name ?? "Honeycomb.Serilog.Sink"; private const string JsonContentType = "application/json"; - private const string HoneycombBaseUri = "https://api.honeycomb.io/"; + private const string DefaultHoneycombUri = "https://api.honeycomb.io/"; private const string HoneycombBatchEndpointTemplate = "/1/batch/{0}"; private const string HoneycombTeamIdHeaderName = "X-Honeycomb-Team"; @@ -53,8 +42,9 @@ private static Lazy _socketsHttpHandler /// The name of the dataset where to send the events to /// The API key given in the Honeycomb ui + /// A builder to aid in creating the HttpClient /// The URL where to send the events. Default https://api.honeycomb.io - public HoneycombSerilogSink(string? dataset, string? apiKey, Func? httpClientFactory = null, string? honeycombUrl = HoneycombBaseUri) + public HoneycombSerilogSink(string? dataset, string? apiKey, Func? httpClientFactory = null, string honeycombUrl = DefaultHoneycombUri) { if (dataset is not null && !string.IsNullOrWhiteSpace(dataset)) { @@ -66,7 +56,7 @@ public HoneycombSerilogSink(string? dataset, string? apiKey, Func? h } if (apiKey is not null && !string.IsNullOrWhiteSpace(apiKey)) { - _apiKey = dataset; + _apiKey = apiKey; } else { @@ -83,26 +73,7 @@ public async Task EmitBatchAsync(IEnumerable events) { using TextWriter writer = new StringWriter(); BuildLogEvent(events, writer); - await SendBatchedEvents(writer!.ToString()).ConfigureAwait(false); - } - - private async Task SendBatchedEvents(Stream events) - { - using var requestMessage = new HttpRequestMessage(HttpMethod.Post, string.Format(HoneycombBatchEndpointTemplate, _teamId)) - { - Content = new StreamContent(events), - Version = new Version(2, 0) - }; - - requestMessage.Headers.Add(HoneycombTeamIdHeaderName, _apiKey); - var response = await SendRequest(requestMessage).ConfigureAwait(false); - if (!response.IsSuccessStatusCode) - { - using Stream contentStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); - using var reader = new StreamReader(contentStream); - var responseContent = await reader.ReadToEndAsync().ConfigureAwait(false); - SelfLog.WriteLine(SelfLogMessageText, response.StatusCode, responseContent); - } + await SendBatchedEvents(writer.ToString()!).ConfigureAwait(false); } private async Task SendBatchedEvents(string events) @@ -126,25 +97,21 @@ private async Task SendBatchedEvents(string events) private async Task SendRequest(HttpRequestMessage request) { -#if NETCOREAPP - using var client = Client; + using var client = BuildHttpClient(); return await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); -#else - return await Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); -#endif } private static void BuildLogEvent(IEnumerable logEvents, TextWriter payload) { payload.Write("["); var eventSeparator = ""; - foreach (var evnt in logEvents) + foreach (var e in logEvents) { - evnt.AddPropertyIfAbsent(new LogEventProperty("library.name", new ScalarValue(LibraryName))); - evnt.AddPropertyIfAbsent(new LogEventProperty("library.version", new ScalarValue(LibraryVersion))); + e.AddPropertyIfAbsent(new LogEventProperty("library.name", new ScalarValue(LibraryName))); + e.AddPropertyIfAbsent(new LogEventProperty("library.version", new ScalarValue(LibraryVersion))); payload.Write(eventSeparator); eventSeparator = ","; - RawJsonFormatter.FormatContent(evnt, payload); + RawJsonFormatter.FormatContent(e, payload); } payload.Write("]"); } @@ -165,10 +132,7 @@ public Task OnEmptyBatchAsync() private void ReleaseUnmanagedResources() { -#if NETCORE - _socketsHttpHandler?.Dispose(); - _socketsHttpHandler = null; -#endif + _messageHandler?.Value.Dispose(); } public void Dispose() diff --git a/test/Honeycomb.Serilog.Sink.Tests/Builders/HttpClientBuilder.cs b/test/Honeycomb.Serilog.Sink.Tests/Builders/HttpClientBuilder.cs index 994dc0a..80a0594 100644 --- a/test/Honeycomb.Serilog.Sink.Tests/Builders/HttpClientBuilder.cs +++ b/test/Honeycomb.Serilog.Sink.Tests/Builders/HttpClientBuilder.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net; using System.Net.Http; @@ -28,13 +28,12 @@ public class HttpClientStub : HttpClient { private readonly HttpMessageHandlerStub _handlerStub; - public HttpRequestMessage RequestSubmitted => _handlerStub.RequestMessage; - public string RequestContent => _handlerStub.RequestContent; + public HttpRequestMessage? RequestSubmitted => _handlerStub.GetRequestMessage(); + public string? RequestContent => _handlerStub.GetRequestContent(); public HttpClientStub(HttpMessageHandlerStub httpMessageHandler) : base(httpMessageHandler) { - BaseAddress = new Uri("http://dummyUri"); _handlerStub = httpMessageHandler; } } diff --git a/test/Honeycomb.Serilog.Sink.Tests/Helpers/HttpMessageHandlerStub.cs b/test/Honeycomb.Serilog.Sink.Tests/Helpers/HttpMessageHandlerStub.cs index cbd9d5c..aedaee2 100644 --- a/test/Honeycomb.Serilog.Sink.Tests/Helpers/HttpMessageHandlerStub.cs +++ b/test/Honeycomb.Serilog.Sink.Tests/Helpers/HttpMessageHandlerStub.cs @@ -1,4 +1,4 @@ -using System.Net; +using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; @@ -8,19 +8,22 @@ namespace Honeycomb.Serilog.Sink.Tests.Helpers public sealed class HttpMessageHandlerStub : HttpMessageHandler { private HttpStatusCode _statusCodeToReturn = HttpStatusCode.NotImplemented; - public HttpRequestMessage RequestMessage { get; private set; } - public string RequestContent { get; private set; } + private HttpRequestMessage? _requestMessage; + private string? _requestContent; protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { - RequestMessage = request; - RequestContent = await request.Content.ReadAsStringAsync(); + _requestMessage = request; + _requestContent = await request.Content.ReadAsStringAsync(); return new HttpResponseMessage(_statusCodeToReturn) { Content = new StringContent("") }; } + public HttpRequestMessage? GetRequestMessage() => _requestMessage; + public string? GetRequestContent() => _requestContent; + public void ReturnsStatusCode(HttpStatusCode statusCode) { _statusCodeToReturn = statusCode; diff --git a/test/Honeycomb.Serilog.Sink.Tests/Helpers/Some.cs b/test/Honeycomb.Serilog.Sink.Tests/Helpers/Some.cs index 83e840f..c312fe2 100644 --- a/test/Honeycomb.Serilog.Sink.Tests/Helpers/Some.cs +++ b/test/Honeycomb.Serilog.Sink.Tests/Helpers/Some.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Serilog; @@ -15,18 +15,16 @@ public static LogEvent LogEvent(string messageTemplate, params object[] property return LogEvent(null, messageTemplate, propertyValues); } - public static LogEvent LogEvent(Exception exception, string messageTemplate, params object[] propertyValues) + public static LogEvent LogEvent(Exception? exception, string messageTemplate, params object[] propertyValues) { return LogEvent(LogEventLevel.Information, exception, messageTemplate, propertyValues); } - public static LogEvent LogEvent(LogEventLevel level, Exception exception, string messageTemplate, params object[] propertyValues) + public static LogEvent LogEvent(LogEventLevel level, Exception? exception, string messageTemplate, params object[] propertyValues) { var log = new LoggerConfiguration().CreateLogger(); - MessageTemplate template; - IEnumerable properties; #pragma warning disable Serilog004 // Constant MessageTemplate verifier - if (!log.BindMessageTemplate(messageTemplate, propertyValues, out template, out properties)) + if (!log.BindMessageTemplate(messageTemplate, propertyValues, out MessageTemplate template, out IEnumerable properties)) #pragma warning restore Serilog004 // Constant MessageTemplate verifier { throw new XunitException("Template could not be bound."); diff --git a/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkStub.cs b/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkStub.cs index 6af9ae0..46f22c5 100644 --- a/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkStub.cs +++ b/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkStub.cs @@ -1,4 +1,3 @@ -using System; using System.Net.Http; using System.Threading.Tasks; @@ -10,15 +9,13 @@ namespace Honeycomb.Serilog.Sink.Tests { internal class HoneycombSerilogSinkStub : HoneycombSerilogSink { - private readonly HttpClient _client; + public HoneycombSerilogSinkStub(HttpClient client, string dataset, string apiKey) + : base(dataset, apiKey, httpClientFactory: () => client) + { } - public HoneycombSerilogSinkStub(HttpClient? client, string dataset, string apiKey) - : base(dataset, apiKey) - { - _client = client ?? throw new ArgumentNullException(nameof(client)); - } - - protected override HttpClient Client => _client; + public HoneycombSerilogSinkStub(HttpClient client, string dataset, string apiKey, string honeycombUrl) + : base(dataset, apiKey, httpClientFactory: () => client, honeycombUrl: honeycombUrl) + { } public Task EmitTestable(params LogEvent[] events) { diff --git a/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkTests.cs b/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkTests.cs index 42cabb0..3803fed 100644 --- a/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkTests.cs +++ b/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkTests.cs @@ -26,7 +26,9 @@ public class HoneycombSerilogSinkTests public void Create_WhenInvalidTeamIdIsProvided_ThrowsArgumentException(string dataset) { const string apiKey = nameof(apiKey); - Action action = () => CreateSut(dataset, apiKey); + HttpClientStub clientStub = A.HttpClient(); + + Action action = () => CreateSut(dataset, apiKey, clientStub); action.Should().Throw() .Which.Message.Should().Contain(nameof(dataset)); @@ -38,7 +40,9 @@ public void Create_WhenInvalidTeamIdIsProvided_ThrowsArgumentException(string da public void Create_WhenInvalidApiKeyIsProvided_ThrowsArgumentException(string apiKey) { const string dataset = nameof(dataset); - Action action = () => CreateSut(dataset, apiKey); + HttpClientStub clientStub = A.HttpClient(); + + Action action = () => CreateSut(dataset, apiKey, clientStub); action.Should().Throw() .Which.Message.Should().Contain(nameof(apiKey)); @@ -56,10 +60,46 @@ public async Task Emit_AlwaysSendsApiKeyAsync() await sut.EmitTestable(new LogEvent(DateTimeOffset.Now, LogEventLevel.Information, null, new MessageTemplate("", Enumerable.Empty()), Enumerable.Empty())); - clientStub.RequestSubmitted.Headers.Should().ContainSingle(h => h.Key == "X-Honeycomb-Team"); + clientStub.RequestSubmitted.Should().NotBeNull(); + clientStub.RequestSubmitted!.Headers.Should().ContainSingle(h => h.Key == "X-Honeycomb-Team"); clientStub.RequestSubmitted.Headers.GetValues("X-Honeycomb-Team").Should().ContainSingle().Which.Should().Be(apiKey); } + [Fact] + public async Task Emit_WhenNoCustomSinkUriIsSet_UsesDefaultUri() + { + const string dataset = nameof(dataset); + const string apiKey = nameof(apiKey); + + HttpClientStub clientStub = A.HttpClient(); + + var sut = CreateSut(dataset, apiKey, clientStub); + + await sut.EmitTestable(new LogEvent(DateTimeOffset.Now, LogEventLevel.Information, null, new MessageTemplate("", Enumerable.Empty()), Enumerable.Empty())); + + clientStub.RequestSubmitted.Should().NotBeNull(); + clientStub.RequestSubmitted!.RequestUri.Host.Should().Be("api.honeycomb.io"); + clientStub.RequestSubmitted!.RequestUri.Scheme.Should().Be("https"); + } + + [Fact] + public async Task Emit_WhenCustomSinkUriIsSet_UsesCustomUri() + { + const string dataset = nameof(dataset); + const string apiKey = nameof(apiKey); + var customUri = new UriBuilder("https", "dummyhost"); + + HttpClientStub clientStub = A.HttpClient(); + + var sut = CreateSut(dataset, apiKey, clientStub, customUri.ToString()); + + await sut.EmitTestable(new LogEvent(DateTimeOffset.Now, LogEventLevel.Information, null, new MessageTemplate("", Enumerable.Empty()), Enumerable.Empty())); + + clientStub.RequestSubmitted.Should().NotBeNull(); + clientStub.RequestSubmitted!.RequestUri.Scheme.Should().Be(customUri.Scheme); + clientStub.RequestSubmitted!.RequestUri.Host.Should().Be(customUri.Host); + } + [Fact] public async Task Emit_CallsEndpointUsingTeamId() { @@ -72,7 +112,8 @@ public async Task Emit_CallsEndpointUsingTeamId() await sut.EmitTestable(new LogEvent(DateTimeOffset.Now, LogEventLevel.Information, null, new MessageTemplate("", Enumerable.Empty()), Enumerable.Empty())); - clientStub.RequestSubmitted.RequestUri.ToString().Should().EndWith(dataset); + clientStub.RequestSubmitted.Should().NotBeNull(); + clientStub.RequestSubmitted!.RequestUri.ToString().Should().EndWith(dataset); } [Fact] @@ -85,15 +126,15 @@ public async Task Emit_GivenNoExceptionIsLogged_SerializesLogMessageAsJson_HasNo var sut = CreateSut(dataset, apiKey, clientStub); - var level = LogEventLevel.Fatal; + const LogEventLevel level = LogEventLevel.Fatal; - var messageTempalteString = "Testing message {message}"; + const string? messageTemplateString = "Testing message {message}"; - var eventToSend = Some.LogEvent(level, messageTempalteString); + var eventToSend = Some.LogEvent(level, messageTemplateString); await sut.EmitTestable(eventToSend); - var requestContent = clientStub.RequestContent; + var requestContent = clientStub.RequestContent!; using (var document = JsonDocument.Parse(requestContent)) using (new AssertionScope()) { @@ -106,7 +147,7 @@ public async Task Emit_GivenNoExceptionIsLogged_SerializesLogMessageAsJson_HasNo JsonElement data = sentEvent.GetProperty("data"); data.GetProperty("level").GetString().Should().Be(level.ToString()); - data.GetProperty("messageTemplate").GetString().Should().Be(messageTempalteString); + data.GetProperty("messageTemplate").GetString().Should().Be(messageTemplateString); data.TryGetProperty("exception", out var ex); ex.ValueKind.Should().Be(JsonValueKind.Undefined); } @@ -124,14 +165,14 @@ public async Task Emit_GivenAnExceptionToLog_SerializesLogMessageAsJson_Includes var level = LogEventLevel.Fatal; - var messageTempalteString = "Testing message {message}"; + var messageTemplateString = "Testing message {message}"; var ex = new TestException("TestException"); - var eventToSend = Some.LogEvent(level, ex, messageTempalteString); + var eventToSend = Some.LogEvent(level, ex, messageTemplateString); await sut.EmitTestable(eventToSend); - var requestContent = clientStub.RequestContent; + var requestContent = clientStub.RequestContent!; using (var document = JsonDocument.Parse(requestContent)) using (new AssertionScope()) { @@ -165,13 +206,13 @@ public async Task Emit_GivenAMessageWithProperties_SendsThemAllAsync() const string property = nameof(property); - var messageTempalteString = $"Testing message property {{{nameof(property)}}}"; + var messageTemplateString = $"Testing message property {{{nameof(property)}}}"; - var eventToSend = Some.LogEvent(level, messageTempalteString, property); + var eventToSend = Some.LogEvent(level, messageTemplateString, property); await sut.EmitTestable(eventToSend); - var requestContent = clientStub.RequestContent; + var requestContent = clientStub.RequestContent!; using (var document = JsonDocument.Parse(requestContent)) using (new AssertionScope()) { @@ -208,7 +249,7 @@ public async Task Emit_GivenAMessageWithEmptyPropertyValue_SkipsSendingProperty( await sut.EmitTestable(eventToSend); - var requestContent = clientStub.RequestContent; + var requestContent = clientStub.RequestContent!; using (var document = JsonDocument.Parse(requestContent)) using (new AssertionScope()) { @@ -227,83 +268,11 @@ public async Task Emit_GivenAMessageWithEmptyPropertyValue_SkipsSendingProperty( } } - [Fact] - public async Task Emit_GivenAMessageWithTraceId_WritesItAsOTELStandard() - { - const string dataset = nameof(dataset); - const string apiKey = nameof(apiKey); - - HttpClientStub clientStub = A.HttpClient(); - - var sut = CreateSut(dataset, apiKey, clientStub); - - var level = LogEventLevel.Information; - - var property = 1; - const string spanId = nameof(spanId); - - var messageTemplateString = $"Testing message property {{{nameof(property)}}} {{TraceId}}"; - - var eventToSend = Some.LogEvent(level, messageTemplateString, property, spanId); - - - await sut.EmitTestable(eventToSend); - - var requestContent = clientStub.RequestContent; - using (var document = JsonDocument.Parse(requestContent)) - using (new AssertionScope()) - { - document.RootElement.ValueKind.Should().Be(JsonValueKind.Array); - document.RootElement.GetArrayLength().Should().Be(1); - JsonElement sentEvent = document.RootElement.EnumerateArray().Single(); - - sentEvent.GetProperty("time").GetDateTimeOffset().Should().Be(eventToSend.Timestamp); - sentEvent.GetProperty("data").ValueKind.Should().Be(JsonValueKind.Object); - sentEvent.GetProperty("data").GetProperty("trace.trace_id").ValueKind.Should().Be(JsonValueKind.String); - sentEvent.GetProperty("data").GetProperty("trace.trace_id").GetString().Should().Be(spanId); - } - } - - [Fact] - public async Task Emit_GivenAMessageWithParentId_WritesItAsOTELStandard() - { - const string dataset = nameof(dataset); - const string apiKey = nameof(apiKey); - - HttpClientStub clientStub = A.HttpClient(); - - var sut = CreateSut(dataset, apiKey, clientStub); - - var level = LogEventLevel.Information; - - var property = 1; - const string parentId = nameof(parentId); - - var messageTemplateString = $"Testing message property {{{nameof(property)}}} {{SpanId}}"; - - var eventToSend = Some.LogEvent(level, messageTemplateString, property, parentId); - - await sut.EmitTestable(eventToSend); - - var requestContent = clientStub.RequestContent; - using (var document = JsonDocument.Parse(requestContent)) - using (new AssertionScope()) - { - document.RootElement.ValueKind.Should().Be(JsonValueKind.Array); - document.RootElement.GetArrayLength().Should().Be(1); - JsonElement sentEvent = document.RootElement.EnumerateArray().Single(); - - sentEvent.GetProperty("time").GetDateTimeOffset().Should().Be(eventToSend.Timestamp); - sentEvent.GetProperty("data").ValueKind.Should().Be(JsonValueKind.Object); - sentEvent.GetProperty("data").GetProperty("trace.parent_id").ValueKind.Should().Be(JsonValueKind.String); - sentEvent.GetProperty("data").GetProperty("trace.parent_id").GetString().Should().Be(parentId); - } - } + private static HoneycombSerilogSinkStub CreateSut(string dataset, string apiKey, HttpClient client) + => new(client, dataset, apiKey); - private static HoneycombSerilogSinkStub CreateSut(string dataset, string apiKey, HttpClient? client = null) - { - return new HoneycombSerilogSinkStub(client, dataset, apiKey); - } + private static HoneycombSerilogSinkStub CreateSut(string dataset, string apiKey, HttpClient client, string honeycombUrl) + => new(client, dataset, apiKey, honeycombUrl); } internal class TestException : Exception From f028332fe7ced38ef86543bad31f941948642176 Mon Sep 17 00:00:00 2001 From: evilpilaf Date: Mon, 15 Mar 2021 17:42:08 +0100 Subject: [PATCH 14/18] feat: allow override of sink url and simplify the logic --- .../HoneycombSinkExtensions.cs | 23 +++++++++++++++---- .../Sink/HoneycombSerilogSink.cs | 4 ++-- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/Honeycomb.Serilog.Sink/HoneycombSinkExtensions.cs b/src/Honeycomb.Serilog.Sink/HoneycombSinkExtensions.cs index eb3a884..4a509a6 100644 --- a/src/Honeycomb.Serilog.Sink/HoneycombSinkExtensions.cs +++ b/src/Honeycomb.Serilog.Sink/HoneycombSinkExtensions.cs @@ -1,6 +1,9 @@ using System; +using System.Net.Http; + using Honeycomb.Serilog.Sink.Enricher; using Honeycomb.Serilog.Sink.Sink; + using Serilog; using Serilog.Configuration; using Serilog.Sinks.PeriodicBatching; @@ -9,32 +12,44 @@ namespace Honeycomb.Serilog.Sink { public static class HoneycombSinkExtensions { + private const string DefaultHoneycombUri = "https://api.honeycomb.io/"; + /// /// The name of the dataset where to send the events to /// The API key given in the Honeycomb ui /// The maximum number of events to include in a single batch. /// The time to wait between checking for event batches. + /// + /// /// See the official Honeycomb documentation for more details. public static LoggerConfiguration HoneycombSink(this LoggerSinkConfiguration loggerConfiguration, string dataset, string apiKey, int batchSizeLimit, - TimeSpan period) + TimeSpan period, + Func? httpClientFactory = null, + string honeycombUrl = DefaultHoneycombUri) { var batchingOptions = new PeriodicBatchingSinkOptions { BatchSizeLimit = batchSizeLimit, Period = period }; - return loggerConfiguration.HoneycombSink(dataset, apiKey, batchingOptions).Enrich.WithActivity(); + return loggerConfiguration.HoneycombSink(dataset, + apiKey, + batchingOptions, + httpClientFactory, + honeycombUrl); } public static LoggerConfiguration HoneycombSink(this LoggerSinkConfiguration loggerConfiguration, string teamId, string apiKey, - PeriodicBatchingSinkOptions? batchingOptions = default) + PeriodicBatchingSinkOptions? batchingOptions = default, + Func? httpClientFactory = null, + string honeycombUrl = DefaultHoneycombUri) { - var honeycombSink = new HoneycombSerilogSink(teamId, apiKey); + var honeycombSink = new HoneycombSerilogSink(teamId, apiKey, httpClientFactory, honeycombUrl); var batchingSink = new PeriodicBatchingSink(honeycombSink, batchingOptions ?? new PeriodicBatchingSinkOptions()); diff --git a/src/Honeycomb.Serilog.Sink/Sink/HoneycombSerilogSink.cs b/src/Honeycomb.Serilog.Sink/Sink/HoneycombSerilogSink.cs index 8d1405a..f7000a5 100644 --- a/src/Honeycomb.Serilog.Sink/Sink/HoneycombSerilogSink.cs +++ b/src/Honeycomb.Serilog.Sink/Sink/HoneycombSerilogSink.cs @@ -44,7 +44,7 @@ private readonly Func _httpClientFactory /// The API key given in the Honeycomb ui /// A builder to aid in creating the HttpClient /// The URL where to send the events. Default https://api.honeycomb.io - public HoneycombSerilogSink(string? dataset, string? apiKey, Func? httpClientFactory = null, string honeycombUrl = DefaultHoneycombUri) + public HoneycombSerilogSink(string? dataset, string? apiKey, Func? httpClientFactory = null, string? honeycombUrl = DefaultHoneycombUri) { if (dataset is not null && !string.IsNullOrWhiteSpace(dataset)) { @@ -66,7 +66,7 @@ public HoneycombSerilogSink(string? dataset, string? apiKey, Func? h { _httpClientFactory = httpClientFactory; } - _honeycombApiUrl = new Uri(honeycombUrl); + _honeycombApiUrl = new Uri(honeycombUrl ?? DefaultHoneycombUri); } public async Task EmitBatchAsync(IEnumerable events) From 496b39fda740af6f0e9e5a6d0036e5534dffab2d Mon Sep 17 00:00:00 2001 From: evilpilaf Date: Mon, 15 Mar 2021 17:42:42 +0100 Subject: [PATCH 15/18] test: more tests --- .../Enricher/ActivityEnricher.cs | 1 + .../Sink/HoneycombSerilogSink.cs | 4 +- .../ActivityEnricherTests.cs | 60 +++++++++++++++ .../Builders/HttpClientBuilder.cs | 1 - .../Helpers/DelegatingSink.cs | 34 +++++++++ .../HoneycombSerilogSinkTests.cs | 26 +++++++ .../HoneycombSinkExtensionsTests.cs | 75 +++++++++++++++++++ 7 files changed, 199 insertions(+), 2 deletions(-) create mode 100644 test/Honeycomb.Serilog.Sink.Tests/ActivityEnricherTests.cs create mode 100644 test/Honeycomb.Serilog.Sink.Tests/Helpers/DelegatingSink.cs create mode 100644 test/Honeycomb.Serilog.Sink.Tests/HoneycombSinkExtensionsTests.cs diff --git a/src/Honeycomb.Serilog.Sink/Enricher/ActivityEnricher.cs b/src/Honeycomb.Serilog.Sink/Enricher/ActivityEnricher.cs index 6efb6fd..8555c8e 100644 --- a/src/Honeycomb.Serilog.Sink/Enricher/ActivityEnricher.cs +++ b/src/Honeycomb.Serilog.Sink/Enricher/ActivityEnricher.cs @@ -1,4 +1,5 @@ using System.Diagnostics; + using Serilog.Core; using Serilog.Events; diff --git a/src/Honeycomb.Serilog.Sink/Sink/HoneycombSerilogSink.cs b/src/Honeycomb.Serilog.Sink/Sink/HoneycombSerilogSink.cs index f7000a5..888e285 100644 --- a/src/Honeycomb.Serilog.Sink/Sink/HoneycombSerilogSink.cs +++ b/src/Honeycomb.Serilog.Sink/Sink/HoneycombSerilogSink.cs @@ -23,6 +23,8 @@ internal class HoneycombSerilogSink : IBatchedLogEventSink, IDisposable #endif ); + private static readonly RawJsonFormatter _rawJsonFormatter = new RawJsonFormatter(); + private readonly Func _httpClientFactory = () => new HttpClient(_messageHandler.Value, disposeHandler: false); @@ -111,7 +113,7 @@ private static void BuildLogEvent(IEnumerable logEvents, TextWriter pa e.AddPropertyIfAbsent(new LogEventProperty("library.version", new ScalarValue(LibraryVersion))); payload.Write(eventSeparator); eventSeparator = ","; - RawJsonFormatter.FormatContent(e, payload); + _rawJsonFormatter.Format(e, payload); } payload.Write("]"); } diff --git a/test/Honeycomb.Serilog.Sink.Tests/ActivityEnricherTests.cs b/test/Honeycomb.Serilog.Sink.Tests/ActivityEnricherTests.cs new file mode 100644 index 0000000..80c1716 --- /dev/null +++ b/test/Honeycomb.Serilog.Sink.Tests/ActivityEnricherTests.cs @@ -0,0 +1,60 @@ +using System; +using System.Diagnostics; + +using FluentAssertions; +using FluentAssertions.Execution; + +using Honeycomb.Serilog.Sink.Enricher; +using Honeycomb.Serilog.Sink.Tests.Helpers; + +using Serilog; +using Serilog.Events; + +using Xunit; + +namespace Honeycomb.Serilog.Sink.Tests +{ + public class ActivityEnricherTests + { + + [Fact] + public void ActivityEnricher_AddsParentAndTraceId() + { + LogEvent? evnt = null; + + var log = new LoggerConfiguration() + .Enrich.WithActivity() + .WriteTo.Sink(new DelegatingSink(e => evnt = e)) + .CreateLogger(); + + string spanId; + string traceId; + + Activity.DefaultIdFormat = ActivityIdFormat.W3C; + + using (var activity = new Activity(nameof(ActivityEnricher_AddsParentAndTraceId))) + { + activity.SetParentId(Guid.NewGuid().ToString()); + activity.Start(); + traceId = activity.GetTraceId(); + spanId = activity.GetSpanId(); + log.Information("Test message"); + } + + using (new AssertionScope()) + { + evnt.Should().NotBeNull(); + evnt!.Properties["trace.parent_id"].LiteralValue().As().Should().Be(spanId); + evnt!.Properties["trace.trace_id"].LiteralValue().As().Should().Be(traceId); + } + } + } + + internal static class Extensions + { + public static object LiteralValue(this LogEventPropertyValue @this) + { + return ((ScalarValue)@this).Value; + } + } +} diff --git a/test/Honeycomb.Serilog.Sink.Tests/Builders/HttpClientBuilder.cs b/test/Honeycomb.Serilog.Sink.Tests/Builders/HttpClientBuilder.cs index 80a0594..c770749 100644 --- a/test/Honeycomb.Serilog.Sink.Tests/Builders/HttpClientBuilder.cs +++ b/test/Honeycomb.Serilog.Sink.Tests/Builders/HttpClientBuilder.cs @@ -1,4 +1,3 @@ -using System; using System.Net; using System.Net.Http; diff --git a/test/Honeycomb.Serilog.Sink.Tests/Helpers/DelegatingSink.cs b/test/Honeycomb.Serilog.Sink.Tests/Helpers/DelegatingSink.cs new file mode 100644 index 0000000..64dc46a --- /dev/null +++ b/test/Honeycomb.Serilog.Sink.Tests/Helpers/DelegatingSink.cs @@ -0,0 +1,34 @@ +using System; + +using Serilog; +using Serilog.Core; +using Serilog.Events; + +namespace Honeycomb.Serilog.Sink.Tests.Helpers +{ + public class DelegatingSink : ILogEventSink + { + readonly Action _write; + + public DelegatingSink(Action write) + { + _write = write ?? throw new ArgumentNullException(nameof(write)); + } + + public void Emit(LogEvent logEvent) + { + _write(logEvent); + } + + public static LogEvent? GetLogEvent(Action writeAction) + { + LogEvent? result = null; + var l = new LoggerConfiguration() + .WriteTo.Sink(new DelegatingSink(le => result = le)) + .CreateLogger(); + + writeAction(l); + return result; + } + } +} diff --git a/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkTests.cs b/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkTests.cs index 3803fed..a428da8 100644 --- a/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkTests.cs +++ b/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkTests.cs @@ -116,6 +116,32 @@ public async Task Emit_CallsEndpointUsingTeamId() clientStub.RequestSubmitted!.RequestUri.ToString().Should().EndWith(dataset); } + [Fact] + public async Task Emit_AlwaysSetsMetaAnnotationType_As_SpanEvent() + { + const string dataset = nameof(dataset); + const string apiKey = nameof(apiKey); + + HttpClientStub clientStub = A.HttpClient(); + + var sut = CreateSut(dataset, apiKey, clientStub); + + await sut.EmitTestable(new LogEvent(DateTimeOffset.Now, LogEventLevel.Information, null, + new MessageTemplate("", Enumerable.Empty()), + Enumerable.Empty())); + + var requestContent = clientStub.RequestContent!; + using (var document = JsonDocument.Parse(requestContent)) + using (new AssertionScope()) + { + JsonElement sentEvent = document.RootElement.EnumerateArray().Single(); + JsonElement data = sentEvent.GetProperty("data"); + + data.GetProperty("meta.annotation_type").Should().NotBeNull(); + data.GetProperty("meta.annotation_type").GetString().Should().Be("span_event"); + } + } + [Fact] public async Task Emit_GivenNoExceptionIsLogged_SerializesLogMessageAsJson_HasNoExceptionInMessageAsync() { diff --git a/test/Honeycomb.Serilog.Sink.Tests/HoneycombSinkExtensionsTests.cs b/test/Honeycomb.Serilog.Sink.Tests/HoneycombSinkExtensionsTests.cs new file mode 100644 index 0000000..fa926f1 --- /dev/null +++ b/test/Honeycomb.Serilog.Sink.Tests/HoneycombSinkExtensionsTests.cs @@ -0,0 +1,75 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Net; +using System.Text.Json; +using System.Threading.Tasks; + +using FluentAssertions; +using FluentAssertions.Execution; + +using Honeycomb.Serilog.Sink.Enricher; +using Honeycomb.Serilog.Sink.Tests.Builders; + +using Serilog; + +using Xunit; + +namespace Honeycomb.Serilog.Sink.Tests +{ + public class HoneycombSinkExtensionsTests + { + [Fact] + public async Task HoneycombSink_AlwaysEnrichesWithActivityInfo() + { + const string dataset = nameof(dataset); + const string apiKey = nameof(apiKey); + + HttpClientStub clientStub = A.HttpClient() + .ThatReturnsStatusCode(HttpStatusCode.OK); + + var log = new LoggerConfiguration() + .WriteTo.HoneycombSink(dataset, + apiKey, + batchSizeLimit: 1, + period: TimeSpan.FromMilliseconds(1), + httpClientFactory: () => clientStub) + .CreateLogger(); + + string traceId; + string spanId; + + Activity.DefaultIdFormat = ActivityIdFormat.W3C; + Activity.ForceDefaultIdFormat = true; + + using (var activity = new Activity(nameof(HoneycombSink_AlwaysEnrichesWithActivityInfo))) + { + activity.Start(); + traceId = activity.GetTraceId(); + spanId = activity.GetSpanId(); + log.Information("This is a test message"); + } + + await Task.Delay(TimeSpan.FromSeconds(1)); + + var requestContent = clientStub.RequestContent!; + + using (var document = JsonDocument.Parse(requestContent)) + using (new AssertionScope()) + { + document.RootElement.ValueKind.Should().Be(JsonValueKind.Array); + document.RootElement.GetArrayLength().Should().Be(1); + JsonElement sentEvent = document.RootElement.EnumerateArray().Single(); + + sentEvent.GetProperty("data").ValueKind.Should().Be(JsonValueKind.Object); + + JsonElement data = sentEvent.GetProperty("data"); + + data.GetProperty("trace.parent_id").ValueKind.Should().Be(JsonValueKind.String); + data.GetProperty("trace.parent_id").GetString().Should().Be(spanId); + data.GetProperty("trace.trace_id").ValueKind.Should().Be(JsonValueKind.String); + data.GetProperty("trace.trace_id").GetString().Should().Be(traceId); + } + } + } +} From 59ed8ab875fea08d7000b477a65213acba0e4fee Mon Sep 17 00:00:00 2001 From: evilpilaf Date: Mon, 15 Mar 2021 17:43:19 +0100 Subject: [PATCH 16/18] boyscout: spelling --- HoneycombSerilogSink.sln.DotSettings | 1 + 1 file changed, 1 insertion(+) diff --git a/HoneycombSerilogSink.sln.DotSettings b/HoneycombSerilogSink.sln.DotSettings index ed014f5..69dcc77 100644 --- a/HoneycombSerilogSink.sln.DotSettings +++ b/HoneycombSerilogSink.sln.DotSettings @@ -1,3 +1,4 @@  True + True True \ No newline at end of file From cc4a79c88d41d55fe9dcf63aec86444bb35ec6a0 Mon Sep 17 00:00:00 2001 From: evilpilaf Date: Mon, 15 Mar 2021 21:08:53 +0100 Subject: [PATCH 17/18] bump: dependencies --- src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj | 2 +- .../Honeycomb.Serilog.Sink.Tests.csproj | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj b/src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj index 2b48787..5defe05 100644 --- a/src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj +++ b/src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj @@ -38,7 +38,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Honeycomb.Serilog.Sink.Tests/Honeycomb.Serilog.Sink.Tests.csproj b/test/Honeycomb.Serilog.Sink.Tests/Honeycomb.Serilog.Sink.Tests.csproj index a01c716..74cdc98 100644 --- a/test/Honeycomb.Serilog.Sink.Tests/Honeycomb.Serilog.Sink.Tests.csproj +++ b/test/Honeycomb.Serilog.Sink.Tests/Honeycomb.Serilog.Sink.Tests.csproj @@ -2,13 +2,13 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all From 13231f85abf5b50cbdfca541616567292af013af Mon Sep 17 00:00:00 2001 From: evilpilaf Date: Mon, 15 Mar 2021 21:09:06 +0100 Subject: [PATCH 18/18] documentation --- README.md | 102 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 91 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 0e92b4f..7fb85c6 100644 --- a/README.md +++ b/README.md @@ -6,30 +6,110 @@ This project aims to provide a Serilog sink to push structured log events to the [Honeycomb](https://www.honeycomb.io/) platform for observability and monitoring purposes. -By hooking up to serilog my objective is to allow all existing applications which already produce structured events for logging to easily include Honeycomb as part of their pipeline. +By hooking up to serilog the goal is to allow all existing applications which already produce structured events for logging to easily include Honeycomb as part of their pipeline. + +This library will add an enricher that adds information about the ongoing [Activity/Span](https://github.com/open-telemetry/opentelemetry-dotnet/blob/main/src/OpenTelemetry.Api/README.md#introduction-to-opentelemetry-net-tracing-api).The moment the log message's created. +This adds a `trace.trace_id` property that matches the activities `TraceId` and a `trace.parent_id` property which matches the `SpanId` of the activity to each log event. +Every event will be tagged with `meta.annotation_type=span_event` in Honeycomb and you'll be able to see them when reviewing a trace. ## Setup To start using this sink simply download the package from [Nuget](https://www.nuget.org/packages/Honeycomb.Serilog.Sink/) and add it to your Serilog configuration as another sink in the pipeline. +### Parameters + +#### Mandatory + +- dataset: The name of the dataset to send the log messages to. +- api key: An API key with `Send Events` permissions on the dataset. + +#### Optional + +- httpClientFactory: a factory which will provide an instance of HttpClient. When passed it's the responsability of the caller to manage the lifecycle of the client. +- honeycombUrl: the url to the honeycomb Events API, change it if you want to test or if using [Refinery](https://docs.honeycomb.io/manage-data-volume/refinery/). It defaults to _https://api.honeycomb.io_ +- Batching Option: + - batchSizeLimit: The maximum number of log events to send in a batch. Defaults to 1000. + - period: The maximum amount of time before flushing the events. Defaults to 2 seconds. + If you see issues with memory utilization troubleshoot the batching options, too big a batch size limmit might result in a lot of memory being used, too low numbers may result in too frequent calls to the API. + ### Download the package ```powershell -> dotnet add package Honeycomb.Serilog.Sink + dotnet add package Honeycomb.Serilog.Sink ``` +#### Example + ```csharp using Honeycomb.Serilog.Sink; -[...] +namespace Example +{ + public static class Program + { + public static int Main(string[] args) + { + Activity.DefaultIdFormat = ActivityIdFormat.W3C; + Activity.ForceDefaultIdFormat = true; + + Log.Logger = new LoggerConfiguration() + .WriteTo.HoneycombSink( + teamId: dataset, + apiKey: apiKey) + .BuildLogger(); + + // Do stuff + } + } +} +``` + +#### Using service provider + +```csharp +namespace Example +{ + public static class Program + { + public static int Main(string[] args) + { + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Override("Microsoft", LogEventLevel.Information) + .Enrich.FromLogContext() + .WriteTo.Console() + .CreateBootstrapLogger(); + + try + { + Log.Information("Starting web host"); + CreateHostBuilder(args).Build().Run(); + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "Host terminated unexpectedly"); + return 1; + } + finally + { + Log.CloseAndFlush(); + } + } -string dataset = "The name of the dataset wher your data will be sent"; -string apiKey = "The api key given to you in Honeycomb"; + public static IHostBuilder CreateWebHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) + .UseSerilog((_, services, configuration) => + { + configuration.WriteTo.HoneycombSink( + teamId: , + apiKey: , + httpClientFactory: () => + services.GetRequiredService() + .CreateClient("honeycomb")); + }); + } + } +} -var logger = new LoggerConfiguration() - .WriteTo - [...] - .HoneycombSink(dataset, apiKey) - [...] - .CreateLogger(); ```