[Logs] OpenTelemetry.Extensions.EventSource extensions project (#3454)
* Add OpenTelemetryEventSourceLogEmitter. * Project rename. * Started tests. * Added behavior comment in example. * Add --prerelease in READMEs. * Example tweaks. * Test improvements. * Added activity_id/related_activity_id test. * Warning cleanup.
This commit is contained in:
parent
1cd84d806a
commit
bfabe9bc26
|
|
@ -235,6 +235,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Extensions.Se
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Extensions.Serilog.Tests", "test\OpenTelemetry.Extensions.Serilog.Tests\OpenTelemetry.Extensions.Serilog.Tests.csproj", "{6A2C122A-C1CD-4B6B-AE09-2ABB7D3C50CE}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Extensions.EventSource", "src\OpenTelemetry.Extensions.EventSource\OpenTelemetry.Extensions.EventSource.csproj", "{7AFB4975-9680-4668-9F5E-C3F0CA41E982}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Extensions.EventSource.Tests", "test\OpenTelemetry.Extensions.EventSource.Tests\OpenTelemetry.Extensions.EventSource.Tests.csproj", "{304FCFFF-97DE-484B-8D8C-612C644426E5}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -481,6 +485,14 @@ Global
|
|||
{6A2C122A-C1CD-4B6B-AE09-2ABB7D3C50CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6A2C122A-C1CD-4B6B-AE09-2ABB7D3C50CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6A2C122A-C1CD-4B6B-AE09-2ABB7D3C50CE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7AFB4975-9680-4668-9F5E-C3F0CA41E982}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7AFB4975-9680-4668-9F5E-C3F0CA41E982}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7AFB4975-9680-4668-9F5E-C3F0CA41E982}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7AFB4975-9680-4668-9F5E-C3F0CA41E982}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{304FCFFF-97DE-484B-8D8C-612C644426E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{304FCFFF-97DE-484B-8D8C-612C644426E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{304FCFFF-97DE-484B-8D8C-612C644426E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{304FCFFF-97DE-484B-8D8C-612C644426E5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
// <copyright file="ExampleEventSource.cs" company="OpenTelemetry Authors">
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// 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.
|
||||
// </copyright>
|
||||
|
||||
using System.Diagnostics.Tracing;
|
||||
|
||||
namespace Examples.LoggingExtensions;
|
||||
|
||||
[EventSource(Name = EventSourceName)]
|
||||
internal sealed class ExampleEventSource : EventSource
|
||||
{
|
||||
public const string EventSourceName = "OpenTelemetry-ExampleEventSource";
|
||||
|
||||
public static ExampleEventSource Log { get; } = new();
|
||||
|
||||
[Event(1, Message = "Example event written with '{0}' reason", Level = EventLevel.Informational)]
|
||||
public void ExampleEvent(string reason)
|
||||
{
|
||||
this.WriteEvent(1, reason);
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@
|
|||
<ItemGroup>
|
||||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.Console\OpenTelemetry.Exporter.Console.csproj" />
|
||||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Extensions.Serilog\OpenTelemetry.Extensions.Serilog.csproj" />
|
||||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Extensions.EventSource\OpenTelemetry.Extensions.EventSource.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -14,14 +14,14 @@
|
|||
// limitations under the License.
|
||||
// </copyright>
|
||||
|
||||
using System.Diagnostics.Tracing;
|
||||
using Examples.LoggingExtensions;
|
||||
using OpenTelemetry.Logs;
|
||||
using OpenTelemetry.Resources;
|
||||
using Serilog;
|
||||
|
||||
var resourceBuilder = ResourceBuilder.CreateDefault().AddService("Examples.LogEmitter");
|
||||
var resourceBuilder = ResourceBuilder.CreateDefault().AddService("Examples.LoggingExtensions");
|
||||
|
||||
// Note: It is important that OpenTelemetryLoggerProvider is disposed when the
|
||||
// app is shutdown. In this example we allow Serilog to do that by calling CloseAndFlush.
|
||||
var openTelemetryLoggerProvider = new OpenTelemetryLoggerProvider(options =>
|
||||
{
|
||||
options.IncludeFormattedMessage = true;
|
||||
|
|
@ -30,18 +30,29 @@ var openTelemetryLoggerProvider = new OpenTelemetryLoggerProvider(options =>
|
|||
.AddConsoleExporter();
|
||||
});
|
||||
|
||||
// Creates an OpenTelemetryEventSourceLogEmitter for routing ExampleEventSource
|
||||
// events into logs
|
||||
using var openTelemetryEventSourceLogEmitter = new OpenTelemetryEventSourceLogEmitter(
|
||||
openTelemetryLoggerProvider, // <- Events will be written to openTelemetryLoggerProvider
|
||||
(name) => name == ExampleEventSource.EventSourceName ? EventLevel.Informational : null,
|
||||
disposeProvider: false); // <- Do not dispose the provider with OpenTelemetryEventSourceLogEmitter since in this case it is shared with Serilog
|
||||
|
||||
// Configure Serilog global logger
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.WriteTo.OpenTelemetry(openTelemetryLoggerProvider, disposeProvider: true) // <- Register OpenTelemetry Serilog sink
|
||||
.WriteTo.OpenTelemetry(
|
||||
openTelemetryLoggerProvider, // <- Register OpenTelemetry Serilog sink writing to openTelemetryLoggerProvider
|
||||
disposeProvider: false) // <- Do not dispose the provider with Serilog since in this case it is shared with OpenTelemetryEventSourceLogEmitter
|
||||
.CreateLogger();
|
||||
|
||||
ExampleEventSource.Log.ExampleEvent("Startup complete");
|
||||
|
||||
// Note: Serilog ForContext API is used to set "CategoryName" on log messages
|
||||
ILogger programLogger = Log.Logger.ForContext<Program>();
|
||||
|
||||
programLogger.Information("Application started {Greeting} {Location}", "Hello", "World");
|
||||
|
||||
programLogger.Information("Message {Array}", new string[] { "value1", "value2" });
|
||||
|
||||
// Note: For Serilog this call flushes all logs and disposes
|
||||
// OpenTelemetryLoggerProvider.
|
||||
// Note: For Serilog this call flushes all logs
|
||||
Log.CloseAndFlush();
|
||||
|
||||
// Manually dispose OpenTelemetryLoggerProvider since it is being shared
|
||||
openTelemetryLoggerProvider.Dispose();
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
#nullable enable
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
#nullable enable
|
||||
OpenTelemetry.Logs.OpenTelemetryEventSourceLogEmitter
|
||||
OpenTelemetry.Logs.OpenTelemetryEventSourceLogEmitter.OpenTelemetryEventSourceLogEmitter(OpenTelemetry.Logs.OpenTelemetryLoggerProvider! openTelemetryLoggerProvider, System.Func<string!, System.Diagnostics.Tracing.EventLevel?>! shouldListenToFunc, bool disposeProvider = true) -> void
|
||||
override OpenTelemetry.Logs.OpenTelemetryEventSourceLogEmitter.Dispose() -> void
|
||||
|
|
@ -0,0 +1 @@
|
|||
#nullable enable
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
#nullable enable
|
||||
OpenTelemetry.Logs.OpenTelemetryEventSourceLogEmitter
|
||||
OpenTelemetry.Logs.OpenTelemetryEventSourceLogEmitter.OpenTelemetryEventSourceLogEmitter(OpenTelemetry.Logs.OpenTelemetryLoggerProvider! openTelemetryLoggerProvider, System.Func<string!, System.Diagnostics.Tracing.EventLevel?>! shouldListenToFunc, bool disposeProvider = true) -> void
|
||||
override OpenTelemetry.Logs.OpenTelemetryEventSourceLogEmitter.Dispose() -> void
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// <copyright file="AssemblyInfo.cs" company="OpenTelemetry Authors">
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// 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.
|
||||
// </copyright>
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: CLSCompliant(false)]
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2" + AssemblyInfo.MoqPublicKey)]
|
||||
|
||||
#if SIGNED
|
||||
internal static class AssemblyInfo
|
||||
{
|
||||
public const string PublicKey = ", PublicKey=002400000480000094000000060200000024000052534131000400000100010051C1562A090FB0C9F391012A32198B5E5D9A60E9B80FA2D7B434C9E5CCB7259BD606E66F9660676AFC6692B8CDC6793D190904551D2103B7B22FA636DCBB8208839785BA402EA08FC00C8F1500CCEF28BBF599AA64FFB1E1D5DC1BF3420A3777BADFE697856E9D52070A50C3EA5821C80BEF17CA3ACFFA28F89DD413F096F898";
|
||||
public const string MoqPublicKey = ", PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7";
|
||||
}
|
||||
#else
|
||||
internal static class AssemblyInfo
|
||||
{
|
||||
public const string PublicKey = "";
|
||||
public const string MoqPublicKey = "";
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
Initial release.
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<!-- OmniSharp/VS Code requires TargetFrameworks to be in descending order for IntelliSense and analysis. -->
|
||||
<TargetFrameworks>netstandard2.1;netstandard2.0</TargetFrameworks>
|
||||
<Description>Extensions for using OpenTelemetry with System.Diagnostics.Tracing.EventSource</Description>
|
||||
<Nullable>enable</Nullable>
|
||||
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
|
||||
<AnalysisLevel>latest</AnalysisLevel>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry\OpenTelemetry.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="$(RepoRoot)\src\OpenTelemetry.Api\Internal\Guard.cs" Link="Includes\Guard.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,224 @@
|
|||
// <copyright file="OpenTelemetryEventSourceLogEmitter.cs" company="OpenTelemetry Authors">
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// 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.
|
||||
// </copyright>
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Tracing;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OpenTelemetry.Internal;
|
||||
|
||||
namespace OpenTelemetry.Logs
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements an <see cref="EventListener"/> which will convert <see
|
||||
/// cref="EventSource"/> events into OpenTelemetry logs.
|
||||
/// </summary>
|
||||
public sealed class OpenTelemetryEventSourceLogEmitter : EventListener
|
||||
{
|
||||
private readonly bool includeFormattedMessage;
|
||||
private readonly OpenTelemetryLoggerProvider openTelemetryLoggerProvider;
|
||||
private readonly LogEmitter logEmitter;
|
||||
private readonly object lockObj = new();
|
||||
private readonly Func<string, EventLevel?> shouldListenToFunc;
|
||||
private readonly List<EventSource> eventSources = new();
|
||||
private readonly List<EventSource>? eventSourcesBeforeConstructor = new();
|
||||
private readonly bool disposeProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see
|
||||
/// cref="OpenTelemetryEventSourceLogEmitter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="openTelemetryLoggerProvider"><see
|
||||
/// cref="OpenTelemetryLoggerProvider"/>.</param>
|
||||
/// <param name="shouldListenToFunc">Callback function used to decide if
|
||||
/// events should be captured for a given <see
|
||||
/// cref="EventSource.Name"/>. Return <see langword="null"/> if no
|
||||
/// events should be captured.</param>
|
||||
/// <param name="disposeProvider">Controls whether or not the supplied
|
||||
/// <paramref name="openTelemetryLoggerProvider"/> will be disposed when
|
||||
/// the <see cref="EventListener"/> is disposed. Default value: <see
|
||||
/// langword="true"/>.</param>
|
||||
public OpenTelemetryEventSourceLogEmitter(
|
||||
OpenTelemetryLoggerProvider openTelemetryLoggerProvider,
|
||||
Func<string, EventLevel?> shouldListenToFunc,
|
||||
bool disposeProvider = true)
|
||||
{
|
||||
Guard.ThrowIfNull(openTelemetryLoggerProvider);
|
||||
Guard.ThrowIfNull(shouldListenToFunc);
|
||||
|
||||
this.includeFormattedMessage = openTelemetryLoggerProvider.IncludeFormattedMessage;
|
||||
this.openTelemetryLoggerProvider = openTelemetryLoggerProvider!;
|
||||
this.disposeProvider = disposeProvider;
|
||||
this.shouldListenToFunc = shouldListenToFunc;
|
||||
|
||||
var logEmitter = this.openTelemetryLoggerProvider.CreateEmitter();
|
||||
Debug.Assert(logEmitter != null, "logEmitter was null");
|
||||
|
||||
this.logEmitter = logEmitter!;
|
||||
|
||||
lock (this.lockObj)
|
||||
{
|
||||
foreach (EventSource eventSource in this.eventSourcesBeforeConstructor)
|
||||
{
|
||||
this.ProcessSource(eventSource);
|
||||
}
|
||||
|
||||
this.eventSourcesBeforeConstructor = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Dispose()
|
||||
{
|
||||
foreach (EventSource eventSource in this.eventSources)
|
||||
{
|
||||
this.DisableEvents(eventSource);
|
||||
}
|
||||
|
||||
this.eventSources.Clear();
|
||||
|
||||
if (this.disposeProvider)
|
||||
{
|
||||
this.openTelemetryLoggerProvider.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
#pragma warning disable CA1062 // Validate arguments of public methods
|
||||
/// <inheritdoc/>
|
||||
protected override void OnEventSourceCreated(EventSource eventSource)
|
||||
{
|
||||
Debug.Assert(eventSource != null, "EventSource was null.");
|
||||
|
||||
try
|
||||
{
|
||||
if (this.eventSourcesBeforeConstructor != null)
|
||||
{
|
||||
lock (this.lockObj)
|
||||
{
|
||||
if (this.eventSourcesBeforeConstructor != null)
|
||||
{
|
||||
this.eventSourcesBeforeConstructor.Add(eventSource!);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.ProcessSource(eventSource!);
|
||||
}
|
||||
finally
|
||||
{
|
||||
base.OnEventSourceCreated(eventSource);
|
||||
}
|
||||
}
|
||||
#pragma warning restore CA1062 // Validate arguments of public methods
|
||||
|
||||
#pragma warning disable CA1062 // Validate arguments of public methods
|
||||
/// <inheritdoc/>
|
||||
protected override void OnEventWritten(EventWrittenEventArgs eventData)
|
||||
{
|
||||
Debug.Assert(eventData != null, "EventData was null.");
|
||||
|
||||
string? rawMessage = eventData!.Message;
|
||||
|
||||
LogRecordData data = new(Activity.Current)
|
||||
{
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
Timestamp = eventData.TimeStamp,
|
||||
#endif
|
||||
EventId = new EventId(eventData.EventId, eventData.EventName),
|
||||
LogLevel = ConvertEventLevelToLogLevel(eventData.Level),
|
||||
};
|
||||
|
||||
LogRecordAttributeList attributes = default;
|
||||
|
||||
attributes.Add("event_source.name", eventData.EventSource.Name);
|
||||
|
||||
if (eventData.ActivityId != Guid.Empty)
|
||||
{
|
||||
attributes.Add("event_source.activity_id", eventData.ActivityId);
|
||||
}
|
||||
|
||||
if (eventData.RelatedActivityId != Guid.Empty)
|
||||
{
|
||||
attributes.Add("event_source.related_activity_id", eventData.RelatedActivityId);
|
||||
}
|
||||
|
||||
int payloadCount = eventData.Payload?.Count ?? 0;
|
||||
|
||||
if (payloadCount > 0 && payloadCount == eventData.PayloadNames?.Count)
|
||||
{
|
||||
for (int i = 0; i < payloadCount; i++)
|
||||
{
|
||||
string name = eventData.PayloadNames[i];
|
||||
|
||||
if (!string.IsNullOrEmpty(rawMessage) && !this.includeFormattedMessage)
|
||||
{
|
||||
// TODO: This code converts the event message from
|
||||
// string.Format syntax (eg: "Some message {0} {1}")
|
||||
// into structured log format (eg: "Some message
|
||||
// {propertyName1} {propertyName2}") but it is
|
||||
// expensive. Probably needs a cache.
|
||||
#if NETSTANDARD2_0
|
||||
rawMessage = rawMessage.Replace($"{{{i}}}", $"{{{name}}}");
|
||||
#else
|
||||
rawMessage = rawMessage.Replace($"{{{i}}}", $"{{{name}}}", StringComparison.Ordinal);
|
||||
#endif
|
||||
}
|
||||
|
||||
attributes.Add(name, eventData.Payload![i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(rawMessage) && this.includeFormattedMessage && payloadCount > 0)
|
||||
{
|
||||
rawMessage = string.Format(CultureInfo.InvariantCulture, rawMessage, eventData.Payload!.ToArray());
|
||||
}
|
||||
|
||||
data.Message = rawMessage;
|
||||
|
||||
this.logEmitter.Emit(in data, in attributes);
|
||||
}
|
||||
#pragma warning restore CA1062 // Validate arguments of public methods
|
||||
|
||||
private static LogLevel ConvertEventLevelToLogLevel(EventLevel eventLevel)
|
||||
{
|
||||
return eventLevel switch
|
||||
{
|
||||
EventLevel.Informational => LogLevel.Information,
|
||||
EventLevel.Warning => LogLevel.Warning,
|
||||
EventLevel.Error => LogLevel.Error,
|
||||
EventLevel.Critical => LogLevel.Critical,
|
||||
_ => LogLevel.Trace,
|
||||
};
|
||||
}
|
||||
|
||||
private void ProcessSource(EventSource eventSource)
|
||||
{
|
||||
EventLevel? eventLevel = this.shouldListenToFunc(eventSource.Name);
|
||||
|
||||
if (eventLevel.HasValue)
|
||||
{
|
||||
this.eventSources.Add(eventSource);
|
||||
this.EnableEvents(eventSource, eventLevel.Value, EventKeywords.All);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
# OpenTelemetry.Extensions.EventSource
|
||||
|
||||
[](https://www.nuget.org/packages/OpenTelemetry.Extensions.EventSource)
|
||||
[](https://www.nuget.org/packages/OpenTelemetry.Extensions.EventSource)
|
||||
|
||||
This project contains an
|
||||
[EventListener](https://docs.microsoft.com/dotnet/api/system.diagnostics.tracing.eventlistener)
|
||||
which can be used to translate events written to an
|
||||
[EventSource](https://docs.microsoft.com/dotnet/api/system.diagnostics.tracing.eventsource)
|
||||
into OpenTelemetry logs.
|
||||
|
||||
## Installation
|
||||
|
||||
```shell
|
||||
dotnet add package OpenTelemetry.Extensions.EventSource --prerelease
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
```csharp
|
||||
// Step 1: Configure OpenTelemetryLoggerProvider...
|
||||
var openTelemetryLoggerProvider = new OpenTelemetryLoggerProvider(options =>
|
||||
{
|
||||
options
|
||||
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("MyService"))
|
||||
.AddConsoleExporter();
|
||||
});
|
||||
|
||||
// Step 2: Create OpenTelemetryEventSourceLogEmitter to listen to events...
|
||||
using var openTelemetryEventSourceLogEmitter = new OpenTelemetryEventSourceLogEmitter(
|
||||
openTelemetryLoggerProvider,
|
||||
(name) => name.StartsWith("OpenTelemetry") ? EventLevel.LogAlways : null,
|
||||
disposeProvider: true);
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
* [OpenTelemetry Project](https://opentelemetry.io/)
|
||||
|
|
@ -10,7 +10,7 @@ writing log messages to OpenTelemetry.
|
|||
## Installation
|
||||
|
||||
```shell
|
||||
dotnet add package OpenTelemetry.Extensions.Serilog
|
||||
dotnet add package OpenTelemetry.Extensions.Serilog --prerelease
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ using System.Runtime.CompilerServices;
|
|||
[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.InMemory" + AssemblyInfo.PublicKey)]
|
||||
[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus" + AssemblyInfo.PublicKey)]
|
||||
[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus.Tests" + AssemblyInfo.PublicKey)]
|
||||
[assembly: InternalsVisibleTo("OpenTelemetry.Extensions.EventSource" + AssemblyInfo.PublicKey)]
|
||||
[assembly: InternalsVisibleTo("OpenTelemetry.Extensions.Hosting.Tests" + AssemblyInfo.PublicKey)]
|
||||
[assembly: InternalsVisibleTo("OpenTelemetry.Extensions.Serilog" + AssemblyInfo.PublicKey)]
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2" + AssemblyInfo.MoqPublicKey)]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
// <copyright file="AssemblyInfo.cs" company="OpenTelemetry Authors">
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// 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.
|
||||
// </copyright>
|
||||
|
||||
using System;
|
||||
|
||||
[assembly: CLSCompliant(false)]
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<Description>Unit test project for OpenTelemetry EventSource extensions</Description>
|
||||
<!-- OmniSharp/VS Code requires TargetFrameworks to be in descending order for IntelliSense and analysis. -->
|
||||
<TargetFrameworks>net6.0;netcoreapp3.1</TargetFrameworks>
|
||||
<TargetFrameworks Condition="$(OS) == 'Windows_NT'">$(TargetFrameworks);net462</TargetFrameworks>
|
||||
<Nullable>enable</Nullable>
|
||||
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
|
||||
<AnalysisLevel>latest</AnalysisLevel>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPkgVer)" />
|
||||
<PackageReference Include="xunit" Version="$(XUnitPkgVer)" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="$(XUnitRunnerVisualStudioPkgVer)">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<DotNetCliToolReference Include="dotnet-xunit" Version="$(DotNetXUnitCliVer)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Extensions.EventSource\OpenTelemetry.Extensions.EventSource.csproj" />
|
||||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.InMemory\OpenTelemetry.Exporter.InMemory.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,391 @@
|
|||
// <copyright file="OpenTelemetryEventSourceLogEmitterTests.cs" company="OpenTelemetry Authors">
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// 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.
|
||||
// </copyright>
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Tracing;
|
||||
using System.Globalization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OpenTelemetry.Logs;
|
||||
using Xunit;
|
||||
|
||||
namespace OpenTelemetry.Extensions.EventSource.Tests
|
||||
{
|
||||
public class OpenTelemetryEventSourceLogEmitterTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void OpenTelemetryEventSourceLogEmitterDisposesProviderTests(bool dispose)
|
||||
{
|
||||
List<LogRecord> exportedItems = new();
|
||||
|
||||
#pragma warning disable CA2000 // Dispose objects before losing scope
|
||||
var openTelemetryLoggerProvider = new WrappedOpenTelemetryLoggerProvider(options =>
|
||||
{
|
||||
options.AddInMemoryExporter(exportedItems);
|
||||
});
|
||||
#pragma warning restore CA2000 // Dispose objects before losing scope
|
||||
|
||||
using (var openTelemetryEventSourceLogEmitter = new OpenTelemetryEventSourceLogEmitter(
|
||||
openTelemetryLoggerProvider,
|
||||
(name) => null,
|
||||
disposeProvider: dispose))
|
||||
{
|
||||
}
|
||||
|
||||
Assert.Equal(dispose, openTelemetryLoggerProvider.Disposed);
|
||||
|
||||
if (!dispose)
|
||||
{
|
||||
openTelemetryLoggerProvider.Dispose();
|
||||
}
|
||||
|
||||
Assert.True(openTelemetryLoggerProvider.Disposed);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("OpenTelemetry.Extensions.EventSource.Tests", EventLevel.LogAlways, 2)]
|
||||
[InlineData("OpenTelemetry.Extensions.EventSource.Tests", EventLevel.Warning, 1)]
|
||||
[InlineData("_invalid_", EventLevel.LogAlways, 0)]
|
||||
public void OpenTelemetryEventSourceLogEmitterFilterTests(string sourceName, EventLevel? eventLevel, int expectedNumberOfLogRecords)
|
||||
{
|
||||
List<LogRecord> exportedItems = new();
|
||||
|
||||
#pragma warning disable CA2000 // Dispose objects before losing scope
|
||||
var openTelemetryLoggerProvider = new WrappedOpenTelemetryLoggerProvider(options =>
|
||||
{
|
||||
options.AddInMemoryExporter(exportedItems);
|
||||
});
|
||||
#pragma warning restore CA2000 // Dispose objects before losing scope
|
||||
|
||||
using (var openTelemetryEventSourceLogEmitter = new OpenTelemetryEventSourceLogEmitter(
|
||||
openTelemetryLoggerProvider,
|
||||
(name) => name == sourceName ? eventLevel : null))
|
||||
{
|
||||
TestEventSource.Log.SimpleEvent();
|
||||
TestEventSource.Log.ComplexEvent("Test_Message", 18);
|
||||
}
|
||||
|
||||
Assert.Equal(expectedNumberOfLogRecords, exportedItems.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OpenTelemetryEventSourceLogEmitterCapturesExistingSourceTest()
|
||||
{
|
||||
List<LogRecord> exportedItems = new();
|
||||
|
||||
#pragma warning disable CA2000 // Dispose objects before losing scope
|
||||
var openTelemetryLoggerProvider = new WrappedOpenTelemetryLoggerProvider(options =>
|
||||
{
|
||||
options.AddInMemoryExporter(exportedItems);
|
||||
});
|
||||
#pragma warning restore CA2000 // Dispose objects before losing scope
|
||||
|
||||
TestEventSource.Log.SimpleEvent();
|
||||
|
||||
using (var openTelemetryEventSourceLogEmitter = new OpenTelemetryEventSourceLogEmitter(
|
||||
openTelemetryLoggerProvider,
|
||||
(name) => name == "OpenTelemetry.Extensions.EventSource.Tests" ? EventLevel.LogAlways : null))
|
||||
{
|
||||
TestEventSource.Log.SimpleEvent();
|
||||
}
|
||||
|
||||
Assert.Single(exportedItems);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OpenTelemetryEventSourceLogEmitterSimpleEventTest()
|
||||
{
|
||||
List<LogRecord> exportedItems = new();
|
||||
|
||||
#pragma warning disable CA2000 // Dispose objects before losing scope
|
||||
var openTelemetryLoggerProvider = new WrappedOpenTelemetryLoggerProvider(options =>
|
||||
{
|
||||
options.AddInMemoryExporter(exportedItems);
|
||||
});
|
||||
#pragma warning restore CA2000 // Dispose objects before losing scope
|
||||
|
||||
using (var openTelemetryEventSourceLogEmitter = new OpenTelemetryEventSourceLogEmitter(
|
||||
openTelemetryLoggerProvider,
|
||||
(name) => name == "OpenTelemetry.Extensions.EventSource.Tests" ? EventLevel.LogAlways : null))
|
||||
{
|
||||
TestEventSource.Log.SimpleEvent();
|
||||
}
|
||||
|
||||
Assert.Single(exportedItems);
|
||||
|
||||
var logRecord = exportedItems[0];
|
||||
|
||||
Assert.NotEqual(DateTime.MinValue, logRecord.Timestamp);
|
||||
Assert.Equal(TestEventSource.SimpleEventMessage, logRecord.FormattedMessage);
|
||||
Assert.Equal(TestEventSource.SimpleEventId, logRecord.EventId.Id);
|
||||
Assert.Equal(nameof(TestEventSource.SimpleEvent), logRecord.EventId.Name);
|
||||
Assert.Equal(LogLevel.Warning, logRecord.LogLevel);
|
||||
Assert.Null(logRecord.CategoryName);
|
||||
Assert.Null(logRecord.Exception);
|
||||
|
||||
Assert.Equal(default, logRecord.TraceId);
|
||||
Assert.Equal(default, logRecord.SpanId);
|
||||
Assert.Null(logRecord.TraceState);
|
||||
Assert.Equal(ActivityTraceFlags.None, logRecord.TraceFlags);
|
||||
|
||||
Assert.NotNull(logRecord.StateValues);
|
||||
Assert.Contains(logRecord.StateValues, kvp => kvp.Key == "event_source.name" && (string?)kvp.Value == "OpenTelemetry.Extensions.EventSource.Tests");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OpenTelemetryEventSourceLogEmitterSimpleEventWithActivityTest()
|
||||
{
|
||||
using var activity = new Activity("Test");
|
||||
activity.Start();
|
||||
|
||||
List<LogRecord> exportedItems = new();
|
||||
|
||||
#pragma warning disable CA2000 // Dispose objects before losing scope
|
||||
var openTelemetryLoggerProvider = new WrappedOpenTelemetryLoggerProvider(options =>
|
||||
{
|
||||
options.AddInMemoryExporter(exportedItems);
|
||||
});
|
||||
#pragma warning restore CA2000 // Dispose objects before losing scope
|
||||
|
||||
using (var openTelemetryEventSourceLogEmitter = new OpenTelemetryEventSourceLogEmitter(
|
||||
openTelemetryLoggerProvider,
|
||||
(name) => name == "OpenTelemetry.Extensions.EventSource.Tests" ? EventLevel.LogAlways : null))
|
||||
{
|
||||
TestEventSource.Log.SimpleEvent();
|
||||
}
|
||||
|
||||
Assert.Single(exportedItems);
|
||||
|
||||
var logRecord = exportedItems[0];
|
||||
|
||||
Assert.NotEqual(default, logRecord.TraceId);
|
||||
|
||||
Assert.Equal(activity.TraceId, logRecord.TraceId);
|
||||
Assert.Equal(activity.SpanId, logRecord.SpanId);
|
||||
Assert.Equal(activity.TraceStateString, logRecord.TraceState);
|
||||
Assert.Equal(activity.ActivityTraceFlags, logRecord.TraceFlags);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void OpenTelemetryEventSourceLogEmitterComplexEventTest(bool formatMessage)
|
||||
{
|
||||
List<LogRecord> exportedItems = new();
|
||||
|
||||
#pragma warning disable CA2000 // Dispose objects before losing scope
|
||||
var openTelemetryLoggerProvider = new WrappedOpenTelemetryLoggerProvider(options =>
|
||||
{
|
||||
options.IncludeFormattedMessage = formatMessage;
|
||||
options.AddInMemoryExporter(exportedItems);
|
||||
});
|
||||
#pragma warning restore CA2000 // Dispose objects before losing scope
|
||||
|
||||
using (var openTelemetryEventSourceLogEmitter = new OpenTelemetryEventSourceLogEmitter(
|
||||
openTelemetryLoggerProvider,
|
||||
(name) => name == "OpenTelemetry.Extensions.EventSource.Tests" ? EventLevel.LogAlways : null))
|
||||
{
|
||||
TestEventSource.Log.ComplexEvent("Test_Message", 18);
|
||||
}
|
||||
|
||||
Assert.Single(exportedItems);
|
||||
|
||||
var logRecord = exportedItems[0];
|
||||
|
||||
Assert.NotEqual(DateTime.MinValue, logRecord.Timestamp);
|
||||
if (!formatMessage)
|
||||
{
|
||||
Assert.Equal(TestEventSource.ComplexEventMessageStructured, logRecord.FormattedMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
string expectedMessage = string.Format(CultureInfo.InvariantCulture, TestEventSource.ComplexEventMessage, "Test_Message", 18);
|
||||
Assert.Equal(expectedMessage, logRecord.FormattedMessage);
|
||||
}
|
||||
|
||||
Assert.Equal(TestEventSource.ComplexEventId, logRecord.EventId.Id);
|
||||
Assert.Equal(nameof(TestEventSource.ComplexEvent), logRecord.EventId.Name);
|
||||
Assert.Equal(LogLevel.Information, logRecord.LogLevel);
|
||||
Assert.Null(logRecord.CategoryName);
|
||||
Assert.Null(logRecord.Exception);
|
||||
|
||||
Assert.Equal(default, logRecord.TraceId);
|
||||
Assert.Equal(default, logRecord.SpanId);
|
||||
Assert.Null(logRecord.TraceState);
|
||||
Assert.Equal(ActivityTraceFlags.None, logRecord.TraceFlags);
|
||||
|
||||
Assert.NotNull(logRecord.StateValues);
|
||||
Assert.Contains(logRecord.StateValues, kvp => kvp.Key == "event_source.name" && (string?)kvp.Value == "OpenTelemetry.Extensions.EventSource.Tests");
|
||||
Assert.Contains(logRecord.StateValues, kvp => kvp.Key == "arg1" && (string?)kvp.Value == "Test_Message");
|
||||
Assert.Contains(logRecord.StateValues, kvp => kvp.Key == "arg2" && (int?)kvp.Value == 18);
|
||||
}
|
||||
|
||||
[Theory(Skip = "Not runnable in CI, see note.")]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void OpenTelemetryEventSourceLogEmitterActivityIdTest(bool enableTplListener)
|
||||
{
|
||||
/*
|
||||
* Note:
|
||||
*
|
||||
* To enable Activity ID the 'System.Threading.Tasks.TplEventSource'
|
||||
* source must be enabled see:
|
||||
* https://docs.microsoft.com/en-us/dotnet/core/diagnostics/eventsource-activity-ids#tracking-work-using-an-activity-id
|
||||
*
|
||||
* Once enabled, it cannot be turned off:
|
||||
* https://github.com/dotnet/runtime/blob/0fbdb1ed6e076829e4693a61ae5d11c4cb23e7ee/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/ActivityTracker.cs#L208
|
||||
*
|
||||
* That behavior makes testing it difficult.
|
||||
*/
|
||||
using var tplListener = enableTplListener ? new TplEventSourceListener() : null;
|
||||
|
||||
List<LogRecord> exportedItems = new();
|
||||
|
||||
#pragma warning disable CA2000 // Dispose objects before losing scope
|
||||
var openTelemetryLoggerProvider = new WrappedOpenTelemetryLoggerProvider(options =>
|
||||
{
|
||||
options.AddInMemoryExporter(exportedItems);
|
||||
});
|
||||
#pragma warning restore CA2000 // Dispose objects before losing scope
|
||||
|
||||
using (var openTelemetryEventSourceLogEmitter = new OpenTelemetryEventSourceLogEmitter(
|
||||
openTelemetryLoggerProvider,
|
||||
(name) => name == "OpenTelemetry.Extensions.EventSource.Tests" ? EventLevel.LogAlways : null))
|
||||
{
|
||||
TestEventSource.Log.WorkStart();
|
||||
|
||||
TestEventSource.Log.SubworkStart();
|
||||
|
||||
TestEventSource.Log.SubworkStop();
|
||||
|
||||
TestEventSource.Log.WorkStop();
|
||||
}
|
||||
|
||||
Assert.Equal(4, exportedItems.Count);
|
||||
|
||||
var logRecord = exportedItems[1];
|
||||
|
||||
if (enableTplListener)
|
||||
{
|
||||
Assert.Contains(logRecord.StateValues, kvp => kvp.Key == "event_source.activity_id");
|
||||
Assert.Contains(logRecord.StateValues, kvp => kvp.Key == "event_source.related_activity_id");
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.DoesNotContain(logRecord.StateValues, kvp => kvp.Key == "event_source.activity_id");
|
||||
Assert.DoesNotContain(logRecord.StateValues, kvp => kvp.Key == "event_source.related_activity_id");
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class WrappedOpenTelemetryLoggerProvider : OpenTelemetryLoggerProvider
|
||||
{
|
||||
public WrappedOpenTelemetryLoggerProvider(Action<OpenTelemetryLoggerOptions> configure)
|
||||
: base(configure)
|
||||
{
|
||||
}
|
||||
|
||||
public bool Disposed { get; private set; }
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
this.Disposed = true;
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
|
||||
[EventSource(Name = "OpenTelemetry.Extensions.EventSource.Tests")]
|
||||
private sealed class TestEventSource : System.Diagnostics.Tracing.EventSource
|
||||
{
|
||||
public const int SimpleEventId = 1;
|
||||
public const string SimpleEventMessage = "Warning event with no arguments.";
|
||||
|
||||
public const int ComplexEventId = 2;
|
||||
public const string ComplexEventMessage = "Information event with two arguments: '{0}' & '{1}'.";
|
||||
public const string ComplexEventMessageStructured = "Information event with two arguments: '{arg1}' & '{arg2}'.";
|
||||
|
||||
public static TestEventSource Log { get; } = new();
|
||||
|
||||
[Event(SimpleEventId, Message = SimpleEventMessage, Level = EventLevel.Warning)]
|
||||
public void SimpleEvent()
|
||||
{
|
||||
this.WriteEvent(SimpleEventId);
|
||||
}
|
||||
|
||||
[Event(ComplexEventId, Message = ComplexEventMessage, Level = EventLevel.Informational)]
|
||||
public void ComplexEvent(string arg1, int arg2)
|
||||
{
|
||||
this.WriteEvent(ComplexEventId, arg1, arg2);
|
||||
}
|
||||
|
||||
[Event(3, Level = EventLevel.Verbose)]
|
||||
public void WorkStart()
|
||||
{
|
||||
this.WriteEvent(3);
|
||||
}
|
||||
|
||||
[Event(4, Level = EventLevel.Verbose)]
|
||||
public void WorkStop()
|
||||
{
|
||||
this.WriteEvent(4);
|
||||
}
|
||||
|
||||
[Event(5, Level = EventLevel.Verbose)]
|
||||
public void SubworkStart()
|
||||
{
|
||||
this.WriteEvent(5);
|
||||
}
|
||||
|
||||
[Event(6, Level = EventLevel.Verbose)]
|
||||
public void SubworkStop()
|
||||
{
|
||||
this.WriteEvent(6);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class TplEventSourceListener : EventListener
|
||||
{
|
||||
private readonly List<System.Diagnostics.Tracing.EventSource> eventSources = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Dispose()
|
||||
{
|
||||
foreach (System.Diagnostics.Tracing.EventSource eventSource in this.eventSources)
|
||||
{
|
||||
this.DisableEvents(eventSource);
|
||||
}
|
||||
|
||||
this.eventSources.Clear();
|
||||
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
protected override void OnEventSourceCreated(System.Diagnostics.Tracing.EventSource eventSource)
|
||||
{
|
||||
if (eventSource.Name == "System.Threading.Tasks.TplEventSource")
|
||||
{
|
||||
// Activity IDs aren't enabled by default.
|
||||
// Enabling Keyword 0x80 on the TplEventSource turns them on
|
||||
this.EnableEvents(eventSource, EventLevel.LogAlways, (EventKeywords)0x80);
|
||||
this.eventSources.Add(eventSource);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OpenTelemetry.Logs;
|
||||
|
|
@ -102,6 +103,46 @@ namespace OpenTelemetry.Extensions.Serilog.Tests
|
|||
Assert.NotNull(logRecord.StateValues);
|
||||
Assert.Single(logRecord.StateValues);
|
||||
Assert.Contains(logRecord.StateValues, kvp => kvp.Key == "greeting" && (string?)kvp.Value == "World");
|
||||
|
||||
Assert.Equal(default, logRecord.TraceId);
|
||||
Assert.Equal(default, logRecord.SpanId);
|
||||
Assert.Null(logRecord.TraceState);
|
||||
Assert.Equal(ActivityTraceFlags.None, logRecord.TraceFlags);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SerilogBasicLogWithActivityTest()
|
||||
{
|
||||
using var activity = new Activity("Test");
|
||||
activity.Start();
|
||||
|
||||
List<LogRecord> exportedItems = new();
|
||||
|
||||
#pragma warning disable CA2000 // Dispose objects before losing scope
|
||||
var openTelemetryLoggerProvider = new OpenTelemetryLoggerProvider(options =>
|
||||
{
|
||||
options.AddInMemoryExporter(exportedItems);
|
||||
});
|
||||
#pragma warning restore CA2000 // Dispose objects before losing scope
|
||||
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.WriteTo.OpenTelemetry(openTelemetryLoggerProvider, disposeProvider: true)
|
||||
.CreateLogger();
|
||||
|
||||
Log.Logger.Information("Hello {greeting}", "World");
|
||||
|
||||
Log.CloseAndFlush();
|
||||
|
||||
Assert.Single(exportedItems);
|
||||
|
||||
var logRecord = exportedItems[0];
|
||||
|
||||
Assert.NotEqual(default, logRecord.TraceId);
|
||||
|
||||
Assert.Equal(activity.TraceId, logRecord.TraceId);
|
||||
Assert.Equal(activity.SpanId, logRecord.SpanId);
|
||||
Assert.Equal(activity.TraceStateString, logRecord.TraceState);
|
||||
Assert.Equal(activity.ActivityTraceFlags, logRecord.TraceFlags);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
Loading…
Reference in New Issue