Skip to content

Commit

Permalink
Merge pull request #147 from nblumhardt/recognize-serilog-tracing
Browse files Browse the repository at this point in the history
Add support for trace spans using `SerilogTracing` conventions
  • Loading branch information
nblumhardt committed Jul 23, 2024
2 parents 8c5bcfc + ce2e295 commit 8f8612e
Show file tree
Hide file tree
Showing 33 changed files with 4,822 additions and 370 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -397,3 +397,5 @@ FodyWeavers.xsd
# JetBrains Rider
*.sln.iml
/test/Serilog.Sinks.OpenTelemetry.Tests/PublicApiVisibilityTests.received.txt

.DS_Store
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ public static class OpenTelemetryLoggerConfigurationExtensions
/// The `WriteTo` configuration object.
/// </param>
/// <param name="configure">The configuration callback.</param>
/// <param name="ignoreEnvironment">If false the configuration will be overridden with values from <see href="https://opentelemetry.io/docs/languages/sdk-configuration/otlp-exporter/">OTLP Exporter Configuration environment variables</see>.</param>
/// <param name="ignoreEnvironment">If false the configuration will be overridden with values
/// from the <see href="https://opentelemetry.io/docs/languages/sdk-configuration/otlp-exporter/">OTLP Exporter
/// Configuration environment variables</see>, if present.</param>
public static LoggerConfiguration OpenTelemetry(
this LoggerSinkConfiguration loggerSinkConfiguration,
Action<BatchedOpenTelemetrySinkOptions> configure,
Expand All @@ -63,18 +65,38 @@ public static LoggerConfiguration OpenTelemetry(
}

var exporter = Exporter.Create(
endpoint: options.Endpoint,
logsEndpoint: options.LogsEndpoint,
tracesEndpoint: options.TracesEndpoint,
protocol: options.Protocol,
headers: new Dictionary<string, string>(options.Headers),
httpMessageHandler: options.HttpMessageHandler ?? CreateDefaultHttpMessageHandler());

var openTelemetrySink = new OpenTelemetrySink(
exporter: exporter,
formatProvider: options.FormatProvider,
resourceAttributes: new Dictionary<string, object>(options.ResourceAttributes),
includedData: options.IncludedData);
ILogEventSink? logsSink = null, tracesSink = null;

return loggerSinkConfiguration.Sink(openTelemetrySink, options.BatchingOptions, options.RestrictedToMinimumLevel, options.LevelSwitch);
if (options.LogsEndpoint != null)
{
var openTelemetryLogsSink = new OpenTelemetryLogsSink(
exporter: exporter,
formatProvider: options.FormatProvider,
resourceAttributes: new Dictionary<string, object>(options.ResourceAttributes),
includedData: options.IncludedData);

logsSink = LoggerSinkConfiguration.CreateSink(wt => wt.Sink(openTelemetryLogsSink, options.BatchingOptions));
}

if (options.TracesEndpoint != null)
{
var openTelemetryTracesSink = new OpenTelemetryTracesSink(
exporter: exporter,
resourceAttributes: new Dictionary<string, object>(options.ResourceAttributes),
includedData: options.IncludedData);

tracesSink = LoggerSinkConfiguration.CreateSink(wt => wt.Sink(openTelemetryTracesSink, options.BatchingOptions));
}

var sink = new OpenTelemetrySink(exporter, logsSink, tracesSink);

return loggerSinkConfiguration.Sink(sink, options.RestrictedToMinimumLevel, options.LevelSwitch);
}

