Skip to content

Commit 02ec0a3

Browse files
authored
Merge pull request #150 from nblumhardt/suppress-instrumentation-scope
Provide a hook to call `OpenTelemetry.SuppressInstrumentationScope.Begin`
2 parents ec43a5d + ddfe9a4 commit 02ec0a3

11 files changed

+170
-11
lines changed

Diff for: README.md

+14
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,20 @@ Serilog `LogEvent` | OpenTelemetry `Span` | Comments
183183
`Properties["SpanStartTimestamp"]` | `StartTimeUnixNano` | Value must be of type `DateTime`; .NET provides 100-nanosecond precision |
184184
`Timestamp` | `EndTimeUnixNano` | .NET provides 100-nanosecond precision |
185185

186+
## Suppressing other instrumentation
187+
188+
If the sink is used in an application that also instruments HTTP or gRPC requests using the OpenTelemetry libraries,
189+
this can be suppressed for outbound requests made by the sink using `OnBeginSuppressInstrumentation`:
190+
191+
```csharp
192+
Log.Logger = new LoggerConfiguration()
193+
.WriteTo.OpenTelemetry(options =>
194+
{
195+
options.OnBeginSuppressInstrumentation =
196+
OpenTelemetry.SuppressInstrumentationScope.Begin;
197+
// ...
198+
```
199+
186200
## Example
187201

188202
The `example/Example` subdirectory contains an example application that logs

Diff for: src/Serilog.Sinks.OpenTelemetry/.DS_Store

-6 KB
Binary file not shown.

Diff for: src/Serilog.Sinks.OpenTelemetry/OpenTelemetryLoggerConfigurationExtensions.cs

+11-5
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public static class OpenTelemetryLoggerConfigurationExtensions
4343
/// Send log events to an OTLP exporter.
4444
/// </summary>
4545
/// <param name="loggerSinkConfiguration">
46-
/// The `WriteTo` configuration object.
46+
/// The <c>WriteTo</c> configuration object.
4747
/// </param>
4848
/// <param name="configure">The configuration callback.</param>
4949
/// <param name="ignoreEnvironment">If false the configuration will be overridden with values
@@ -69,7 +69,10 @@ public static LoggerConfiguration OpenTelemetry(
6969
tracesEndpoint: options.TracesEndpoint,
7070
protocol: options.Protocol,
7171
headers: new Dictionary<string, string>(options.Headers),
72-
httpMessageHandler: options.HttpMessageHandler ?? CreateDefaultHttpMessageHandler());
72+
httpMessageHandler: options.HttpMessageHandler ?? CreateDefaultHttpMessageHandler(),
73+
onBeginSuppressInstrumentation: options.OnBeginSuppressInstrumentation != null ?
74+
() => options.OnBeginSuppressInstrumentation(true)
75+
: null);
7376

7477
ILogEventSink? logsSink = null, tracesSink = null;
7578

