CSharp: LogHelper

cs 复制代码
/*
 # encoding: utf-8
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述: 日志
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : vs2026 c# .net 10
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/07/05 23:16
# User      :  geovindu
# Product   : Visual Studio 2026
# Project   : CSharpAlgorithms
# File      : LogHelper.cs
 
 */
 
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
using System.IO.Compression;
 
namespace CSharpAlgorithms.common
{
    /// <summary>
    /// 日志级别
    /// </summary>
    public enum LogLevel
    {
        Off = 0,
        Error = 1,
        Info = 2,
        Debug = 3
    }
 
    /// <summary>
    /// 脱敏委托:级别、模块、原始消息 -> 脱敏后消息
    /// </summary>
    public delegate string DesensitizeDelegate(string level, string module, string msg);
 
    /// <summary>
    /// 告警回调委托
    /// </summary>
    public delegate void AlertCallbackDelegate(string eventType, string message, Dictionary<string, object> ext);
 
    /// <summary>
    /// 告警常量
    /// </summary>
    public static class AlertEvent
    {
        public const string DiskOverLimit = "DISK_OVER_LIMIT";
        public const string QueueBlocked = "QUEUE_BLOCKED";
    }
 
    /// <summary>
    /// 日志队列单元
    /// </summary>
    internal sealed class LogItem
    {
        public string Level { get; set; } = string.Empty;
        public string Module { get; set; } = string.Empty;
        public string TextData { get; set; } = string.Empty;
        public Dictionary<string, object>? JsonData { get; set; }
        public string StackTrace { get; set; } = string.Empty;
    }
 
    /// <summary>
    /// .NET10 高性能日志核心类,修复空引用异常
    /// </summary>
    public sealed class LogHelper
    {
        #region 单例 严格顺序初始化
        private static readonly Lazy<LogHelper> _lazyInstance = new Lazy<LogHelper>(() => new LogHelper(), LazyThreadSafetyMode.ExecutionAndPublication);
        public static LogHelper Instance => _lazyInstance.Value;
 
        // 【强制提前实例化】所有成员字段直接初始化,杜绝null
        private readonly ConcurrentQueue<LogItem> _logQueue = new ConcurrentQueue<LogItem>();
        private readonly CancellationTokenSource _cts = new CancellationTokenSource();
        private readonly object _fileLock = new object();
        private readonly Random _random = new Random();
        private readonly Dictionary<string, ConsoleColor> _colorMap = new Dictionary<string, ConsoleColor>();
        private readonly Dictionary<string, double> _levelDiskLimit = new()
        {
            { "debug", 150 },
            { "info", 250 },
            { "error", 100 }
        };
        private readonly Dictionary<string, double> _levelSamplingRate = new()
        {
            { "debug", 1.0 },
            { "info", 1.0 },
            { "error", 1.0 }
        };
 
        private Task? _writeTask;
        private DateTime _lastCleanDay = DateTime.MinValue;
        private long _lastDiskCheckTs;
        private readonly int _pid = Environment.ProcessId;
 
        #region 配置属性
        public string LogRoot { get; private set; }
        public string DefaultModule { get; set; } = "default";
        public int KeepDays { get; set; } = 7;
        public long MaxFileSizeMb { get; set; } = 10;
        public int QueueMaxSize { get; set; } = 10000;
        public int QueueAlertThreshold { get; set; } = 7000;
        public int BatchWriteCount { get; set; } = 10;
 
        public bool EnableConsole { get; set; } = true;
        public bool EnableJsonFormat { get; set; } = false;
        public bool CompressBackupLog { get; set; } = true;
 
        public double MaxDiskTotalMb { get; set; } = 500;
        public double GlobalSamplingRate { get; set; } = 1.0;
        public int DiskCheckIntervalSec { get; set; } = 60;
        public string TimeFormat { get; set; } = "yyyy-MM-dd HH:mm:ss";
        public LogLevel MinLogLevel { get; set; } = LogLevel.Debug;
 
        // 回调
        public DesensitizeDelegate? DesensitizeCallback { get; private set; }
        public AlertCallbackDelegate? AlertCallback { get; private set; }
        #endregion
 
        // 私有构造:仅赋值、初始化,最后再启动线程
        private LogHelper()
        {
            LogRoot = Path.Combine(Directory.GetCurrentDirectory(), "Logs");
            Directory.CreateDirectory(LogRoot);
 
            // 颜色初始化
            _colorMap["DEBUG"] = ConsoleColor.Blue;
            _colorMap["INFO"] = ConsoleColor.Green;
            _colorMap["ERROR"] = ConsoleColor.Red;
 
            // Windows控制台编码兼容
            if (OperatingSystem.IsWindows() && !Console.IsOutputRedirected)
            {
                try
                {
                    Console.OutputEncoding = Encoding.UTF8;
                }
                catch
                {
                    EnableConsole = true;
                }
            }
 
            // 【关键点】所有变量初始化完毕,最后再启动后台写入线程
            StartBackgroundWriter();
        }
        #endregion
 
        #region 回调注册
        /// <summary>
        /// 注册日志脱敏回调
        /// </summary>
        public void RegisterDesensitize(DesensitizeDelegate func)
        {
            DesensitizeCallback = func;
        }
 
        /// <summary>
        /// 注册告警回调
        /// </summary>
        public void RegisterAlertCallback(AlertCallbackDelegate func)
        {
            AlertCallback = func;
        }
 
        /// <summary>
        /// 触发告警
        /// </summary>
        private void TriggerAlert(string eventType, string msg, Dictionary<string, object> ext)
        {
            if (AlertCallback == null) return;
            try
            {
                AlertCallback.Invoke(eventType, msg, ext);
            }
            catch
            {
            }
        }
        #endregion
 
        #region 对外配置API
        public void SetLogLevel(LogLevel level) => MinLogLevel = level;
        public void SwitchJsonFormat(bool enable) => EnableJsonFormat = enable;
        public void SwitchConsole(bool enable) => EnableConsole = enable;
 
