Files
MRCC/launcher/PCL-CE/PCL.Core/Logging/Logger.cs
T
xyou f70b061d1a
CI / Go Backend (push) Canceled after 0s
feat: 项目初始化 + 3D方块世界原型 + AI助搭系统
初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器

HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块

Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚

AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
2026-08-08 14:07:56 +08:00

193 lines
6.7 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using PCL.Core.Utils.Exts;
namespace PCL.Core.Logging;
public sealed class Logger : IAsyncDisposable
{
public Logger(LoggerConfiguration configuration)
{
Configuration = configuration;
_CreateNewFile();
_processingTask = _ProcessLogQueueAsync();
}
// Data stream
private StreamWriter? _currentStream;
private FileStream? _currentFile;
private readonly List<string> _files = [];
// Statis
private long _droppedCount;
public long DroppedLogCount => Interlocked.Read(ref _droppedCount);
// Processor
private readonly Task _processingTask;
private readonly Channel<string> _logChannel = Channel.CreateUnbounded<string>(new UnboundedChannelOptions()
{
SingleReader = true
});
public ReadOnlyCollection<string> CurrentLogFiles => _files.AsReadOnly();
public LoggerConfiguration Configuration { get; }
private void _CreateNewFile()
{
var now = DateTime.Now;
var nameFormat = (Configuration.FileNameFormat ?? $"Launch-{now.ToString("yyyy-M-d", CultureInfo.InvariantCulture)}-{{0}}") + ".log";
var filename = nameFormat.Replace("{0}", now.ToString("HHmmssfff", CultureInfo.InvariantCulture));
var filePath = Path.Combine(Configuration.StoreFolder, filename);
_files.Add(filePath);
var lastWriter = _currentStream;
var lastFile = _currentFile;
Directory.CreateDirectory(Configuration.StoreFolder);
_currentFile = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Read);
_currentStream = new StreamWriter(_currentFile);
_ = Task.Run(async () =>
{
if (lastWriter is not null)
try
{
await lastWriter.DisposeAsync().ConfigureAwait(false);
}
catch (Exception) { /* Don't care */ }
if (lastFile is not null)
try
{
await lastFile.DisposeAsync().ConfigureAwait(false);
}
catch (Exception) { /* Don't care */ }
if (!Configuration.AutoDeleteOldFile)
return;
var logFiles = Directory.GetFiles(
Configuration.StoreFolder,
"*.log",
SearchOption.TopDirectoryOnly);
var needToDelete = logFiles.Select(x => new FileInfo(x))
.OrderBy(x => x.CreationTime)
.Take(logFiles.Length - Configuration.MaxKeepOldFile);
foreach (var logFile in needToDelete)
logFile.Delete();
});
}
public void Trace(string message) => Log($"[{_GetTimeFormatted()}] [TRA] {message}");
public void Debug(string message) => Log($"[{_GetTimeFormatted()}] [DBG] {message}");
public void Info(string message) => Log($"[{_GetTimeFormatted()}] [INFO] {message}");
public void Warn(string message) => Log($"[{_GetTimeFormatted()}] [WARN] {message}");
public void Error(string message) => Log($"[{_GetTimeFormatted()}] [ERR!] {message}");
public void Fatal(string message) => Log($"[{_GetTimeFormatted()}] [FTL!] {message}");
private static string _GetTimeFormatted() => $"{DateTime.Now:HH:mm:ss.fff}";
public void Log(string message)
{
if (_disposed) return;
if (!_logChannel.Writer.TryWrite(message))
{
Interlocked.Increment(ref _droppedCount);
Console.WriteLine($"Log dropped error: {message}");
}
}
private async Task _ProcessLogQueueAsync()
{
const int maxBatchLines = 198;
var writeTimeout = TimeSpan.FromMilliseconds(325);
var batch = new StringBuilder(4096);
var lineCount = 0u;
var lastFlush = Stopwatch.GetTimestamp();
try
{
while (!_disposed || _logChannel.Reader.TryPeek(out _))
{
if (_logChannel.Reader.TryRead(out var message))
{
#if DEBUG
message = message.ReplaceLineBreak("\r\n");
Console.WriteLine(message);
System.Diagnostics.Debug.WriteLine(message);
#endif
batch.AppendLine(message);
lineCount++;
var elapsed = Stopwatch.GetElapsedTime(lastFlush);
if (lineCount >= maxBatchLines || elapsed > writeTimeout)
{
await DoRefreshAsync().ConfigureAwait(false);
}
}
else
{
if (lineCount != 0)
{
await DoRefreshAsync().ConfigureAwait(false);
}
await Task.Delay(80).ConfigureAwait(false);
}
}
async Task DoRefreshAsync()
{
await _DoWriteAsync(batch).ConfigureAwait(false);
batch.Clear();
lineCount = 0;
lastFlush = Stopwatch.GetTimestamp();
}
}
catch (Exception e)
{
// 出错了先干到标准输出流中吧 Orz
Console.WriteLine($"[{_GetTimeFormatted()}] [ERROR] An error occurred while processing log queue: {e.Message}");
throw;
}
}
private async Task _DoWriteAsync(StringBuilder ctx)
{
try
{
if (_currentFile?.Length >= Configuration.MaxFileSize)
{
_CreateNewFile();
}
await _currentStream!.WriteAsync(ctx).ConfigureAwait(false);
await _currentStream.FlushAsync().ConfigureAwait(false);
}
catch (Exception e)
{
Console.WriteLine($"[{_GetTimeFormatted()}] [ERROR] An error occurred while writing log file: {e.Message}");
await File.AppendAllTextAsync(Path.Combine(Configuration.StoreFolder, "Error.log"), $"[{_GetTimeFormatted}] LogCycle Error: {e}\n");
throw;
}
}
private bool _disposed;
public async ValueTask DisposeAsync()
{
if (_disposed) return;
_disposed = true;
_logChannel.Writer.Complete();
await _processingTask.ConfigureAwait(false);
if (_currentStream is not null)
await _currentStream.DisposeAsync().ConfigureAwait(false);
if (_currentFile is not null)
await _currentFile.DisposeAsync().ConfigureAwait(false);
}
}