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 { private object _cfgLock = new object(); public delegate void LogCfgChangedHandle(); public static event LogCfgChangedHandle OnLogCfgChanged; public NLogConfig() { LogConfig(); NConfig.OnConfigChanged += () => { LogCfgChanged(); }; } protected static void LogCfgChanged() { OnLogCfgChanged?.Invoke(); } 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 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 = 0; private static StreamWriter _logSw = null; 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).ToString()); _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) { bool 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; string 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.ToString() + ")"; } } 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; } else { 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; } else { 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 } }