        /// <summary>
        /// 设置单级别采样率 0~1
        /// </summary>
        public void SetSamplingRate(string level, double rate)
        {
            rate = Math.Clamp(rate, 0, 1);
            string lv = level.ToLower();
            if (_levelSamplingRate.ContainsKey(lv))
                _levelSamplingRate[lv] = rate;
        }
 
        /// <summary>
        /// 判断本条日志是否采样放行
        /// </summary>
        private bool NeedSample(string level)
        {
            string lv = level.ToLower();
            double lvRate = _levelSamplingRate.TryGetValue(lv, out var val) ? val : 1;
            double final = GlobalSamplingRate * lvRate;
            if (final >= 1) return true;
            return _random.NextDouble() <= final;
        }
        #endregion
 
        #region 工具静态/私有方法
        /// <summary>
        /// 模块名清洗
        /// </summary>
        private string SafeModuleName(string module)
        {
            if (string.IsNullOrWhiteSpace(module)) return DefaultModule;
            return Regex.Replace(module.Trim(), @"[^\w\-]", "_");
        }
 
        private string GetModuleLogDir(string module)
        {
            string mod = SafeModuleName(module);
            string dir = Path.Combine(LogRoot, mod);
            Directory.CreateDirectory(dir);
            return dir;
        }
 
        private double GetDirTotalMb(string folder, string? filterPrefix = null)
        {
            if (!Directory.Exists(folder)) return 0;
            long totalBytes = 0;
            var files = Directory.GetFiles(folder, "*.*", SearchOption.TopDirectoryOnly);
            foreach (var file in files)
            {
                string name = Path.GetFileName(file);
                if (!name.EndsWith(".log") && !name.EndsWith(".zip")) continue;
                if (filterPrefix != null && !name.StartsWith(filterPrefix + "_")) continue;
                totalBytes += new FileInfo(file).Length;
            }
            return totalBytes / 1024.0 / 1024.0;
        }
 
        private List<string> GetSortedLogFiles(string folder, string? filterPrefix)
        {
            var list = new List<(long mtime, string path)>();
            if (!Directory.Exists(folder)) return new List<string>();
            foreach (var file in Directory.GetFiles(folder))
            {
                string name = Path.GetFileName(file);
                if (!name.EndsWith(".log") && !name.EndsWith(".zip")) continue;
                if (filterPrefix != null && !name.StartsWith(filterPrefix + "_")) continue;
                list.Add((new FileInfo(file).LastWriteTimeUtc.Ticks, file));
            }
            return list.OrderBy(x => x.mtime).Select(x => x.path).ToList();
        }
 
        private bool TryFreeDiskSpace(string? level)
        {
            while (true)
            {
                double usage, limit;
                if (!string.IsNullOrEmpty(level))
                {
                    usage = GetDirTotalMb(LogRoot, level.ToLower());
                    limit = _levelDiskLimit[level.ToLower()];
                    if (usage < limit) return true;
                }
                else
                {
                    usage = GetDirTotalMb(LogRoot);
                    limit = MaxDiskTotalMb;
                    if (usage < limit) return true;
                }
 
                var files = GetSortedLogFiles(LogRoot, level?.ToLower());
                if (files.Count == 0) return false;
                bool deleted = false;
 
                foreach (var fp in files.Where(x => x.EndsWith(".zip")))
                {
                    try
                    {
                        File.Delete(fp);
                        Console.WriteLine($"[LogHelper CLEAN] 删除老旧压缩包:{Path.GetFileName(fp)}");
                        deleted = true;
                        break;
                    }
                    catch { }
                }
                if (deleted) continue;
 
                foreach (var fp in files.Where(x => x.EndsWith(".log")))
                {
                    try
                    {
                        File.Delete(fp);
                        Console.WriteLine($"[LogHelper CLEAN] 删除老旧日志:{Path.GetFileName(fp)}");
                        deleted = true;
                        break;
                    }
                    catch { }
                }
                if (!deleted) return false;
            }
        }
 
        private bool CheckDiskLimit(string level)
        {
            long nowTs = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
            long last = Interlocked.Read(ref _lastDiskCheckTs);
            if (nowTs - last < DiskCheckIntervalSec) return false;
            Interlocked.Exchange(ref _lastDiskCheckTs, nowTs);
 
            string lv = level.ToLower();
            double lvUsage = GetDirTotalMb(LogRoot, lv);
            double lvLimit = _levelDiskLimit[lv];
            double totalUsage = GetDirTotalMb(LogRoot);
            bool over = false;
 
            if (lvUsage >= lvLimit)
            {
                var ext = new Dictionary<string, object>()
                {
                    {"level", level}, {"usage_mb", lvUsage}, {"limit_mb", lvLimit}, {"total_usage", totalUsage}
                };
                TriggerAlert(AlertEvent.DiskOverLimit, $"日志【{level}】分级磁盘占用超限({lvUsage:F2}MB/{lvLimit:F2}MB),开始清理", ext);
                if (!TryFreeDiskSpace(lv)) over = true;
            }
            else if (totalUsage >= MaxDiskTotalMb)
            {
                var ext = new Dictionary<string, object>()
                {
                    {"usage_mb", totalUsage}, {"limit_mb", MaxDiskTotalMb}
                };
                TriggerAlert(AlertEvent.DiskOverLimit, $"日志总磁盘占用超限({totalUsage:F2}MB/{MaxDiskTotalMb:F2}MB),开始清理", ext);
                if (!TryFreeDiskSpace(null)) over = true;
            }
            if (over)
                Console.WriteLine($"[LogHelper FATAL] 磁盘清理后依旧超限,暂停持久化【{level}】日志");
            return over;
        }
 
        private void CleanExpireLogs()
        {
            DateTime expire = DateTime.Now.AddDays(-KeepDays);
            foreach (var file in Directory.GetFiles(LogRoot, "*.*", SearchOption.AllDirectories))
            {
                string name = Path.GetFileName(file);
                if (!name.EndsWith(".log") && !name.EndsWith(".zip")) continue;
                var fi = new FileInfo(file);
                if (fi.LastWriteTime < expire)
                {
                    try { File.Delete(file); } catch { }
                }
            }
        }
 
