Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions GFramework.Core.Abstractions/logging/ILogAppender.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace GFramework.Core.Abstractions.logging;

/// <summary>
/// 日志输出器接口,负责将日志条目写入特定目标
/// </summary>
public interface ILogAppender
{
/// <summary>
/// 追加日志条目
/// </summary>
/// <param name="entry">日志条目</param>
void Append(LogEntry entry);

/// <summary>
/// 刷新缓冲区,确保所有日志已写入
/// </summary>
void Flush();
}
14 changes: 14 additions & 0 deletions GFramework.Core.Abstractions/logging/ILogFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace GFramework.Core.Abstractions.logging;

/// <summary>
/// 日志过滤器接口,用于决定是否应该记录某条日志
/// </summary>
public interface ILogFilter
{
/// <summary>
/// 判断是否应该记录该日志条目
/// </summary>
/// <param name="entry">日志条目</param>
/// <returns>如果应该记录返回 true,否则返回 false</returns>
bool ShouldLog(LogEntry entry);
}
14 changes: 14 additions & 0 deletions GFramework.Core.Abstractions/logging/ILogFormatter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace GFramework.Core.Abstractions.logging;

/// <summary>
/// 日志格式化器接口,用于将日志条目格式化为字符串
/// </summary>
public interface ILogFormatter
{
/// <summary>
/// 将日志条目格式化为字符串
/// </summary>
/// <param name="entry">日志条目</param>
/// <returns>格式化后的日志字符串</returns>
string Format(LogEntry entry);
}
44 changes: 44 additions & 0 deletions GFramework.Core.Abstractions/logging/ILogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -309,4 +309,48 @@ public interface ILogger
void Fatal(string msg, Exception t);

#endregion

#region Generic Log Methods

/// <summary>
/// 使用指定的日志级别记录消息
/// </summary>
/// <param name="level">日志级别</param>
/// <param name="message">要记录的消息字符串</param>
void Log(LogLevel level, string message);

/// <summary>
/// 使用指定的日志级别根据格式和参数记录消息
/// </summary>
/// <param name="level">日志级别</param>
/// <param name="format">格式字符串</param>
/// <param name="arg">参数</param>
void Log(LogLevel level, string format, object arg);

/// <summary>
/// 使用指定的日志级别根据格式和参数记录消息
/// </summary>
/// <param name="level">日志级别</param>
/// <param name="format">格式字符串</param>
/// <param name="arg1">第一个参数</param>
/// <param name="arg2">第二个参数</param>
void Log(LogLevel level, string format, object arg1, object arg2);

/// <summary>
/// 使用指定的日志级别根据格式和参数数组记录消息
/// </summary>
/// <param name="level">日志级别</param>
/// <param name="format">格式字符串</param>
/// <param name="arguments">参数数组</param>
void Log(LogLevel level, string format, params object[] arguments);

/// <summary>
/// 使用指定的日志级别记录消息和异常
/// </summary>
/// <param name="level">日志级别</param>
/// <param name="message">伴随异常的消息</param>
/// <param name="exception">要记录的异常</param>
void Log(LogLevel level, string message, Exception exception);

#endregion
}
24 changes: 24 additions & 0 deletions GFramework.Core.Abstractions/logging/IStructuredLogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace GFramework.Core.Abstractions.logging;

/// <summary>
/// 支持结构化日志的日志记录器接口
/// </summary>
public interface IStructuredLogger : ILogger
{
/// <summary>
/// 使用指定的日志级别记录消息和结构化属性
/// </summary>
/// <param name="level">日志级别</param>
/// <param name="message">日志消息</param>
/// <param name="properties">结构化属性键值对</param>
void Log(LogLevel level, string message, params (string Key, object? Value)[] properties);

/// <summary>
/// 使用指定的日志级别记录消息、异常和结构化属性
/// </summary>
/// <param name="level">日志级别</param>
/// <param name="message">日志消息</param>
/// <param name="exception">异常对象</param>
/// <param name="properties">结构化属性键值对</param>
void Log(LogLevel level, string message, Exception? exception, params (string Key, object? Value)[] properties);
}
117 changes: 117 additions & 0 deletions GFramework.Core.Abstractions/logging/LogContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
namespace GFramework.Core.Abstractions.logging;

