Initial version
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OpenTelemetry" Version="1.15.0" />
|
||||
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.0" />
|
||||
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.15.0" />
|
||||
<PackageReference Include="System.Threading.AccessControl" Version="10.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\EonaCat.LogStack\EonaCat.LogStack.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
33
EonaCat.LogStack.OpenTelemetryFlow/LogBuilder.cs
Normal file
33
EonaCat.LogStack.OpenTelemetryFlow/LogBuilder.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using EonaCat.LogStack.Configuration;
|
||||
using EonaCat.LogStack.Core;
|
||||
using EonaCat.LogStack.Flows;
|
||||
using OpenTelemetry.Exporter;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace EonaCat.LogStack.Flows.WindowsEventLog
|
||||
{
|
||||
// This file is part of the EonaCat project(s) which is released under the Apache License.
|
||||
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
|
||||
|
||||
public static class EonaCatLogStackExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Write to OpenTelemetry
|
||||
/// </summary>
|
||||
public static LogBuilder WriteToOpenTelemetry(this LogBuilder logBuilder,
|
||||
string serviceName,
|
||||
Uri endpoint,
|
||||
OtlpExportProtocol protocol = OtlpExportProtocol.Grpc,
|
||||
LogLevel minimumLevel = LogLevel.Trace)
|
||||
{
|
||||
logBuilder.AddFlow(new OpenTelemetryFlow(
|
||||
serviceName,
|
||||
endpoint,
|
||||
protocol,
|
||||
minimumLevel));
|
||||
return logBuilder;
|
||||
}
|
||||
}
|
||||
}
|
||||
162
EonaCat.LogStack.OpenTelemetryFlow/OpenTelemetryFlow.cs
Normal file
162
EonaCat.LogStack.OpenTelemetryFlow/OpenTelemetryFlow.cs
Normal file
@@ -0,0 +1,162 @@
|
||||
using EonaCat.LogStack.Core;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OpenTelemetry.Exporter;
|
||||
using OpenTelemetry.Logs;
|
||||
using OpenTelemetry.Resources;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LogLevel = EonaCat.LogStack.Core.LogLevel;
|
||||
|
||||
namespace EonaCat.LogStack.Flows
|
||||
{
|
||||
public sealed class OpenTelemetryFlow : FlowBase
|
||||
{
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public OpenTelemetryFlow(string serviceName, Uri endpoint, OtlpExportProtocol protocol = OtlpExportProtocol.Grpc, LogLevel minimumLevel = LogLevel.Trace) : base("OpenTelemetry:" + serviceName, minimumLevel)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(serviceName))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(serviceName));
|
||||
}
|
||||
|
||||
if (endpoint == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(endpoint));
|
||||
}
|
||||
|
||||
_loggerFactory = LoggerFactory.Create(builder =>
|
||||
{
|
||||
builder.ClearProviders();
|
||||
|
||||
builder.AddOpenTelemetry(options =>
|
||||
{
|
||||
options.SetResourceBuilder(
|
||||
ResourceBuilder.CreateDefault()
|
||||
.AddService(serviceName)
|
||||
.AddAttributes(new Dictionary<string, object>
|
||||
{
|
||||
["host.name"] = Environment.MachineName,
|
||||
["process.id"] = Process.GetCurrentProcess().Id
|
||||
}));
|
||||
|
||||
options.AddOtlpExporter(otlp =>
|
||||
{
|
||||
otlp.Endpoint = endpoint;
|
||||
otlp.Protocol = protocol;
|
||||
});
|
||||
|
||||
options.IncludeScopes = true;
|
||||
options.IncludeFormattedMessage = true;
|
||||
options.ParseStateValues = true;
|
||||
});
|
||||
});
|
||||
|
||||
_logger = _loggerFactory.CreateLogger(serviceName);
|
||||
}
|
||||
|
||||
public override Task<WriteResult> BlastAsync(
|
||||
LogEvent logEvent,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!IsEnabled || !IsLogLevelEnabled(logEvent))
|
||||
{
|
||||
return Task.FromResult(WriteResult.LevelFiltered);
|
||||
}
|
||||
|
||||
WriteLog(logEvent);
|
||||
Interlocked.Increment(ref BlastedCount);
|
||||
|
||||
return Task.FromResult(WriteResult.Success);
|
||||
}
|
||||
|
||||
public override Task<WriteResult> BlastBatchAsync(
|
||||
ReadOnlyMemory<LogEvent> logEvents,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!IsEnabled)
|
||||
{
|
||||
return Task.FromResult(WriteResult.FlowDisabled);
|
||||
}
|
||||
|
||||
foreach (var e in logEvents.Span)
|
||||
{
|
||||
if (e.Level < MinimumLevel)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
WriteLog(e);
|
||||
Interlocked.Increment(ref BlastedCount);
|
||||
}
|
||||
|
||||
return Task.FromResult(WriteResult.Success);
|
||||
}
|
||||
|
||||
private void WriteLog(LogEvent log)
|
||||
{
|
||||
var state = new List<KeyValuePair<string, object>>();
|
||||
|
||||
if (!string.IsNullOrEmpty(log.Category))
|
||||
{
|
||||
state.Add(new KeyValuePair<string, object>("category", log.Category));
|
||||
}
|
||||
|
||||
foreach (var prop in log.Properties)
|
||||
{
|
||||
state.Add(new KeyValuePair<string, object>(prop.Key, prop.Value ?? "null"));
|
||||
}
|
||||
|
||||
if (log.Exception != null)
|
||||
{
|
||||
state.Add(new KeyValuePair<string, object>("exception.type", log.Exception.GetType().FullName));
|
||||
state.Add(new KeyValuePair<string, object>("exception.message", log.Exception.Message));
|
||||
state.Add(new KeyValuePair<string, object>("exception.stacktrace", log.Exception.StackTrace));
|
||||
}
|
||||
|
||||
_logger.Log(
|
||||
MapLevel(log.Level),
|
||||
new EventId(0, log.Category),
|
||||
state,
|
||||
log.Exception,
|
||||
(s, e) => log.Message.ToString());
|
||||
}
|
||||
|
||||
private static Microsoft.Extensions.Logging.LogLevel MapLevel(LogLevel level)
|
||||
{
|
||||
return level switch
|
||||
{
|
||||
LogLevel.Trace => Microsoft.Extensions.Logging.LogLevel.Trace,
|
||||
LogLevel.Debug => Microsoft.Extensions.Logging.LogLevel.Debug,
|
||||
LogLevel.Information => Microsoft.Extensions.Logging.LogLevel.Information,
|
||||
LogLevel.Warning => Microsoft.Extensions.Logging.LogLevel.Warning,
|
||||
LogLevel.Error => Microsoft.Extensions.Logging.LogLevel.Error,
|
||||
LogLevel.Critical => Microsoft.Extensions.Logging.LogLevel.Critical,
|
||||
_ => Microsoft.Extensions.Logging.LogLevel.Information
|
||||
};
|
||||
}
|
||||
|
||||
public override async ValueTask DisposeAsync()
|
||||
{
|
||||
if (!IsEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IsEnabled = false;
|
||||
|
||||
_loggerFactory?.Dispose();
|
||||
|
||||
await base.DisposeAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public override Task FlushAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user