        private string GetLogPath(string level, string module)
        {
            string dir = GetModuleLogDir(module);
            string date = DateTime.Now.ToString("yyyyMMdd");
            return Path.Combine(dir, $"{level.ToLower()}_{date}.log");
        }
 
        private void SplitLogFile(string logPath)
        {
            lock (_fileLock)
            {
                if (!File.Exists(logPath)) return;
                var fi = new FileInfo(logPath);
                long maxByte = MaxFileSizeMb * 1024 * 1024;
                if (fi.Length < maxByte) return;
 
                string basePath = Path.ChangeExtension(logPath, null);
                string bakPath = $"{basePath}_{DateTime.Now:HHmmss}.log";
                File.Move(logPath, bakPath);
                if (CompressBackupLog)
                    _ = Task.Run(() => CompressFile(bakPath));
            }
        }
 
        private void CompressFile(string srcPath)
        {
            if (!File.Exists(srcPath)) return;
            string zipPath = Path.ChangeExtension(srcPath, ".zip");
            try
            {
                using var fs = new FileStream(zipPath, FileMode.Create);
                using var zip = new ZipArchive(fs, ZipArchiveMode.Create);
                var entry = zip.CreateEntry(Path.GetFileName(srcPath));
                using var entryStream = entry.Open();
                using var srcStream = File.OpenRead(srcPath);
                srcStream.CopyTo(entryStream);
                File.Delete(srcPath);
            }
            catch { }
        }
 
        private static (string fileName, string funcName, int line) GetCallerInfo(int skipFrames)
        {
            var stack = new StackTrace(skipFrames, true);
            var frame = stack.GetFrame(0);
            if (frame == null) return ("unknown", "unknown", 0);
            string file = Path.GetFileName(frame.GetFileName() ?? "unknown");
            string func = frame.GetMethod()?.Name ?? "unknown";
            int line = frame.GetFileLineNumber();
            return (file, func, line);
        }
 
        private static string GetThreadName()
        {
            string name = Thread.CurrentThread.Name;
            return string.IsNullOrWhiteSpace(name) ? $"Thread_{Thread.CurrentThread.ManagedThreadId}" : name;
        }
 
        private (string text, Dictionary<string, object>? json) BuildLogData(string level, string module, string msg, string stack)
        {
            string modSafe = SafeModuleName(module);
            if (DesensitizeCallback != null)
            {
                try
                {
                    msg = DesensitizeCallback.Invoke(level, modSafe, msg);
                }
                catch { }
            }
 
            var now = DateTime.Now;
            string timeStr = now.ToString(TimeFormat);
            double timestamp = now.Ticks / 10000000.0;
            string threadName = GetThreadName();
            var (file, func, lineNo) = GetCallerInfo(4);
 
            if (EnableJsonFormat)
            {
                var jsonObj = new Dictionary<string, object>()
                {
                    ["time"] = timeStr,
                    ["timestamp"] = timestamp,
                    ["level"] = level,
                    ["module"] = modSafe,
                    ["pid"] = _pid,
                    ["thread"] = threadName,
                    ["file"] = file,
                    ["function"] = func,
                    ["line"] = lineNo,
                    ["message"] = msg,
                    ["stack_trace"] = stack
                };
                return (string.Empty, jsonObj);
            }
 
            StringBuilder sb = new();
            sb.Append($"[{timeStr}] [{level}] [MODULE:{modSafe}] [PID:{_pid}] [T:{threadName}] [{func}·{lineNo}行·{file}] {msg}");
            if (!string.IsNullOrEmpty(stack))
                sb.AppendLine().Append(stack);
            sb.AppendLine();
            return (sb.ToString(), null);
        }
        #endregion
 
        #region 队列、写入逻辑(修复buffer空引用)
        private void EnqueueLog(string level, string module, string msg, string stack)
        {
            if (level == "DEBUG" && MinLogLevel < LogLevel.Debug) return;
            if (level == "INFO" && MinLogLevel < LogLevel.Info) return;
            if (level == "ERROR" && MinLogLevel < LogLevel.Error) return;
 
            if (!NeedSample(level)) return;
            var (text, json) = BuildLogData(level, module, msg, stack);
 
            if (EnableConsole)
            {
                string consoleStr;
                if (json != null)
                {
                    consoleStr = JsonSerializer.Serialize(json, new JsonSerializerOptions { WriteIndented = true });
                }
                else
                {
                    consoleStr = text.TrimEnd('\n', '\r');
                }
 
                if (_colorMap.TryGetValue(level, out var color))
                {
                    Console.ForegroundColor = color;
                    Console.WriteLine(consoleStr);
                    Console.ResetColor();
                }
                else
                {
                    Console.WriteLine(consoleStr);
                }
            }
 
            var item = new LogItem()
            {
                Level = level.ToLower(),
                Module = module,
                TextData = text,
                JsonData = json,
                StackTrace = stack
            };
 
            if (_logQueue.Count < QueueMaxSize)
                _logQueue.Enqueue(item);
            else
                Console.WriteLine("[LogHelper WARN] 队列已满,丢弃持久化日志");
        }
 
        private void StartBackgroundWriter()
        {
            _writeTask = Task.Run(async () =>
            {
                // 【修复】循环内每次直接初始化字典,杜绝null
                Dictionary<string, List<LogItem>> buffer = new Dictionary<string, List<LogItem>>();
                var ticker = new PeriodicTimer(TimeSpan.FromMilliseconds(800));
                while (!_cts.Token.IsCancellationRequested)
                {
                    try
                    {
                        while (_logQueue.TryDequeue(out var item))
                        {
                            string key = $"{item.Level}|{item.Module}";
                            // 安全判断,不会空引用
                            if (!buffer.ContainsKey(key))
                                buffer[key] = new List<LogItem>();
 
                            buffer[key].Add(item);
                            if (buffer[key].Count >= BatchWriteCount)
                                FlushBuffer(buffer, key);
                        }
 
                        if (await ticker.WaitForNextTickAsync(_cts.Token))
                            FlushAllBuffer(buffer);
                    }
                    catch
                    {
                    }
                }
                FlushAllBuffer(buffer);
            }, _cts.Token);
        }
 
