Skip to content

Commit d4b7b90

Browse files
authored
Merge pull request #68 from evilpilaf/otel
Add open telemetry properties and other improvements
2 parents 6a38fdc + 13231f8 commit d4b7b90

24 files changed

Lines changed: 723 additions & 145 deletions

.vscode/launch.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"version": "0.2.0",
3+
"configurations": [
4+
{
5+
// Use IntelliSense to find out which attributes exist for C# debugging
6+
// Use hover for the description of the existing attributes
7+
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
8+
"name": ".NET Core Launch (console)",
9+
"type": "coreclr",
10+
"request": "launch",
11+
"preLaunchTask": "build",
12+
// If you have changed target frameworks, make sure to update the program path.
13+
"program": "${workspaceFolder}/test/Honeycomb.Serilog.Sink.Tests/bin/Debug/netcoreapp2.1/Honeycomb.Serilog.Sink.Tests.dll",
14+
"args": [],
15+
"cwd": "${workspaceFolder}/test/Honeycomb.Serilog.Sink.Tests",
16+
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
17+
"console": "internalConsole",
18+
"stopAtEntry": false
19+
},
20+
{
21+
"name": ".NET Core Attach",
22+
"type": "coreclr",
23+
"request": "attach",
24+
"processId": "${command:pickProcess}"
25+
}
26+
]
27+
}

.vscode/tasks.json

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"version": "2.0.0",
3+
"tasks": [
4+
{
5+
"label": "build",
6+
"command": "dotnet",
7+
"type": "process",
8+
"args": [
9+
"build",
10+
"${workspaceFolder}/test/Honeycomb.Serilog.Sink.Tests/Honeycomb.Serilog.Sink.Tests.csproj",
11+
"/property:GenerateFullPaths=true",
12+
"/consoleloggerparameters:NoSummary"
13+
],
14+
"problemMatcher": "$msCompile"
15+
},
16+
{
17+
"label": "publish",
18+
"command": "dotnet",
19+
"type": "process",
20+
"args": [
21+
"publish",
22+
"${workspaceFolder}/test/Honeycomb.Serilog.Sink.Tests/Honeycomb.Serilog.Sink.Tests.csproj",
23+
"/property:GenerateFullPaths=true",
24+
"/consoleloggerparameters:NoSummary"
25+
],
26+
"problemMatcher": "$msCompile"
27+
},
28+
{
29+
"label": "watch",
30+
"command": "dotnet",
31+
"type": "process",
32+
"args": [
33+
"watch",
34+
"run",
35+
"${workspaceFolder}/test/Honeycomb.Serilog.Sink.Tests/Honeycomb.Serilog.Sink.Tests.csproj",
36+
"/property:GenerateFullPaths=true",
37+
"/consoleloggerparameters:NoSummary"
38+
],
39+
"problemMatcher": "$msCompile"
40+
}
41+
]
42+
}

Directory.Build.props

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<Project>
2+
<PropertyGroup Label="Build">
3+
<IsWindows Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true'">true</IsWindows>
4+
<LangVersion>latest</LangVersion>
5+
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
6+
<AnalysisLevel>latest</AnalysisLevel>
7+
<Nullable>enable</Nullable>
8+
<NoWarn>$(NoWarn),NETSDK1138,NETSDK1138</NoWarn>
9+
<Deterministic>true</Deterministic>
10+
<ContinuousIntegrationBuild Condition="'$(TF_BUILD)' != ''">true</ContinuousIntegrationBuild>
11+
</PropertyGroup>
12+
13+
<PropertyGroup Label="Package">
14+
<Authors>evilpilaf</Authors>
15+
<RepositoryUrl>https://github.com/evilpilaf/HoneycombSerilogSink/</RepositoryUrl>
16+
<Copyright>evilpilaf © $([System.DateTime]::Now.Year)</Copyright>
17+
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
18+
<PackageLicenseFile>LICENSE.TXT</PackageLicenseFile>
19+
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
20+
<PublishRepositoryUrl>true</PublishRepositoryUrl>
21+
<EmbedUntrackedSources>true</EmbedUntrackedSources>
22+
<IncludeSymbols>true</IncludeSymbols>
23+
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
24+
</PropertyGroup>
25+
26+
<ItemGroup>
27+
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
28+
</ItemGroup>
29+
30+
</Project>

