优化IOCP接收处理数据时避免递归嵌套的防御性代码
智能大石头 authored at 2023-12-20 21:13:47 大石头 committed at 2024-01-01 11:10:10
13.54 KiB
X
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;

#if __WIN__

using System.Windows.Forms;

#endif

using NewLife.Reflection;
using NewLife.Threading;
using NewLife.Windows;

#nullable enable

namespace NewLife.Log;

/// <summary>日志类,包含跟踪调试功能</summary>
/// <remarks>
/// 文档 https://newlifex.com/core/log
///
/// 该静态类包括写日志、写调用栈和Dump进程内存等调试功能。
///
/// 默认写日志到文本文件,可通过修改<see cref="Log"/>属性来增加日志输出方式。
/// 对于控制台工程,可以直接通过UseConsole方法,把日志输出重定向为控制台输出,并且可以为不同线程使用不同颜色。
/// </remarks>
public static class XTrace
{
    #region 写日志

    /// <summary>文本文件日志</summary>
    private static ILog _Log = Logger.Null;

    /// <summary>日志提供者,默认使用文本文件日志</summary>
    public static ILog Log { get { InitLog(); return _Log; } set { _Log = value; } }

        /// <summary>输出日志</summary>
        /// <param name="msg">信息</param>
        public static void WriteLine(String msg)
        {
            if (!InitLog()) return;

            WriteVersion();

            Log.Info(msg);
        }

        /// <summary>写日志</summary>
        /// <param name="format"></param>
        /// <param name="args"></param>
        public static void WriteLine(String format, params Object?[] args)
        {
            if (!InitLog()) return;

            WriteVersion();

            Log.Info(format, args);
        }

        ///// <summary>异步写日志</summary>
        ///// <param name="format"></param>
        ///// <param name="args"></param>
        //public static void WriteLineAsync(String format, params Object[] args)
        //{
        //    ThreadPool.QueueUserWorkItem(s => WriteLine(format, args));
        //}

        /// <summary>输出异常日志</summary>
        /// <param name="ex">异常信息</param>
        public static void WriteException(Exception ex)
        {
            if (!InitLog()) return;

            WriteVersion();

        Log.Error("{0}", ex);
    }

    #endregion 写日志

    #region 构造

    static XTrace()
    {
        AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
        TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
        AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
#if NETCOREAPP
        System.Runtime.Loader.AssemblyLoadContext.Default.Unloading += ctx => OnProcessExit(null, EventArgs.Empty);
#endif

            ThreadPoolX.Init();

            try
            {
                var set = Setting.Current;
                Debug = set.Debug;
                LogPath = set.LogPath;
            }
            catch { }
        }

    private static void CurrentDomain_UnhandledException(Object sender, UnhandledExceptionEventArgs e)
    {
        if (e.ExceptionObject is Exception ex)
        {
            // 全局异常埋点
            DefaultTracer.Instance?.NewError(ex.GetType().Name, ex);
            WriteException(ex);
        }
        if (e.IsTerminating)
        {
            Log.Fatal("异常退出!");

            OnProcessExit(null, EventArgs.Empty);
        }
    }

        private static void TaskScheduler_UnobservedTaskException(Object? sender, UnobservedTaskExceptionEventArgs e)
        {
            if (!e.Observed && e.Exception != null)
            {
                //WriteException(e.Exception);
                foreach (var ex in e.Exception.Flatten().InnerExceptions)
                {
                    WriteException(ex);
                }
                e.SetObserved();
            }
        }

    private static void OnProcessExit(Object? sender, EventArgs e)
    {
        if (Log is CompositeLog compositeLog)
        {
            var log = compositeLog.Get<TextFileLog>();
            log.TryDispose();
        }
        else
        {
            Log.TryDispose();
        }
    }

    private static readonly Object _lock = new();
    private static Int32 _initing = 0;