@@ -103,7 +106,7 @@ public static LoggerConfiguration OpenTelemetry(
103106
/// Send log events to an OTLP exporter.
104107
/// </summary>
105108
/// <param name="loggerSinkConfiguration">
106-
/// The `WriteTo` configuration object.
109+
/// The <c>WriteTo</c> configuration object.
107110
/// </param>
108111
/// <param name="endpoint">
109112
/// The full URL of the OTLP exporter endpoint.
@@ -156,7 +159,7 @@ public static LoggerConfiguration OpenTelemetry(
156159
/// Audit to an OTLP exporter, waiting for each event to be acknowledged, and propagating errors to the caller.
157160
/// </summary>
158161
/// <param name="loggerAuditSinkConfiguration">
159-
/// The `AuditTo` configuration object.
162+
/// The <c>AuditTo</c> configuration object.
160163
/// </param>
161164
/// <param name="configure">The configuration callback.</param>
162165
public static LoggerConfiguration OpenTelemetry(
@@ -173,7 +176,10 @@ public static LoggerConfiguration OpenTelemetry(
173176
tracesEndpoint: options.TracesEndpoint,
174177
protocol: options.Protocol,
175178
headers: new Dictionary<string, string>(options.Headers),
176-
httpMessageHandler: options.HttpMessageHandler ?? CreateDefaultHttpMessageHandler());
179+
httpMessageHandler: options.HttpMessageHandler ?? CreateDefaultHttpMessageHandler(),
180+
onBeginSuppressInstrumentation: options.OnBeginSuppressInstrumentation != null ?
181+
() => options.OnBeginSuppressInstrumentation(true)
182+
: null);
177183

178184
ILogEventSink? logsSink = null, tracesSink = null;
179185

Diff for: src/Serilog.Sinks.OpenTelemetry/Serilog.Sinks.OpenTelemetry.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<PropertyGroup>
33
<Description>This Serilog sink transforms Serilog events into OpenTelemetry
44
logs and sends them to an OTLP (gRPC or HTTP) endpoint.</Description>
5-
<VersionPrefix>4.0.1</VersionPrefix>
5+
<VersionPrefix>4.1.0</VersionPrefix>
66
<Authors>Serilog Contributors</Authors>
77
<!-- .NET Framework version targeting is frozen at these two TFMs. -->
88
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT'">net471;net462</TargetFrameworks>

Diff for: src/Serilog.Sinks.OpenTelemetry/Sinks/OpenTelemetry/Exporters/Exporter.cs

+9-4
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,18 @@ public static IExporter Create(
2121
string? tracesEndpoint,
2222
OtlpProtocol protocol,
2323
IReadOnlyDictionary<string, string> headers,
24-
HttpMessageHandler? httpMessageHandler)
24+
HttpMessageHandler? httpMessageHandler,
25+
Func<IDisposable>? onBeginSuppressInstrumentation)
2526
{
26-
return protocol switch
27+
var exporter = protocol switch
2728
{
28-
OtlpProtocol.HttpProtobuf => new HttpExporter(logsEndpoint, tracesEndpoint, headers, httpMessageHandler),
29+
OtlpProtocol.HttpProtobuf => (IExporter)new HttpExporter(logsEndpoint, tracesEndpoint, headers, httpMessageHandler),
2930
OtlpProtocol.Grpc => new GrpcExporter(logsEndpoint, tracesEndpoint, headers, httpMessageHandler),
3031
_ => throw new NotSupportedException($"OTLP protocol {protocol} is unsupported.")
3132
};
33+
34+
return onBeginSuppressInstrumentation != null
35+
? new InstrumentationSuppressingExporter(exporter, onBeginSuppressInstrumentation)
36+
: exporter;
3237
}
33-
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright © Serilog Contributors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using OpenTelemetry.Proto.Collector.Logs.V1;
16+
using OpenTelemetry.Proto.Collector.Trace.V1;
17+
18+
namespace Serilog.Sinks.OpenTelemetry.Exporters;
19+
20+
/// <summary>
21+
/// Uses a callback to suppress instrumentation while telemetry is being exported by the wrapped exporter.
22+
/// </summary>
23+
sealed class InstrumentationSuppressingExporter : IExporter, IDisposable
24+
{
25+
readonly IExporter _exporter;
26+
readonly Func<IDisposable> _onBeginSuppressInstrumentation;
27+
28+
public InstrumentationSuppressingExporter(IExporter exporter, Func<IDisposable> onBeginSuppressInstrumentation)
29+
{
30+
_exporter = exporter;
31+
_onBeginSuppressInstrumentation = onBeginSuppressInstrumentation;
32+
}
33+
34+
public void Export(ExportLogsServiceRequest request)
35+
{
36+
using (_onBeginSuppressInstrumentation())
37+
{
38+
_exporter.Export(request);
39+
}
40+
}
41+
42+
public async Task ExportAsync(ExportLogsServiceRequest request)
43+
{
44+
using (_onBeginSuppressInstrumentation())
45+
{
46+
await _exporter.ExportAsync(request);
47+
}
48+
}
49+
50+
public void Export(ExportTraceServiceRequest request)
51+
{
52+
using (_onBeginSuppressInstrumentation())
53+
{
54+
_exporter.Export(request);
55+
}
56+
}
57+
58+
public async Task ExportAsync(ExportTraceServiceRequest request)
59+
{
60+
using (_onBeginSuppressInstrumentation())
61+
{
62+
await _exporter.ExportAsync(request);
63+
}
64+
}
65+
66+
public void Dispose()
67+
{
68+
(_exporter as IDisposable)?.Dispose();
69+
}
70+
}

Diff for: src/Serilog.Sinks.OpenTelemetry/Sinks/OpenTelemetry/OpenTelemetrySinkOptions.cs

+12
Original file line numberDiff line numberDiff line change
@@ -158,4 +158,16 @@ public string? TracesEndpoint
158158
/// to be changed at runtime.
159159
/// </summary>
160160
public LoggingLevelSwitch? LevelSwitch { get; set; }
161+
162+
/// <summary>
163+
/// A callback used by the sink before triggering behaviors that may themselves generate log or trace information.
164+
/// Set this value to <c>OpenTelemetry.SuppressInstrumentationScope.Begin</c> when using this sink in applications
165+
/// that instrument HTTP or gRPC requests using OpenTelemetry.
166+
/// </summary>
167+
/// <example>
168+
/// options.OnBeginSuppressInstrumentation = OpenTelemetry.SuppressInstrumentationScope.Begin;
169+
/// </example>
170+
/// <remarks>This callback accepts a <c langword="bool"/> in order to match the signature of the OpenTelemetry SDK method
171+
/// that is typically assigned to it. The sink always provides the callback with the value <c langword="true" />.</remarks>
172+
public Func<bool, IDisposable>? OnBeginSuppressInstrumentation { get; set; }
161173
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using OpenTelemetry.Proto.Collector.Logs.V1;
2+
using Serilog.Sinks.OpenTelemetry.Exporters;
3+
using Serilog.Sinks.OpenTelemetry.Tests.Support;
4+
using Xunit;
5+
6+
namespace Serilog.Sinks.OpenTelemetry.Tests;
7+
8+
public class InstrumentationSuppressingExporterTests
9+
{
10+
[Fact]
11+
public void RequestsAreNotInstrumentedWhenSuppressed()
12+
{
13+
var exporter = new CollectingExporter();
14+
15+
exporter.Export(new ExportLogsServiceRequest());
16+
Assert.Equal(1, exporter.InstrumentedRequestCount);
17+
Assert.Single(exporter.ExportLogsServiceRequests);
18+
19+
var wrapper = new InstrumentationSuppressingExporter(exporter, TestSuppressInstrumentationScope.Begin);
20+
wrapper.Export(new ExportLogsServiceRequest());
21+
Assert.Equal(1, exporter.InstrumentedRequestCount);
22+
Assert.Equal(2, exporter.ExportLogsServiceRequests.Count);
23+
}
24+
}

Diff for: test/Serilog.Sinks.OpenTelemetry.Tests/PublicApiVisibilityTests.approved.txt

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ namespace Serilog.Sinks.OpenTelemetry
3838
public Serilog.Sinks.OpenTelemetry.IncludedData IncludedData { get; set; }
3939
public Serilog.Core.LoggingLevelSwitch? LevelSwitch { get; set; }
4040
public string? LogsEndpoint { get; set; }
41+
public System.Func<bool, System.IDisposable>? OnBeginSuppressInstrumentation { get; set; }
4142
public Serilog.Sinks.OpenTelemetry.OtlpProtocol Protocol { get; set; }
4243
public System.Collections.Generic.IDictionary<string, object> ResourceAttributes { get; set; }
4344
public Serilog.Events.LogEventLevel RestrictedToMinimumLevel { get; set; }

Diff for: test/Serilog.Sinks.OpenTelemetry.Tests/Support/CollectingExporter.cs

+6-1
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,33 @@ namespace Serilog.Sinks.OpenTelemetry.Tests.Support;
55

66
class CollectingExporter: IExporter
77
{
8+
public int InstrumentedRequestCount { get; private set; }
89
public List<ExportLogsServiceRequest> ExportLogsServiceRequests { get; } = new();
910
public List<ExportTraceServiceRequest> ExportTraceServiceRequests { get; } = new();
1011

1112
public void Export(ExportLogsServiceRequest request)
1213
{
14+
if (!TestSuppressInstrumentationScope.IsSuppressed) InstrumentedRequestCount++;
1315
ExportLogsServiceRequests.Add(request);
1416
}
1517

1618
public Task ExportAsync(ExportLogsServiceRequest request)
1719
{
20+
if (!TestSuppressInstrumentationScope.IsSuppressed) InstrumentedRequestCount++;
1821
Export(request);
1922
return Task.CompletedTask;
2023
}
2124

2225
public void Export(ExportTraceServiceRequest request)
2326
{
27+
if (!TestSuppressInstrumentationScope.IsSuppressed) InstrumentedRequestCount++;
2428
ExportTraceServiceRequests.Add(request);
2529
}
2630

2731
public Task ExportAsync(ExportTraceServiceRequest request)
2832
{
33+
if (!TestSuppressInstrumentationScope.IsSuppressed) InstrumentedRequestCount++;
2934
Export(request);
3035
return Task.CompletedTask;
3136
}
32-
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
namespace Serilog.Sinks.OpenTelemetry.Tests.Support;
2+
3+
class TestSuppressInstrumentationScope: IDisposable
4+
{
5+
static readonly AsyncLocal<int> Depth = new();
6+
bool _disposed;
7+
8+
public static bool IsSuppressed => Depth.Value > 0;
9+
10+
public static IDisposable Begin()
11+
{
12+
Depth.Value++;
13+
return new TestSuppressInstrumentationScope();
14+
}
15+
16+
public void Dispose()
17+
{
18+
if (_disposed) return;
19+
_disposed = true;
20+
Depth.Value--;
21+
}
22+
}

0 commit comments

Comments
 (0)