Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

线程安全的控制台日志输出 #21

Merged
merged 6 commits into from
Aug 22, 2024
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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ internal static class LoggerStartup
// 设置日志级别为 Debug。
.WithLevel(LogLevel.Debug)
// 添加一个控制台日志写入器,这样控制台里就可以看到日志输出了。
.AddWriter(new ConsoleLogger()
.AddConsoleLogger(b => b
.WithThreadSafe(LogWritingThreadMode.ProducerConsumer)
.FilterConsoleTagsFromCommandLineArgs(args))
// 如果有一些库使用了本日志框架(使用源生成器,不带依赖的那种),那么可以通过这个方法将它们的日志桥接到本日志框架中。
.AddBridge(LoggerBridgeLinker.Default)
Expand Down
2 changes: 1 addition & 1 deletion samples/LoggerSample.MainApp/LoggerSample.MainApp.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>WinExe</OutputType>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<DCUseGeneratedLogger>preferReference</DCUseGeneratedLogger>
</PropertyGroup>
Expand Down
31 changes: 23 additions & 8 deletions samples/LoggerSample.MainApp/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
using dotnetCampus.Logging.Attributes;
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using dotnetCampus.Logging.Attributes;
using dotnetCampus.Logging.Configurations;
using dotnetCampus.Logging.Writers;

Expand All @@ -21,16 +25,27 @@ public static void Main(string[] args)
{
LogLevel = LogLevel.Debug,
})
.AddWriter(new ConsoleLogger
{
// Options = new ConsoleLoggerOptions
// {
// IncludeScopes = true,
// },
})
.AddConsoleLogger(b => b
.WithThreadSafe(LogWritingThreadMode.ProducerConsumer)
.FilterConsoleTagsFromCommandLineArgs(args))
.AddBridge(LoggerBridgeLinker.Default)
.Build()
.IntoGlobalStaticLog();

Run();
Thread.Sleep(5000);
}

private static void Run()
{
var stopwatch = Stopwatch.StartNew();
Log.Debug($"[TEST] 开始 {stopwatch.ElapsedMilliseconds}ms");
Parallel.For(0, 0x00004000, i =>
{
Thread.Sleep(0);
Log.Debug($"[TEST] {DateTime.Now:HH:mm:ss}");
});
Log.Debug($"[TEST] 完成 {stopwatch.ElapsedMilliseconds}ms");
}
}

Expand Down
6 changes: 2 additions & 4 deletions src/dotnetCampus.Logger/LoggerBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using dotnetCampus.Logging.Bridges;
using dotnetCampus.Logging.Configurations;
using dotnetCampus.Logging.Writers;

namespace dotnetCampus.Logging;

