Skip to content

Commit 41f4367

Browse files
committed
feat: 增加Serilog 写入 Exceptionless 源码
1 parent da6683f commit 41f4367

File tree

8 files changed

+405
-89
lines changed

8 files changed

+405
-89
lines changed

framework/src/Bing.Logging.Sinks.Exceptionless/Bing.Logging.Sinks.Exceptionless.csproj

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,4 @@
44
<Import Project="project.dependency.props" />
55

66
<Import Project="..\..\..\framework.props" />
7-
8-
<ItemGroup>
9-
<PackageReference Include="Serilog.Sinks.Exceptionless" Version="3.1.5" />
10-
</ItemGroup>
117
</Project>

framework/src/Bing.Logging.Sinks.Exceptionless/Bing/Logging/Sinks/Exceptionless/Internals/LogLevelSwitcher.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,23 @@ internal static class LogLevelSwitcher
2323
_ => global::Exceptionless.Logging.LogLevel.Off
2424
};
2525
}
26+
27+
/// <summary>
28+
/// 转换日志级别
29+
/// </summary>
30+
/// <param name="level">Serilog日志级别</param>
31+
public static global::Exceptionless.Logging.LogLevel Switch(Serilog.Events.LogEventLevel level)
32+
{
33+
return level switch
34+
{
35+
Serilog.Events.LogEventLevel.Verbose => global::Exceptionless.Logging.LogLevel.Trace,
36+
Serilog.Events.LogEventLevel.Debug => global::Exceptionless.Logging.LogLevel.Debug,
37+
Serilog.Events.LogEventLevel.Information => global::Exceptionless.Logging.LogLevel.Info,
38+
Serilog.Events.LogEventLevel.Warning => global::Exceptionless.Logging.LogLevel.Warn,
39+
Serilog.Events.LogEventLevel.Error => global::Exceptionless.Logging.LogLevel.Error,
40+
Serilog.Events.LogEventLevel.Fatal => global::Exceptionless.Logging.LogLevel.Fatal,
41+
_ => global::Exceptionless.Logging.LogLevel.Other
42+
};
43+
}
2644
}
2745
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
using System;
2+
using Exceptionless;
3+
using Serilog.Configuration;
4+
using Serilog.Events;
5+
using Serilog.Sinks.Exceptionless;
6+
7+
namespace Serilog
8+
{
9+
/// <summary>
10+
/// 日志接收器配置(<see cref="LoggerSinkConfiguration"/>) 扩展
11+
/// </summary>
12+
public static partial class LoggerSinkConfigurationExtensions
13+
{
14+
/// <summary>
15+
/// 创建一个基于Exceptionless的日志接收器
16+
/// </summary>
17+
/// <param name="loggerSinkConfiguration">日志接收器配置</param>
18+
/// <param name="apiKey">API密钥</param>
19+
/// <param name="additionalOperation">附加信息操作函数</param>
20+
/// <param name="includeProperties">是否包含属性列表</param>
21+
/// <param name="restrictedToMinimumLevel">最小日志事件级别</param>
22+
/// <exception cref="ArgumentNullException"></exception>
23+
public static LoggerConfiguration Exceptionless(
24+
this LoggerSinkConfiguration loggerSinkConfiguration,
25+
string apiKey,
26+
Func<EventBuilder, EventBuilder> additionalOperation = null,
27+
bool includeProperties = true,
28+
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum)
29+
{
30+
if (loggerSinkConfiguration == null)
31+
throw new ArgumentNullException(nameof(loggerSinkConfiguration));
32+
if (apiKey == null)
33+
throw new ArgumentNullException(nameof(apiKey));
34+
return loggerSinkConfiguration.Sink(new ExceptionlessSink(apiKey, null, null, additionalOperation, includeProperties), restrictedToMinimumLevel);
35+
}
36+
37+
/// <summary>
38+
/// 创建一个基于Exceptionless的日志接收器
39+
/// </summary>
40+
/// <param name="loggerSinkConfiguration">日志接收器配置</param>
41+
/// <param name="serverUrl">Exceptionless服务器地址</param>
42+
/// <param name="apiKey">API密钥</param>
43+
/// <param name="defaultTags">默认标签数组</param>
44+
/// <param name="additionalOperation">附加信息操作函数</param>
45+
/// <param name="includeProperties">是否包含属性列表</param>
46+
/// <param name="restrictedToMinimumLevel">最小日志事件级别</param>
47+
/// <exception cref="ArgumentNullException"></exception>
48+
public static LoggerConfiguration Exceptionless(
49+
this LoggerSinkConfiguration loggerSinkConfiguration,
50+
string apiKey,
51+
string serverUrl = null,
52+
string[] defaultTags = null,
53+
Func<EventBuilder, EventBuilder> additionalOperation = null,
54+
bool includeProperties = true,
55+
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum)
56+
{
57+
if (loggerSinkConfiguration == null)
58+
throw new ArgumentNullException(nameof(loggerSinkConfiguration));
59+
if (apiKey == null)
60+
throw new ArgumentNullException(nameof(apiKey));
61+
return loggerSinkConfiguration.Sink(new ExceptionlessSink(apiKey, serverUrl, defaultTags, additionalOperation, includeProperties), restrictedToMinimumLevel);
62+
}
63+
64+
/// <summary>
65+
/// 创建一个基于Exceptionless的日志接收器
66+
/// </summary>
67+
/// <param name="loggerSinkConfiguration">日志接收器配置</param>
68+
/// <param name="additionalOperation">附加信息操作函数</param>
69+
/// <param name="includeProperties">是否包含属性列表</param>
70+
/// <param name="restrictedToMinimumLevel">最小日志事件级别</param>
71+
/// <param name="client">Exceptionless客户端</param>
72+
public static LoggerConfiguration Exceptionless(
73+
this LoggerSinkConfiguration loggerSinkConfiguration,
74+
Func<EventBuilder, EventBuilder> additionalOperation = null,
75+
bool includeProperties = true,
76+
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
77+
ExceptionlessClient client = null)
78+
{
79+
if (loggerSinkConfiguration == null)
80+
throw new ArgumentNullException(nameof(loggerSinkConfiguration));
81+
return loggerSinkConfiguration.Sink(new ExceptionlessSink(additionalOperation, includeProperties, client));
82+
}
83+
}
84+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
using System.Collections.Generic;
2+
using Bing.Logging.Sinks.Exceptionless.Internals;
3+
using Exceptionless;
4+
using Serilog.Core;
5+
using Serilog.Events;
6+
7+
namespace Serilog.Sinks.Exceptionless
8+
{
9+
/// <summary>
10+
/// Exceptionless客户端(<see cref="ExceptionlessClient"/>) 扩展
11+
/// </summary>
12+
public static class ExceptionlessClientExtensions
13+
{
14+
/// <summary>
15+
/// 从Serilog日志事件创建事件构建器
16+
/// </summary>
17+
/// <param name="client">Exceptionless客户端</param>
18+
/// <param name="log">Serilog日志事件</param>
19+
public static EventBuilder CreateFromLogEvent(this ExceptionlessClient client, LogEvent log)
20+
{
21+
var message = log.RenderMessage();
22+
23+
var builder = log.Exception != null
24+
? client.CreateException(log.Exception)
25+
: client.CreateLog(log.GetSource(), message, LogLevelSwitcher.Switch(log.Level));
26+
27+
builder.Target.Date = log.Timestamp;
28+
if (log.Level == LogEventLevel.Fatal)
29+
builder.MarkAsCritical();
30+
31+
if (!string.IsNullOrWhiteSpace(message))
32+
builder.SetMessage(message);
33+
34+
return builder;
35+
}
36+
37+
/// <summary>
38+
/// 从Serilog日志事件提交
39+
/// </summary>
40+
/// <param name="client">Exceptionless客户端</param>
41+
/// <param name="log">Serilog日志事件</param>
42+
public static void SubmitFromLogEvent(this ExceptionlessClient client, LogEvent log) => CreateFromLogEvent(client,log).Submit();
43+
44+
/// <summary>
45+
/// 获取来源
46+
/// </summary>
47+
/// <param name="log">日志事件</param>
48+
internal static string GetSource(this LogEvent log)
49+
{
50+
if (log.Properties.TryGetValue(Constants.SourceContextPropertyName, out var value))
51+
return value.FlattenProperties()?.ToString();
52+
return null;
53+
}
54+
55+
/// <summary>
56+
/// 展开属性
57+
/// <para>
58+
/// 删除了<see cref="LogEventPropertyValue"/>实现的结构化。
59+
/// </para>
60+
/// </summary>
61+
/// <param name="value">日志事件属性值</param>
62+
internal static object FlattenProperties(this LogEventPropertyValue value)
63+
{
64+
if (value == null)
65+
return null;
66+
67+
if (value is ScalarValue scalar)
68+
return scalar.Value;
69+
70+
if (value is SequenceValue sequence)
71+
{
72+
var flattenedProperties = new List<object>(sequence.Elements.Count);
73+
foreach (var element in sequence.Elements)
74+
flattenedProperties.Add(element.FlattenProperties());
75+
return flattenedProperties;
76+
}
77+
78+
if (value is StructureValue structure)
79+
{
80+
var flattenedProperties = new Dictionary<string, object>(structure.Properties.Count);
81+
foreach (var property in structure.Properties)
82+
flattenedProperties.Add(property.Name, property.Value.FlattenProperties());
83+
return flattenedProperties;
84+
}
85+
86+
if (value is DictionaryValue dictionary)
87+
{
88+
var flattenedProperties = new Dictionary<object, object>(dictionary.Elements.Count);
89+
foreach (var element in dictionary.Elements)
90+
flattenedProperties.Add(element.Key.Value, element.Value.FlattenProperties());
91+
return flattenedProperties;
92+
}
93+
94+
return value;
95+
}
96+
}
97+
}
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Bing.Logging.Sinks.Exceptionless.Internals;
4+
using Exceptionless;
5+
using Exceptionless.Dependency;
6+
using Exceptionless.Logging;
7+
using Exceptionless.Models;
8+
using Exceptionless.Models.Data;
9+
using Serilog.Core;
10+
using Serilog.Events;
11+
12+
namespace Serilog.Sinks.Exceptionless
13+
{
14+
/// <summary>
15+
/// Exceptionless 接收器
16+
/// </summary>
17+
public class ExceptionlessSink : ILogEventSink, IDisposable
18+
{
19+
/// <summary>
20+
/// 默认标签数组
21+
/// </summary>
22+
private readonly string[] _defaultTags;
23+
24+
/// <summary>
25+
/// 附加信息操作函数
26+
/// </summary>
27+
private readonly Func<EventBuilder, EventBuilder> _additionalOperation;
28+
29+
/// <summary>
30+
/// 是否包含属性列表
31+
/// </summary>
32+
private readonly bool _includeProperties;
33+
34+
/// <summary>
35+
/// Exceptionless 客户端
36+
/// </summary>
37+
private readonly ExceptionlessClient _client;
38+
39+
/// <summary>
40+
/// 初始化一个<see cref="ExceptionlessSink"/>类型的实例
41+
/// </summary>
42+
/// <param name="apiKey">API密钥</param>
43+
/// <param name="serverUrl">Exceptionless服务器地址</param>
44+
/// <param name="defaultTags">默认标签数组</param>
45+
/// <param name="additionalOperation">附加信息操作函数</param>
46+
/// <param name="includeProperties">是否包含属性列表</param>
47+
public ExceptionlessSink(
48+
string apiKey,
49+
string serverUrl = null,
50+
string[] defaultTags = null,
51+
Func<EventBuilder, EventBuilder> additionalOperation = null,
52+
bool includeProperties = true)
53+
{
54+
if (apiKey == null)
55+
throw new ArgumentNullException(nameof(apiKey));
56+
_client = new ExceptionlessClient(config =>
57+
{
58+
if (!string.IsNullOrEmpty(apiKey) && apiKey != "API_KEY_HERE")
59+
config.ApiKey = apiKey;
60+
if (!string.IsNullOrEmpty(serverUrl))
61+
config.ServerUrl = serverUrl;
62+
config.UseInMemoryStorage();
63+
config.UseLogger(new SelfLogLogger());
64+
});
65+
_defaultTags = defaultTags;
66+
_additionalOperation = additionalOperation;
67+
_includeProperties = includeProperties;
68+
}
69+
70+
/// <summary>
71+
/// 初始化一个<see cref="ExceptionlessSink"/>类型的实例
72+
/// </summary>
73+
/// <param name="additionalOperation">附加信息操作函数</param>
74+
/// <param name="includeProperties">是否包含属性列表</param>
75+
/// <param name="client">Exceptionless客户端</param>
76+
public ExceptionlessSink(
77+
Func<EventBuilder, EventBuilder> additionalOperation = null,
78+
bool includeProperties = true,
79+
ExceptionlessClient client = null)
80+
{
81+
_additionalOperation = additionalOperation;
82+
_includeProperties = includeProperties;
83+
_client = client ?? ExceptionlessClient.Default;
84+
if (_client.Configuration.Resolver.HasDefaultRegistration<IExceptionlessLog, NullExceptionlessLog>())
85+
_client.Configuration.UseLogger(new SelfLogLogger());
86+
}
87+
88+
/// <summary>提交.</summary>
89+
/// <param name="logEvent">日志事件</param>
90+
public void Emit(LogEvent logEvent)
91+
{
92+
if (logEvent == null || _client.Configuration.IsValid)
93+
return;
94+
var minLogLevel = _client.Configuration.Settings.GetMinLogLevel(logEvent.GetSource());
95+
if (LogLevelSwitcher.Switch(logEvent.Level) < minLogLevel)
96+
return;
97+
98+
var builder = _client
99+
.CreateFromLogEvent(logEvent)
100+
.AddTags(_defaultTags);
101+
102+
if (_includeProperties && logEvent.Properties != null)
103+
{
104+
foreach (var property in logEvent.Properties)
105+
{
106+
switch (property.Key)
107+
{
108+
case Constants.SourceContextPropertyName:
109+
continue;
110+
case Event.KnownDataKeys.UserInfo when property.Value is StructureValue uis && string.Equals(nameof(UserInfo), uis.TypeTag):
111+
var userInfo = uis.FlattenProperties() as Dictionary<string, object>;
112+
if (userInfo is null)
113+
continue;
114+
// 忽略数据属性
115+
var identity = userInfo[nameof(UserInfo.Identity)] as string;
116+
var name = userInfo[nameof(UserInfo.Name)] as string;
117+
if (!string.IsNullOrWhiteSpace(identity) || !string.IsNullOrWhiteSpace(name))
118+
builder.SetUserIdentity(identity, name);
119+
break;
120+
121+
case Event.KnownDataKeys.UserDescription when property.Value is StructureValue uds && string.Equals(nameof(UserDescription), uds.TypeTag):
122+
var userDescription = uds.FlattenProperties() as Dictionary<string, object>;
123+
if (userDescription is null)
124+
continue;
125+
// 忽略数据属性
126+
var emailAddress = userDescription[nameof(UserDescription.EmailAddress)] as string;
127+
var description = userDescription[nameof(UserDescription.Description)] as string;
128+
if (!string.IsNullOrWhiteSpace(emailAddress) || !string.IsNullOrWhiteSpace(description))
129+
builder.SetUserDescription(emailAddress, description);
130+
break;
131+
default:
132+
builder.SetProperty(property.Key, property.Value.FlattenProperties());
133+
break;
134+
}
135+
}
136+
}
137+
138+
_additionalOperation?.Invoke(builder);
139+
builder.Submit();
140+
}
141+
142+
/// <summary>
143+
/// 释放资源
144+
/// </summary>
145+
public void Dispose() => _client?.ProcessQueue();
146+
}
147+
}

0 commit comments

Comments
 (0)