    /// <summary>
    /// 2012.11.05 修正初次调用的时候,由于同步BUG,导致Log为空的问题。
    /// </summary>
    private static Boolean InitLog()
    {
        /*
         * 日志初始化可能会触发配置模块,其内部又写日志导致死循环。
         * 1,外部写日志引发初始化
         * 2,标识日志初始化正在进行中
         * 3,初始化日志提供者
         * 4,此时如果再次引发写入日志,发现正在进行中,放弃写入的日志
         * 5,标识日志初始化已完成
         * 6,正常写入日志
         */

            if (_Log != null && _Log != Logger.Null) return true;
            if (_initing > 0 && _initing == Thread.CurrentThread.ManagedThreadId) return false;

            lock (_lock)
            {
                if (_Log != null && _Log != Logger.Null) return true;

                _initing = Thread.CurrentThread.ManagedThreadId;

                var set = Setting.Current;
                if (LogPath.IsNullOrEmpty() || LogPath == "Log") LogPath = set.LogPath;
                if (set.LogFileFormat.Contains("{1}"))
                    _Log = new LevelLog(LogPath, set.LogFileFormat);
                else
                    _Log = TextFileLog.Create(LogPath);

                if (!set.NetworkLog.IsNullOrEmpty())
                {
                    var nlog = new NetworkLog(set.NetworkLog);
                    _Log = new CompositeLog(_Log, nlog);
                }

                _initing = 0;
            }

            //WriteVersion();

        return true;
    }

    #endregion 构造

    #region 使用控制台输出

    private static Boolean _useConsole;

    /// <summary>使用控制台输出日志,只能调用一次</summary>
    /// <param name="useColor">是否使用颜色,默认使用</param>
    /// <param name="useFileLog">是否同时使用文件日志,默认使用</param>
    public static void UseConsole(Boolean useColor = true, Boolean useFileLog = true)
    {
        if (_useConsole) return;
        _useConsole = true;

            //if (!Runtime.IsConsole) return;
            Runtime.IsConsole = true;

            // 适当加大控制台窗口
            try
            {
#if !NETFRAMEWORK
                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                {
                    if (Console.WindowWidth <= 80) Console.WindowWidth = Console.WindowWidth * 3 / 2;
                    if (Console.WindowHeight <= 25) Console.WindowHeight = Console.WindowHeight * 3 / 2;
                }
#else
            if (Console.WindowWidth <= 80) Console.WindowWidth = Console.WindowWidth * 3 / 2;
            if (Console.WindowHeight <= 25) Console.WindowHeight = Console.WindowHeight * 3 / 2;
#endif
            }
            catch { }

        var clg = new ConsoleLog { UseColor = useColor };
        if (useFileLog)
            _Log = new CompositeLog(clg, Log);
        else
            _Log = clg;
    }

    #endregion 使用控制台输出

    #region 控制台禁用快捷编辑

    /// <summary>
    /// 禁用控制台快捷编辑,在UseConsole方法之后调用
    /// </summary>
    public static void DisableConsoleEdit()
    {
        if (!_useConsole) return;
        try
        {
            if (Runtime.Windows)
            {
                ConsoleHelper.DisableQuickEditMode();
            }
        }
        catch { }
    }

    /// <summary>
    /// 禁用控制台关闭按钮
    /// </summary>
    /// <param name="consoleTitle">控制台程序名称,可使用Console.Title动态设置的值</param>
    public static void DisableConsoleCloseButton(String consoleTitle)
    {
        try
        {
            if (Runtime.Windows)
            {
                ConsoleHelper.DisableCloseButton(consoleTitle);
            }
        }
        catch { }
    }

    #endregion 控制台禁用关闭按钮

    #region 拦截WinForm异常

#if __WIN__
        private static Int32 initWF = 0;
        private static Boolean _ShowErrorMessage;
        //private static String _Title;

        /// <summary>拦截WinForm异常并记录日志,可指定是否用<see cref="MessageBox"/>显示。</summary>
        /// <param name="showErrorMessage">发为捕获异常时,是否显示提示,默认显示</param>
        public static void UseWinForm(Boolean showErrorMessage = true)
        {
            Runtime.IsConsole = false;

            _ShowErrorMessage = showErrorMessage;

            if (initWF > 0 || Interlocked.CompareExchange(ref initWF, 1, 0) != 0) return;
            //if (!Application.MessageLoop) return;

            AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException2;
            Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
            Application.ThreadException += Application_ThreadException;
        }

