using System; using System.Collections.Concurrent; using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using GeneratorCode.Configure; using JetBrains.Annotations; namespace GeneratorCode.Logs { public enum NLogLevel { Fatal = 0, Crash, Error, Warring, Info, Debug, MaxLevel } public enum CacheOptMode { Drop = 0, Replease } public class NLogConfig { public delegate void LogCfgChangedHandle(); private readonly object _cfgLock = new object(); public NLogConfig() { LogConfig(); NConfig.OnConfigChanged += () => { LogCfgChanged(); }; } public bool LogEnable { get; set; } public NLogLevel LogLevel { get; set; } public NLogLevel DefaultLevel { get; set; } public bool AsyncMode { get; set; } public bool ForceNewLine { get; set; } public bool ShowDate { get; set; } public bool ShowTime { get; set; } public bool ShowMSec { get; set; } public bool ShowLevel { get; set; } public bool ShowCodeFile { get; set; } public bool ShowFunction { get; set; } public bool ShowCodeLine { get; set; } public bool EnConsole { get; set; } public bool EnTrace { get; set; } public bool EnDebug { get; set; } public bool EnFile { get; set; } public int MaxItemsCache { get; set; } public CacheOptMode CacheMode { get; set; } public int SleepTime { get; set; } public int NumOutItems { get; set; } public string DirPath { get; set; } public string FileNamePre { get; set; } public bool EnSplitLog { get; set; } public bool SplitByData { get; set; } public int SplitBySize { get; set; } public bool RoolbackFile { get; set; } public int MaxFileNum { get; set; } public static event LogCfgChangedHandle OnLogCfgChanged; protected static void LogCfgChanged() { OnLogCfgChanged?.Invoke(); } public void LogConfig() { lock (_cfgLock) { LogEnable = NConfig.GetCfgValue("LogGlobal", "LogEnable", false); LogLevel = (NLogLevel) NConfig.GetCfgValue("LogGlobal", "LogLevel", (int) NLogLevel.MaxLevel); DefaultLevel = (NLogLevel) NConfig.GetCfgValue("LogGlobal", "DefaultLogLevel", (int) NLogLevel.Info); AsyncMode = NConfig.GetCfgValue("LogGlobal", "AsyncMode", false); ForceNewLine = NConfig.GetCfgValue("LogGlobal", "AutoForceNewLine", false); ShowDate = NConfig.GetCfgValue("LogFormat", "ShowDate", true); ShowTime = NConfig.GetCfgValue("LogFormat", "ShowTime", true); ShowMSec = NConfig.GetCfgValue("LogFormat", "ShowMSec", true); ShowLevel = NConfig.GetCfgValue("LogFormat", "ShowLevel", true); ShowCodeFile = NConfig.GetCfgValue("LogFormat", "ShowCodeFile", true); ShowFunction = NConfig.GetCfgValue("LogFormat", "ShowFunction", true); ShowCodeLine = NConfig.GetCfgValue("LogFormat", "ShowCodeLine", true); EnConsole = NConfig.GetCfgValue("LogOutput", "Console", true); EnTrace = NConfig.GetCfgValue("LogOutput", "Trace", false); EnDebug = NConfig.GetCfgValue("LogOutput", "Debug", true); EnFile = NConfig.GetCfgValue("LogOutput", "File", false); if (AsyncMode) { MaxItemsCache = NConfig.GetCfgValue("AsyncLogSetting", "MaxItemsCache", 0); if (MaxItemsCache == 0) MaxItemsCache = int.MaxValue; CacheMode = (CacheOptMode) NConfig.GetCfgValue("AsyncLogSetting", "CacheFullOpts", (int) CacheOptMode.Drop); NumOutItems = NConfig.GetCfgValue("AsyncLogSetting", "NumItemsOutEachTime", 10); } SleepTime = NConfig.GetCfgValue("AsyncLogSetting", "ThreadSleep", 10); if (EnFile) { DirPath = NConfig.GetCfgValue("FileLogSetting", "Path", @""); if (DirPath == "" || !Directory.Exists(DirPath) || DirPath == @".\") DirPath = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName) + @"\"; if (!Directory.Exists(DirPath)) Directory.CreateDirectory(DirPath); FileNamePre = NConfig.GetCfgValue("FileLogSetting", "FileNamePrefix", ""); EnSplitLog = NConfig.GetCfgValue("FileLogSetting", "AutoSplitFile", true); if (EnSplitLog) { SplitByData = NConfig.GetCfgValue("SplitFiles", "SplitByDate", true); SplitBySize = NConfig.GetCfgValue("SplitFiles", "SplitBySize", 4); RoolbackFile = NConfig.GetCfgValue("SplitFiles", "FileNameRollback", true); MaxFileNum = NConfig.GetCfgValue("SplitFiles", "MaxFileNameNum", 10); } } } } } public class NLogItem { public NLogItem(NLogLevel level = NLogLevel.Debug, [NotNull] string logContent = "", [NotNull] string fileName = "", [NotNull] string funName = "", int lineNo = 0, DateTime? dt = null) { if (dt == null) LogStamp = DateTime.Now; else LogStamp = (DateTime) dt; LogLevel = level; LogContent = logContent; CodeFile = fileName; CodeFunction = funName; CodeLine = lineNo; } public DateTime LogStamp { get; set; } public NLogLevel LogLevel { get; set; } public string LogContent { get; set; } public string CodeFile { get; set; } public int CodeLine { get; set; } public string CodeFunction { get; set; } } public static class NLog { private static NLogConfig _logCfg; private static readonly ConcurrentQueue _logItemCollection = new ConcurrentQueue(); private static readonly object _logOutputLock = new object(); private static string _logFileName = ""; private static uint _logFileNum; private static StreamWriter _logSw; private static void CreateLogFileHead() { _logSw?.WriteLine("FileName: " + _logFileName); _logSw?.WriteLine("CreateTime: " + string.Format("{0:yyyy-MM-dd HH:mm:ss}", DateTime.Now)); _logSw?.WriteLine("Program: " + Process.GetCurrentProcess().MainModule.FileName); _logSw?.WriteLine("PID: " + Process.GetCurrentProcess().Id); _logSw?.WriteLine("Split Count: " + (_logFileNum - 1)); _logSw?.WriteLine("--------------------------------------------------"); _logSw?.Flush(); } private static void ConfigInit() { if (_logCfg.EnFile) { _logFileName = string.Format("{0}{1}[{3:yyyy-MM-dd_HH-mm}][{4:d}]_{2}", _logCfg.DirPath, _logCfg.FileNamePre.Length > 0 ? _logCfg.FileNamePre + "_" : "", Path.GetFileNameWithoutExtension(Process.GetCurrentProcess().MainModule.FileName), DateTime.Now, Process.GetCurrentProcess().Id); if (_logCfg.EnSplitLog && _logCfg.RoolbackFile && _logCfg.MaxFileNum > 0) { _logFileName += $"_{_logFileNum:d3}"; _logFileNum = Convert.ToUInt32((_logFileNum + 1) % _logCfg.MaxFileNum); } _logFileName += ".log"; if (File.Exists(_logFileName)) if (_logCfg.EnSplitLog) File.Delete(_logFileName); _logSw = new StreamWriter(_logFileName, true); CreateLogFileHead(); } } public static void NLog_Finish() { if (_logCfg.EnFile) { _logSw?.Flush(); _logSw?.Close(); } } public static void NLog_Init() { _logCfg = new NLogConfig(); NLogConfig.OnLogCfgChanged += () => { _logSw?.Close(); _logCfg.LogConfig(); ConfigInit(); }; ConfigInit(); var asynWork = new Thread(() => { uint cnt = 0; while (true) { var lastDt = DateTime.Now; var tolOut = Math.Min(_logCfg.NumOutItems, _logItemCollection.Count); foreach (var val in Enumerable.Range(1, tolOut)) if (_logItemCollection.TryDequeue(out var logItem)) LogOutput(logItem); Thread.Sleep(_logCfg.SleepTime); // 每秒执行一次维护工作 if (++cnt % (1000 / _logCfg.SleepTime) != 0) continue; _logSw?.Flush(); if (_logCfg.EnSplitLog) { var isNeedSplit = false; if (_logCfg.SplitByData && lastDt.Day != DateTime.Now.Day) isNeedSplit = true; else if (_logCfg.SplitBySize > 0 && new FileInfo(_logFileName).Length > _logCfg.SplitBySize * 1024 * 1024) isNeedSplit = true; if (isNeedSplit) { _logSw?.Close(); _logSw?.Dispose(); _logSw = null; var parttn = string.Format("*[{0:d3}]_{1}", Process.GetCurrentProcess().Id, Path.GetFileNameWithoutExtension(Process.GetCurrentProcess().MainModule.FileName)); _logFileName = string.Format("{0}{1}[{3:yyyy-MM-dd_HH-mm}][{4:d}]_{2}", _logCfg.DirPath, _logCfg.FileNamePre.Length > 0 ? _logCfg.FileNamePre + "_" : "", Path.GetFileNameWithoutExtension(Process.GetCurrentProcess().MainModule.FileName), DateTime.Now, Process.GetCurrentProcess().Id); if (_logCfg.EnSplitLog && _logCfg.RoolbackFile && _logCfg.MaxFileNum > 0) { _logFileName += $"_{_logFileNum:d3}"; parttn += $"_{_logFileNum:d3}"; _logFileNum = Convert.ToUInt32((_logFileNum + 1) % _logCfg.MaxFileNum); } _logFileName += ".log"; parttn += ".log"; foreach (var f in Directory.GetFiles(_logCfg.DirPath, parttn, SearchOption.TopDirectoryOnly)) { File.Delete(f); Trace.WriteLine("Delect Rollback log: " + f); } _logSw = new StreamWriter(_logFileName, true); CreateLogFileHead(); } } } }) { Name = "Log Async Output Thread", IsBackground = true }; asynWork.Start(); } private static string LogLevelToString([NotNull] NLogLevel logLevel) { string[] level = { "F", "C", "E", "I", "W", "D" }; if ((int) logLevel < level.Length && (int) logLevel >= 0) return level[(int) logLevel]; return "U"; } private static string LogFormat(NLogLevel logLevel, string logMsg, string fileName, string funName, int lineNo, DateTime dt) { var msg = ""; if (_logCfg.ShowDate || _logCfg.ShowTime) msg += "["; if (_logCfg.ShowDate) { msg += dt.ToString("yyyy-MM-dd"); if (_logCfg.ShowTime) msg += " "; } if (_logCfg.ShowTime) { msg += dt.ToString("HH:mm:ss"); if (_logCfg.ShowMSec) msg += "." + dt.ToString("fff"); } if (_logCfg.ShowDate || _logCfg.ShowTime) msg += "]"; if (_logCfg.ShowLevel) msg += " [" + LogLevelToString(logLevel) + "] "; if (_logCfg.ShowCodeFile) msg += "[" + Path.GetFileName(fileName) + "] "; if (_logCfg.ShowFunction) msg += "- " + funName; if (_logCfg.ShowCodeFile || _logCfg.ShowFunction) if (_logCfg.ShowCodeLine) msg += "(" + lineNo + ")"; msg += ": " + logMsg; return msg; } private static void LogOutput(NLogItem logItem) { var msg = LogFormat(logItem.LogLevel, logItem.LogContent, logItem.CodeFile, logItem.CodeFunction, logItem.CodeLine, logItem.LogStamp); if (_logCfg.ForceNewLine) msg += Environment.NewLine; lock (_logOutputLock) { if (_logCfg.EnConsole) Console.Write(msg); if (_logCfg.EnDebug | _logCfg.EnTrace) { if (_logCfg.EnTrace) Trace.Write(msg); else System.Diagnostics.Debug.WriteLine(msg); } if (_logCfg.EnFile) _logSw?.Write(msg); } } private static void LogOutput2(string logMsg) { lock (_logOutputLock) { if (_logCfg.EnConsole) Console.Write(logMsg); if (_logCfg.EnDebug | _logCfg.EnTrace) { if (_logCfg.EnTrace) Trace.Write(logMsg); else System.Diagnostics.Debug.WriteLine(logMsg); } if (_logCfg.EnFile) _logSw?.Write(logMsg); } } public static void LogOut([NotNull] string logMsg = "", [CallerFilePath] string fileName = "", [CallerMemberName] string funName = "", [CallerLineNumber] int lineNo = 0) { if (NLogLevel.Debug >= _logCfg.LogLevel || !_logCfg.LogEnable) return; if (_logCfg.AsyncMode) { if (_logItemCollection.Count >= _logCfg.MaxItemsCache) { if (_logCfg.CacheMode == CacheOptMode.Drop) return; NLogItem val; _logItemCollection.TryDequeue(out val); } var logItem = new NLogItem(_logCfg.DefaultLevel, logMsg, fileName, funName, lineNo, DateTime.Now); _logItemCollection.Enqueue(logItem); } else { var logItem = new NLogItem(_logCfg.DefaultLevel, logMsg, fileName, funName, lineNo, DateTime.Now); LogOutput(logItem); } } public static void LogOut(NLogLevel logLevel = NLogLevel.Info, [NotNull] string logMsg = "", [CallerFilePath] string fileName = "", [CallerMemberName] string funName = "", [CallerLineNumber] int lineNo = 0) { if (logLevel >= _logCfg.LogLevel || !_logCfg.LogEnable) return; if (_logCfg.AsyncMode) { if (_logItemCollection.Count >= _logCfg.MaxItemsCache) { if (_logCfg.CacheMode == CacheOptMode.Drop) return; NLogItem val; _logItemCollection.TryDequeue(out val); } var logItem = new NLogItem(NLogLevel.Debug, logMsg, fileName, funName, lineNo, DateTime.Now); _logItemCollection.Enqueue(logItem); } else { var logItem = new NLogItem(NLogLevel.Debug, logMsg, fileName, funName, lineNo, DateTime.Now); LogOutput(logItem); } } #region LogOutputMethod public static void Debug([NotNull] string logMsg = "", [CallerFilePath] string fileName = "", [CallerMemberName] string funName = "", [CallerLineNumber] int lineNo = 0) { LogOut(NLogLevel.Debug, logMsg, fileName, funName, lineNo); } public static void Warring([NotNull] string logMsg = "", [CallerFilePath] string fileName = "", [CallerMemberName] string funName = "", [CallerLineNumber] int lineNo = 0) { LogOut(NLogLevel.Warring, logMsg, fileName, funName, lineNo); } public static void Info([NotNull] string logMsg = "", [CallerFilePath] string fileName = "", [CallerMemberName] string funName = "", [CallerLineNumber] int lineNo = 0) { LogOut(NLogLevel.Info, logMsg, fileName, funName, lineNo); } public static void Error([NotNull] string logMsg = "", [CallerFilePath] string fileName = "", [CallerMemberName] string funName = "", [CallerLineNumber] int lineNo = 0) { LogOut(NLogLevel.Error, logMsg, fileName, funName, lineNo); } public static void Crash([NotNull] string logMsg = "", [CallerFilePath] string fileName = "", [CallerMemberName] string funName = "", [CallerLineNumber] int lineNo = 0) { LogOut(NLogLevel.Crash, logMsg, fileName, funName, lineNo); } public static void Fatal([NotNull] string logMsg = "", [CallerFilePath] string fileName = "", [CallerMemberName] string funName = "", [CallerLineNumber] int lineNo = 0) { LogOut(NLogLevel.Fatal, logMsg, fileName, funName, lineNo); } #endregion } }