Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use SourceContext to populate InstrumentationScope.Name #113

Merged
merged 5 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions example/Example/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@

// ReSharper disable ExplicitCallerInfoArgument

#nullable enable

using Serilog;
using System.Diagnostics;
using Serilog.Sinks.OpenTelemetry;
Expand All @@ -30,11 +28,9 @@ static void Main()
{
// create an ActivitySource (that is listened to) for creating an Activity
// to test the trace and span ID enricher
using var listener = new ActivityListener
{
ShouldListenTo = _ => true,
Sample = (ref ActivityCreationOptions<ActivityContext> _) => ActivitySamplingResult.AllData,
};
using var listener = new ActivityListener();
listener.ShouldListenTo = _ => true;
listener.Sample = (ref ActivityCreationOptions<ActivityContext> _) => ActivitySamplingResult.AllData;

ActivitySource.AddActivityListener(listener);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

using Serilog.Configuration;
using Serilog.Sinks.OpenTelemetry;
using Serilog.Sinks.OpenTelemetry.Exporters;
using Serilog.Sinks.PeriodicBatching;

namespace Serilog;
Expand All @@ -39,15 +40,18 @@ public static LoggerConfiguration OpenTelemetry(
var options = new BatchedOpenTelemetrySinkOptions();
configure(options);

var openTelemetrySink = new OpenTelemetrySink(
var exporter = Exporter.Create(
endpoint: options.Endpoint,
protocol: options.Protocol,
formatProvider: options.FormatProvider,
resourceAttributes: new Dictionary<string, object>(options.ResourceAttributes),
headers: new Dictionary<string, string>(options.Headers),
includedData: options.IncludedData,
httpMessageHandler: options.HttpMessageHandler);

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

var sink = new PeriodicBatchingSink(openTelemetrySink, options.BatchingOptions);

return loggerSinkConfiguration.Sink(sink, options.RestrictedToMinimumLevel, options.LevelSwitch);
Expand Down Expand Up @@ -97,15 +101,18 @@ public static LoggerConfiguration OpenTelemetry(

configure(options);

var sink = new OpenTelemetrySink(
var exporter = Exporter.Create(
endpoint: options.Endpoint,
protocol: options.Protocol,
formatProvider: options.FormatProvider,
resourceAttributes: new Dictionary<string, object>(options.ResourceAttributes),
headers: new Dictionary<string, string>(options.Headers),
includedData: options.IncludedData,
httpMessageHandler: options.HttpMessageHandler);

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

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<PropertyGroup>
<Description>This Serilog sink transforms Serilog events into OpenTelemetry
logs and sends them to an OTLP (gRPC or HTTP) endpoint.</Description>
<VersionPrefix>1.2.1</VersionPrefix>
<VersionPrefix>2.0.0</VersionPrefix>
<Authors>Serilog Contributors</Authors>
<TargetFrameworks>net6.0;netstandard2.1;netstandard2.0;net462</TargetFrameworks>
<PackageTags>serilog;sink;opentelemetry</PackageTags>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright 2022 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.

namespace Serilog.Sinks.OpenTelemetry.Exporters;

static class Exporter
{
public static IExporter Create(
string endpoint,
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),
_ => throw new NotSupportedException($"OTLP protocol {protocol} is unsupported.")
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
using Grpc.Net.Client;
using OpenTelemetry.Proto.Collector.Logs.V1;

namespace Serilog.Sinks.OpenTelemetry;
namespace Serilog.Sinks.OpenTelemetry.Exporters;

/// <summary>
/// Implements an IExporter that sends OpenTelemetry Log requests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
using Google.Protobuf;
using OpenTelemetry.Proto.Collector.Logs.V1;

namespace Serilog.Sinks.OpenTelemetry;
namespace Serilog.Sinks.OpenTelemetry.Exporters;

/// <summary>
/// Implements an IExporter that sends OpenTelemetry Log requests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,11 @@ public enum IncludedData
/// <summary>
/// Include pre-rendered values for any message template placeholders that use custom format specifiers, in <c>message_template.renderings</c>.
/// </summary>
MessageTemplateRenderingsAttribute = 64
MessageTemplateRenderingsAttribute = 64,

/// <summary>
/// Preserve the value of the <c>SourceContext</c> property, in addition to using it as the OTLP <c>InstrumentationScope</c> name. If
/// not specified, the <c>SourceContext</c> property will be omitted from the individual log record attributes.
/// </summary>
SourceContextAttribute = 128
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using System.Globalization;
using OpenTelemetry.Proto.Common.V1;
using OpenTelemetry.Proto.Logs.V1;
using Serilog.Core;
using Serilog.Events;
using Serilog.Parsing;
using Serilog.Sinks.OpenTelemetry.Formatting;
Expand All @@ -26,18 +27,18 @@ namespace Serilog.Sinks.OpenTelemetry;

static class LogRecordBuilder
{
public static LogRecord ToLogRecord(LogEvent logEvent, IFormatProvider? formatProvider, IncludedData includedFields)
public static (LogRecord logRecord, string? scopeName) ToLogRecord(LogEvent logEvent, IFormatProvider? formatProvider, IncludedData includedData)
{
var logRecord = new LogRecord();

ProcessProperties(logRecord, logEvent);
ProcessProperties(logRecord, logEvent, includedData, out var scopeName);
ProcessTimestamp(logRecord, logEvent);
ProcessMessage(logRecord, logEvent, includedFields, formatProvider);
ProcessMessage(logRecord, logEvent, includedData, formatProvider);
ProcessLevel(logRecord, logEvent);
ProcessException(logRecord, logEvent);
ProcessIncludedFields(logRecord, logEvent, includedFields);
ProcessIncludedFields(logRecord, logEvent, includedData);

return logRecord;
return (logRecord, scopeName);
}

public static void ProcessMessage(LogRecord logRecord, LogEvent logEvent, IncludedData includedFields, IFormatProvider? formatProvider)
Expand Down Expand Up @@ -70,10 +71,20 @@ public static void ProcessLevel(LogRecord logRecord, LogEvent logEvent)
logRecord.SeverityNumber = PrimitiveConversions.ToSeverityNumber(level);
}

public static void ProcessProperties(LogRecord logRecord, LogEvent logEvent)
public static void ProcessProperties(LogRecord logRecord, LogEvent logEvent, IncludedData includedData, out string? scopeName)
{
scopeName = null;
foreach (var property in logEvent.Properties)
{
if (property is { Key: Constants.SourceContextPropertyName, Value: ScalarValue { Value: string sourceContext } })
{
scopeName = sourceContext;
if ((includedData & IncludedData.SourceContextAttribute) != IncludedData.SourceContextAttribute)
{
continue;
}
}

var v = PrimitiveConversions.ToOpenTelemetryAnyValue(property.Value);
logRecord.Attributes.Add(PrimitiveConversions.NewAttribute(property.Key, v));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// limitations under the License.

using OpenTelemetry.Proto.Collector.Logs.V1;
using OpenTelemetry.Proto.Logs.V1;
using Serilog.Core;
using Serilog.Events;
using Serilog.Sinks.OpenTelemetry.ProtocolHelpers;
Expand All @@ -23,26 +24,17 @@ namespace Serilog.Sinks.OpenTelemetry;
class OpenTelemetrySink : IBatchedLogEventSink, ILogEventSink, IDisposable
{
readonly IFormatProvider? _formatProvider;
readonly ExportLogsServiceRequest _requestTemplate;
readonly ResourceLogs _resourceLogsTemplate;
readonly IExporter _exporter;
readonly IncludedData _includedData;

public OpenTelemetrySink(
string endpoint,
OtlpProtocol protocol,
IFormatProvider? formatProvider,
IReadOnlyDictionary<string, object> resourceAttributes,
IReadOnlyDictionary<string, string> headers,
IncludedData includedData,
HttpMessageHandler? httpMessageHandler)
IExporter exporter,
IFormatProvider? formatProvider,
IReadOnlyDictionary<string, object> resourceAttributes,
IncludedData includedData)
{
_exporter = protocol switch
{
OtlpProtocol.HttpProtobuf => new HttpExporter(endpoint, headers, httpMessageHandler),
OtlpProtocol.Grpc => new GrpcExporter(endpoint, headers, httpMessageHandler),
_ => throw new NotSupportedException($"OTLP protocol {protocol} is unsupported.")
};

_exporter = exporter;
_formatProvider = formatProvider;
_includedData = includedData;

Expand All @@ -51,7 +43,7 @@ public OpenTelemetrySink(
resourceAttributes = RequiredResourceAttributes.AddDefaults(resourceAttributes);
}

_requestTemplate = RequestTemplateFactory.CreateRequestTemplate(resourceAttributes);
_resourceLogsTemplate = RequestTemplateFactory.CreateResourceLogs(resourceAttributes);
}

/// <summary>
Expand All @@ -62,25 +54,47 @@ public void Dispose()
(_exporter as IDisposable)?.Dispose();
}

void AddLogEventToRequest(LogEvent logEvent, ExportLogsServiceRequest request)
{
var logRecord = LogRecordBuilder.ToLogRecord(logEvent, _formatProvider, _includedData);
request.ResourceLogs[0].ScopeLogs[0].LogRecords.Add(logRecord);
}

/// <summary>
/// Transforms and sends the given batch of LogEvent objects
/// to an OTLP endpoint.
/// </summary>
public Task EmitBatchAsync(IEnumerable<LogEvent> batch)
{
var request = _requestTemplate.Clone();
var resourceLogs = _resourceLogsTemplate.Clone();

var anonymousScope = (ScopeLogs?)null;
var namedScopes = (Dictionary<string, ScopeLogs>?)null;

foreach (var logEvent in batch)
{
AddLogEventToRequest(logEvent, request);
var (logRecord, scopeName) = LogRecordBuilder.ToLogRecord(logEvent, _formatProvider, _includedData);
if (scopeName == null)
{
if (anonymousScope == null)
{
anonymousScope = RequestTemplateFactory.CreateScopeLogs(null);
resourceLogs.ScopeLogs.Add(anonymousScope);
}

anonymousScope.LogRecords.Add(logRecord);
}
else
{
namedScopes ??= new Dictionary<string, ScopeLogs>();
if (!namedScopes.TryGetValue(scopeName, out var namedScope))
{
namedScope = RequestTemplateFactory.CreateScopeLogs(scopeName);
namedScopes.Add(scopeName, namedScope);
resourceLogs.ScopeLogs.Add(namedScope);
}

namedScope.LogRecords.Add(logRecord);
}
}

var request = new ExportLogsServiceRequest();
request.ResourceLogs.Add(resourceLogs);

return _exporter.ExportAsync(request);
}

Expand All @@ -90,8 +104,13 @@ public Task EmitBatchAsync(IEnumerable<LogEvent> batch)
/// </summary>
public void Emit(LogEvent logEvent)
{
var request = _requestTemplate.Clone();
AddLogEventToRequest(logEvent, request);
var (logRecord, scopeName) = LogRecordBuilder.ToLogRecord(logEvent, _formatProvider, _includedData);
var scopeLogs = RequestTemplateFactory.CreateScopeLogs(scopeName);
scopeLogs.LogRecords.Add(logRecord);
var resourceLogs = _resourceLogsTemplate.Clone();
resourceLogs.ScopeLogs.Add(scopeLogs);
var request = new ExportLogsServiceRequest();
request.ResourceLogs.Add(resourceLogs);
_exporter.Export(request);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,12 @@ namespace Serilog.Sinks.OpenTelemetry.ProtocolHelpers;

static class PackageIdentity
{
public static string GetInstrumentationScopeName()
{
return typeof(RequestTemplateFactory).Assembly.GetName().Name
// Best we know about this, if it occurs.
?? throw new InvalidOperationException("Sink assembly name could not be retrieved.");
}

public static string GetInstrumentationScopeVersion()
public static string GetTelemetrySdkVersion()
{
return typeof(RequestTemplateFactory).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!.InformationalVersion;
}

public const string TelemetrySdkName = "serilog";

public static string GetTelemetrySdkVersion() => GetInstrumentationScopeVersion();

public const string TelemetrySdkLanguage = "dotnet";
}
Loading