    private static void CurrentDomain_UnhandledException2(Object sender, UnhandledExceptionEventArgs e)
    {
        var show = _ShowErrorMessage && Application.MessageLoop;
        var ex = e.ExceptionObject as Exception;
        var title = e.IsTerminating ? "异常退出" : "出错";
        if (show) MessageBox.Show(ex?.Message, title, MessageBoxButtons.OK, MessageBoxIcon.Error);
    }

    private static void Application_ThreadException(Object sender, ThreadExceptionEventArgs e)
    {
        WriteException(e.Exception);

            var show = _ShowErrorMessage && Application.MessageLoop;
            if (show) MessageBox.Show(e.Exception == null ? "" : e.Exception.Message, "出错", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }

        /// <summary>在WinForm控件上输出日志,主要考虑非UI线程操作</summary>
        /// <remarks>不是常用功能,为了避免干扰常用功能,保持UseWinForm开头</remarks>
        /// <param name="control">要绑定日志输出的WinForm控件</param>
        /// <param name="useFileLog">是否同时使用文件日志,默认使用</param>
        /// <param name="maxLines">最大行数</param>
        public static void UseWinFormControl(this Control control, Boolean useFileLog = true, Int32 maxLines = 1000)
        {
            var clg = _Log as TextControlLog;
            var ftl = _Log as TextFileLog;
            if (_Log is CompositeLog cmp)
            {
                ftl = cmp.Get<TextFileLog>();
                clg = cmp.Get<TextControlLog>();
            }

        // 控制控制台日志
        clg ??= new TextControlLog();
        clg.Control = control;
        clg.MaxLines = maxLines;

        if (!useFileLog)
        {
            Log = clg;
            ftl?.Dispose();
        }
        else
        {
            ftl ??= TextFileLog.Create(null);
            Log = new CompositeLog(clg, ftl);
        }
    }

        /// <summary>控件绑定到日志,生成混合日志</summary>
        /// <param name="control"></param>
        /// <param name="log"></param>
        /// <param name="maxLines"></param>
        /// <returns></returns>
        public static ILog Combine(this Control control, ILog log, Int32 maxLines = 1000)
        {
            //if (control == null || log == null) return log;

            var clg = new TextControlLog
            {
                Control = control,
                MaxLines = maxLines
            };

        return new CompositeLog(log, clg);
    }

#endif

    #endregion 拦截WinForm异常

    #region 属性

    /// <summary>是否调试。</summary>
    public static Boolean Debug { get; set; }

        /// <summary>文本日志目录</summary>
        public static String? LogPath { get; set; }

    ///// <summary>临时目录</summary>
    //public static String TempPath { get; set; } = Setting.Current.TempPath;

    #endregion 属性

    #region 版本信息

    private static Int32 _writeVersion;

    /// <summary>输出核心库和启动程序的版本号</summary>
    public static void WriteVersion()
    {
        if (_writeVersion > 0 || Interlocked.CompareExchange(ref _writeVersion, 1, 0) != 0) return;

            var asm = Assembly.GetExecutingAssembly();
            WriteVersion(asm);

            var asm2 = Assembly.GetEntryAssembly();
            if (asm2 != null && asm2 != asm) WriteVersion(asm2);
        }

        /// <summary>输出程序集版本</summary>
        /// <param name="asm"></param>
        public static void WriteVersion(this Assembly asm)
        {
            if (asm == null) return;

            var asmx = AssemblyX.Create(asm);
            if (asmx != null)
            {
                var ver = "";
                //var tar = asm.GetCustomAttribute<TargetFrameworkAttribute>();
                //if (tar != null)
                //{
                //    ver = tar.FrameworkDisplayName;
                //    if (ver.IsNullOrEmpty()) ver = tar.FrameworkName;
                //}

            WriteLine("{0} v{1} Build {2:yyyy-MM-dd HH:mm:ss} {3}", asmx.Name, asmx.FileVersion, asmx.Compile, ver);
            var att = asmx.Asm.GetCustomAttribute<AssemblyCopyrightAttribute>();
            WriteLine("{0} {1}", asmx.Title, att?.Copyright);
        }
    }

    #endregion 版本信息
}

#nullable restore