        private void FlushBuffer(Dictionary<string, List<LogItem>> buffer, string key)
        {
            if (!buffer.TryGetValue(key, out var items) || items.Count == 0) return;
            string[] parts = key.Split('|');
            string level = parts[0];
            string module = parts[1];
 
            if (CheckDiskLimit(level))
            {
                buffer.Remove(key);
                return;
            }
 
            string logPath = GetLogPath(level, module);
            SplitLogFile(logPath);
 
            StringBuilder writeSb = new StringBuilder();
            foreach (var it in items)
            {
                if (it.JsonData != null)
                {
                    string jsonLine = JsonSerializer.Serialize(it.JsonData);
                    writeSb.AppendLine(jsonLine);
                }
                else
                {
                    writeSb.Append(it.TextData);
                }
            }
 
            try
            {
                File.AppendAllText(logPath, writeSb.ToString(), Encoding.UTF8);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[LogHelper] 写入失败 level:{level} module:{module} err:{ex.Message}");
            }
            buffer.Remove(key);
 
            DateTime today = DateTime.Now.Date;
            if (_lastCleanDay != today)
            {
                CleanExpireLogs();
                _lastCleanDay = today;
            }
        }
 
        private void FlushAllBuffer(Dictionary<string, List<LogItem>> buffer)
        {
            var keys = buffer.Keys.ToList();
            foreach (var k in keys)
                FlushBuffer(buffer, k);
        }
        #endregion
 
        #region 对外日志快捷方法
        public void Debug(string msg) => EnqueueLog("DEBUG", DefaultModule, msg, string.Empty);
        public void Info(string msg) => EnqueueLog("INFO", DefaultModule, msg, string.Empty);
        public void Error(string msg, Exception? ex = null)
        {
            string stack = ex == null ? string.Empty : ex.ToString();
            EnqueueLog("ERROR", DefaultModule, msg, stack);
        }
 
        public void DebugMod(string module, string msg) => EnqueueLog("DEBUG", module, msg, string.Empty);
        public void InfoMod(string module, string msg) => EnqueueLog("INFO", module, msg, string.Empty);
        public void ErrorMod(string module, string msg, Exception? ex = null)
        {
            string stack = ex == null ? string.Empty : ex.ToString();
            EnqueueLog("ERROR", module, msg, stack);
        }
 
        /// <summary>
        /// 优雅关闭
        /// </summary>
        public async Task ShutdownAsync()
        {
            _cts.Cancel();
            if (_writeTask != null)
                await _writeTask;
        }
        #endregion
    }
 
    /// <summary>
    /// 全局静态快捷入口
    /// </summary>
    public static class Log
    {
        public static void Debug(string msg) => LogHelper.Instance.Debug(msg);
        public static void Info(string msg) => LogHelper.Instance.Info(msg);
        public static void Error(string msg, Exception? ex = null) => LogHelper.Instance.Error(msg, ex);
 
        public static void DebugMod(string module, string msg) => LogHelper.Instance.DebugMod(module, msg);
        public static void InfoMod(string module, string msg) => LogHelper.Instance.InfoMod(module, msg);
        public static void ErrorMod(string module, string msg, Exception? ex = null) => LogHelper.Instance.ErrorMod(module, msg, ex);
 
        public static void RegisterDesensitize(DesensitizeDelegate func) => LogHelper.Instance.RegisterDesensitize(func);
        public static void RegisterAlertCallback(AlertCallbackDelegate func) => LogHelper.Instance.RegisterAlertCallback(func);
        public static void SetSamplingRate(string level, double rate) => LogHelper.Instance.SetSamplingRate(level, rate);
        public static void SwitchJsonFormat(bool enable) => LogHelper.Instance.SwitchJsonFormat(enable);
        public static void SwitchConsole(bool enable) => LogHelper.Instance.SwitchConsole(enable);
        public static Task ShutdownAsync() => LogHelper.Instance.ShutdownAsync();
    }
}

修改一下:

cs 复制代码
/*
 # encoding: utf-8
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述: Algorithm
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : vs2026 c# .net 10
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/07/05 23:16
# User      :  geovindu
# Product   : Visual Studio 2026
# Project   : CSharpAlgorithms
# File      : LogLevel.cs
 
 */
 
using System;
using System.Collections.Generic;
using System.Text;
 
namespace CSharpAlgorithms.common
{
    /// <summary>
    /// 日志级别
    /// </summary>
    public enum LogLevel
    {
        Off = 0,
        Error = 1,
        Info = 2,
        Debug = 3
    }
}
 
 
 
 
/*
 # encoding: utf-8
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述: Algorithm
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : vs2026 c# .net 10
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/07/05 23:16
# User      :  geovindu
# Product   : Visual Studio 2026
# Project   : CSharpAlgorithms
# File      : AlertCallbackDelegate.cs
 
 */
 
using System;
using System.Collections.Generic;
using System.Text;
 
namespace CSharpAlgorithms.common
{
    /// <summary>
    /// 告警回调委托
    /// </summary>
    public delegate void AlertCallbackDelegate(string eventType, string message, Dictionary<string, object> ext);
}
 
 
 
/*
 # encoding: utf-8
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述: Algorithm
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : vs2026 c# .net 10
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/07/05 23:16
# User      :  geovindu
# Product   : Visual Studio 2026
# Project   : CSharpAlgorithms
# File      : DesensitizeDelegate.cs
 
 */
 
using System;
using System.Collections.Generic;
using System.Text;
 
namespace CSharpAlgorithms.common
{
    /// <summary>
    /// 脱敏委托:级别、模块、原始消息 -> 脱敏后消息
    /// </summary>
    public delegate string DesensitizeDelegate(string level, string module, string msg);
}
 
 
 
/*
 # encoding: utf-8
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述: Algorithm
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : vs2026 c# .net 10
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/07/05 23:16
# User      :  geovindu
# Product   : Visual Studio 2026
# Project   : CSharpAlgorithms
# File      : AlertEvent.cs
 
 */
 
using System;
using System.Collections.Generic;
using System.Text;
 
namespace CSharpAlgorithms.common
{
 
