Skip to content

Commit

Permalink
Merge pull request #21 from dotnet-campus/t/walterlv/thread-safe-cons…
Browse files Browse the repository at this point in the history
…ole-logger

线程安全的控制台日志输出
  • Loading branch information
lindexi authored Aug 22, 2024
2 parents 4212e79 + 573f89c commit 379eabc
Show file tree
Hide file tree
Showing 9 changed files with 369 additions and 65 deletions.
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

0 comments on commit 379eabc

Please sign in to comment.