Skip to content

Commit

Permalink
Provide a hook to call OpenTelemetry.SuppressInstrumentationScope.Beg…
Browse files Browse the repository at this point in the history
…in before exporting HTTP or gRPC payloads
  • Loading branch information
nblumhardt committed Aug 12, 2024
1 parent ec43a5d commit aafb957
Show file tree
Hide file tree
Showing 9 changed files with 160 additions and 7 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,20 @@ Serilog `LogEvent` | OpenTelemetry `Span` | Comments
`Properties["SpanStartTimestamp"]` | `StartTimeUnixNano` | Value must be of type `DateTime`; .NET provides 100-nanosecond precision |
`Timestamp` | `EndTimeUnixNano` | .NET provides 100-nanosecond precision |

## Suppressing other instrumentation

If the sink is used in an application that also instruments HTTP or gRPC requests using the OpenTelemetry libraries,
this can be suppressed for outbound requests made by the sink using `OnBeginSuppressInstrumentation`:

```csharp
Log.Logger = new LoggerConfiguration()
.WriteTo.OpenTelemetry(options =>
{
options.OnBeginSuppressInstrumentation =
OpenTelemetry.SuppressInstrumentationScope.Begin;
// ...
```

## Example

The `example/Example` subdirectory contains an example application that logs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ public static LoggerConfiguration OpenTelemetry(
tracesEndpoint: options.TracesEndpoint,
protocol: options.Protocol,
headers: new Dictionary<string, string>(options.Headers),
httpMessageHandler: options.HttpMessageHandler ?? CreateDefaultHttpMessageHandler());
httpMessageHandler: options.HttpMessageHandler ?? CreateDefaultHttpMessageHandler(),
onBeginSuppressInstrumentation: options.OnBeginSuppressInstrumentation);

ILogEventSink? logsSink = null, tracesSink = null;

Expand Down Expand Up @@ -173,7 +174,8 @@ public static LoggerConfiguration OpenTelemetry(
tracesEndpoint: options.TracesEndpoint,
protocol: options.Protocol,
headers: new Dictionary<string, string>(options.Headers),
httpMessageHandler: options.HttpMessageHandler ?? CreateDefaultHttpMessageHandler());
httpMessageHandler: options.HttpMessageHandler ?? CreateDefaultHttpMessageHandler(),
onBeginSuppressInstrumentation: options.OnBeginSuppressInstrumentation);