    /// <summary>
    /// 告警常量
    /// </summary>
    public static class AlertEvent
    {
        public const string DiskOverLimit = "DISK_OVER_LIMIT";
        public const string QueueBlocked = "QUEUE_BLOCKED";
    }
}
 
 
/*
 # encoding: utf-8
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述: Algorithm
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : vs2026 c# .net 10
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/07/05 23:16
# User      :  geovindu
# Product   : Visual Studio 2026
# Project   : CSharpAlgorithms
# File      : LogItem.cs
 
 */
 
using System;
using System.Collections.Generic;
using System.Text;
 
namespace CSharpAlgorithms.common
{
    /// <summary>
    /// 日志队列单元
    /// </summary>
    internal sealed class LogItem
    {
        public string Level { get; set; } = string.Empty;
        public string Module { get; set; } = string.Empty;
        public string TextData { get; set; } = string.Empty;
        public Dictionary<string, object>? JsonData { get; set; }
        public string StackTrace { get; set; } = string.Empty;
    }
}
 
 
 
/*
 # encoding: utf-8
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述: Algorithm
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : vs2026 c# .net 10
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/07/05 23:16
# User      :  geovindu
# Product   : Visual Studio 2026
# Project   : CSharpAlgorithms
# File      : Log.cs
 
 */
 
 
using System;
using System.Collections.Generic;
using System.Text;
 
namespace CSharpAlgorithms.common
{
 
 
    /// <summary>
    /// 全局静态快捷入口
    /// </summary>
    public static class Log
    {
        public static void Debug(string msg) => LogHelper.Instance.Debug(msg);
        public static void Info(string msg) => LogHelper.Instance.Info(msg);
        public static void Error(string msg, Exception? ex = null) => LogHelper.Instance.Error(msg, ex);
 
        public static void DebugMod(string module, string msg) => LogHelper.Instance.DebugMod(module, msg);
        public static void InfoMod(string module, string msg) => LogHelper.Instance.InfoMod(module, msg);
        public static void ErrorMod(string module, string msg, Exception? ex = null) => LogHelper.Instance.ErrorMod(module, msg, ex);
 
        public static void RegisterDesensitize(DesensitizeDelegate func) => LogHelper.Instance.RegisterDesensitize(func);
        public static void RegisterAlertCallback(AlertCallbackDelegate func) => LogHelper.Instance.RegisterAlertCallback(func);
        public static void SetSamplingRate(string level, double rate) => LogHelper.Instance.SetSamplingRate(level, rate);
        public static void SwitchJsonFormat(bool enable) => LogHelper.Instance.SwitchJsonFormat(enable);
        public static void SwitchConsole(bool enable) => LogHelper.Instance.SwitchConsole(enable);
        public static Task ShutdownAsync() => LogHelper.Instance.ShutdownAsync();
    }
}
 
 
/*
 # encoding: utf-8
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述: Algorithm
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : vs2026 c# .net 10
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/07/05 23:16
# User      :  geovindu
# Product   : Visual Studio 2026
# Project   : CSharpAlgorithms
# File      : LogHelper.cs
 
 */
 
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
using System.IO.Compression;
 
namespace CSharpAlgorithms.common
{
 
 
    /// <summary>
    /// .NET10 高性能日志核心类,修复空引用异常
    /// </summary>
    public sealed class LogHelper
    {
        #region 单例 严格顺序初始化
        private static readonly Lazy<LogHelper> _lazyInstance = new Lazy<LogHelper>(() => new LogHelper(), LazyThreadSafetyMode.ExecutionAndPublication);
        public static LogHelper Instance => _lazyInstance.Value;
 
        // 【强制提前实例化】所有成员字段直接初始化,杜绝null
        private readonly ConcurrentQueue<LogItem> _logQueue = new ConcurrentQueue<LogItem>();
        private readonly CancellationTokenSource _cts = new CancellationTokenSource();
        private readonly object _fileLock = new object();
        private readonly Random _random = new Random();
        private readonly Dictionary<string, ConsoleColor> _colorMap = new Dictionary<string, ConsoleColor>();
 
        /// <summary>
        ///
        /// </summary>
        private readonly Dictionary<string, double> _levelDiskLimit = new()
        {
            { "debug", 150 },
            { "info", 250 },
            { "error", 100 }
        };
        /// <summary>
        ///
        /// </summary>
        private readonly Dictionary<string, double> _levelSamplingRate = new()
        {
            { "debug", 1.0 },
            { "info", 1.0 },
            { "error", 1.0 }
        };
 
        private Task? _writeTask;
        private DateTime _lastCleanDay = DateTime.MinValue;
        private long _lastDiskCheckTs;
        private readonly int _pid = Environment.ProcessId;
 
        #region 配置属性
        public string LogRoot { get; private set; }
        public string DefaultModule { get; set; } = "default";
        public int KeepDays { get; set; } = 7;
        public long MaxFileSizeMb { get; set; } = 10;
        public int QueueMaxSize { get; set; } = 10000;
        public int QueueAlertThreshold { get; set; } = 7000;
        public int BatchWriteCount { get; set; } = 10;
 
        public bool EnableConsole { get; set; } = true;
        public bool EnableJsonFormat { get; set; } = false;
        public bool CompressBackupLog { get; set; } = true;
 
        public double MaxDiskTotalMb { get; set; } = 500;
        public double GlobalSamplingRate { get; set; } = 1.0;
        public int DiskCheckIntervalSec { get; set; } = 60;
        public string TimeFormat { get; set; } = "yyyy-MM-dd HH:mm:ss";
        public LogLevel MinLogLevel { get; set; } = LogLevel.Debug;
 
        // 回调
        public DesensitizeDelegate? DesensitizeCallback { get; private set; }
        public AlertCallbackDelegate? AlertCallback { get; private set; }
        #endregion
 
