c# log4net 日志的使用
参考:C# log4net的使用。输出的日志内容添加文件名和行号。_log4net 发布 输出的文件中行号-CSDN博客
1.NuGet增加log4net

2.增加log4net.config文件

log4net.config的内容如下,没有的功能可以搜一搜,设置 log4net.config的属性 复制到输出目录 设为始终复制
<?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/> </configSections> <!--(高) OFF > FATAL > ERROR > WARN > INFO > DEBUG > ALL (低)--> <log4net> <!--调试信息--> <logger name="InfoLog"> <level value="ALL" /> <appender-ref ref="InfoAppender" /> </logger> <!--错误信息--> <logger name="ErrorLog"> <level value="ALL" /> <appender-ref ref="ErrorAppender" /> </logger> <!--Info日志附加介质--> <appender name="InfoAppender" type="log4net.Appender.RollingFileAppender"> <!--日志路径--> <param name= "File" value= "Logs\"/> <!--是否是向文件中追加日志--> <param name= "AppendToFile" value= "true"/> <!--log保留天数--> <param name= "MaxSizeRollBackups" value= "100"/> <!--写到一个文件--> <staticLogFileName value="false"/> <!--单个文件大小。单位:KB|MB|GB--> <maximumFileSize value="200MB"/> <!--最多保留的文件数,设为"-1"则不限--> <maxSizeRollBackups value="-1"/> <!--日志文件名格式--> <param name= "DatePattern" value= "yyyyMM\\yyyyMMdd'_InfoLog.log'"/> <!--不以独占方式记录日志,仅在记录每个日志的最短时间内锁定,因为部署到服务器上遇到了文件被占用无法下载日志--> <lockingModel type="log4net.Appender.FileAppender+MinimalLock" /> <!--日志根据日期滚动--> <param name= "RollingStyle" value= "Date"/> <!--日志文本格式--> <layout type="log4net.Layout.PatternLayout"> <!--%d 时间,等价于 date--> <!--%t 线程--> <!--%-5p 日志级别--> <!--%C 出错类,等价于 class,可以使用:%class{1},如果给出了精度说明符, 则只会打印类名中最右边的组件的相应数量。默认情况下,类名以完全限定形式输出。--> <!--%L 出错行--> <!--%M 方法名,等价于 method--> <!--%m 日志信息,等价于 message--> <!--%n 换行--> <!--这种情况输出的类名方法名输出的是封装的类名<LogHelper>方法名每有意义--> <!--<param name="ConversionPattern" value="**********%d 线程[%t] 日志级别[%p] **********%n类名:%C 方法名:%M - 第 [%L] 行%n消息:%m%n%n" />--> <param name="ConversionPattern" value="**********%d 线程[%t] 日志级别[%p]**********%n消息:%m%n%n"/> </layout> </appender> <!--Error日志附加介质--> <appender name="ErrorAppender" type="log4net.Appender.RollingFileAppender"> <!--日志路径--> <param name= "File" value= "Logs\"/> <!--是否是向文件中追加日志--> <param name= "AppendToFile" value= "true"/> <!--log保留天数--> <param name= "MaxSizeRollBackups" value= "100"/> <!--日志文件名是否是固定不变的--> <param name= "StaticLogFileName" value= "false"/> <!--日志文件名格式--> <param name= "DatePattern" value= "yyyyMM\\yyyyMMdd'_ErrorLog.log'"/> <!--日志根据日期滚动--> <param name= "RollingStyle" value= "Date"/> <param name="MaxFileSize" value="1"/> <!--日志文本格式--> <layout type="log4net.Layout.PatternLayout"> <!--%d 时间,等价于 date--> <!--%t 线程--> <!--%-5p 日志级别--> <!--%C 出错类,等价于 class,可以使用:%class{1},如果给出了精度说明符, 则只会打印类名中最右边的组件的相应数量。默认情况下,类名以完全限定形式输出。--> <!--%L 出错行--> <!--%M 方法名,等价于 method--> <!--%m 日志信息,等价于 message--> <!--%n 换行--> <!--这种情况输出的类名方法名输出的是封装的类名<LogHelper>方法名每有意义--> <!--<param name="ConversionPattern" value="**********%d 线程[%t] 日志级别[%p] **********%n类名:%C 方法名:%M - 第 [%L] 行%n消息:%m%n%n" />--> <param name="ConversionPattern" value="**********%d 线程[%t] 日志级别[%p]**********%n消息:%m%n%n"/> </layout> </appender> </log4net> </configuration>
3.创建LogHelper.cs类
using System.Diagnostics; using System.IO; using log4net; [assembly: log4net.Config.XmlConfigurator(ConfigFile = "Log4net\\log4net.config", Watch = true)] namespace TestLog4net.Log4net { /// <summary> /// 日志助手类 /// </summary> internal class LogHelper { private static ILog InfoLog = LogManager.GetLogger("InfoLog"); private static ILog ErrorLog = LogManager.GetLogger("ErrorLog"); //Debug Info Warn Error方法 在日志里体现在日志级别上 /// <summary> /// 写日志 /// </summary> /// <param name="message">日志信息</param>
public static void WriteInfoLog(string message)
{
InfoLog.Info(AppendClassLine(message));
}
/// <summary> /// 写错误日志 /// </summary> /// <param name="message">日志信息</param> public static void WriteErrorLog(string message) { ErrorLog.Error(AppendClassLine(message)); } /// <summary> /// 给日志信息附加所在文件名和行 /// </summary> /// <param name="msg">日志信息</param> /// <returns>附加后的日志信息</returns> static string AppendClassLine(string msg) { string logStr = msg; try { //测试了一下这个new StackTrace(true) 10w次耗时1.3秒,性能可以接收啊 //这个原理是读pdb文件,也许大项目读这个文件会更耗时 StackTrace st = new StackTrace(true); StackFrame sf = st.GetFrame(2); logStr = $"{msg} [{Path.GetFileName(sf.GetFileName())}:{sf.GetFileLineNumber().ToString()}]"; } catch { logStr = $"{msg} [没找到文件名和所在行]"; } return logStr; } } }
这里的new StackTrace(true)性能测试代码如下
using System; using System.Diagnostics; namespace ConsoleApp1 { internal class Program { static void Main(string[] args) { const int iterations = 100000; // 测试 new StackTrace(false) 的性能 var stopwatch1 = Stopwatch.StartNew(); for (int i = 0; i < iterations; i++) { var stackTrace = new StackTrace(false); } stopwatch1.Stop(); Console.WriteLine($"new StackTrace(false) 耗时: {stopwatch1.ElapsedMilliseconds} 毫秒"); // 测试 new StackTrace(true) 的性能 var stopwatch2 = Stopwatch.StartNew(); for (int i = 0; i < iterations; i++) { var stackTrace = new StackTrace(true); } stopwatch2.Stop(); Console.WriteLine($"new StackTrace(true) 耗时: {stopwatch2.ElapsedMilliseconds} 毫秒"); } } }
4.日志调用
using TestLog4net.Log4net; namespace TestLog4net { internal class Program { static void Main(string[] args) { LogHelper.WriteInfoLog("info日志"); LogHelper.WriteErrorLog("error日志"); } } }
5.日志效果

如果要动态指定日志文件的名称怎么办呢,应该代码配置appender 如下
using System; using System.Diagnostics; using System.IO; using System.Windows.Forms; using log4net; using log4net.Appender; using log4net.Core; using log4net.Layout; using log4net.Repository.Hierarchy; namespace SKDBurninSystem.log4net { internal class LogHelper { private readonly ILog InfoLog; public LogHelper(string InfoFileName) { InfoLog = GetLoggerByName(InfoFileName); } /// <summary> /// 根据日志文件名获取日志记录器 /// </summary> /// <param name="LogName"></param> /// <returns></returns> private ILog GetLoggerByName(string LogName) { try { //检查日志记录器是否存在 if (LogManager.Exists(LogName) == null) { // 定义日志输出的模式布局 PatternLayout patternLayout = new PatternLayout(); // 设置日志输出的转换模式,这里定义为日期 - 消息,每条日志记录后换行 patternLayout.ConversionPattern = "**********%d 线程[%t] 日志级别[%p]**********%n消息:%m%n%n"; // 激活模式布局的配置选项,使配置生效 patternLayout.ActivateOptions(); // 配置 RollingFileAppender 对象,用于将日志滚动写入文件 RollingFileAppender appender = new RollingFileAppender(); //文件名 appender.Name = LogName; //日志追加写 appender.AppendToFile = true; //日志加上路径,文件名也可以加路径 appender.File = $"Logs\\{LogName}\\{DateTime.Now:yyyyMMdd}.log"; appender.DatePattern = "yyyyMMdd.log"; //日志文件名在滚动时保持静态 appender.StaticLogFileName = true; //保留日志文件的扩展名 appender.PreserveLogFileNameExtension = false; // 设置文件锁定模式为最小化锁定,减少文件锁定时间,提高并发性能 appender.LockingModel = new FileAppender.MinimalLock(); // 为 appender 设置日志布局,这里使用传入的 patternLayout appender.Layout = patternLayout; // 设置日志文件备份的最大数量,这里最多保留 5 个备份文件 appender.MaxSizeRollBackups = 5; // 设置单个日志文件的最大大小为 200MB,达到这个大小后会进行滚动操作 appender.MaximumFileSize = "200MB"; // 设置滚动模式为复合模式,结合多种滚动条件进行滚动 appender.RollingStyle = RollingFileAppender.RollingMode.Date; // 激活 appender 的配置选项,使配置生效 appender.ActivateOptions(); // 获取 LogManager 的仓库,并将其转换为 Hierarchy 类型 Hierarchy hierarchy = (Hierarchy)LogManager.GetRepository(); // 获取指定名称的日志记录器,使用指定的 LoggerFactory var loger = hierarchy.GetLogger(LogName, hierarchy.LoggerFactory); //!!! 此处写法是重点,不容更改 // 设置日志记录器的层次结构 loger.Hierarchy = hierarchy; // 为日志记录器添加 appender,使其使用上面配置的文件追加器 loger.AddAppender(appender); // 设置日志记录器的日志级别为所有级别,即记录所有类型的日志 loger.Level = Level.All; // 基本配置器进行配置,使日志系统生效 //BasicConfigurator.Configure();//!!! 此处写法是重点,不容更改 hierarchy.Configured = true; } // 获取指定名称的日志记录器 var logger = LogManager.GetLogger(LogName); return logger; } catch (Exception ex) { MessageBox.Show($"{LogName}log4net初始化时失败" + ex.Message); return null; } } /// <summary> /// 给日志信息附加所在文件名和行 /// </summary> /// <param name="msg">日志信息</param> /// <returns>附加后的日志信息</returns> private string AppendClassLine(string msg) { string logStr = msg; try { //测试了一下这个new StackTrace(true) 10w次耗时1.3秒,性能可以接收啊 //这个原理是读pdb文件,也许大项目读这个文件会更耗时,要保证*.pdb文件在时才能读到 StackTrace st = new StackTrace(true); StackFrame sf = st.GetFrame(2); logStr = $"{msg} [{Path.GetFileName(sf.GetFileName())}:{sf.GetFileLineNumber().ToString()}]"; } catch { logStr = $"{msg} [没找到文件名和所在行]"; } return logStr; } public void WriteInfoLog(string message) { InfoLog.Info(AppendClassLine(message)); } public void WriteErrorLog(string message) { InfoLog.Error(AppendClassLine(message)); } } }
调用如下
internal class Program { static void Main(string[] args) { Test1 t1 = new Test1(); } } public class Test1 { private static readonly LogHelper _log = new LogHelper(Path.Combine("TTTT", "Info_2025-04-16")); public Test1() { new Thread(Execute) { Name = "TTTT" }.Start(); } private void Execute() { for (int i = 0; i < 3; i++) { _log.WriteInfoLog("Test1 info"); Thread.Sleep(1000); } } }
生成的日志如下

这里日志的内容是上次测试的内容,这次是修改文件名的问题,日志内容就用了上次的有出入,请自行测试

有可能会生成一些奇怪名字的日志文件,但内容却是空的
有可能是log4net.config这个文件影响了,用代码就用不到这个文件了,但测试代码是从有log4net.config文件改成代码形式的,删除这个文件试试,从项目里改那个”复制到输出目录“
这个后缀名与按天更新有关,我就是想要自定义log名字又要按日期更新,先这样吧,不要后缀名了
这里用了一个方法是名称用作文件夹名称,文件名还是YYYYMMDD.log,这样文件夹名用来表示意义,文件名还可以按日期自动更新
更多的时候不用配置文件更简单一点直接上代码 AI润色过
/// <summary> /// 日志帮助类,按日志名称(对应文件夹名)提供日志记录器实例 /// 使用静态缓存确保同一名称只初始化一次,避免重复创建 Appender /// </summary> internal class LogHelper { // 日志记录器实例 private readonly ILog _logger; // 静态缓存:存储所有已创建的 LogHelper 实例(线程安全) private static readonly ConcurrentDictionary<string, LogHelper> _cache = new ConcurrentDictionary<string, LogHelper>(); // 私有构造函数,外部只能通过 GetLogger 获取实例 private LogHelper(string logName) { _logger = GetLoggerByName(logName); } /// <summary> /// 获取或创建指定名称的日志记录器 /// </summary> /// <param name="logName">日志名称(将作为日志文件的存放文件夹名)</param> /// <returns>LogHelper 实例</returns> public static LogHelper GetLogger(string logName) { if (string.IsNullOrWhiteSpace(logName)) throw new ArgumentException("日志名称不能为空", nameof(logName)); // GetOrAdd 确保同一个 logName 只执行一次创建委托 return _cache.GetOrAdd(logName, name => new LogHelper(name)); } /// <summary> /// 【修改点4】将 xx.xx.xx 转换为多级路径,并清理非法路径字符 /// </summary> private static string NameToDirectoryPath(string logName) { // 1. 统一将斜杠和反斜杠替换为点,保证只用点作为层级分隔符 string normalized = logName.Replace('\\', '.').Replace('/', '.'); // 2. 按点分割,过滤空段(连续点会生成空字符串) var parts = normalized.Split('.') .Select(p => p.Trim()) .Where(p => !string.IsNullOrEmpty(p)); // 3. 对每一段过滤 Path.GetInvalidFileNameChars() 中的非法字符 char[] invalidChars = Path.GetInvalidFileNameChars(); var safeParts = parts .Select(p => new string(p.Where(c => !invalidChars.Contains(c)).ToArray())) .Where(p => !string.IsNullOrEmpty(p)); // 4. 用路径分隔符组合,若没有任何有效段则返回空字符串(日志将直接放在 Logs 下) return string.Join(Path.DirectorySeparatorChar.ToString(), safeParts); } /// <summary> /// 【修改点5】生成安全的 Appender 名称(仅允许字母、数字、下划线) /// </summary> private static string GetSafeAppenderName(string logName) { string safe = Regex.Replace(logName, @"[^a-zA-Z0-9_]", "_"); if (string.IsNullOrEmpty(safe)) safe = "Appender_" + Guid.NewGuid().ToString("N"); else safe = "Appender_" + safe; return safe; } /// <summary> /// 【修改点2+3】创建并配置 log4net 的 ILog 对象 /// 移除了全局锁和 Exists 检查,直接创建并添加 Appender /// </summary> private static ILog GetLoggerByName(string logName) { // 由于 ConcurrentDictionary 保证此方法只对每个 logName 执行一次, // 无需加锁,也无需检查 LogManager.Exists(直接创建确保我们的 Appender 必定添加) // 定义日志输出模式布局 var patternLayout = new PatternLayout { ConversionPattern = "**********%d 线程[%t] 日志级别[%p]**********%n消息:%m%n%n" }; patternLayout.ActivateOptions(); // 转换多级目录路径 string folderPath = NameToDirectoryPath(logName); string logBaseFile = Path.Combine("Logs", folderPath, "log"); string logDirectory = Path.GetDirectoryName(logBaseFile); // 创建多级目录 if (!string.IsNullOrEmpty(logDirectory) && !Directory.Exists(logDirectory)) { Directory.CreateDirectory(logDirectory); } // 配置滚动文件 Appender var appender = new RollingFileAppender { Name = GetSafeAppenderName(logName), // 使用安全名称 AppendToFile = true, File = logBaseFile, RollingStyle = RollingFileAppender.RollingMode.Date, DatePattern = "yyyyMMdd'.log'", StaticLogFileName = false, PreserveLogFileNameExtension = true, LockingModel = new FileAppender.ExclusiveLock(), MaxSizeRollBackups = 5, MaximumFileSize = "800MB", Layout = patternLayout }; appender.ActivateOptions(); // 获取 log4net 仓库层次结构 var hierarchy = (Hierarchy)LogManager.GetRepository(); // 直接获取(或创建)Logger,并添加 Appender(不再检查是否存在) var logger = hierarchy.GetLogger(logName, hierarchy.LoggerFactory); logger.Hierarchy = hierarchy; logger.AddAppender(appender); // 日志级别仍然硬编码为 All(第6点不做修改) logger.Level = Level.All; // 保留原 Configured 设置(第1点不做修改) hierarchy.Configured = true; return LogManager.GetLogger(logName); } // ---------- 对外公开的日志方法(仅 Info 和 Error,第7点不做修改) ---------- public void Info(string message) { _logger.Info(message); } public void Error(string message) { _logger.Error(message); } }
调用代码
private LogHelper _log = LogHelper.GetLogger("DataPool"); _log.Info("DataPool 单例初始化完成。");


浙公网安备 33010602011771号