ILogEventSink? logsSink = null, tracesSink = null;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,18 @@ public static IExporter Create(
string? tracesEndpoint,
OtlpProtocol protocol,
IReadOnlyDictionary<string, string> headers,
HttpMessageHandler? httpMessageHandler)
HttpMessageHandler? httpMessageHandler,
Func<IDisposable>? onBeginSuppressInstrumentation)
{
return protocol switch
var exporter = protocol switch
{
OtlpProtocol.HttpProtobuf => new HttpExporter(logsEndpoint, tracesEndpoint, headers, httpMessageHandler),
OtlpProtocol.HttpProtobuf => (IExporter)new HttpExporter(logsEndpoint, tracesEndpoint, headers, httpMessageHandler),
OtlpProtocol.Grpc => new GrpcExporter(logsEndpoint, tracesEndpoint, headers, httpMessageHandler),
_ => throw new NotSupportedException($"OTLP protocol {protocol} is unsupported.")
};

return onBeginSuppressInstrumentation != null
? new InstrumentationSuppressingExporter(exporter, onBeginSuppressInstrumentation)
: exporter;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright © 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 OpenTelemetry.Proto.Collector.Logs.V1;
using OpenTelemetry.Proto.Collector.Trace.V1;

namespace Serilog.Sinks.OpenTelemetry.Exporters;

/// <summary>
/// Uses a callback to suppress instrumentation while telemetry is being exported by the wrapped exporter.
/// </summary>
sealed class InstrumentationSuppressingExporter : IExporter, IDisposable
{
readonly IExporter _exporter;
readonly Func<IDisposable> _onBeginSuppressInstrumentation;

public InstrumentationSuppressingExporter(IExporter exporter, Func<IDisposable> onBeginSuppressInstrumentation)
{
_exporter = exporter;
_onBeginSuppressInstrumentation = onBeginSuppressInstrumentation;
}

public void Export(ExportLogsServiceRequest request)
{
using (_onBeginSuppressInstrumentation())
{
_exporter.Export(request);
}
}

public async Task ExportAsync(ExportLogsServiceRequest request)
{
using (_onBeginSuppressInstrumentation())
{
await _exporter.ExportAsync(request);
}
}

public void Export(ExportTraceServiceRequest request)
{
using (_onBeginSuppressInstrumentation())
{
_exporter.Export(request);
}
}

public async Task ExportAsync(ExportTraceServiceRequest request)
{
using (_onBeginSuppressInstrumentation())
{
await _exporter.ExportAsync(request);
}
}

public void Dispose()
{
(_exporter as IDisposable)?.Dispose();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -158,4 +158,14 @@ public string? TracesEndpoint
/// to be changed at runtime.
/// </summary>
public LoggingLevelSwitch? LevelSwitch { get; set; }

/// <summary>
/// A callback used by the sink before triggering behaviors that may themselves generate log or trace information.
/// Set this value to <c>OpenTelemetry.SuppressInstrumentationScope.Begin</c> when using this sink in applications
/// that instrument HTTP or gRPC requests using OpenTelemetry.
/// </summary>
/// <example>
/// options.OnBeginSuppressInstrumentation = OpenTelemetry.SuppressInstrumentationScope.Begin;
/// </example>
public Func<IDisposable>? OnBeginSuppressInstrumentation { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using OpenTelemetry.Proto.Collector.Logs.V1;
using Serilog.Sinks.OpenTelemetry.Exporters;
using Serilog.Sinks.OpenTelemetry.Tests.Support;
using Xunit;

namespace Serilog.Sinks.OpenTelemetry.Tests;

public class InstrumentationSuppressingExporterTests
{
[Fact]
public void RequestsAreNotInstrumentedWhenSuppressed()
{
var exporter = new CollectingExporter();

exporter.Export(new ExportLogsServiceRequest());
Assert.Equal(1, exporter.InstrumentedRequestCount);
Assert.Single(exporter.ExportLogsServiceRequests);

var wrapper = new InstrumentationSuppressingExporter(exporter, TestSuppressInstrumentationScope.Begin);
wrapper.Export(new ExportLogsServiceRequest());
Assert.Equal(1, exporter.InstrumentedRequestCount);
Assert.Equal(2, exporter.ExportLogsServiceRequests.Count);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ namespace Serilog.Sinks.OpenTelemetry
public Serilog.Sinks.OpenTelemetry.IncludedData IncludedData { get; set; }
public Serilog.Core.LoggingLevelSwitch? LevelSwitch { get; set; }
public string? LogsEndpoint { get; set; }
public System.Func<System.IDisposable>? OnBeginSuppressInstrumentation { get; set; }
public Serilog.Sinks.OpenTelemetry.OtlpProtocol Protocol { get; set; }
public System.Collections.Generic.IDictionary<string, object> ResourceAttributes { get; set; }
public Serilog.Events.LogEventLevel RestrictedToMinimumLevel { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,33 @@ namespace Serilog.Sinks.OpenTelemetry.Tests.Support;

class CollectingExporter: IExporter
{
public int InstrumentedRequestCount { get; private set; }
public List<ExportLogsServiceRequest> ExportLogsServiceRequests { get; } = new();
public List<ExportTraceServiceRequest> ExportTraceServiceRequests { get; } = new();

public void Export(ExportLogsServiceRequest request)
{
if (!TestSuppressInstrumentationScope.IsSuppressed) InstrumentedRequestCount++;
ExportLogsServiceRequests.Add(request);
}

public Task ExportAsync(ExportLogsServiceRequest request)
{
if (!TestSuppressInstrumentationScope.IsSuppressed) InstrumentedRequestCount++;
Export(request);
return Task.CompletedTask;
}

public void Export(ExportTraceServiceRequest request)
{
if (!TestSuppressInstrumentationScope.IsSuppressed) InstrumentedRequestCount++;
ExportTraceServiceRequests.Add(request);
}

public Task ExportAsync(ExportTraceServiceRequest request)
{
if (!TestSuppressInstrumentationScope.IsSuppressed) InstrumentedRequestCount++;
Export(request);
return Task.CompletedTask;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Serilog.Sinks.OpenTelemetry.Tests.Support;

class TestSuppressInstrumentationScope: IDisposable
{
static readonly AsyncLocal<int> Depth = new();
bool _disposed;

public static bool IsSuppressed => Depth.Value > 0;

public static IDisposable Begin()
{
Depth.Value++;
return new TestSuppressInstrumentationScope();
}

public void Dispose()
{
if (_disposed) return;
_disposed = true;
Depth.Value--;
}
}

0 comments on commit aafb957

Please sign in to comment.