        // 私有构造:仅赋值、初始化,最后再启动线程
        private LogHelper()
        {
            LogRoot = Path.Combine(Directory.GetCurrentDirectory(), "Logs");
            Directory.CreateDirectory(LogRoot);
 
            // 颜色初始化
            _colorMap["DEBUG"] = ConsoleColor.Blue;
            _colorMap["INFO"] = ConsoleColor.Green;
            _colorMap["ERROR"] = ConsoleColor.Red;
 
            // Windows控制台编码兼容
            if (OperatingSystem.IsWindows() && !Console.IsOutputRedirected)
            {
                try
                {
                    Console.OutputEncoding = Encoding.UTF8;
                }
                catch
                {
                    EnableConsole = true;
                }
            }
 
            // 【关键点】所有变量初始化完毕,最后再启动后台写入线程
            StartBackgroundWriter();
        }
        #endregion
 
        #region 回调注册
        /// <summary>
        /// 注册日志脱敏回调
        /// </summary>
        public void RegisterDesensitize(DesensitizeDelegate func)
        {
            DesensitizeCallback = func;
        }
 
        /// <summary>
        /// 注册告警回调
        /// </summary>
        public void RegisterAlertCallback(AlertCallbackDelegate func)
        {
            AlertCallback = func;
        }
 
        /// <summary>
        /// 触发告警
        /// </summary>
        private void TriggerAlert(string eventType, string msg, Dictionary<string, object> ext)
        {
            if (AlertCallback == null) return;
            try
            {
                AlertCallback.Invoke(eventType, msg, ext);
            }
            catch
            {
            }
        }
        #endregion
 
        #region 对外配置API
        public void SetLogLevel(LogLevel level) => MinLogLevel = level;
        public void SwitchJsonFormat(bool enable) => EnableJsonFormat = enable;
        public void SwitchConsole(bool enable) => EnableConsole = enable;
 
        /// <summary>
        /// 设置单级别采样率 0~1
        /// </summary>
        public void SetSamplingRate(string level, double rate)
        {
            rate = Math.Clamp(rate, 0, 1);
            string lv = level.ToLower();
            if (_levelSamplingRate.ContainsKey(lv))
                _levelSamplingRate[lv] = rate;
        }
 
        /// <summary>
        /// 判断本条日志是否采样放行
        /// </summary>
        private bool NeedSample(string level)
        {
            string lv = level.ToLower();
            double lvRate = _levelSamplingRate.TryGetValue(lv, out var val) ? val : 1;
            double final = GlobalSamplingRate * lvRate;
            if (final >= 1) return true;
            return _random.NextDouble() <= final;
        }
        #endregion
 
        #region 工具静态/私有方法
        /// <summary>
        /// 模块名清洗
        /// </summary>
        private string SafeModuleName(string module)
        {
            if (string.IsNullOrWhiteSpace(module)) return DefaultModule;
            return Regex.Replace(module.Trim(), @"[^\w\-]", "_");
        }
 
        private string GetModuleLogDir(string module)
        {
            string mod = SafeModuleName(module);
            string dir = Path.Combine(LogRoot, mod);
            Directory.CreateDirectory(dir);
            return dir;
        }
 
        private double GetDirTotalMb(string folder, string? filterPrefix = null)
        {
            if (!Directory.Exists(folder)) return 0;
            long totalBytes = 0;
            var files = Directory.GetFiles(folder, "*.*", SearchOption.TopDirectoryOnly);
            foreach (var file in files)
            {
                string name = Path.GetFileName(file);
                if (!name.EndsWith(".log") && !name.EndsWith(".zip")) continue;
                if (filterPrefix != null && !name.StartsWith(filterPrefix + "_")) continue;
                totalBytes += new FileInfo(file).Length;
            }
            return totalBytes / 1024.0 / 1024.0;
        }
 
        private List<string> GetSortedLogFiles(string folder, string? filterPrefix)
        {
            var list = new List<(long mtime, string path)>();
            if (!Directory.Exists(folder)) return new List<string>();
            foreach (var file in Directory.GetFiles(folder))
            {
                string name = Path.GetFileName(file);
                if (!name.EndsWith(".log") && !name.EndsWith(".zip")) continue;
                if (filterPrefix != null && !name.StartsWith(filterPrefix + "_")) continue;
                list.Add((new FileInfo(file).LastWriteTimeUtc.Ticks, file));
            }
            return list.OrderBy(x => x.mtime).Select(x => x.path).ToList();
        }
 
        private bool TryFreeDiskSpace(string? level)
        {
            while (true)
            {
                double usage, limit;
                if (!string.IsNullOrEmpty(level))
                {
                    usage = GetDirTotalMb(LogRoot, level.ToLower());
                    limit = _levelDiskLimit[level.ToLower()];
                    if (usage < limit) return true;
                }
                else
                {
                    usage = GetDirTotalMb(LogRoot);
                    limit = MaxDiskTotalMb;
                    if (usage < limit) return true;
                }
 
                var files = GetSortedLogFiles(LogRoot, level?.ToLower());
                if (files.Count == 0) return false;
                bool deleted = false;
 
                foreach (var fp in files.Where(x => x.EndsWith(".zip")))
                {
                    try
                    {
                        File.Delete(fp);
                        Console.WriteLine($"[LogHelper CLEAN] 删除老旧压缩包:{Path.GetFileName(fp)}");
                        deleted = true;
                        break;
                    }
                    catch { }
                }
                if (deleted) continue;
 
                foreach (var fp in files.Where(x => x.EndsWith(".log")))
                {
                    try
                    {
                        File.Delete(fp);
                        Console.WriteLine($"[LogHelper CLEAN] 删除老旧日志:{Path.GetFileName(fp)}");
                        deleted = true;
                        break;
                    }
                    catch { }
                }
                if (!deleted) return false;
            }
        }
 