/// <summary>
Expand Down Expand Up @@ -144,20 +166,35 @@ public static LoggerConfiguration OpenTelemetry(
if (configure == null) throw new ArgumentNullException(nameof(configure));

var options = new OpenTelemetrySinkOptions();

configure(options);

var exporter = Exporter.Create(
endpoint: options.Endpoint,
logsEndpoint: options.LogsEndpoint,
tracesEndpoint: options.TracesEndpoint,
protocol: options.Protocol,
headers: new Dictionary<string, string>(options.Headers),
httpMessageHandler: options.HttpMessageHandler ?? CreateDefaultHttpMessageHandler());

var sink = new OpenTelemetrySink(
exporter: exporter,
formatProvider: options.FormatProvider,
resourceAttributes: new Dictionary<string, object>(options.ResourceAttributes),
includedData: options.IncludedData);
ILogEventSink? logsSink = null, tracesSink = null;

if (options.LogsEndpoint != null)
{
logsSink = new OpenTelemetryLogsSink(
exporter: exporter,
formatProvider: options.FormatProvider,
resourceAttributes: new Dictionary<string, object>(options.ResourceAttributes),
includedData: options.IncludedData);
}

if (options.TracesEndpoint != null)
{
tracesSink = new OpenTelemetryTracesSink(
exporter: exporter,
resourceAttributes: new Dictionary<string, object>(options.ResourceAttributes),
includedData: options.IncludedData);
}

var sink = new OpenTelemetrySink(exporter, logsSink, tracesSink);

return loggerAuditSinkConfiguration.Sink(sink, options.RestrictedToMinimumLevel, options.LevelSwitch);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@
<PropertyGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
<DefineConstants>$(DefineConstants);FEATURE_CWT_ADDORUPDATE;FEATURE_ACTIVITY;FEATURE_HALF;FEATURE_DATE_AND_TIME_ONLY;FEATURE_SYNC_HTTP_SEND;FEATURE_SOCKETS_HTTP_HANDLER</DefineConstants>
</PropertyGroup>


<ItemGroup Condition=" '$(TargetFramework)' == 'net471' or '$(TargetFramework)' == 'net462' ">
<Using Include="System.Net.Http" />
</ItemGroup>

<ItemGroup>
<None Include="../../assets/serilog-sink-nuget.png" Pack="true" Visible="false" PackagePath="/" />
<None Include="../../README.md" Pack="true" Visible="false" PackagePath="/" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,6 @@ public static void Configure(BatchedOpenTelemetrySinkOptions options, Func<strin
if (getEnvironmentVariable(EndpointVarName) is { Length: > 1 } endpoint)
options.Endpoint = endpoint;

if (options.Protocol == OtlpProtocol.HttpProtobuf && !string.IsNullOrEmpty(options.Endpoint) && !options.Endpoint.EndsWith("/v1/logs"))
options.Endpoint = $"{options.Endpoint.TrimEnd('/')}/v1/logs";

FillHeadersIfPresent(getEnvironmentVariable(HeaderVarName), options.Headers);

FillHeadersResourceAttributesIfPresent(getEnvironmentVariable(ResourceAttributesVarName), options.ResourceAttributes);
Expand Down
43 changes: 43 additions & 0 deletions src/Serilog.Sinks.OpenTelemetry/Sinks/OpenTelemetry/Constants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright 2024 Serilog Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using System.Diagnostics;
using Serilog.Events;

namespace Serilog.Sinks.OpenTelemetry;

static class Constants
{
/// <summary>
/// The name of the entry in <see cref="LogEvent.Properties"/> that carries a
/// span's start timestamp. Log events will be interpreted as spans if and only if
/// this property contains a scalar <see cref="DateTime"/> value, as well as
/// valid <see cref="LogEvent.TraceId"/> and <see cref="LogEvent.SpanId"/> values.
/// </summary>
public const string SpanStartTimestampPropertyName = "SpanStartTimestamp";

/// <summary>
/// The name of the entry in <see cref="LogEvent.Properties"/> that carries a
/// span's <see cref="System.Diagnostics.Activity.ParentId"/>, if there is one.
/// </summary>
public const string ParentSpanIdPropertyName = "ParentSpanId";

/// <summary>
/// The name of the entry in <see cref="LogEvent.Properties"/> that carries a
/// span's kind. The value will be an <see cref="ActivityKind"/>. The span kind is
/// unset for <see cref="ActivityKind.Internal"/> spans: any span without an explicit
/// kind should be assumed to be internal.
/// </summary>
public const string SpanKindPropertyName = "SpanKind";
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.

using System.Net.Http;

namespace Serilog.Sinks.OpenTelemetry.Exporters;

static class Exporter
{
public static IExporter Create(
string endpoint,
string? logsEndpoint,
string? tracesEndpoint,
OtlpProtocol protocol,
IReadOnlyDictionary<string, string> headers,
HttpMessageHandler? httpMessageHandler)
{
return protocol switch
{
OtlpProtocol.HttpProtobuf => new HttpExporter(endpoint, headers, httpMessageHandler),
OtlpProtocol.Grpc => new GrpcExporter(endpoint, headers, httpMessageHandler),
OtlpProtocol.HttpProtobuf => new HttpExporter(logsEndpoint, tracesEndpoint, headers, httpMessageHandler),
OtlpProtocol.Grpc => new GrpcExporter(logsEndpoint, tracesEndpoint, headers, httpMessageHandler),
_ => throw new NotSupportedException($"OTLP protocol {protocol} is unsupported.")
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.

using System.Net.Http;
using Grpc.Core;
using Grpc.Net.Client;
using OpenTelemetry.Proto.Collector.Logs.V1;
using OpenTelemetry.Proto.Collector.Trace.V1;

namespace Serilog.Sinks.OpenTelemetry.Exporters;

Expand All @@ -25,26 +25,30 @@ namespace Serilog.Sinks.OpenTelemetry.Exporters;
/// </summary>
sealed class GrpcExporter : IExporter, IDisposable
{
readonly LogsService.LogsServiceClient _client;
readonly GrpcChannel? _logsChannel, _tracesChannel;

readonly GrpcChannel _channel;
readonly LogsService.LogsServiceClient? _logsClient;
readonly TraceService.TraceServiceClient? _tracesClient;

readonly Metadata _headers;

/// <summary>
/// Creates a new instance of a GrpcExporter that writes an
/// ExportLogsServiceRequest to a gRPC endpoint.
/// </summary>
/// <param name="endpoint">
/// The full OTLP endpoint to which logs are sent.
/// <param name="logsEndpoint">
/// The gRPC endpoint to which logs are sent.
/// </param>
/// <param name="tracesEndpoint">
/// The gRPC endpoint to which traces are sent.
/// </param>
/// <param name="headers">
/// A dictionary containing the request headers.
/// </param>
/// <param name="httpMessageHandler">
/// Custom HTTP message handler.
/// </param>
public GrpcExporter(string endpoint, IReadOnlyDictionary<string, string> headers,
public GrpcExporter(string? logsEndpoint, string? tracesEndpoint, IReadOnlyDictionary<string, string> headers,
HttpMessageHandler? httpMessageHandler = null)
{
var grpcChannelOptions = new GrpcChannelOptions();
Expand All @@ -53,9 +57,18 @@ public GrpcExporter(string endpoint, IReadOnlyDictionary<string, string> headers
grpcChannelOptions.HttpClient = new HttpClient(httpMessageHandler);
grpcChannelOptions.DisposeHttpClient = true;
}

_channel = GrpcChannel.ForAddress(endpoint, grpcChannelOptions);
_client = new LogsService.LogsServiceClient(_channel);

if (logsEndpoint != null)
{
_logsChannel = GrpcChannel.ForAddress(logsEndpoint, grpcChannelOptions);
_logsClient = new LogsService.LogsServiceClient(_logsChannel);
}

if (tracesEndpoint != null)
{
_tracesChannel = GrpcChannel.ForAddress(tracesEndpoint, grpcChannelOptions);
_tracesClient = new TraceService.TraceServiceClient(_logsChannel);
}

_headers = new Metadata();
foreach (var header in headers)
Expand All @@ -66,16 +79,27 @@ public GrpcExporter(string endpoint, IReadOnlyDictionary<string, string> headers

public void Dispose()
{
_channel.Dispose();
_logsChannel?.Dispose();
_tracesChannel?.Dispose();
}

public void Export(ExportLogsServiceRequest request)
{
_client.Export(request, _headers);
_logsClient?.Export(request, _headers);
}

public Task ExportAsync(ExportLogsServiceRequest request)
{
return _client.ExportAsync(request, _headers).ResponseAsync;
return _logsClient?.ExportAsync(request, _headers).ResponseAsync ?? Task.CompletedTask;
}

public void Export(ExportTraceServiceRequest request)
{
_tracesClient?.Export(request, _headers);
}

public Task ExportAsync(ExportTraceServiceRequest request)
{
return _tracesClient?.ExportAsync(request, _headers).ResponseAsync ?? Task.CompletedTask;
}
}
Loading

0 comments on commit 8f8612e

Please sign in to comment.