/// <summary>
/// 日志上下文,用于在异步流中传递结构化属性
/// </summary>
public sealed class LogContext : IDisposable
{
private static readonly AsyncLocal<Dictionary<string, object?>?> _context = new();
private readonly bool _hadPreviousValue;
private readonly string _key;
private readonly object? _previousValue;

private LogContext(string key, object? value)
{
_key = key;

var current = _context.Value;
if (current?.TryGetValue(key, out var prev) == true)
{
_previousValue = prev;
_hadPreviousValue = true;
}

EnsureContext();
_context.Value![key] = value;
}

/// <summary>
/// 获取当前上下文中的所有属性
/// </summary>
public static IReadOnlyDictionary<string, object?> Current
{
get
{
var context = _context.Value;
return context ??
(IReadOnlyDictionary<string, object?>)new Dictionary<string, object?>(StringComparer.Ordinal);
}
}

/// <summary>
/// 释放上下文,恢复之前的值
/// </summary>
public void Dispose()
{
var current = _context.Value;
if (current == null) return;

if (_hadPreviousValue)
{
current[_key] = _previousValue;
}
else
{
current.Remove(_key);
if (current.Count == 0)
{
_context.Value = null;
}
}
}

/// <summary>
/// 向当前上下文添加一个属性
/// </summary>
/// <param name="key">属性键</param>
/// <param name="value">属性值</param>
/// <returns>可释放的上下文对象,释放时会恢复之前的值</returns>
public static IDisposable Push(string key, object? value)
{
if (string.IsNullOrWhiteSpace(key))
throw new ArgumentException("Key cannot be null or whitespace.", nameof(key));

return new LogContext(key, value);
}

/// <summary>
/// 向当前上下文添加多个属性
/// </summary>
/// <param name="properties">属性键值对</param>
/// <returns>可释放的上下文对象,释放时会恢复之前的值</returns>
public static IDisposable PushProperties(params (string Key, object? Value)[] properties)
{
if (properties == null || properties.Length == 0)
throw new ArgumentException("Properties cannot be null or empty.", nameof(properties));

return new CompositeDisposable(properties.Select(p => Push(p.Key, p.Value)).ToArray());
}

/// <summary>
/// 清除当前上下文中的所有属性
/// </summary>
public static void Clear()
{
_context.Value = null;
}

private static void EnsureContext()
{
_context.Value ??= new Dictionary<string, object?>(StringComparer.Ordinal);
}

/// <summary>
/// 组合多个可释放对象
/// </summary>
private sealed class CompositeDisposable(IDisposable[] disposables) : IDisposable
{
public void Dispose()
{
// 按相反顺序释放
for (int i = disposables.Length - 1; i >= 0; i--)
{
disposables[i].Dispose();
}
}
}
}
37 changes: 37 additions & 0 deletions GFramework.Core.Abstractions/logging/LogEntry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
namespace GFramework.Core.Abstractions.logging;

/// <summary>
/// 日志条目,包含完整的日志信息
/// </summary>
public sealed record LogEntry(
DateTime Timestamp,
LogLevel Level,
string LoggerName,
string Message,
Exception? Exception,
IReadOnlyDictionary<string, object?>? Properties)
{
/// <summary>
/// 获取合并了上下文属性的所有属性
/// </summary>
/// <returns>包含日志属性和上下文属性的字典</returns>
public IReadOnlyDictionary<string, object?> GetAllProperties()
{
var contextProps = LogContext.Current;

if (Properties == null || Properties.Count == 0)
return contextProps;

if (contextProps.Count == 0)
return Properties;

// 合并属性,日志属性优先
var merged = new Dictionary<string, object?>(contextProps, StringComparer.Ordinal);
foreach (var prop in Properties)
{
merged[prop.Key] = prop.Value;
}

return merged;
}
}
Loading
Loading