        private bool CheckDiskLimit(string level)
        {
            long nowTs = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
            long last = Interlocked.Read(ref _lastDiskCheckTs);
            if (nowTs - last < DiskCheckIntervalSec) return false;
            Interlocked.Exchange(ref _lastDiskCheckTs, nowTs);
 
            string lv = level.ToLower();
            double lvUsage = GetDirTotalMb(LogRoot, lv);
            double lvLimit = _levelDiskLimit[lv];
            double totalUsage = GetDirTotalMb(LogRoot);
            bool over = false;
 
            if (lvUsage >= lvLimit)
            {
                var ext = new Dictionary<string, object>()
                {
                    {"level", level}, {"usage_mb", lvUsage}, {"limit_mb", lvLimit}, {"total_usage", totalUsage}
                };
                TriggerAlert(AlertEvent.DiskOverLimit, $"日志【{level}】分级磁盘占用超限({lvUsage:F2}MB/{lvLimit:F2}MB),开始清理", ext);
                if (!TryFreeDiskSpace(lv)) over = true;
            }
            else if (totalUsage >= MaxDiskTotalMb)
            {
                var ext = new Dictionary<string, object>()
                {
                    {"usage_mb", totalUsage}, {"limit_mb", MaxDiskTotalMb}
                };
                TriggerAlert(AlertEvent.DiskOverLimit, $"日志总磁盘占用超限({totalUsage:F2}MB/{MaxDiskTotalMb:F2}MB),开始清理", ext);
                if (!TryFreeDiskSpace(null)) over = true;
            }
            if (over)
                Console.WriteLine($"[LogHelper FATAL] 磁盘清理后依旧超限,暂停持久化【{level}】日志");
            return over;
        }
 
        private void CleanExpireLogs()
        {
            DateTime expire = DateTime.Now.AddDays(-KeepDays);
            foreach (var file in Directory.GetFiles(LogRoot, "*.*", SearchOption.AllDirectories))
            {
                string name = Path.GetFileName(file);
                if (!name.EndsWith(".log") && !name.EndsWith(".zip")) continue;
                var fi = new FileInfo(file);
                if (fi.LastWriteTime < expire)
                {
                    try { File.Delete(file); } catch { }
                }
            }
        }
 
        private string GetLogPath(string level, string module)
        {
            string dir = GetModuleLogDir(module);
            string date = DateTime.Now.ToString("yyyyMMdd");
            return Path.Combine(dir, $"{level.ToLower()}_{date}.log");
        }
 
        private void SplitLogFile(string logPath)
        {
            lock (_fileLock)
            {
                if (!File.Exists(logPath)) return;
                var fi = new FileInfo(logPath);
                long maxByte = MaxFileSizeMb * 1024 * 1024;
                if (fi.Length < maxByte) return;
 
                string basePath = Path.ChangeExtension(logPath, null);
                string bakPath = $"{basePath}_{DateTime.Now:HHmmss}.log";
                File.Move(logPath, bakPath);
                if (CompressBackupLog)
                    _ = Task.Run(() => CompressFile(bakPath));
            }
        }
 
        private void CompressFile(string srcPath)
        {
            if (!File.Exists(srcPath)) return;
            string zipPath = Path.ChangeExtension(srcPath, ".zip");
            try
            {
                using var fs = new FileStream(zipPath, FileMode.Create);
                using var zip = new ZipArchive(fs, ZipArchiveMode.Create);
                var entry = zip.CreateEntry(Path.GetFileName(srcPath));
                using var entryStream = entry.Open();
                using var srcStream = File.OpenRead(srcPath);
                srcStream.CopyTo(entryStream);
                File.Delete(srcPath);
            }
            catch { }
        }
 
        private static (string fileName, string funcName, int line) GetCallerInfo(int skipFrames)
        {
            var stack = new StackTrace(skipFrames, true);
            var frame = stack.GetFrame(0);
            if (frame == null) return ("unknown", "unknown", 0);
            string file = Path.GetFileName(frame.GetFileName() ?? "unknown");
            string func = frame.GetMethod()?.Name ?? "unknown";
            int line = frame.GetFileLineNumber();
            return (file, func, line);
        }
 
        private static string GetThreadName()
        {
            string name = Thread.CurrentThread.Name;
            return string.IsNullOrWhiteSpace(name) ? $"Thread_{Thread.CurrentThread.ManagedThreadId}" : name;
        }
 
        private (string text, Dictionary<string, object>? json) BuildLogData(string level, string module, string msg, string stack)
        {
            string modSafe = SafeModuleName(module);
            if (DesensitizeCallback != null)
            {
                try
                {
                    msg = DesensitizeCallback.Invoke(level, modSafe, msg);
                }
                catch { }
            }
 
            var now = DateTime.Now;
            string timeStr = now.ToString(TimeFormat);
            double timestamp = now.Ticks / 10000000.0;
            string threadName = GetThreadName();
            var (file, func, lineNo) = GetCallerInfo(4);
 
            if (EnableJsonFormat)
            {
                var jsonObj = new Dictionary<string, object>()
                {
                    ["time"] = timeStr,
                    ["timestamp"] = timestamp,
                    ["level"] = level,
                    ["module"] = modSafe,
                    ["pid"] = _pid,
                    ["thread"] = threadName,
                    ["file"] = file,
                    ["function"] = func,
                    ["line"] = lineNo,
                    ["message"] = msg,
                    ["stack_trace"] = stack
                };
                return (string.Empty, jsonObj);
            }
 
            StringBuilder sb = new();
            sb.Append($"[{timeStr}] [{level}] [MODULE:{modSafe}] [PID:{_pid}] [T:{threadName}] [{func}·{lineNo}行·{file}] {msg}");
            if (!string.IsNullOrEmpty(stack))
                sb.AppendLine().Append(stack);
            sb.AppendLine();
            return (sb.ToString(), null);
        }
        #endregion
 
        #region 队列、写入逻辑(修复buffer空引用)
        private void EnqueueLog(string level, string module, string msg, string stack)
        {
            if (level == "DEBUG" && MinLogLevel < LogLevel.Debug) return;
            if (level == "INFO" && MinLogLevel < LogLevel.Info) return;
            if (level == "ERROR" && MinLogLevel < LogLevel.Error) return;
 
            if (!NeedSample(level)) return;
            var (text, json) = BuildLogData(level, module, msg, stack);
 
            if (EnableConsole)
            {
                string consoleStr;
                if (json != null)
                {
                    consoleStr = JsonSerializer.Serialize(json, new JsonSerializerOptions { WriteIndented = true });
                }
                else
                {
                    consoleStr = text.TrimEnd('\n', '\r');
                }
 
                if (_colorMap.TryGetValue(level, out var color))
                {
                    Console.ForegroundColor = color;
                    Console.WriteLine(consoleStr);
                    Console.ResetColor();
                }
                else
                {
                    Console.WriteLine(consoleStr);
                }
            }
 
            var item = new LogItem()
            {
                Level = level.ToLower(),
                Module = module,
                TextData = text,
                JsonData = json,
                StackTrace = stack
            };
 
            if (_logQueue.Count < QueueMaxSize)
                _logQueue.Enqueue(item);
            else
                Console.WriteLine("[LogHelper WARN] 队列已满,丢弃持久化日志");
        }
 