/// <summary>
/// 辅助创建日志记录器的构建器。
/// </summary>
public class LoggerBuilder
public sealed class LoggerBuilder
{
private LogOptions? _options;
private readonly List<ILogger> _writers = [];
Expand Down
134 changes: 83 additions & 51 deletions src/dotnetCampus.Logger/Writers/ConsoleLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@

namespace dotnetCampus.Logging.Writers;

/// <summary>
/// 在控制台输出日志的日志记录器。
/// </summary>
public class ConsoleLogger : ILogger
{
/// <summary>
Expand All @@ -16,18 +19,40 @@ public class ConsoleLogger : ILogger
private int _isCursorMovementEnabled = 3;

private readonly RepeatLoggerDetector _repeat;
private TagFilterManager? _tagFilterManager;

/// <summary>
/// 高于或等于此级别的日志才会被记录
/// 创建一个 <see cref="ConsoleLogger"/> 的新实例
/// </summary>
public LogLevel Level { get; set; }
/// <param name="threadMode">指定控制台日志的线程安全模式。</param>
/// <param name="mainArgs">Main 方法的参数。</param>
public ConsoleLogger(LogWritingThreadMode threadMode = LogWritingThreadMode.NotThreadSafe, string[]? mainArgs = null)
: this(threadMode.CreateCoreLogWriter(), TagFilterManager.FromCommandLineArgs(mainArgs ?? []))
{
}

public ConsoleLogger()
internal ConsoleLogger(ICoreLogWriter coreWriter, TagFilterManager? tagManager)
{
_repeat = new(ClearAndMoveToLastLine);
_repeat = new RepeatLoggerDetector(ClearAndMoveToLastLine);
CoreWriter = coreWriter;
TagManager = tagManager;
}

/// <summary>
/// 高于或等于此级别的日志才会被记录。
/// </summary>
public LogLevel Level { get; init; }

/// <summary>
/// 最终日志写入器。
/// </summary>
private ICoreLogWriter CoreWriter { get; }

/// <summary>
/// 管理控制台日志的标签过滤。
/// </summary>
private TagFilterManager? TagManager { get; }

/// <inheritdoc />
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{
if (logLevel < Level)
Expand All @@ -36,24 +61,37 @@ public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Except
}

var message = formatter(state, exception);
if (_tagFilterManager?.IsTagEnabled(message) is false)
if (TagManager?.IsTagEnabled(message) is false)
{
return;
}

var traceTag = TraceTag;
var debugTag = DebugTag;
var informationTag = InformationTag;
var warningTag = WarningTag;
var errorTag = ErrorTag;
var criticalTag = CriticalTag;
LogCore(logLevel, exception, message, m => logLevel switch
{
LogLevel.Trace => $"{TraceTag} {TraceText}{m}{Reset}",
LogLevel.Debug => $"{DebugTag} {DebugText}{m}{Reset}",
LogLevel.Information => $"{InformationTag} {InformationText}{m}{Reset}",
LogLevel.Warning => $"{WarningTag} {WarningText}{m}{Reset}",
LogLevel.Error => $"{ErrorTag} {ErrorText}{m}{Reset}",
LogLevel.Critical => $"{CriticalTag} {CriticalText}{m}{Reset}",
LogLevel.Trace => $"{traceTag} {TraceText}{m}{Reset}",
LogLevel.Debug => $"{debugTag} {DebugText}{m}{Reset}",
LogLevel.Information => $"{informationTag} {InformationText}{m}{Reset}",
LogLevel.Warning => $"{warningTag} {WarningText}{m}{Reset}",
LogLevel.Error => $"{errorTag} {ErrorText}{m}{Reset}",
LogLevel.Critical => $"{criticalTag} {CriticalText}{m}{Reset}",
_ => null,
});
}

private void LogCore(LogLevel logLevel, Exception? exception, string message, Func<string, string?> formatter)
/// <summary>
/// 记录日志。在必要的情况下会保证线程安全。
/// </summary>
/// <param name="logLevel"></param>
/// <param name="exception"></param>
/// <param name="message"></param>
/// <param name="formatter"></param>
private void LogCore(LogLevel logLevel, Exception? exception, string message, Func<string, string?> formatter) => CoreWriter.Do(() =>
{
if (_repeat.RepeatOrResetLastLog(logLevel, message, exception) is var count and > 1)
{
Expand All @@ -77,9 +115,15 @@ private void LogCore(LogLevel logLevel, Exception? exception, string message, Fu
{tag}{exception}
""", formatter);
}
}
});

private static void ConsoleMultilineMessage(string message, Func<string, string?> formatter, bool forceSingleLine = false)
/// <summary>
/// 记录多行日志。
/// </summary>
/// <param name="message"></param>
/// <param name="formatter"></param>
/// <param name="forceSingleLine"></param>
private void ConsoleMultilineMessage(string message, Func<string, string?> formatter, bool forceSingleLine = false)
{
if (forceSingleLine || !message.Contains('\n'))
{
Expand All @@ -96,46 +140,34 @@ private static void ConsoleMultilineMessage(string message, Func<string, string?
}

/// <summary>
/// 高于或等于此级别的日志才会被记录
/// 清空当前行并移动光标到上一行
/// </summary>
public ConsoleLogger UseLevel(LogLevel level)
{
Level = level;
return this;
}

/// <summary>
/// 从命令行参数中提取过滤标签。
/// </summary>
/// <param name="args">命令行参数。</param>
public ConsoleLogger FilterConsoleTagsFromCommandLineArgs(string[] args)
{
_tagFilterManager = TagFilterManager.FromCommandLineArgs(args);
return this;
}

/// <param name="repeatCount">此移动光标,是因为日志已重复第几次。</param>
private void ClearAndMoveToLastLine(int repeatCount)
{
if (_isCursorMovementEnabled > 0 && repeatCount > 2)
if (_isCursorMovementEnabled <= 0 || repeatCount <= 2)
{
try
{
var desiredY = Console.CursorTop - 1;
var y = Math.Clamp(desiredY, 0, Console.WindowHeight - 1);
Console.SetCursorPosition(0, y);
Console.Write(new string(' ', Console.WindowWidth));
Console.SetCursorPosition(0, y);
}
catch (IOException)
{
// 日志记录时,如果无法移动光标,说明可能当前输出位置不在缓冲区内。
// 如果多次尝试失败,则认为当前控制台缓冲区不支持光标移动,遂放弃。
_isCursorMovementEnabled--;
}
catch (ArgumentException)
{
// 日志记录时,有可能已经移动到头了,就不要移动了。
}
// 如果光标控制不可用,或者还没有重复次数,则不尝试移动光标。
return;
}

try
{
var desiredY = Console.CursorTop - 1;
var y = Math.Clamp(desiredY, 0, Console.WindowHeight - 1);
Console.SetCursorPosition(0, y);
Console.Write(new string(' ', Console.WindowWidth));
Console.SetCursorPosition(0, y);
}
catch (IOException)
{
// 日志记录时,如果无法移动光标,说明可能当前输出位置不在缓冲区内。
// 如果多次尝试失败,则认为当前控制台缓冲区不支持光标移动,遂放弃。
_isCursorMovementEnabled--;
}
catch (ArgumentException)
{
// 日志记录时,有可能已经移动到头了,就不要移动了。
}
}

Expand Down
84 changes: 84 additions & 0 deletions src/dotnetCampus.Logger/Writers/ConsoleLoggerBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System;
using dotnetCampus.Logging.Writers.Helpers;

namespace dotnetCampus.Logging.Writers;

/// <summary>
/// 辅助创建控制台日志记录器的构建器。
/// </summary>
public sealed class ConsoleLoggerBuilder
{
private TagFilterManager? _tagFilterManager;
private ICoreLogWriter _coreWriter = new NotThreadSafeLogWriter();

/// <summary>
/// 高于或等于此级别的日志才会被记录。
/// </summary>
public LogLevel Level { get; set; }

/// <summary>
/// 高于或等于此级别的日志才会被记录。
/// </summary>
public ConsoleLoggerBuilder WithLevel(LogLevel level)
{
Level = level;
return this;
}

/// <summary>
/// 指定控制台日志的线程安全模式。
/// </summary>
/// <param name="threadMode">线程安全模式。</param>
/// <returns>构造器模式。</returns>
/// <exception cref="ArgumentOutOfRangeException">线程安全模式不支持。</exception>
public ConsoleLoggerBuilder WithThreadSafe(LogWritingThreadMode threadMode)
{
_coreWriter = threadMode switch
{
LogWritingThreadMode.NotThreadSafe => new NotThreadSafeLogWriter(),
LogWritingThreadMode.Lock => new LockLogWriter(),
LogWritingThreadMode.ProducerConsumer => new ProducerConsumerLogWriter(),
_ => throw new ArgumentOutOfRangeException(nameof(threadMode)),
};
return this;
}

/// <summary>
/// 从命令行参数中提取过滤标签,使得控制台日志支持过滤标签行为。
/// </summary>
/// <param name="args">命令行参数。</param>
/// <returns>构造器模式。</returns>
public ConsoleLoggerBuilder FilterConsoleTagsFromCommandLineArgs(string[] args)
{
_tagFilterManager = TagFilterManager.FromCommandLineArgs(args);
return this;
}

/// <summary>
/// 创建控制台日志记录器。
/// </summary>
/// <returns>控制台日志记录器。</returns>
internal ConsoleLogger Build() => new(_coreWriter, _tagFilterManager)
{
Level = Level,
};
}

/// <summary>
/// 辅助创建控制台日志记录器。
/// </summary>
public static class ConsoleLoggerBuilderExtensions
{
/// <summary>
/// 添加控制台日志记录器。
/// </summary>
/// <param name="builder">日志构建器。</param>
/// <param name="configure">配置控制台日志记录器。</param>
/// <returns>日志构建器。</returns>
public static LoggerBuilder AddConsoleLogger(this LoggerBuilder builder, Action<ConsoleLoggerBuilder> configure)
{
var consoleLoggerBuilder = new ConsoleLoggerBuilder();
configure(consoleLoggerBuilder);
return builder.AddWriter(consoleLoggerBuilder.Build());
}
}
Loading