使用log4net
注意
稍大点的应用推荐这种方式。可定义参数多。
安装
Nuget安装log4net
项目根目录添加log4net.config
设置属性
下面两种任选一种即可。
下面两种方式主要是封装的要解决无法在日志中显示异常位置的问题。
默认(推荐)
log4net.config
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| <?xml version="1.0" encoding="utf-8" ?> <log4net> <logger name="logerror"> <level value="ALL" /> <appender-ref ref="ErrorAppender" /> </logger> <logger name="loginfo"> <level value="ALL" /> <appender-ref ref="InfoAppender" /> </logger> <appender name="ErrorAppender" type="log4net.Appender.RollingFileAppender"> <param name="File" value="Log\\LogError\\" /> <param name="AppendToFile" value="true" /> <param name="MaxSizeRollBackups" value="100" /> <param name="MaxFileSize" value="10240" /> <param name="StaticLogFileName" value="false" /> <param name="DatePattern" value="yyyyMMddHHmm".htm"" /> <param name="RollingStyle" value="Date" /> <layout type="log4net.Layout.PatternLayout"> <param name="ConversionPattern" value="<HR COLOR=red>%n异常时间:%d [%t] <BR>%n异常级别:%-5p <BR>%n异常类:%class:%line <BR>%n%m <BR>%n <HR Size=1>" /> </layout> </appender> <appender name="InfoAppender" type="log4net.Appender.RollingFileAppender"> <param name="File" value="Log\\LogInfo\\" /> <param name="AppendToFile" value="true" /> <param name="MaxFileSize" value="10240" /> <param name="MaxSizeRollBackups" value="100" /> <param name="StaticLogFileName" value="false" /> <param name="DatePattern" value="yyyyMMdd".htm"" /> <param name="RollingStyle" value="Date" /> <layout type="log4net.Layout.PatternLayout"> <param name="ConversionPattern" value="<HR COLOR=blue>%n日志时间:%d [%t] <BR>%n日志级别:%-5p <BR>%n日志类:%class:%line <BR>%n%m <BR>%n <HR Size=1>" /> </layout> </appender> </log4net>
|
其中
%class 显示包名和类名
%class{1} 只显示类名
工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| using System; using System.IO;
using log4net; using log4net.Config;
namespace Z.Utils.Common { public class ZLogHelper { public static readonly ILog Loginfo = LogManager.GetLogger("loginfo"); public static readonly ILog Logerror = LogManager.GetLogger("logerror");
public static void InitLog4Net() { FileInfo logCfg = new FileInfo(AppDomain.CurrentDomain.BaseDirectory + "log4net.config"); XmlConfigurator.ConfigureAndWatch(logCfg); }
public static void StopLog() { log4net.LogManager.Shutdown(); } } }
|
调用
1 2 3
| ZLogHelper.Loginfo.Info("课堂启动");
ZLogHelper.Logerror.Error("异常退出:",ex);
|
如果在应用结束前要上传日志文件,我们就要停止日志,防止文件占用
打印示例
日志时间:2023-11-06 15:21:41,108 [1]
日志级别:INFO
日志类:SchoolClient.MyApp:35
课堂启动
注意
这里之所以没有把打印日志封装是因为:打印的类和行号是被调用的类,如果封装后,显示的都会是封装的类,那么打印类名和行号就没有意义了。
简单封装(不推荐)
配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| <?xml version="1.0" encoding="utf-8" ?> <log4net> <logger name="logerror"> <level value="ALL" /> <appender-ref ref="ErrorAppender" /> </logger> <logger name="loginfo"> <level value="ALL" /> <appender-ref ref="InfoAppender" /> </logger> <appender name="ErrorAppender" type="log4net.Appender.RollingFileAppender"> <param name="File" value="Log\\LogError\\" /> <param name="AppendToFile" value="true" /> <param name="MaxSizeRollBackups" value="100" /> <param name="MaxFileSize" value="10240" /> <param name="StaticLogFileName" value="false" /> <param name="DatePattern" value="yyyyMMddHHmm".htm"" /> <param name="RollingStyle" value="Date" /> <layout type="log4net.Layout.PatternLayout"> <param name="ConversionPattern" value="<HR COLOR=red>%n异常时间:%d [%t] <BR>%n异常级别:%-5p<BR>%n%m <BR>%n <HR Size=1>" /> </layout> </appender> <appender name="InfoAppender" type="log4net.Appender.RollingFileAppender"> <param name="File" value="Log\\LogInfo\\" /> <param name="AppendToFile" value="true" /> <param name="MaxFileSize" value="10240" /> <param name="MaxSizeRollBackups" value="100" /> <param name="StaticLogFileName" value="false" /> <param name="DatePattern" value="yyyyMMdd".htm"" /> <param name="RollingStyle" value="Date" /> <layout type="log4net.Layout.PatternLayout"> <param name="ConversionPattern" value="<HR COLOR=blue>%n日志时间:%d [%t] <BR>%n日志级别:%-5p<BR>%n%m <BR>%n <HR Size=1>" /> </layout> </appender> </log4net>
|
添加工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| using System; using System.IO;
using log4net; using log4net.Config;
namespace Z.Utils.Common { public class ZLogHelper { private static readonly ILog Loginfo = LogManager.GetLogger("loginfo"); private static readonly ILog Logerror = LogManager.GetLogger("logerror");
public static void InitLog4Net() { var logCfg = new FileInfo(AppDomain.CurrentDomain.BaseDirectory + "log4net.config"); XmlConfigurator.ConfigureAndWatch(logCfg); } public static void StopLog() { log4net.LogManager.Shutdown(); }
public static void WriteInfoLog ( string message, [System.Runtime.CompilerServices.CallerFilePath] string callerFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int callerLineNumber = 0 ) { Loginfo.InfoFormat( "日志类:{0}:{1}<br>{2}", callerFilePath, callerLineNumber, message ); }
public static void WriteErrLog ( string message, Exception ex, [System.Runtime.CompilerServices.CallerFilePath] string callerFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int callerLineNumber = 0 ) { Logerror.InfoFormat( "日志类:{0}:{1}<br>{2}<br>{3}", callerFilePath, callerLineNumber, message, ex.Message ); } } }
|
注意
在调用记录日志前,要调用InitLog4Net方法。
因为封装了,所以自带的打印类和行数就不生效了,我们可以自己进行格式化拼接。
调用
1 2
| ZLogHelper.WriteInfoLog("课堂启动"); ZLogHelper.WriteErrLog("异常退出:",ex);
|
打印示例
这种方式获取到的文件的路径,暂时没找到获取包名+类名的方式。
日志时间:2023-11-06 14:59:36,168 [1]
日志级别:INFO
日志类:D:\Project\csharp\xh-schoolclient\MyApp.xaml.cs:35
课堂启动
自定义日志类
注意
小应用可以使用这种方式。
工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| using System; using System.IO; using System.Threading;
namespace Z.Utils.Common { public class LogHelper { private static readonly ReaderWriterLockSlim LogWriteLock = new ReaderWriterLockSlim();
public static void LogWrite(Exception ex) { if (!Directory.Exists("Log")) { Directory.CreateDirectory("Log"); } string logpath = @"Log\" + DateTime.Now.ToString("yyyy-MM-dd-HH") + ".html"; string log = "<div style='border: 1px solid #CCC;margin-bottom:10px;padding:10px;'>" + "<h3>" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "</h3>" + ex.Message + "<br>" + ex.InnerException + "<br>" + ex.StackTrace + "</div>"; try { LogWriteLock.EnterWriteLock(); File.AppendAllText(logpath, log); } finally { LogWriteLock.ExitWriteLock(); } } } }
|
调用时
1 2
| LogHelper.LogWrite(ex);
|
异常测试
1
| LogHelper.LogWrite(new Exception("主页面启动"));
|
全局异常捕获
这里的示例采用的第三方库的方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
|
public partial class App : Application { protected override void OnStartup(StartupEventArgs e) { ZLogHelper.InitLog4Net(); RegisterEvents(); base.OnStartup(e); }
private void RegisterEvents() { DispatcherUnhandledException += App_DispatcherUnhandledException;
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; }
private static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) { e.SetObserved(); if (e.Exception is Exception exception) { HandleException(exception); } }
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) { if (e.ExceptionObject is Exception exception) { HandleException(exception); } }
private static void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e) { HandleException(e.Exception); e.Handled = true; }
private static void HandleException(Exception ex) { ZLogHelper.Logerror.Error(ex.Message); Current.Dispatcher.Invoke( () => { Current.Shutdown(); } ); } }
|
在处理 TaskScheduler.UnobservedTaskException 事件时,确保调用 e.SetObserved() 将异常标记为已观察,以便避免默认的行为,即将异常重新抛出。
也就是说要先调用 e.SetObserved(),之后再写异常处理的逻辑。
应用退出
方式1
Environment.Exit(0) 方法可以在非 UI 线程中调用,用于终止整个应用程序并返回给操作系统。
Environment.Exit(0) 方法立即终止当前进程,不管当前线程是 UI 线程还是其他线程。
它会终止所有线程,关闭所有打开的句柄,释放所有资源,并立即退出应用程序。
这种方式我只在两个地方用
应用退出时
1 2 3 4 5 6
| protected override void OnExit(ExitEventArgs e) { base.OnExit(e); Environment.Exit(0); }
|
检测程序唯一时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| private static void CheckProcess() { Console.WriteLine(@"检查是否已开启"); Process current = Process.GetCurrentProcess(); string strProcessName = Process.GetCurrentProcess().ProcessName; Process[] processList = Process.GetProcessesByName(strProcessName); if (processList.Length <= 1) { return; } MessageWindow.Show("当前程序已在运行,请勿重复运行。"); foreach (Process process in processList) { if (process.Id == current.Id) continue; int hWnd = FindWindow(null, process.MainWindowTitle); SetForegroundWindow(hWnd); } Environment.Exit(0); }
protected override void OnStartup(StartupEventArgs e) { CheckProcess(); base.OnStartup(e); }
|
其它的地方都使用Application.Current.Shutdown(),否则不会触发程序的OnExit生命周期事件。
方式2
1
| Application.Current.Shutdown();
|
是的,Application.Current.Shutdown() 方法只能在 UI 线程中调用,否则会抛出 InvalidOperationException 异常。
在 WPF 应用程序中,Application.Current 是一个静态属性,它代表了当前应用程序的 Application 实例。调用 Shutdown 方法将停止应用程序并关闭窗口,结束应用程序的 UI 线程。
在非 UI 线程(例如后台线程或 Task 中)调用 Application.Current.Shutdown() 方法可能会导致已经启动的 UI 线程被阻塞或退出应用程序,因为它们处于不同的线程上下文。
如果必须在非 UI 线程中关闭应用程序,可以通过将调用封装在 Dispatcher.Invoke 或 Dispatcher.BeginInvoke 方法中来实现在 UI 线程上下文中调用 Application.Current.Shutdown() 方法。
例如:
1 2 3
| Application.Current.Dispatcher.Invoke(() => { Application.Current.Shutdown(); });
|
这样可以确保 Shutdown() 方法在正确的线程上下文中调用。
应用退出删除空日志
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| protected override void OnExit(ExitEventArgs e) { Console.WriteLine(@"----------------------------------------"); Console.WriteLine(@"应用退出"); ZLogHelper.StopLog(); string[] files = Directory.GetFiles(ZConfig.LogPath, "*.htm"); foreach (string filePath in files) { if (ZFileUtil.IsFileInUse(filePath)) { continue; } if (new FileInfo(filePath).Length != 0) { continue; } try { File.Delete(filePath); } catch (Exception ex) { Console.WriteLine(ex); } } base.OnExit(e); Environment.Exit(0); }
|
文件是否占用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| [DllImport("kernel32.dll")] public static extern IntPtr _lopen(string lpPathName, int iReadWrite);
[DllImport("kernel32.dll")] public static extern bool CloseHandle(IntPtr hObject);
public static bool IsFileInUse(string fileName)
{ const int ofReadwrite = 2; const int ofShareDenyNone = 0x40; IntPtr hfileError = new IntPtr(-1); if (!File.Exists(fileName)) { return false; }
IntPtr vHandle = _lopen(fileName, ofReadwrite | ofShareDenyNone);
if (vHandle == hfileError) { return true; }
CloseHandle(vHandle);
return false; }
|