        private void StartBackgroundWriter()
        {
            _writeTask = Task.Run(async () =>
            {
                // 【修复】循环内每次直接初始化字典,杜绝null
                Dictionary<string, List<LogItem>> buffer = new Dictionary<string, List<LogItem>>();
                var ticker = new PeriodicTimer(TimeSpan.FromMilliseconds(800));
                while (!_cts.Token.IsCancellationRequested)
                {
                    try
                    {
                        while (_logQueue.TryDequeue(out var item))
                        {
                            string key = $"{item.Level}|{item.Module}";
                            // 安全判断,不会空引用
                            if (!buffer.ContainsKey(key))
                                buffer[key] = new List<LogItem>();
 
                            buffer[key].Add(item);
                            if (buffer[key].Count >= BatchWriteCount)
                                FlushBuffer(buffer, key);
                        }
 
                        if (await ticker.WaitForNextTickAsync(_cts.Token))
                            FlushAllBuffer(buffer);
                    }
                    catch
                    {
                    }
                }
                FlushAllBuffer(buffer);
            }, _cts.Token);
        }
 
        private void FlushBuffer(Dictionary<string, List<LogItem>> buffer, string key)
        {
            if (!buffer.TryGetValue(key, out var items) || items.Count == 0) return;
            string[] parts = key.Split('|');
            string level = parts[0];
            string module = parts[1];
 
            if (CheckDiskLimit(level))
            {
                buffer.Remove(key);
                return;
            }
 
            string logPath = GetLogPath(level, module);
            SplitLogFile(logPath);
 
            StringBuilder writeSb = new StringBuilder();
            foreach (var it in items)
            {
                if (it.JsonData != null)
                {
                    string jsonLine = JsonSerializer.Serialize(it.JsonData);
                    writeSb.AppendLine(jsonLine);
                }
                else
                {
                    writeSb.Append(it.TextData);
                }
            }
 
            try
            {
                File.AppendAllText(logPath, writeSb.ToString(), Encoding.UTF8);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[LogHelper] 写入失败 level:{level} module:{module} err:{ex.Message}");
            }
            buffer.Remove(key);
 
            DateTime today = DateTime.Now.Date;
            if (_lastCleanDay != today)
            {
                CleanExpireLogs();
                _lastCleanDay = today;
            }
        }
 
        private void FlushAllBuffer(Dictionary<string, List<LogItem>> buffer)
        {
            var keys = buffer.Keys.ToList();
            foreach (var k in keys)
                FlushBuffer(buffer, k);
        }
        #endregion
 
        #region 对外日志快捷方法
        public void Debug(string msg) => EnqueueLog("DEBUG", DefaultModule, msg, string.Empty);
        public void Info(string msg) => EnqueueLog("INFO", DefaultModule, msg, string.Empty);
        public void Error(string msg, Exception? ex = null)
        {
            string stack = ex == null ? string.Empty : ex.ToString();
            EnqueueLog("ERROR", DefaultModule, msg, stack);
        }
 
        public void DebugMod(string module, string msg) => EnqueueLog("DEBUG", module, msg, string.Empty);
        public void InfoMod(string module, string msg) => EnqueueLog("INFO", module, msg, string.Empty);
        public void ErrorMod(string module, string msg, Exception? ex = null)
        {
            string stack = ex == null ? string.Empty : ex.ToString();
            EnqueueLog("ERROR", module, msg, stack);
        }
 
        /// <summary>
        /// 优雅关闭
        /// </summary>
        public async Task ShutdownAsync()
        {
            _cts.Cancel();
            if (_writeTask != null)
                await _writeTask;
        }
        #endregion
    }
}

还需要完善下。

调用:

cs 复制代码
// 脱敏注册
 Log.RegisterDesensitize((level, module, msg) =>
 {
     return Regex.Replace(msg, @"1[3-9]\d{9}", "1*******");
 });
 
 // 告警注册
 Log.RegisterAlertCallback((type, msg, ext) =>
 {
     Console.WriteLine($"\n【告警】{type}:{msg}");
 });
 
 Log.SetSamplingRate("debug", 0.2);
 Log.Debug("手机号13900001111测试脱敏日志");
 Log.DebugMod("Server", "手机号13900001111测试脱敏日志");
 Log.Info("系统启动成功");
 try
 {
     int i = 0;
     int k = 1 / i;
 }
 catch (Exception e)
 {
     Log.Error("计算异常", e);
 }
 
 // 程序退出等待落地
// await Log.ShutdownAsync();
相关推荐
奈斯先生Vector1 小时前
大模型 Agentic Workflow 架构解构:异构 API 调度与 Token 路由的多模态系统设计
开发语言·前端·架构·prompt·aigc·音视频
冻柠檬飞冰走茶1 小时前
PTA基础编程题目集 7-20 打印九九口诀表(C语言实现)
c语言·开发语言·数据结构·算法
caishenzhibiao1 小时前
顺势捕猎者副图 同花顺期货通指标
java·c语言·c#
fliter1 小时前
Go设计取舍之一: goroutine 为什么保持匿名、无状态
后端
fliter1 小时前
Go设计取舍之二: maps.Keys和Values为什么返回迭代器
后端
热心市民lcj2 小时前
Spring Boot 整合 Caffeine 本地缓存实战
spring boot·后端·缓存
Revolution612 小时前
Nest.js 是什么:怎样用它写出第一个后端接口
后端·node.js·nestjs
Richard.Wong2 小时前
qt生成dll供C#调用
开发语言·qt
aiopencode2 小时前
SwiftUI Introspect生产环境完全指南:为什么它是安全可靠的选择
后端·ios