[Logs] Serilog extensions project (#3438)
* Add Serilog extensions project. * Improve provider disposal when using serilog. * Warning fixes. * Fix README. * Code review. * Move Serilog version to props. * Rename LogEmitter.Log -> Emit. * Added unit tests. * Warning cleanup. * Code review. * Standard README jazz. * Removed ApiCompat block.
This commit is contained in:
parent
a19d106e0c
commit
ab10e119ba
|
|
@ -229,6 +229,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "correlation", "docs\logs\co
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Tests.Stress.Logs", "test\OpenTelemetry.Tests.Stress.Logs\OpenTelemetry.Tests.Stress.Logs.csproj", "{4298057B-24E0-47B3-BB76-C17E81AF6B39}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples.LoggingExtensions", "examples\LoggingExtensions\Examples.LoggingExtensions.csproj", "{F5EFF065-7AF5-4D7D-8038-CC419ABD8777}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Extensions.Serilog", "src\OpenTelemetry.Extensions.Serilog\OpenTelemetry.Extensions.Serilog.csproj", "{0D85558E-15B9-4251-BDBD-9CB7933B57E2}"
|
||||
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
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -463,6 +469,18 @@ Global
|
|||
{4298057B-24E0-47B3-BB76-C17E81AF6B39}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4298057B-24E0-47B3-BB76-C17E81AF6B39}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4298057B-24E0-47B3-BB76-C17E81AF6B39}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F5EFF065-7AF5-4D7D-8038-CC419ABD8777}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F5EFF065-7AF5-4D7D-8038-CC419ABD8777}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F5EFF065-7AF5-4D7D-8038-CC419ABD8777}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F5EFF065-7AF5-4D7D-8038-CC419ABD8777}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{0D85558E-15B9-4251-BDBD-9CB7933B57E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0D85558E-15B9-4251-BDBD-9CB7933B57E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0D85558E-15B9-4251-BDBD-9CB7933B57E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0D85558E-15B9-4251-BDBD-9CB7933B57E2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6A2C122A-C1CD-4B6B-AE09-2ABB7D3C50CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{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
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -500,6 +518,7 @@ Global
|
|||
{41B784AA-3301-4126-AF9F-1D59BD04B0BF} = {3277B1C0-BDFE-4460-9B0D-D9A661FB48DB}
|
||||
{6C7A1595-36D6-4229-BBB5-5A6B5791791D} = {3862190B-E2C5-418E-AFDC-DB281FB5C705}
|
||||
{9A07D215-90AC-4BAF-BCDB-73D74FD3A5C5} = {3862190B-E2C5-418E-AFDC-DB281FB5C705}
|
||||
{F5EFF065-7AF5-4D7D-8038-CC419ABD8777} = {E359BB2B-9AEC-497D-B321-7DF2450C3B8E}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {55639B5C-0770-4A22-AB56-859604650521}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
<MicrosoftSourceLinkGitHubPkgVer>[1.0.0,2.0)</MicrosoftSourceLinkGitHubPkgVer>
|
||||
<OpenTracingPkgVer>[0.12.1,0.13)</OpenTracingPkgVer>
|
||||
<OTelPreviousStableVer>1.3.0</OTelPreviousStableVer>
|
||||
<SerilogPkgVer>[2.8.0,3.0)</SerilogPkgVer>
|
||||
<StyleCopAnalyzersPkgVer>[1.2.0-beta.354,2.0)</StyleCopAnalyzersPkgVer>
|
||||
<SystemCollectionsImmutablePkgVer>1.4.0</SystemCollectionsImmutablePkgVer>
|
||||
<SystemDiagnosticSourcePkgVer>6.0.0</SystemDiagnosticSourcePkgVer>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.Console\OpenTelemetry.Exporter.Console.csproj" />
|
||||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Extensions.Serilog\OpenTelemetry.Extensions.Serilog.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
// <copyright file="Program.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 OpenTelemetry.Logs;
|
||||
using OpenTelemetry.Resources;
|
||||
using Serilog;
|
||||
|
||||
var resourceBuilder = ResourceBuilder.CreateDefault().AddService("Examples.LogEmitter");
|
||||
|
||||
// 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;
|
||||
options
|
||||
.SetResourceBuilder(resourceBuilder)
|
||||
.AddConsoleExporter();
|
||||
});
|
||||
|
||||
// Configure Serilog global logger
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.WriteTo.OpenTelemetry(openTelemetryLoggerProvider, disposeProvider: true) // <- Register OpenTelemetry Serilog sink
|
||||
.CreateLogger();
|
||||
|
||||
// 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.
|
||||
Log.CloseAndFlush();
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# OpenTelemetry Logging Extensions Example
|
||||
|
||||
This project contains examples of the `LogEmitter` API being used to extend
|
||||
existing logging platforms to write into OpenTelemetry logs.
|
||||
|
||||
* Serilog: Using OpenTelemetry.Extensions.Serilog
|
||||
|
||||
## References
|
||||
|
||||
* [OpenTelemetry Project](https://opentelemetry.io/)
|
||||
|
|
@ -0,0 +1 @@
|
|||
#nullable enable
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
#nullable enable
|
||||
Serilog.OpenTelemetrySerilogExtensions
|
||||
static Serilog.OpenTelemetrySerilogExtensions.OpenTelemetry(this Serilog.Configuration.LoggerSinkConfiguration! loggerConfiguration, OpenTelemetry.Logs.OpenTelemetryLoggerProvider! openTelemetryLoggerProvider, bool disposeProvider = true) -> Serilog.LoggerConfiguration!
|
||||
|
|
@ -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,24 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<!-- OmniSharp/VS Code requires TargetFrameworks to be in descending order for IntelliSense and analysis. -->
|
||||
<TargetFrameworks>netstandard2.0</TargetFrameworks>
|
||||
<Description>Extensions to enable OpenTelemetry logging when using the Serilog library</Description>
|
||||
<PackageTags>$(PackageTags);serilog;logging</PackageTags>
|
||||
<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>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Serilog" Version="$(SerilogPkgVer)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
// <copyright file="OpenTelemetrySerilogExtensions.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 OpenTelemetry.Internal;
|
||||
using OpenTelemetry.Logs;
|
||||
using Serilog.Configuration;
|
||||
|
||||
namespace Serilog
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains Serilog extension methods.
|
||||
/// </summary>
|
||||
public static class OpenTelemetrySerilogExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a sink to Serilog <see cref="LoggerConfiguration"/> which will
|
||||
/// write to OpenTelemetry.
|
||||
/// </summary>
|
||||
/// <param name="loggerConfiguration"><see
|
||||
/// cref="LoggerSinkConfiguration"/>.</param>
|
||||
/// <param name="openTelemetryLoggerProvider"><see
|
||||
/// cref="OpenTelemetryLoggerProvider"/>.</param>
|
||||
/// <param name="disposeProvider">Controls whether or not the supplied
|
||||
/// <paramref name="openTelemetryLoggerProvider"/> will be disposed when
|
||||
/// the logger is disposed. Default value: <see
|
||||
/// langword="true"/>.</param>
|
||||
/// <returns>Supplied <see cref="LoggerConfiguration"/> for chaining calls.</returns>
|
||||
public static LoggerConfiguration OpenTelemetry(
|
||||
this LoggerSinkConfiguration loggerConfiguration,
|
||||
OpenTelemetryLoggerProvider openTelemetryLoggerProvider,
|
||||
bool disposeProvider = true)
|
||||
{
|
||||
Guard.ThrowIfNull(loggerConfiguration);
|
||||
Guard.ThrowIfNull(openTelemetryLoggerProvider);
|
||||
|
||||
#pragma warning disable CA2000 // Dispose objects before losing scope
|
||||
return loggerConfiguration.Sink(new OpenTelemetrySerilogSink(openTelemetryLoggerProvider, disposeProvider));
|
||||
#pragma warning restore CA2000 // Dispose objects before losing scope
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
// <copyright file="OpenTelemetrySerilogSink.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 Microsoft.Extensions.Logging;
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace OpenTelemetry.Logs
|
||||
{
|
||||
internal sealed class OpenTelemetrySerilogSink : ILogEventSink, IDisposable
|
||||
{
|
||||
private readonly OpenTelemetryLoggerProvider openTelemetryLoggerProvider;
|
||||
private readonly bool includeFormattedMessage;
|
||||
private readonly LogEmitter logEmitter;
|
||||
private readonly bool disposeProvider;
|
||||
|
||||
public OpenTelemetrySerilogSink(OpenTelemetryLoggerProvider openTelemetryLoggerProvider, bool disposeProvider)
|
||||
{
|
||||
Debug.Assert(openTelemetryLoggerProvider != null, "openTelemetryLoggerProvider was null");
|
||||
|
||||
this.openTelemetryLoggerProvider = openTelemetryLoggerProvider!;
|
||||
this.disposeProvider = disposeProvider;
|
||||
|
||||
var logEmitter = this.openTelemetryLoggerProvider.CreateEmitter();
|
||||
Debug.Assert(logEmitter != null, "logEmitter was null");
|
||||
|
||||
this.logEmitter = logEmitter!;
|
||||
|
||||
// TODO: This project can only access IncludeFormattedMessage
|
||||
// because it can see SDK internals. At some point this is likely
|
||||
// not to be the case. Need to figure out where to put
|
||||
// IncludeFormattedMessage so that extensions can see it. Ideas:
|
||||
// Make it public on OpenTelemetryLoggerProvider or expose it on
|
||||
// LogEmitter instance.
|
||||
this.includeFormattedMessage = this.openTelemetryLoggerProvider.IncludeFormattedMessage;
|
||||
}
|
||||
|
||||
public void Emit(LogEvent logEvent)
|
||||
{
|
||||
Debug.Assert(logEvent != null, "LogEvent was null.");
|
||||
|
||||
LogRecordData data = new(Activity.Current)
|
||||
{
|
||||
Timestamp = logEvent!.Timestamp.UtcDateTime,
|
||||
LogLevel = (LogLevel)(int)logEvent.Level,
|
||||
Message = this.includeFormattedMessage ? logEvent.RenderMessage() : logEvent.MessageTemplate.Text,
|
||||
Exception = logEvent.Exception,
|
||||
};
|
||||
|
||||
LogRecordAttributeList attributes = default;
|
||||
foreach (KeyValuePair<string, LogEventPropertyValue> property in logEvent.Properties)
|
||||
{
|
||||
// TODO: Serilog supports complex type logging. This is not yet
|
||||
// supported in OpenTelemetry.
|
||||
if (property.Key == Constants.SourceContextPropertyName
|
||||
&& property.Value is ScalarValue sourceContextValue)
|
||||
{
|
||||
data.CategoryName = sourceContextValue.Value as string;
|
||||
}
|
||||
else if (property.Value is ScalarValue scalarValue)
|
||||
{
|
||||
attributes.Add(property.Key, scalarValue.Value);
|
||||
}
|
||||
else if (property.Value is SequenceValue sequenceValue)
|
||||
{
|
||||
IReadOnlyList<LogEventPropertyValue> elements = sequenceValue.Elements;
|
||||
if (elements.Count > 0)
|
||||
{
|
||||
// Note: The goal here is to build a typed array (eg
|
||||
// int[]) if all the element types match otherwise
|
||||
// fallback to object[]
|
||||
|
||||
Type? elementType = null;
|
||||
Array? values = null;
|
||||
|
||||
for (int i = 0; i < elements.Count; i++)
|
||||
{
|
||||
if (elements[i] is ScalarValue value)
|
||||
{
|
||||
Type currentElementType = value.Value?.GetType() ?? typeof(object);
|
||||
|
||||
if (values == null)
|
||||
{
|
||||
elementType = currentElementType;
|
||||
values = Array.CreateInstance(elementType, elements.Count);
|
||||
}
|
||||
else if (!elementType!.IsAssignableFrom(currentElementType))
|
||||
{
|
||||
// Array with mixed types detected
|
||||
object[] newValues = new object[elements.Count];
|
||||
values.CopyTo(newValues, 0);
|
||||
values = newValues;
|
||||
elementType = typeof(object);
|
||||
}
|
||||
|
||||
values.SetValue(value.Value, i);
|
||||
}
|
||||
}
|
||||
|
||||
if (values != null)
|
||||
{
|
||||
attributes.Add(property.Key, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.logEmitter.Emit(in data, in attributes);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (this.disposeProvider)
|
||||
{
|
||||
this.openTelemetryLoggerProvider.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
# OpenTelemetry.Extensions.Serilog
|
||||
|
||||
[](https://www.nuget.org/packages/OpenTelemetry.Extensions.Serilog)
|
||||
[](https://www.nuget.org/packages/OpenTelemetry.Extensions.Serilog)
|
||||
|
||||
This project contains a [Serilog](https://github.com/serilog/)
|
||||
[sink](https://github.com/serilog/serilog/wiki/Configuration-Basics#sinks) for
|
||||
writing log messages to OpenTelemetry.
|
||||
|
||||
## Installation
|
||||
|
||||
```shell
|
||||
dotnet add package OpenTelemetry.Extensions.Serilog
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
```csharp
|
||||
// Step 1: Configure OpenTelemetryLoggerProvider...
|
||||
var openTelemetryLoggerProvider = new OpenTelemetryLoggerProvider(options =>
|
||||
{
|
||||
options
|
||||
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("MyService"))
|
||||
.AddConsoleExporter();
|
||||
});
|
||||
|
||||
// Step 2: Register OpenTelemetry sink with Serilog...
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.WriteTo.OpenTelemetry(openTelemetryLoggerProvider, disposeProvider: true)
|
||||
.CreateLogger();
|
||||
|
||||
// Step 3: When application is shutdown flush all log messages and dispose provider...
|
||||
Log.CloseAndFlush();
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
* [OpenTelemetry Project](https://opentelemetry.io/)
|
||||
|
|
@ -21,5 +21,6 @@ using System.Runtime.CompilerServices;
|
|||
[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus" + AssemblyInfo.PublicKey)]
|
||||
[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus.Tests" + AssemblyInfo.PublicKey)]
|
||||
[assembly: InternalsVisibleTo("OpenTelemetry.Extensions.Hosting.Tests" + AssemblyInfo.PublicKey)]
|
||||
[assembly: InternalsVisibleTo("OpenTelemetry.Extensions.Serilog" + AssemblyInfo.PublicKey)]
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2" + AssemblyInfo.MoqPublicKey)]
|
||||
[assembly: InternalsVisibleTo("Benchmarks" + AssemblyInfo.PublicKey)]
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ namespace OpenTelemetry.Logs
|
|||
/// </summary>
|
||||
/// <param name="data"><see cref="LogRecordData"/>.</param>
|
||||
/// <param name="attributes"><see cref="LogRecordAttributeList"/>.</param>
|
||||
public void Log(in LogRecordData data, in LogRecordAttributeList attributes = default)
|
||||
public void Emit(in LogRecordData data, in LogRecordAttributeList attributes = default)
|
||||
{
|
||||
var provider = this.loggerProvider;
|
||||
var processor = provider.Processor;
|
||||
|
|
|
|||
|
|
@ -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 Serilog 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.Serilog\OpenTelemetry.Extensions.Serilog.csproj" />
|
||||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.InMemory\OpenTelemetry.Exporter.InMemory.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,260 @@
|
|||
// <copyright file="OpenTelemetrySerilogSinkTests.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.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OpenTelemetry.Logs;
|
||||
using Serilog;
|
||||
using Xunit;
|
||||
using ILogger = Serilog.ILogger;
|
||||
|
||||
namespace OpenTelemetry.Extensions.Serilog.Tests
|
||||
{
|
||||
public class OpenTelemetrySerilogSinkTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void SerilogDisposesProviderTests(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
|
||||
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.WriteTo.OpenTelemetry(openTelemetryLoggerProvider, disposeProvider: dispose)
|
||||
.CreateLogger();
|
||||
|
||||
Log.CloseAndFlush();
|
||||
|
||||
Assert.Equal(dispose, openTelemetryLoggerProvider.Disposed);
|
||||
|
||||
if (!dispose)
|
||||
{
|
||||
openTelemetryLoggerProvider.Dispose();
|
||||
}
|
||||
|
||||
Assert.True(openTelemetryLoggerProvider.Disposed);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void SerilogBasicLogTests(bool includeFormattedMessage)
|
||||
{
|
||||
List<LogRecord> exportedItems = new();
|
||||
|
||||
#pragma warning disable CA2000 // Dispose objects before losing scope
|
||||
var openTelemetryLoggerProvider = new OpenTelemetryLoggerProvider(options =>
|
||||
{
|
||||
options.IncludeFormattedMessage = includeFormattedMessage;
|
||||
|
||||
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);
|
||||
|
||||
LogRecord logRecord = exportedItems[0];
|
||||
|
||||
if (!includeFormattedMessage)
|
||||
{
|
||||
Assert.Equal("Hello {greeting}", logRecord.FormattedMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal("Hello \"World\"", logRecord.FormattedMessage);
|
||||
}
|
||||
|
||||
Assert.NotEqual(DateTime.MinValue, logRecord.Timestamp);
|
||||
Assert.Equal(DateTimeKind.Utc, logRecord.Timestamp.Kind);
|
||||
Assert.Equal(LogLevel.Information, logRecord.LogLevel);
|
||||
Assert.Null(logRecord.CategoryName);
|
||||
|
||||
Assert.NotNull(logRecord.StateValues);
|
||||
Assert.Single(logRecord.StateValues);
|
||||
Assert.Contains(logRecord.StateValues, kvp => kvp.Key == "greeting" && (string?)kvp.Value == "World");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SerilogCategoryNameTest()
|
||||
{
|
||||
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();
|
||||
|
||||
// Note: Serilog ForContext API is used to set "CategoryName" on log messages
|
||||
ILogger logger = Log.Logger.ForContext<OpenTelemetrySerilogSinkTests>();
|
||||
|
||||
logger.Information("Hello {greeting}", "World");
|
||||
|
||||
Log.CloseAndFlush();
|
||||
|
||||
Assert.Single(exportedItems);
|
||||
|
||||
LogRecord logRecord = exportedItems[0];
|
||||
|
||||
Assert.Equal("OpenTelemetry.Extensions.Serilog.Tests.OpenTelemetrySerilogSinkTests", logRecord.CategoryName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SerilogComplexMessageTemplateTest()
|
||||
{
|
||||
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();
|
||||
|
||||
ComplexType complexType = new();
|
||||
|
||||
Log.Logger.Information("Hello {greeting} {id} {@complexObj} {$complexStr}", "World", 18, complexType, complexType);
|
||||
|
||||
Log.CloseAndFlush();
|
||||
|
||||
Assert.Single(exportedItems);
|
||||
|
||||
LogRecord logRecord = exportedItems[0];
|
||||
|
||||
Assert.NotNull(logRecord.StateValues);
|
||||
Assert.Equal(3, logRecord.StateValues!.Count); // Note: complexObj is currently not supported/ignored.
|
||||
Assert.Contains(logRecord.StateValues, kvp => kvp.Key == "greeting" && (string?)kvp.Value == "World");
|
||||
Assert.Contains(logRecord.StateValues, kvp => kvp.Key == "id" && (int?)kvp.Value == 18);
|
||||
Assert.Contains(logRecord.StateValues, kvp => kvp.Key == "complexStr" && (string?)kvp.Value == "ComplexTypeToString");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SerilogArrayMessageTemplateTest()
|
||||
{
|
||||
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();
|
||||
|
||||
ComplexType complexType = new();
|
||||
|
||||
var intArray = new int[] { 0, 1, 2, 3, 4 };
|
||||
var mixedArray = new object?[] { 0, null, "3", 18.0D };
|
||||
|
||||
Log.Logger.Information("Int array {data}", intArray);
|
||||
Log.Logger.Information("Mixed array {data}", new object[] { mixedArray });
|
||||
|
||||
Log.CloseAndFlush();
|
||||
|
||||
Assert.Equal(2, exportedItems.Count);
|
||||
|
||||
LogRecord logRecord = exportedItems[0];
|
||||
|
||||
Assert.Contains(logRecord.StateValues, kvp => kvp.Key == "data" && kvp.Value is int[] typedArray && intArray.SequenceEqual(typedArray));
|
||||
|
||||
logRecord = exportedItems[1];
|
||||
|
||||
Assert.Contains(logRecord.StateValues, kvp => kvp.Key == "data" && kvp.Value is object?[] typedArray && mixedArray.SequenceEqual(typedArray));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SerilogExceptionTest()
|
||||
{
|
||||
List<LogRecord> exportedItems = new();
|
||||
|
||||
InvalidOperationException ex = 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();
|
||||
|
||||
ComplexType complexType = new();
|
||||
|
||||
Log.Logger.Information(ex, "Exception");
|
||||
|
||||
Log.CloseAndFlush();
|
||||
|
||||
Assert.Single(exportedItems);
|
||||
|
||||
LogRecord logRecord = exportedItems[0];
|
||||
|
||||
Assert.Equal(ex, logRecord.Exception);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ComplexType
|
||||
{
|
||||
public override string ToString() => "ComplexTypeToString";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -38,7 +38,7 @@ namespace OpenTelemetry.Logs.Tests
|
|||
|
||||
Exception ex = new InvalidOperationException();
|
||||
|
||||
logEmitter.Log(
|
||||
logEmitter.Emit(
|
||||
new()
|
||||
{
|
||||
CategoryName = "LogEmitter",
|
||||
|
|
@ -96,7 +96,7 @@ namespace OpenTelemetry.Logs.Tests
|
|||
activity.ActivityTraceFlags = ActivityTraceFlags.Recorded;
|
||||
activity.TraceStateString = "key1=value1";
|
||||
|
||||
logEmitter.Log(new(activity));
|
||||
logEmitter.Emit(new(activity));
|
||||
|
||||
Assert.Single(exportedItems);
|
||||
|
||||
|
|
@ -128,7 +128,7 @@ namespace OpenTelemetry.Logs.Tests
|
|||
new DateTime(2022, 6, 30, 16, 0, 0),
|
||||
DateTimeKind.Local);
|
||||
|
||||
logEmitter.Log(new()
|
||||
logEmitter.Emit(new()
|
||||
{
|
||||
Timestamp = timestamp,
|
||||
});
|
||||
|
|
@ -159,7 +159,7 @@ namespace OpenTelemetry.Logs.Tests
|
|||
new DateTime(2022, 6, 30, 16, 0, 0),
|
||||
DateTimeKind.Unspecified);
|
||||
|
||||
logEmitter.Log(new()
|
||||
logEmitter.Emit(new()
|
||||
{
|
||||
Timestamp = timestamp,
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue