123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- using Microsoft.Extensions.Logging;
- using System;
- using System.Collections.Concurrent;
- using System.Text;
- using System.Text.Encodings.Web;
- using System.Text.Json;
- using System.Text.Unicode;
- using System.Threading.Channels;
- namespace IES.ExamServer.DI
- {
- public class CustomFileLoggerProvider : ILoggerProvider
- {
- private readonly string _logDirectory;
- private readonly bool _enableConsoleOutput;
- private readonly string _timestamp;
- private readonly ConcurrentDictionary<string, CustomFileLogger> _loggers = new ConcurrentDictionary<string, CustomFileLogger>();
- public CustomFileLoggerProvider(string logDirectory, bool enableConsoleOutput)
- {
- _logDirectory = logDirectory;
- _enableConsoleOutput = enableConsoleOutput;
- _timestamp = DateTime.Now.ToString("yyMMddHH"); // 生成时间戳
- if (!Directory.Exists(_logDirectory))
- {
- Directory.CreateDirectory(_logDirectory);
- }
- }
- public ILogger CreateLogger(string categoryName)
- {
- return _loggers.GetOrAdd(categoryName, name => new CustomFileLogger(_logDirectory, name, _enableConsoleOutput, _timestamp));
- }
- public void Dispose()
- {
- _loggers.Clear();
- }
- }
- public class CustomFileLogger : ILogger
- {
- private readonly string _logDirectory;
- private readonly string _categoryName;
- private readonly bool _enableConsoleOutput;
- private static readonly SemaphoreSlim _fileLock = new SemaphoreSlim(1, 1);
- private readonly List<string> _logBuffer = new List<string>();
- // private readonly Timer _flushTimer;
- private readonly string _timestamp;
- private readonly Channel<string> _consoleLogChannel;
- private readonly Channel<string> _fileLogChannel;
- public CustomFileLogger(string logDirectory, string categoryName, bool enableConsoleOutput, string timestamp)
- {
- _logDirectory = logDirectory;
- _categoryName = categoryName;
- _enableConsoleOutput = enableConsoleOutput;
- // 每 5 秒刷新一次日志缓冲区
- // _flushTimer = new Timer(FlushLogs, null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5));
- _timestamp=timestamp;
- // 创建无界 Channel
- _consoleLogChannel = Channel.CreateUnbounded<string>();
- // 创建无界 Channel
- _fileLogChannel = Channel.CreateUnbounded<string>();
- // 启动后台任务处理控制台日志
- _ = Task.Run(ProcessConsoleLogs);
- // 启动后台任务处理文件日志
- _ = Task.Run(ProcessFileLogs);
- }
- public IDisposable BeginScope<TState>(TState state) => null;
- public bool IsEnabled(LogLevel logLevel) => true;
- public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
- {
- var logMessage = formatter(state, exception);
- var logFileName = $"{logLevel}_{_timestamp}.log"; // 按时间戳命名文件
- var logFilePath = Path.Combine(_logDirectory, logFileName);
- // 异步写入文件
- // _ = WriteToFileAsync(logFilePath, logLevel, logMessage);
- // 将日志消息写入 Channel(不阻塞)
- _fileLogChannel.Writer.TryWrite($"{logFilePath}|||{DateTime.Now:yyyy-MM-dd HH:mm:ss} [{logLevel}] {logMessage}{Environment.NewLine}");
- // 输出到控制台
- if (_enableConsoleOutput)
- {
- // Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} [{logLevel}] {logMessage}");
- // _ = Task.Run(() => Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} [{logLevel}] {logMessage}"));
- _consoleLogChannel.Writer.TryWrite($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} [{logLevel}] {logMessage}");
- }
- }
- private async Task ProcessConsoleLogs()
- {
- await foreach (var logMessage in _consoleLogChannel.Reader.ReadAllAsync())
- {
- Console.WriteLine(logMessage); // 输出到控制台
- }
- }
- private async Task ProcessFileLogs()
- {
- await foreach (var logEntry in _fileLogChannel.Reader.ReadAllAsync())
- {
- try
- {
- // 解析日志条目
- var parts = logEntry.Split("|||");
- var logFilePath = parts[0];
- var logMessage = parts[1];
- // 异步写入文件
- await File.AppendAllTextAsync(logFilePath, logMessage);
- }
- catch (Exception ex)
- {
- // 处理文件写入异常
- // Console.WriteLine($"Failed to write log to file: {ex.Message}");
- _consoleLogChannel.Writer.TryWrite($"Failed to write log to file: {ex.Message}");
- }
- }
- }
-
- //private async void FlushLogs(object state)
- //{
- // List<string> logsToWrite;
- // lock (_logBuffer)
- // {
- // if (_logBuffer.Count == 0) return;
- // logsToWrite = new List<string>(_logBuffer);
- // _logBuffer.Clear();
- // }
- // var logFileName = $"{_categoryName}_{_timestamp}.log";
- // var logFilePath = Path.Combine(_logDirectory, logFileName);
- // await _fileLock.WaitAsync(); // 获取锁
- // try
- // {
- // await File.AppendAllLinesAsync(logFilePath, logsToWrite);
- // }
- // finally
- // {
- // _fileLock.Release(); // 释放锁
- // }
- //}
- public void Dispose()
- {
- // _flushTimer?.Change(Timeout.Infinite, 0); // 停止定时器
- // FlushLogs(null); // 最后一次刷新日志
- }
- }
- public static class LoggerExtensions
- {
- //private static readonly SemaphoreSlim _dataFileLock = new SemaphoreSlim(1, 1);
- //private static readonly Channel<string> _fileLogChannel = Channel.CreateUnbounded<string>();
- //private static readonly Channel<string> _consoleLogChannel = Channel.CreateUnbounded<string>();
- private static readonly Channel<string> _fileLogChannel = Channel.CreateBounded<string>(new BoundedChannelOptions(10000)
- {
- FullMode = BoundedChannelFullMode.DropWrite //
- });
- private static readonly Channel<string> _consoleLogChannel = Channel.CreateBounded<string>(new BoundedChannelOptions(10000)
- {
- //DropOldest: 此模式会移除并丢弃通道中最旧的那个数据项,以便为正在写入的新数据腾出空间。通道里最早进入的数据会被舍弃,从而让新的数据可以进入通道。
- //DropNewest: 在这种模式下,为了给要写入的新数据项腾出空间,会移除并丢弃通道中最新的那个数据项。也就是说,新的数据会覆盖掉原本在通道里最新进入的元素,保证新数据能被写入。
- //Wait: 当使用这种模式时,如果尝试向已满的有界通道写入数据,调用写入操作的线程会等待,直到通道中有空间可用,然后再完成写入操作。这意味着线程会被阻塞,直到可以成功放入新的数据项。
- FullMode = BoundedChannelFullMode.DropWrite // 直接丢弃当前正要写入的这个数据项,通道中的内容保持不变,相当于放弃这次写入操作。
- });
- //懒加载
- private static readonly Lazy<Task> _fileLogProcessor = new Lazy<Task>(() => Task.Run(ProcessFileLogs));
- private static readonly Lazy<Task> _consoleLogProcessor = new Lazy<Task>(() => Task.Run(ProcessConsoleLogs));
-
-
- public static void LogData<T>(this ILogger logger, T data, string id)
- {
- // 确保后台任务已启动,现在就要用确保启动
- _ = _fileLogProcessor.Value;
- _ = _consoleLogProcessor.Value;
- var logDirectory = Path.Combine(Directory.GetCurrentDirectory(), "Logs", "DataLogs");
- if (!Directory.Exists(logDirectory))
- {
- Directory.CreateDirectory(logDirectory);
- }
- if (!string.IsNullOrWhiteSpace(id))
- {
- var logFilePath = Path.Combine(logDirectory, $"{id}.log");
- var logMessage = data?.ToJsonString();
- _fileLogChannel.Writer.TryWrite($"{logFilePath}|||{DateTime.Now:yyyy-MM-dd HH:mm:ss} [Data] {logMessage}{Environment.NewLine}");
- }
- else {
- _consoleLogChannel.Writer.TryWrite($"日志id为空,日志数据:{data?.ToJsonString()}");
- }
- //_ = Task.Run(async () =>
- //{
- // await _dataFileLock.WaitAsync(); // 获取锁
- // try
- // {
- // await File.AppendAllTextAsync(logFilePath, $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} [Data] {logMessage}{Environment.NewLine}");
- // }
- // finally
- // {
- // _dataFileLock.Release(); // 释放锁
- // }
- //});
- }
- static async Task ProcessFileLogs()
- {
- try
- {
- await foreach (var logEntry in _fileLogChannel.Reader.ReadAllAsync())
- {
-
- try
- {
- // 解析日志条目
- var parts = logEntry.Split("|||");
- var logFilePath = parts[0];
- var logMessage = parts[1];
- // 异步写入文件
- await File.AppendAllTextAsync(logFilePath, logMessage, Encoding.UTF8);
- }
- catch (Exception ex)
- {
- // 处理文件写入异常
- // Console.WriteLine($"Failed to write log to file: {ex.Message}");
- _consoleLogChannel.Writer.TryWrite($"Failed to write log to file: {ex.Message}");
- }
- }
- }
- catch (Exception ex) {
- _consoleLogChannel.Writer.TryWrite($"Failed to write log to file: {ex.Message}");
- }
-
- // 循环结束后,检查是否有剩余的日志条目
-
- }
-
- static async Task ProcessConsoleLogs()
- {
- await foreach (var logMessage in _consoleLogChannel.Reader.ReadAllAsync())
- {
- Console.WriteLine(logMessage); // 输出到控制台
- }
- }
- }
- }
|