HoneycombSerilogSink.sln

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Honeycomb.Serilog.Sink", "src\Honeycomb.Serilog.Sink\Honeycomb.Serilog.Sink.csproj", "{053B3440-863F-4330-9FD1-56BC473A6C00}"
77
EndProject
88
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F5B2D9C6-6E1E-482F-A216-143C5DB44239}"
9+
ProjectSection(SolutionItems) = preProject
10+
src\Directory.Build.props = src\Directory.Build.props
11+
EndProjectSection
912
EndProject
1013
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E4F55B6D-46A4-49ED-B2B6-34CF950436F7}"
1114
ProjectSection(SolutionItems) = preProject
@@ -14,16 +17,25 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
1417
.gitignore = .gitignore
1518
azure-pipelines.yml = azure-pipelines.yml
1619
CODE_OF_CONDUCT.md = CODE_OF_CONDUCT.md
20+
Directory.Build.props = Directory.Build.props
1721
global.json = global.json
1822
LICENSE.TXT = LICENSE.TXT
1923
README.md = README.md
2024
release-pipeline.yml = release-pipeline.yml
2125
EndProjectSection
2226
EndProject
2327
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{87AFA633-EBC9-4A0C-AE07-C6616BAFB602}"
28+
ProjectSection(SolutionItems) = preProject
29+
test\Directory.Build.props = test\Directory.Build.props
30+
EndProjectSection
2431
EndProject
2532
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}"
2633
EndProject
34+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ci", "ci", "{F33664C2-6205-4DB6-B00F-0C18887E8272}"
35+
ProjectSection(SolutionItems) = preProject
36+
ci\templates\build-and-package.yml = ci\templates\build-and-package.yml
37+
EndProjectSection
38+
EndProject
2739
Global
2840
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2941
Debug|Any CPU = Debug|Any CPU
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
22
<s:Boolean x:Key="/Default/UserDictionary/Words/=dataset/@EntryIndexedValue">True</s:Boolean>
3+
<s:Boolean x:Key="/Default/UserDictionary/Words/=Enricher/@EntryIndexedValue">True</s:Boolean>
34
<s:Boolean x:Key="/Default/UserDictionary/Words/=Serilog/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

README.md

Lines changed: 91 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,110 @@
66

77
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.
88

9-
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.
9+
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.
10+
11+
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.
12+
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.
13+
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.
1014

1115
## Setup
1216

1317
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.
1418

19+
### Parameters
20+
21+
#### Mandatory
22+
23+
- dataset: The name of the dataset to send the log messages to.
24+
- api key: An API key with `Send Events` permissions on the dataset.
25+
26+
#### Optional
27+
28+
- 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.
29+
- 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_
30+
- Batching Option:
31+
- batchSizeLimit: The maximum number of log events to send in a batch. Defaults to 1000.
32+
- period: The maximum amount of time before flushing the events. Defaults to 2 seconds.
33+
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.
34+
1535
### Download the package
1636

1737
```powershell
18-
> dotnet add package Honeycomb.Serilog.Sink
38+
dotnet add package Honeycomb.Serilog.Sink
1939
```
2040

41+
#### Example
42+
2143
```csharp
2244
using Honeycomb.Serilog.Sink;
2345

24-
[...]
46+
namespace Example
47+
{
48+
public static class Program
49+
{
50+
public static int Main(string[] args)
51+
{
52+
Activity.DefaultIdFormat = ActivityIdFormat.W3C;
53+
Activity.ForceDefaultIdFormat = true;
54+
55+
Log.Logger = new LoggerConfiguration()
56+
.WriteTo.HoneycombSink(
57+
teamId: dataset,
58+
apiKey: apiKey)
59+
.BuildLogger();
60+
61+
// Do stuff
62+
}
63+
}
64+
}
65+
```
66+
67+
#### Using service provider
68+
69+
```csharp
70+
namespace Example
71+
{
72+
public static class Program
73+
{
74+
public static int Main(string[] args)
75+
{
76+
Log.Logger = new LoggerConfiguration()
77+
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
78+
.Enrich.FromLogContext()
79+
.WriteTo.Console()
80+
.CreateBootstrapLogger();
81+
82+
try
83+
{
84+
Log.Information("Starting web host");
85+
CreateHostBuilder(args).Build().Run();
86+
return 0;
87+
}
88+
catch (Exception ex)
89+
{
90+
Log.Fatal(ex, "Host terminated unexpectedly");
91+
return 1;
92+
}
93+
finally
94+
{
95+
Log.CloseAndFlush();
96+
}
97+
}
2598

26-
string dataset = "The name of the dataset wher your data will be sent";
27-
string apiKey = "The api key given to you in Honeycomb";
99+
public static IHostBuilder CreateWebHostBuilder(string[] args)
100+
{
101+
return Host.CreateDefaultBuilder(args)
102+
.UseSerilog((_, services, configuration) =>
103+
{
104+
configuration.WriteTo.HoneycombSink(
105+
teamId: <Dataset>,
106+
apiKey: <Api Key>,
107+
httpClientFactory: () =>
108+
services.GetRequiredService<IHttpClientFactory>()
109+
.CreateClient("honeycomb"));
110+
});
111+
}
112+
}
113+
}
28114

29-
var logger = new LoggerConfiguration()
30-
.WriteTo
31-
[...]
32-
.HoneycombSink(dataset, apiKey)
33-
[...]
34-
.CreateLogger();
35115
```

