Skip to content

Commit

Permalink
Merge pull request #68 from evilpilaf/otel
Browse files Browse the repository at this point in the history
Add open telemetry properties and other improvements
  • Loading branch information
evilpilaf authored Mar 15, 2021
2 parents 6a38fdc + 13231f8 commit d4b7b90
Show file tree
Hide file tree
Showing 24 changed files with 723 additions and 145 deletions.
27 changes: 27 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -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}"
}
]
}
42 changes: 42 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/test/Honeycomb.Serilog.Sink.Tests/Honeycomb.Serilog.Sink.Tests.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/test/Honeycomb.Serilog.Sink.Tests/Honeycomb.Serilog.Sink.Tests.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"${workspaceFolder}/test/Honeycomb.Serilog.Sink.Tests/Honeycomb.Serilog.Sink.Tests.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
}
]
}
30 changes: 30 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<Project>
<PropertyGroup Label="Build">
<IsWindows Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true'">true</IsWindows>
<LangVersion>latest</LangVersion>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<AnalysisLevel>latest</AnalysisLevel>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn),NETSDK1138,NETSDK1138</NoWarn>
<Deterministic>true</Deterministic>
<ContinuousIntegrationBuild Condition="'$(TF_BUILD)' != ''">true</ContinuousIntegrationBuild>
</PropertyGroup>

<PropertyGroup Label="Package">
<Authors>evilpilaf</Authors>
<RepositoryUrl>https://github.com/evilpilaf/HoneycombSerilogSink/</RepositoryUrl>
<Copyright>evilpilaf © $([System.DateTime]::Now.Year)</Copyright>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<PackageLicenseFile>LICENSE.TXT</PackageLicenseFile>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>

</Project>
12 changes: 12 additions & 0 deletions HoneycombSerilogSink.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -14,16 +17,25 @@ 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
release-pipeline.yml = release-pipeline.yml
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
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
Expand Down
1 change: 1 addition & 0 deletions HoneycombSerilogSink.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
<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">
<s:Boolean x:Key="/Default/UserDictionary/Words/=dataset/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Enricher/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Serilog/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
102 changes: 91 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: <Dataset>,
apiKey: <Api Key>,
httpClientFactory: () =>
services.GetRequiredService<IHttpClientFactory>()
.CreateClient("honeycomb"));
});
}
}
}

var logger = new LoggerConfiguration()
.WriteTo
[...]
.HoneycombSink(dataset, apiKey)
[...]
.CreateLogger();
```
18 changes: 18 additions & 0 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project>
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />

<PropertyGroup Label="Build">
<TargetFrameworks>netcoreapp2.1;netcoreapp3.1</TargetFrameworks>
<TargetFrameworks Condition="$(IsWindows) == true">$(TargetFrameworks);net461</TargetFrameworks>
</PropertyGroup>

<PropertyGroup Label="Package">
<PackageTags>Honeycomb Serilog Sink Observability Logging Monitoring</PackageTags>
<Description>A sink for pushing Serilog structured log events into a Honeycomb instance.</Description>
</PropertyGroup>

<ItemGroup>
<None Include="../../LICENSE.TXT" Pack="true" PackagePath="$(PackageLicenseFile)" />
</ItemGroup>

</Project>
21 changes: 21 additions & 0 deletions src/Honeycomb.Serilog.Sink/Enricher/ActivityEnricher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Diagnostics;

using Serilog.Core;
using Serilog.Events;

namespace Honeycomb.Serilog.Sink.Enricher
{
public class ActivityEnricher : ILogEventEnricher
{
/// <inheritdoc/>
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())));
}
}
}
}
64 changes: 64 additions & 0 deletions src/Honeycomb.Serilog.Sink/Enricher/ActivityExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
namespace Honeycomb.Serilog.Sink.Enricher
{
using System.Diagnostics;

/// <summary>
/// <see cref="Activity"/> extension methods.
/// </summary>
internal static class ActivityExtensions
{
/// <summary>
/// Gets the span unique identifier regardless of the activity identifier format.
/// </summary>
/// <param name="activity">The activity.</param>
/// <returns>The span unique identifier.</returns>
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;
}

/// <summary>
/// Gets the span trace unique identifier regardless of the activity identifier format.
/// </summary>
/// <param name="activity">The activity.</param>
/// <returns>The span trace unique identifier.</returns>
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;
}

/// <summary>
/// Gets the span parent unique identifier regardless of the activity identifier format.
/// </summary>
/// <param name="activity">The activity.</param>
/// <returns>The span parent unique identifier.</returns>
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;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using Serilog;
using Serilog.Configuration;

namespace Honeycomb.Serilog.Sink.Enricher
{
/// <summary>
/// <see cref="LoggerEnrichmentConfiguration"/> extension methods.
/// </summary>
public static class LoggerEnrichmentConfigurationExtensions
{
public static LoggerConfiguration WithActivity(this LoggerEnrichmentConfiguration self)
{
if (self is null)
{
throw new ArgumentNullException(nameof(self));
}
return self.With<ActivityEnricher>();
}
}
}
Loading

0 comments on commit d4b7b90

Please sign in to comment.