src/Directory.Build.props

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project>
2+
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />
3+
4+
<PropertyGroup Label="Build">
5+
<TargetFrameworks>netcoreapp2.1;netcoreapp3.1</TargetFrameworks>
6+
<TargetFrameworks Condition="$(IsWindows) == true">$(TargetFrameworks);net461</TargetFrameworks>
7+
</PropertyGroup>
8+
9+
<PropertyGroup Label="Package">
10+
<PackageTags>Honeycomb Serilog Sink Observability Logging Monitoring</PackageTags>
11+
<Description>A sink for pushing Serilog structured log events into a Honeycomb instance.</Description>
12+
</PropertyGroup>
13+
14+
<ItemGroup>
15+
<None Include="../../LICENSE.TXT" Pack="true" PackagePath="$(PackageLicenseFile)" />
16+
</ItemGroup>
17+
18+
</Project>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System.Diagnostics;
2+
3+
using Serilog.Core;
4+
using Serilog.Events;
5+
6+
namespace Honeycomb.Serilog.Sink.Enricher
7+
{
8+
public class ActivityEnricher : ILogEventEnricher
9+
{
10+
/// <inheritdoc/>
11+
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
12+
{
13+
var current = Activity.Current;
14+
if (current is not null)
15+
{
16+
logEvent.AddPropertyIfAbsent(new LogEventProperty("trace.parent_id", new ScalarValue(current.GetSpanId())));
17+
logEvent.AddPropertyIfAbsent(new LogEventProperty("trace.trace_id", new ScalarValue(current.GetTraceId())));
18+
}
19+
}
20+
}
21+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
namespace Honeycomb.Serilog.Sink.Enricher
2+
{
3+
using System.Diagnostics;
4+
5+
/// <summary>
6+
/// <see cref="Activity"/> extension methods.
7+
/// </summary>
8+
internal static class ActivityExtensions
9+
{
10+
/// <summary>
11+
/// Gets the span unique identifier regardless of the activity identifier format.
12+
/// </summary>
13+
/// <param name="activity">The activity.</param>
14+
/// <returns>The span unique identifier.</returns>
15+
public static string GetSpanId(this Activity activity)
16+
{
17+
var spanId = activity.IdFormat switch
18+
{
19+
ActivityIdFormat.Hierarchical => activity.Id,
20+
ActivityIdFormat.W3C => activity.SpanId.ToHexString(),
21+
ActivityIdFormat.Unknown => null,
22+
_ => null,
23+
};
24+
25+
return spanId ?? string.Empty;
26+
}
27+
28+
/// <summary>
29+
/// Gets the span trace unique identifier regardless of the activity identifier format.
30+
/// </summary>
31+
/// <param name="activity">The activity.</param>
32+
/// <returns>The span trace unique identifier.</returns>
33+
public static string GetTraceId(this Activity activity)
34+
{
35+
var traceId = activity.IdFormat switch
36+
{
37+
ActivityIdFormat.Hierarchical => activity.RootId,
38+
ActivityIdFormat.W3C => activity.TraceId.ToHexString(),
39+
ActivityIdFormat.Unknown => null,
40+
_ => null,
41+
};
42+
43+
return traceId ?? string.Empty;
44+
}
45+
46+
/// <summary>
47+
/// Gets the span parent unique identifier regardless of the activity identifier format.
48+
/// </summary>
49+
/// <param name="activity">The activity.</param>
50+
/// <returns>The span parent unique identifier.</returns>
51+
public static string GetParentId(this Activity activity)
52+
{
53+
var parentId = activity.IdFormat switch
54+
{
55+
ActivityIdFormat.Hierarchical => activity.ParentId,
56+
ActivityIdFormat.W3C => activity.ParentSpanId.ToHexString(),
57+
ActivityIdFormat.Unknown => null,
58+
_ => null,
59+
};
60+
61+
return parentId ?? string.Empty;
62+
}
63+
}
64+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System;
2+
using Serilog;
3+
using Serilog.Configuration;
4+
5+
namespace Honeycomb.Serilog.Sink.Enricher
6+
{
7+
/// <summary>
8+
/// <see cref="LoggerEnrichmentConfiguration"/> extension methods.
9+
/// </summary>
10+
public static class LoggerEnrichmentConfigurationExtensions
11+
{
12+
public static LoggerConfiguration WithActivity(this LoggerEnrichmentConfiguration self)
13+
{
14+
if (self is null)
15+
{
16+
throw new ArgumentNullException(nameof(self));
17+
}
18+
return self.With<ActivityEnricher>();
19+
}
20+
}
21+
}

0 commit comments

Comments
 (0)