c# - 将 log4net 配置序列化为 XML?
c# - Serialize log4net Configuration to XML?
我在静态日志记录中封装 log4net 时遇到了一些问题 class。
我正在使用以下(未完成)class 动态生成 log4Net 配置:
public class eLogConfig
{
public bool AppendToFile = true;
/// <summary>
/// This is the Path to the folder that will contain the
/// Configuation and Logs folder
/// Recommended path is ProgramData\CompanyName
/// </summary>
public string ParentPath { get; set; }
public string LogPattern = "%date [%thread] %-5level %logger - %message%newline";
public int MaxLogFiles = 10;
public int MaxLogSizeInMB = 10;
public Level Level = Level.Debug;
public string LogFilePath
{
get
{
return Path.Combine(LogFolder,
Assembly.GetEntryAssembly().GetName().Name
.e().ToFileName() + ".Log.txt");
}
}
public string ConfigFilePath
{
get
{
return Path.GetFileNameWithoutExtension(
Assembly.GetEntryAssembly().Location
).e().ToFileName() + ".Log4Net.config";
}
}
private string ConfigurationFolder
{
get
{
return Path.Combine(this.ParentPath, "Configuration");
}
}
private string LogFolder
{
get
{
return Path.Combine(this.ParentPath, "Logs");
}
}
/// <summary>
/// Initializes a new eLogConfiguration object, for use with initializing a new eLog Logger
/// </summary>
/// <param name="ParentPath">The path which will contain the Configuration and Logs folders</param>
public eLogConfig(DirectoryInfo ParentPath)
{
this.ParentPath = ParentPath.FullName;
if (!Directory.Exists(this.ParentPath))
Directory.CreateDirectory(this.ParentPath);
if (!Directory.Exists(ConfigurationFolder))
Directory.CreateDirectory(ConfigurationFolder);
if (!Directory.Exists(LogFolder))
Directory.CreateDirectory(LogFolder);
}
private bool initialized = false;
public void Setup()
{
if (!initialized || !File.Exists(LogFilePath))
{
Hierarchy hierarchy = (Hierarchy)LogManager.GetRepository();
PatternLayout patternLayout = new PatternLayout();
patternLayout.ConversionPattern = this.LogPattern;
patternLayout.ActivateOptions();
RollingFileAppender roller = new RollingFileAppender();
roller.AppendToFile = this.AppendToFile;
roller.File = this.LogFilePath;
roller.Layout = patternLayout;
roller.MaxSizeRollBackups = MaxLogFiles;
roller.MaximumFileSize = "{0}MB".e(this.MaxLogSizeInMB);
roller.RollingStyle = RollingFileAppender.RollingMode.Size;
roller.StaticLogFileName = true;
roller.ActivateOptions();
hierarchy.Root.AddAppender(roller);
MemoryAppender memory = new MemoryAppender();
memory.ActivateOptions();
hierarchy.Root.AddAppender(memory);
hierarchy.Root.Level = this.Level;
hierarchy.Configured = true;
//eFile.SaveAsString(hierarchy.Serialize(), ConfigurationFolder);
initialized = true;
}
}
}
代码可以在存储库中创建一个新的 ILogger,我可以开始记录,但配置似乎已保存到 运行 应用程序配置中,但我不知道如何获取它将配置保存到文件中。
如您所见,我已经注释掉了序列化和保存层次结构对象的那一行,因为我知道那是错误的——这不是序列化配置,它会抛出一个异常,错误反映层次结构的内部类型。
我觉得我想多了,我的代码可能会有很大不同。我真的只想要一个 log4Net Config-File-Generator,我可以将其交回我的静态记录器 class 以用于 XmlConfigurator.ConfigureAndWatch(string newConfigPath)
或 DOMConfigurator.ConfigureAndWatch(string newConfigPath)
供参考:
我的静态日志记录 class 看起来像这样(另外,它目前不起作用,因为我一直在使用它来实现我的新配置 Generator\Wrapper):
/// <summary>
/// Log4Net wrapper, based on (http://code.google.com/p/codecampserver/source/list)
/// </summary>
public static partial class eLog
{
private static readonly Dictionary<Assembly, Dictionary<Type, ILog>> _loggers = new Dictionary<Assembly, Dictionary<Type, ILog>>();
private static bool _logInitialized;
private static readonly object _lock = new object();
public static string SerializeException(Exception exception)
{
return SerializeException(exception, string.Empty);
}
private static string SerializeException(Exception e, string exceptionMessage)
{
if (e == null) return string.Empty;
exceptionMessage = string.Format(CultureInfo.InvariantCulture,
"{0}{1}{2}\n{3}",
exceptionMessage,
string.IsNullOrEmpty(exceptionMessage) ? string.Empty : "\n\n",
e.Message,
e.StackTrace);
if (e.InnerException != null)
exceptionMessage = SerializeException(e.InnerException, exceptionMessage);
return exceptionMessage;
}
private static ILog getLogger(Type source, Assembly assem)
{
EnsureInitialized();
//.Location.Split('.')[0]
lock (_lock)
{
if (!_loggers.ContainsKey(assem))
{
_loggers.Add(assem, new Dictionary<Type, ILog>());
}
if (_loggers[assem] == null)
{
_loggers[assem] = new Dictionary<Type, ILog>();
}
if (!_loggers[assem].ContainsKey(source))
{
var logger = LogManager.GetLogger(assem, source);
_loggers[assem].Add(source, logger);
}
return _loggers[assem][source];
}
}
/* Log a message object */
public static void Debug(object source, string message)
{
Debug(source.GetType(), message);
}
public static void Debug(object source, string message, params object[] ps)
{
Debug(source.GetType(), string.Format(message, ps));
}
public static void Debug(Type source, string message)
{
ILog logger = getLogger(source, Assembly.GetEntryAssembly());
if (logger.IsDebugEnabled)
logger.Debug(message);
}
public static void Info(object source, object message)
{
Info(source.GetType(), message);
}
public static void Info(Type source, object message)
{
ILog logger = getLogger(source, Assembly.GetEntryAssembly());
if (logger.IsInfoEnabled)
logger.Info(message);
}
public static void Warn(object source, object message)
{
Warn(source.GetType(), message);
}
public static void Warn(Type source, object message)
{
ILog logger = getLogger(source, Assembly.GetEntryAssembly());
if (logger.IsWarnEnabled)
logger.Warn(message);
}
public static void Error(object source, object message)
{
Error(source.GetType(), message);
}
public static void Error(Type source, object message)
{
ILog logger = getLogger(source, Assembly.GetEntryAssembly());
if (logger.IsErrorEnabled)
logger.Error(message);
}
public static void Fatal(object source, object message)
{
Fatal(source.GetType(), message);
}
public static void Fatal(Type source, object message)
{
ILog logger = getLogger(source, Assembly.GetEntryAssembly());
if (logger.IsFatalEnabled)
logger.Fatal(message);
}
/* Log a message object and exception */
public static void Debug(object source, object message, Exception exception)
{
Debug(source.GetType(), message, exception);
}
public static void Debug(Type source, object message, Exception exception)
{
getLogger(source, Assembly.GetEntryAssembly()).Debug(message, exception);
}
public static void Info(object source, object message, Exception exception)
{
Info(source.GetType(), message, exception);
}
public static void Info(Type source, object message, Exception exception)
{
getLogger(source, Assembly.GetEntryAssembly()).Info(message, exception);
}
public static void Warn(object source, object message, Exception exception)
{
Warn(source.GetType(), message, exception);
}
public static void Warn(Type source, object message, Exception exception)
{
getLogger(source, Assembly.GetEntryAssembly()).Warn(message, exception);
}
public static void Error(object source, object message, Exception exception)
{
Error(source.GetType(), message, exception);
}
public static void Error(Type source, object message, Exception exception)
{
getLogger(source, Assembly.GetEntryAssembly()).Error(message, exception);
}
public static void Fatal(object source, object message, Exception exception)
{
Fatal(source.GetType(), message, exception);
}
public static void Fatal(Type source, object message, Exception exception)
{
getLogger(source, Assembly.GetEntryAssembly()).Fatal(message, exception);
}
private static void initialize()
{
string path = Path.Combine(
eWindows.Paths.ProgramData, // ProgramData
Assembly.GetEntryAssembly().FullName.Split('.')[0]);
eLogConfig config = new eLogConfig(new DirectoryInfo(path));
config.Setup();
//XmlConfigurator.ConfigureAndWatch(new FileInfo(path));
//DOMConfigurator.Configure()
_logInitialized = true;
}
public static void EnsureInitialized()
{
if (!_logInitialized)
{
initialize();
}
}
}
我已经创建了以下简单的 Log4Net 配置和静态日志记录 class(基于 codecampserver 的工作),它使用 IXmlSerializable 接口进行序列化。静态日志 class 自动处理记录器的所有配置,并允许从引用日志 class 的任何程序集进行单行日志记录,它将在 C:\ProgramData\{MyRootNameSpace}\Configuration\
中创建新的配置文件和文件夹并记录C:\ProgramData\{MyRootNameSpace}\Logs\
目录中的文件。
*注意:此实现仅支持将日志记录到每个入口程序集的 1 个 Appender。可以添加其他记录器,但可能需要修改序列化和记录 classes。有关详细信息,请参阅底部的 "To-Do"。
using log4net;
using log4net.Appender;
using log4net.Config;
using log4net.Core;
using log4net.Layout;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Xml.Linq;
using System.Runtime.CompilerServices;
using System.Diagnostics;
using System.Security.AccessControl; // Used in IsWritable(directory)
using System.Security.Principal; // Used in IsWritable(directory)
namespace LogWrapper
{
public class LogConfig
{
/// <summary>
/// Initializes a new LogConfig object, for use with initializing a new Logger
/// </summary>
public LogConfig(Type type, Assembly assem)
{
Type = type;
EntryAssembly = assem;
}
private Type Type { get; set; }
private Assembly assembly = Assembly.GetEntryAssembly();
private Assembly EntryAssembly
{
get
{
return assembly;
}
set
{
if (assembly != null)
assembly = value;
this.Update();
}
}
public bool AppendToFile { get; set; } = true;
private string parentPath = String.Empty;
/// <summary>
/// This is the Path to the folder that will contain the
/// Configuation and Logs folder
/// Recommended path is ProgramData\CompanyName
/// </summary>
private string ParentPath
{
get
{
if (String.IsNullOrEmpty(parentPath))
{
return Path.Combine(
ProgramData, // ProgramData
EntryAssembly.FullName.Split('.')[0]);
}
else
{
return parentPath;
}
}
set
{
if (IsWritable(new DirectoryInfo(value)))
{
parentPath = value;
}
this.Update();
}
}
private string _pattern = "%-5level\t%date{yyyy-MM-dd HH:mm:ss.fff}\t[%thread]\t%logger\t%message%newline";
public string LogPattern
{
get
{
return this._pattern;
}
set
{
if (!String.IsNullOrEmpty(value))
{
this._pattern = value;
this.Update();
}
}
}
public int MaxLogFiles = 5;
public int MaxLogSizeInMB = 5;
private Level _level = Level.Debug;
public Level Level
{
get
{
return this._level;
}
set
{
_level = value;
this.Update();
}
}
private string LogFilePath
{
get
{
return Path.Combine(LogFolder,
EntryAssembly.GetName().Name
.ToFileName() + ".log");
}
}
private string ConfigFilePath
{
get
{
return Path.Combine(ConfigurationFolder,
Path.GetFileNameWithoutExtension(
EntryAssembly.Location
).ToFileName() + ".Log4Net.config.xml");
}
}
private string ConfigurationFolder
{
get
{
return Path.Combine(this.ParentPath, "Configuration");
}
}
private string LogFolder
{
get
{
return Path.Combine(this.ParentPath, "Logs");
}
}
private bool initialized = false;
internal ILog Setup()
{
return this.Update(false);
}
internal ILog Update(bool overwrite = true)
{
if (!Directory.Exists(this.ParentPath))
Directory.CreateDirectory(this.ParentPath);
if (!Directory.Exists(ConfigurationFolder))
Directory.CreateDirectory(ConfigurationFolder);
if (!Directory.Exists(LogFolder))
Directory.CreateDirectory(LogFolder);
if (!File.Exists(ConfigFilePath) || overwrite)
{
var config = this.Serialize();
File.Create(ConfigFilePath).Close();
File.WriteAllText(ConfigFilePath, config.ToString());
}
var logger = LogManager.GetLogger(EntryAssembly, Type);
XmlConfigurator.ConfigureAndWatch(new FileInfo(ConfigFilePath));
if (!initialized)
{
initialized = true;
}
return logger;
}
private XDocument Serialize()
{
XDocument xDoc = new XDocument();
//apender
var param = new XElement("param");
param.SetAttributeValue("name", "ConversionPattern");
param.SetAttributeValue("value", LogPattern);
var layout = new XElement("layout", param);
layout.SetAttributeValue("type", typeof(PatternLayout));
var file = new XElement("file");
file.SetAttributeValue("value", LogFilePath);
var append = new XElement("appendToFile");
append.SetAttributeValue("value", AppendToFile);
var rollingstyle = new XElement("rollingStyle");
rollingstyle.SetAttributeValue("value",
Enum.GetName(typeof(RollingFileAppender.RollingMode)
, RollingFileAppender.RollingMode.Size));
var maxFiles = new XElement("maxSizeRollBackups");
maxFiles.SetAttributeValue("value", MaxLogFiles);
var maxFileSize = new XElement("maximumFileSize");
maxFileSize.SetAttributeValue("value", "{0}MB".FormatWith(MaxLogSizeInMB));
var staticLogFile = new XElement("StaticLogFileName");
staticLogFile.SetAttributeValue("value", true);
var appender = new XElement("appender"
, file
, append
, rollingstyle
, maxFiles
, maxFileSize
, staticLogFile
, layout);
appender.SetAttributeValue("name", EntryAssembly.GetName().Name);
appender.SetAttributeValue("type", typeof(RollingFileAppender));
//Root
var level = new XElement("level");
level.SetAttributeValue("value", Level.Name);
var appRef = new XElement("appender-ref");
appRef.SetAttributeValue("ref", EntryAssembly.GetName().Name);
var root = new XElement("root", level, appRef);
//Config
var section = new XElement("section");
section.SetAttributeValue("name", "log4net");
section.SetAttributeValue("type", "{0}, {1}".FormatWith(typeof(Log4NetConfigurationSectionHandler), "log4net"));
var configSection = new XElement("configSections", section);
var configuration = new XElement("configuration",
configSection,
new XElement("log4net",
appender, root));
return XDocument.Parse(configuration.ToString());
}
//Helper Methods
private static string ProgramData
{
get
{
return Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
}
}
private static bool IsWritable(DirectoryInfo destDir)
{
if (string.IsNullOrEmpty(destDir.FullName) || !Directory.Exists(destDir.FullName)) return false;
try
{
DirectorySecurity security = Directory.GetAccessControl(destDir.FullName);
SecurityIdentifier users = new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null);
foreach (AuthorizationRule rule in security.GetAccessRules(true, true, typeof(SecurityIdentifier)))
{
if (rule.IdentityReference == users)
{
FileSystemAccessRule rights = ((FileSystemAccessRule)rule);
if (rights.AccessControlType == AccessControlType.Allow)
{
if (rights.FileSystemRights == (rights.FileSystemRights | FileSystemRights.Modify)) return true;
}
}
}
return false;
}
catch
{
return false;
}
}
}
/// <summary>
/// Log4Net wrapper, modified from codecampserver (http://code.google.com/p/codecampserver/source/list)
/// </summary>
public static partial class Log
{
private static readonly Dictionary<Assembly, Dictionary<Type, ILog>> _loggers = new Dictionary<Assembly, Dictionary<Type, ILog>>();
private static bool _logInitialized;
private static readonly object _lock = new object();
public static string SerializException(Exception exception)
{
return SerializException(exception, string.Empty);
}
private static string SerializException(Exception e, string exceptionMessage)
{
if (e == null) return string.Empty;
exceptionMessage = string.Format(CultureInfo.InvariantCulture,
"{0}{1}{2}\n{3}",
exceptionMessage,
string.IsNullOrEmpty(exceptionMessage) ? string.Empty : "\n\n",
e.Message,
e.StackTrace);
if (e.InnerException != null)
exceptionMessage = SerializException(e.InnerException, exceptionMessage);
return exceptionMessage;
}
private static ILog getLogger(Type source, Assembly assem)
{
//EnsureInitialized(assem, source);
lock (_lock)
{
if (!_loggers.ContainsKey(assem))
{
_loggers.Add(assem, new Dictionary<Type, ILog>());
}
if (_loggers[assem] == null)
{
_loggers[assem] = new Dictionary<Type, ILog>();
}
if (!_loggers[assem].ContainsKey(source))
{
_loggers[assem].Add(source, new LogConfig(source, assem).Setup());
}
var logger = _loggers[assem][source];
return logger;
}
}
public static void Debug(string message = "", Exception ex = null, [CallerMemberName] string methodName = "")
{
MethodBase method = new StackTrace().GetFrame(1).GetMethod();
Debug(method.DeclaringType, "{0}()\t{1}".FormatWith(methodName, message.ToCSVFormat()), ex);
}
public static void Info(string message = "", Exception ex = null, [CallerMemberName] string methodName = "")
{
MethodBase method = new StackTrace().GetFrame(1).GetMethod();
Info(method.DeclaringType, "{0}()\t{1}".FormatWith(methodName, message.ToCSVFormat()), ex);
}
public static void Warn(string message = "", Exception ex = null, [CallerMemberName] string methodName = "")
{
MethodBase method = new StackTrace().GetFrame(1).GetMethod();
Warn(method.DeclaringType, "{0}()\t{1}".FormatWith(methodName, message.ToCSVFormat()), ex);
}
public static void Error(string message = "", Exception ex = null, [CallerMemberName] string methodName = "", [CallerFilePath] string file = "")
{
MethodBase method = new StackTrace().GetFrame(1).GetMethod();
Error(method.DeclaringType, "{0}()\t{1} in {2}".FormatWith(methodName, message.ToCSVFormat(), file), ex);
}
public static void Error(Exception ex, [CallerMemberName] string methodName = "", [CallerFilePath] string file = "")
{
MethodBase method = new StackTrace().GetFrame(1).GetMethod();
Error(method.DeclaringType, "{0}()\t{1} in {2}".FormatWith(methodName, ex.Message.ToCSVFormat(), file), ex);
}
public static void Fatal(string message = "", Exception ex = null, [CallerMemberName] string methodName = "", [CallerLineNumber] int line = -1, [CallerFilePath] string file = "")
{
MethodBase method = new StackTrace().GetFrame(1).GetMethod();
Fatal(method.DeclaringType, "{0}()\t{1} at Line# {2} in [{3}]".FormatWith(methodName, message.ToCSVFormat()), ex);
}
public static void Fatal(Exception ex = null, [CallerMemberName] string methodName = "", [CallerLineNumber] int line = -1, [CallerFilePath] string file = "")
{
MethodBase method = new StackTrace().GetFrame(1).GetMethod();
Fatal(method.DeclaringType, "{0}()\t{1} at Line# {2} in [{3}]".FormatWith(methodName, ex.Message.ToCSVFormat()), ex);
}
public static LogConfig Config
{
get
{
return new LogConfig(MethodBase.GetCurrentMethod().DeclaringType, Assembly.GetEntryAssembly());
}
set
{
_loggers[Assembly.GetEntryAssembly()][MethodBase.GetCurrentMethod().DeclaringType] = value.Update();
}
}
/* Log a message object */
private static void Debug(Type source, string message)
{
ILog logger = getLogger(source, Assembly.GetEntryAssembly());
if (logger.IsDebugEnabled)
logger.Debug(message);
}
private static void Info(Type source, object message)
{
ILog logger = getLogger(source, Assembly.GetEntryAssembly());
if (logger.IsInfoEnabled)
logger.Info(message);
}
private static void Warn(Type source, object message)
{
ILog logger = getLogger(source, Assembly.GetEntryAssembly());
if (logger.IsWarnEnabled)
logger.Warn(message);
}
private static void Error(Type source, object message)
{
ILog logger = getLogger(source, Assembly.GetEntryAssembly());
if (logger.IsErrorEnabled)
logger.Error(message);
}
private static void Fatal(Type source, object message)
{
ILog logger = getLogger(source, Assembly.GetEntryAssembly());
if (logger.IsFatalEnabled)
logger.Fatal(message);
}
/* Log a message object and exception */
private static void Debug(object source, object message, Exception exception)
{
Debug(source.GetType(), message, exception);
}
private static void Debug(Type source, object message, Exception exception)
{
getLogger(source, Assembly.GetEntryAssembly()).Debug(message, exception);
}
private static void Debug(Type source, string message, Exception exception)
{
getLogger(source, Assembly.GetEntryAssembly()).Debug(message, exception);
}
private static void Info(object source, object message, Exception exception)
{
Info(source.GetType(), message, exception);
}
private static void Info(Type source, object message, Exception exception)
{
getLogger(source, Assembly.GetEntryAssembly()).Info(message, exception);
}
private static void Info(Type source, string message, Exception exception)
{
getLogger(source, Assembly.GetEntryAssembly()).Info(message, exception);
}
private static void Warn(object source, object message, Exception exception)
{
Warn(source.GetType(), message, exception);
}
private static void Warn(Type source, object message, Exception exception)
{
getLogger(source, Assembly.GetEntryAssembly()).Warn(message, exception);
}
private static void Warn(Type source, string message, Exception exception)
{
getLogger(source, Assembly.GetEntryAssembly()).Warn(message, exception);
}
private static void Error(object source, object message, Exception exception)
{
Error(source.GetType(), message, exception);
}
private static void Error(Type source, object message, Exception exception)
{
getLogger(source, Assembly.GetEntryAssembly()).Error(message, exception);
}
private static void Error(Type source, string message, Exception exception)
{
getLogger(source, Assembly.GetEntryAssembly()).Error(message, exception);
}
private static void Fatal(object source, object message, Exception exception)
{
Fatal(source.GetType(), message, exception);
}
private static void Fatal(Type source, object message, Exception exception)
{
getLogger(source, Assembly.GetEntryAssembly()).Fatal(message, exception);
}
private static void Fatal(Type source, string message, Exception exception)
{
getLogger(source, Assembly.GetEntryAssembly()).Fatal(message, exception);
}
private static void initialize(Assembly assem, Type t)
{
_logInitialized = true;
}
private static void EnsureInitialized(Assembly assem, Type t)
{
if (!_logInitialized)
{
initialize(assem, t);
}
}
}
}
用法和输出
用法示例:
namespace Test.Sandbox
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
//Example 1:
private void button1_Click(object sender, RoutedEventArgs e)
{
Log.Debug();
Log.Debug("button1 Clicked");
Log.Debug("Fake Error Occured", new Exception("Fake Error"));
}
//Example 2:
private void button2_Click(object sender, RoutedEventArgs e)
{
Log.Config.Level = Level.Error; // Set error level (updates logconfig.xml)
Log.Debug(); //Does not get written because Level has been set to 'Error', above
Log.Error();
Log.Error("This is an Error");
Log.Error(new Exception("Fake Error 2"));
}
}
}
来自button1_Click的输出:
DEBUG 2015-12-07 13:11:05.618 [9] Test.Sandbox.MainWindow button1_Click()
DEBUG 2015-12-07 13:11:05.623 [9] Test.Sandbox.MainWindow button1_Click() button1 Clicked
DEBUG 2015-12-07 13:11:05.623 [9] Test.Sandbox.MainWindow button1_Click() Fake Error Occured
System.Exception: Fake Error
来自button2_Click的输出:
ERROR 2015-12-07 13:11:07.033 [9] Test.Sandbox.MainWindow button2_Click() in C:\Projects\Test.NET\Test.Sandbox\MainWindow.xaml.cs
ERROR 2015-12-07 13:11:07.034 [9] Test.Sandbox.MainWindow button2_Click() This is an Error in C:\Projects\Test.NET\Test.Sandbox\MainWindow.xaml.cs
ERROR 2015-12-07 13:11:07.034 [9] Test.Sandbox.MainWindow button2_Click() Fake Error 2 in C:\Projects\Test.NET\Test.Sandbox\MainWindow.xaml.cs
System.Exception: Fake Error 2
输出文件夹结构:
记录器可配置属性:
设置这些属性将自动更新与 Entry-Assembly(& 类型)关联的 logger.config.xml 文件
Log.Config.AppendToFile = true;
Log.Config.Level = Level.Debug;
Log.Config.LogPattern = "%-5level %date %message";
Log.Config.MaxLogFiles = 5;
Log.Config.MaxLogSizeInMB = 5;
待办事项:
- (性能改进)找到另一种获取
CallingMethod.DeclaringType
的替代方法,而无需在 Log.Debug()
、Log.Info()
和 Log.Warn()
消息上使用 StackTrace。
- 为
Log.Error()
和 Log.Fatal()
收集额外的 StackTrace 信息
- 多 Appender 支持
- 可配置的输出文件夹路径
- 实施 PatternBuilder class & 接口
我在静态日志记录中封装 log4net 时遇到了一些问题 class。
我正在使用以下(未完成)class 动态生成 log4Net 配置:
public class eLogConfig
{
public bool AppendToFile = true;
/// <summary>
/// This is the Path to the folder that will contain the
/// Configuation and Logs folder
/// Recommended path is ProgramData\CompanyName
/// </summary>
public string ParentPath { get; set; }
public string LogPattern = "%date [%thread] %-5level %logger - %message%newline";
public int MaxLogFiles = 10;
public int MaxLogSizeInMB = 10;
public Level Level = Level.Debug;
public string LogFilePath
{
get
{
return Path.Combine(LogFolder,
Assembly.GetEntryAssembly().GetName().Name
.e().ToFileName() + ".Log.txt");
}
}
public string ConfigFilePath
{
get
{
return Path.GetFileNameWithoutExtension(
Assembly.GetEntryAssembly().Location
).e().ToFileName() + ".Log4Net.config";
}
}
private string ConfigurationFolder
{
get
{
return Path.Combine(this.ParentPath, "Configuration");
}
}
private string LogFolder
{
get
{
return Path.Combine(this.ParentPath, "Logs");
}
}
/// <summary>
/// Initializes a new eLogConfiguration object, for use with initializing a new eLog Logger
/// </summary>
/// <param name="ParentPath">The path which will contain the Configuration and Logs folders</param>
public eLogConfig(DirectoryInfo ParentPath)
{
this.ParentPath = ParentPath.FullName;
if (!Directory.Exists(this.ParentPath))
Directory.CreateDirectory(this.ParentPath);
if (!Directory.Exists(ConfigurationFolder))
Directory.CreateDirectory(ConfigurationFolder);
if (!Directory.Exists(LogFolder))
Directory.CreateDirectory(LogFolder);
}
private bool initialized = false;
public void Setup()
{
if (!initialized || !File.Exists(LogFilePath))
{
Hierarchy hierarchy = (Hierarchy)LogManager.GetRepository();
PatternLayout patternLayout = new PatternLayout();
patternLayout.ConversionPattern = this.LogPattern;
patternLayout.ActivateOptions();
RollingFileAppender roller = new RollingFileAppender();
roller.AppendToFile = this.AppendToFile;
roller.File = this.LogFilePath;
roller.Layout = patternLayout;
roller.MaxSizeRollBackups = MaxLogFiles;
roller.MaximumFileSize = "{0}MB".e(this.MaxLogSizeInMB);
roller.RollingStyle = RollingFileAppender.RollingMode.Size;
roller.StaticLogFileName = true;
roller.ActivateOptions();
hierarchy.Root.AddAppender(roller);
MemoryAppender memory = new MemoryAppender();
memory.ActivateOptions();
hierarchy.Root.AddAppender(memory);
hierarchy.Root.Level = this.Level;
hierarchy.Configured = true;
//eFile.SaveAsString(hierarchy.Serialize(), ConfigurationFolder);
initialized = true;
}
}
}
代码可以在存储库中创建一个新的 ILogger,我可以开始记录,但配置似乎已保存到 运行 应用程序配置中,但我不知道如何获取它将配置保存到文件中。 如您所见,我已经注释掉了序列化和保存层次结构对象的那一行,因为我知道那是错误的——这不是序列化配置,它会抛出一个异常,错误反映层次结构的内部类型。
我觉得我想多了,我的代码可能会有很大不同。我真的只想要一个 log4Net Config-File-Generator,我可以将其交回我的静态记录器 class 以用于 XmlConfigurator.ConfigureAndWatch(string newConfigPath)
或 DOMConfigurator.ConfigureAndWatch(string newConfigPath)
供参考:
我的静态日志记录 class 看起来像这样(另外,它目前不起作用,因为我一直在使用它来实现我的新配置 Generator\Wrapper):
/// <summary>
/// Log4Net wrapper, based on (http://code.google.com/p/codecampserver/source/list)
/// </summary>
public static partial class eLog
{
private static readonly Dictionary<Assembly, Dictionary<Type, ILog>> _loggers = new Dictionary<Assembly, Dictionary<Type, ILog>>();
private static bool _logInitialized;
private static readonly object _lock = new object();
public static string SerializeException(Exception exception)
{
return SerializeException(exception, string.Empty);
}
private static string SerializeException(Exception e, string exceptionMessage)
{
if (e == null) return string.Empty;
exceptionMessage = string.Format(CultureInfo.InvariantCulture,
"{0}{1}{2}\n{3}",
exceptionMessage,
string.IsNullOrEmpty(exceptionMessage) ? string.Empty : "\n\n",
e.Message,
e.StackTrace);
if (e.InnerException != null)
exceptionMessage = SerializeException(e.InnerException, exceptionMessage);
return exceptionMessage;
}
private static ILog getLogger(Type source, Assembly assem)
{
EnsureInitialized();
//.Location.Split('.')[0]
lock (_lock)
{
if (!_loggers.ContainsKey(assem))
{
_loggers.Add(assem, new Dictionary<Type, ILog>());
}
if (_loggers[assem] == null)
{
_loggers[assem] = new Dictionary<Type, ILog>();
}
if (!_loggers[assem].ContainsKey(source))
{
var logger = LogManager.GetLogger(assem, source);
_loggers[assem].Add(source, logger);
}
return _loggers[assem][source];
}
}
/* Log a message object */
public static void Debug(object source, string message)
{
Debug(source.GetType(), message);
}
public static void Debug(object source, string message, params object[] ps)
{
Debug(source.GetType(), string.Format(message, ps));
}
public static void Debug(Type source, string message)
{
ILog logger = getLogger(source, Assembly.GetEntryAssembly());
if (logger.IsDebugEnabled)
logger.Debug(message);
}
public static void Info(object source, object message)
{
Info(source.GetType(), message);
}
public static void Info(Type source, object message)
{
ILog logger = getLogger(source, Assembly.GetEntryAssembly());
if (logger.IsInfoEnabled)
logger.Info(message);
}
public static void Warn(object source, object message)
{
Warn(source.GetType(), message);
}
public static void Warn(Type source, object message)
{
ILog logger = getLogger(source, Assembly.GetEntryAssembly());
if (logger.IsWarnEnabled)
logger.Warn(message);
}
public static void Error(object source, object message)
{
Error(source.GetType(), message);
}
public static void Error(Type source, object message)
{
ILog logger = getLogger(source, Assembly.GetEntryAssembly());
if (logger.IsErrorEnabled)
logger.Error(message);
}
public static void Fatal(object source, object message)
{
Fatal(source.GetType(), message);
}
public static void Fatal(Type source, object message)
{
ILog logger = getLogger(source, Assembly.GetEntryAssembly());
if (logger.IsFatalEnabled)
logger.Fatal(message);
}
/* Log a message object and exception */
public static void Debug(object source, object message, Exception exception)
{
Debug(source.GetType(), message, exception);
}
public static void Debug(Type source, object message, Exception exception)
{
getLogger(source, Assembly.GetEntryAssembly()).Debug(message, exception);
}
public static void Info(object source, object message, Exception exception)
{
Info(source.GetType(), message, exception);
}
public static void Info(Type source, object message, Exception exception)
{
getLogger(source, Assembly.GetEntryAssembly()).Info(message, exception);
}
public static void Warn(object source, object message, Exception exception)
{
Warn(source.GetType(), message, exception);
}
public static void Warn(Type source, object message, Exception exception)
{
getLogger(source, Assembly.GetEntryAssembly()).Warn(message, exception);
}
public static void Error(object source, object message, Exception exception)
{
Error(source.GetType(), message, exception);
}
public static void Error(Type source, object message, Exception exception)
{
getLogger(source, Assembly.GetEntryAssembly()).Error(message, exception);
}
public static void Fatal(object source, object message, Exception exception)
{
Fatal(source.GetType(), message, exception);
}
public static void Fatal(Type source, object message, Exception exception)
{
getLogger(source, Assembly.GetEntryAssembly()).Fatal(message, exception);
}
private static void initialize()
{
string path = Path.Combine(
eWindows.Paths.ProgramData, // ProgramData
Assembly.GetEntryAssembly().FullName.Split('.')[0]);
eLogConfig config = new eLogConfig(new DirectoryInfo(path));
config.Setup();
//XmlConfigurator.ConfigureAndWatch(new FileInfo(path));
//DOMConfigurator.Configure()
_logInitialized = true;
}
public static void EnsureInitialized()
{
if (!_logInitialized)
{
initialize();
}
}
}
我已经创建了以下简单的 Log4Net 配置和静态日志记录 class(基于 codecampserver 的工作),它使用 IXmlSerializable 接口进行序列化。静态日志 class 自动处理记录器的所有配置,并允许从引用日志 class 的任何程序集进行单行日志记录,它将在 C:\ProgramData\{MyRootNameSpace}\Configuration\
中创建新的配置文件和文件夹并记录C:\ProgramData\{MyRootNameSpace}\Logs\
目录中的文件。
*注意:此实现仅支持将日志记录到每个入口程序集的 1 个 Appender。可以添加其他记录器,但可能需要修改序列化和记录 classes。有关详细信息,请参阅底部的 "To-Do"。
using log4net;
using log4net.Appender;
using log4net.Config;
using log4net.Core;
using log4net.Layout;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Xml.Linq;
using System.Runtime.CompilerServices;
using System.Diagnostics;
using System.Security.AccessControl; // Used in IsWritable(directory)
using System.Security.Principal; // Used in IsWritable(directory)
namespace LogWrapper
{
public class LogConfig
{
/// <summary>
/// Initializes a new LogConfig object, for use with initializing a new Logger
/// </summary>
public LogConfig(Type type, Assembly assem)
{
Type = type;
EntryAssembly = assem;
}
private Type Type { get; set; }
private Assembly assembly = Assembly.GetEntryAssembly();
private Assembly EntryAssembly
{
get
{
return assembly;
}
set
{
if (assembly != null)
assembly = value;
this.Update();
}
}
public bool AppendToFile { get; set; } = true;
private string parentPath = String.Empty;
/// <summary>
/// This is the Path to the folder that will contain the
/// Configuation and Logs folder
/// Recommended path is ProgramData\CompanyName
/// </summary>
private string ParentPath
{
get
{
if (String.IsNullOrEmpty(parentPath))
{
return Path.Combine(
ProgramData, // ProgramData
EntryAssembly.FullName.Split('.')[0]);
}
else
{
return parentPath;
}
}
set
{
if (IsWritable(new DirectoryInfo(value)))
{
parentPath = value;
}
this.Update();
}
}
private string _pattern = "%-5level\t%date{yyyy-MM-dd HH:mm:ss.fff}\t[%thread]\t%logger\t%message%newline";
public string LogPattern
{
get
{
return this._pattern;
}
set
{
if (!String.IsNullOrEmpty(value))
{
this._pattern = value;
this.Update();
}
}
}
public int MaxLogFiles = 5;
public int MaxLogSizeInMB = 5;
private Level _level = Level.Debug;
public Level Level
{
get
{
return this._level;
}
set
{
_level = value;
this.Update();
}
}
private string LogFilePath
{
get
{
return Path.Combine(LogFolder,
EntryAssembly.GetName().Name
.ToFileName() + ".log");
}
}
private string ConfigFilePath
{
get
{
return Path.Combine(ConfigurationFolder,
Path.GetFileNameWithoutExtension(
EntryAssembly.Location
).ToFileName() + ".Log4Net.config.xml");
}
}
private string ConfigurationFolder
{
get
{
return Path.Combine(this.ParentPath, "Configuration");
}
}
private string LogFolder
{
get
{
return Path.Combine(this.ParentPath, "Logs");
}
}
private bool initialized = false;
internal ILog Setup()
{
return this.Update(false);
}
internal ILog Update(bool overwrite = true)
{
if (!Directory.Exists(this.ParentPath))
Directory.CreateDirectory(this.ParentPath);
if (!Directory.Exists(ConfigurationFolder))
Directory.CreateDirectory(ConfigurationFolder);
if (!Directory.Exists(LogFolder))
Directory.CreateDirectory(LogFolder);
if (!File.Exists(ConfigFilePath) || overwrite)
{
var config = this.Serialize();
File.Create(ConfigFilePath).Close();
File.WriteAllText(ConfigFilePath, config.ToString());
}
var logger = LogManager.GetLogger(EntryAssembly, Type);
XmlConfigurator.ConfigureAndWatch(new FileInfo(ConfigFilePath));
if (!initialized)
{
initialized = true;
}
return logger;
}
private XDocument Serialize()
{
XDocument xDoc = new XDocument();
//apender
var param = new XElement("param");
param.SetAttributeValue("name", "ConversionPattern");
param.SetAttributeValue("value", LogPattern);
var layout = new XElement("layout", param);
layout.SetAttributeValue("type", typeof(PatternLayout));
var file = new XElement("file");
file.SetAttributeValue("value", LogFilePath);
var append = new XElement("appendToFile");
append.SetAttributeValue("value", AppendToFile);
var rollingstyle = new XElement("rollingStyle");
rollingstyle.SetAttributeValue("value",
Enum.GetName(typeof(RollingFileAppender.RollingMode)
, RollingFileAppender.RollingMode.Size));
var maxFiles = new XElement("maxSizeRollBackups");
maxFiles.SetAttributeValue("value", MaxLogFiles);
var maxFileSize = new XElement("maximumFileSize");
maxFileSize.SetAttributeValue("value", "{0}MB".FormatWith(MaxLogSizeInMB));
var staticLogFile = new XElement("StaticLogFileName");
staticLogFile.SetAttributeValue("value", true);
var appender = new XElement("appender"
, file
, append
, rollingstyle
, maxFiles
, maxFileSize
, staticLogFile
, layout);
appender.SetAttributeValue("name", EntryAssembly.GetName().Name);
appender.SetAttributeValue("type", typeof(RollingFileAppender));
//Root
var level = new XElement("level");
level.SetAttributeValue("value", Level.Name);
var appRef = new XElement("appender-ref");
appRef.SetAttributeValue("ref", EntryAssembly.GetName().Name);
var root = new XElement("root", level, appRef);
//Config
var section = new XElement("section");
section.SetAttributeValue("name", "log4net");
section.SetAttributeValue("type", "{0}, {1}".FormatWith(typeof(Log4NetConfigurationSectionHandler), "log4net"));
var configSection = new XElement("configSections", section);
var configuration = new XElement("configuration",
configSection,
new XElement("log4net",
appender, root));
return XDocument.Parse(configuration.ToString());
}
//Helper Methods
private static string ProgramData
{
get
{
return Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
}
}
private static bool IsWritable(DirectoryInfo destDir)
{
if (string.IsNullOrEmpty(destDir.FullName) || !Directory.Exists(destDir.FullName)) return false;
try
{
DirectorySecurity security = Directory.GetAccessControl(destDir.FullName);
SecurityIdentifier users = new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null);
foreach (AuthorizationRule rule in security.GetAccessRules(true, true, typeof(SecurityIdentifier)))
{
if (rule.IdentityReference == users)
{
FileSystemAccessRule rights = ((FileSystemAccessRule)rule);
if (rights.AccessControlType == AccessControlType.Allow)
{
if (rights.FileSystemRights == (rights.FileSystemRights | FileSystemRights.Modify)) return true;
}
}
}
return false;
}
catch
{
return false;
}
}
}
/// <summary>
/// Log4Net wrapper, modified from codecampserver (http://code.google.com/p/codecampserver/source/list)
/// </summary>
public static partial class Log
{
private static readonly Dictionary<Assembly, Dictionary<Type, ILog>> _loggers = new Dictionary<Assembly, Dictionary<Type, ILog>>();
private static bool _logInitialized;
private static readonly object _lock = new object();
public static string SerializException(Exception exception)
{
return SerializException(exception, string.Empty);
}
private static string SerializException(Exception e, string exceptionMessage)
{
if (e == null) return string.Empty;
exceptionMessage = string.Format(CultureInfo.InvariantCulture,
"{0}{1}{2}\n{3}",
exceptionMessage,
string.IsNullOrEmpty(exceptionMessage) ? string.Empty : "\n\n",
e.Message,
e.StackTrace);
if (e.InnerException != null)
exceptionMessage = SerializException(e.InnerException, exceptionMessage);
return exceptionMessage;
}
private static ILog getLogger(Type source, Assembly assem)
{
//EnsureInitialized(assem, source);
lock (_lock)
{
if (!_loggers.ContainsKey(assem))
{
_loggers.Add(assem, new Dictionary<Type, ILog>());
}
if (_loggers[assem] == null)
{
_loggers[assem] = new Dictionary<Type, ILog>();
}
if (!_loggers[assem].ContainsKey(source))
{
_loggers[assem].Add(source, new LogConfig(source, assem).Setup());
}
var logger = _loggers[assem][source];
return logger;
}
}
public static void Debug(string message = "", Exception ex = null, [CallerMemberName] string methodName = "")
{
MethodBase method = new StackTrace().GetFrame(1).GetMethod();
Debug(method.DeclaringType, "{0}()\t{1}".FormatWith(methodName, message.ToCSVFormat()), ex);
}
public static void Info(string message = "", Exception ex = null, [CallerMemberName] string methodName = "")
{
MethodBase method = new StackTrace().GetFrame(1).GetMethod();
Info(method.DeclaringType, "{0}()\t{1}".FormatWith(methodName, message.ToCSVFormat()), ex);
}
public static void Warn(string message = "", Exception ex = null, [CallerMemberName] string methodName = "")
{
MethodBase method = new StackTrace().GetFrame(1).GetMethod();
Warn(method.DeclaringType, "{0}()\t{1}".FormatWith(methodName, message.ToCSVFormat()), ex);
}
public static void Error(string message = "", Exception ex = null, [CallerMemberName] string methodName = "", [CallerFilePath] string file = "")
{
MethodBase method = new StackTrace().GetFrame(1).GetMethod();
Error(method.DeclaringType, "{0}()\t{1} in {2}".FormatWith(methodName, message.ToCSVFormat(), file), ex);
}
public static void Error(Exception ex, [CallerMemberName] string methodName = "", [CallerFilePath] string file = "")
{
MethodBase method = new StackTrace().GetFrame(1).GetMethod();
Error(method.DeclaringType, "{0}()\t{1} in {2}".FormatWith(methodName, ex.Message.ToCSVFormat(), file), ex);
}
public static void Fatal(string message = "", Exception ex = null, [CallerMemberName] string methodName = "", [CallerLineNumber] int line = -1, [CallerFilePath] string file = "")
{
MethodBase method = new StackTrace().GetFrame(1).GetMethod();
Fatal(method.DeclaringType, "{0}()\t{1} at Line# {2} in [{3}]".FormatWith(methodName, message.ToCSVFormat()), ex);
}
public static void Fatal(Exception ex = null, [CallerMemberName] string methodName = "", [CallerLineNumber] int line = -1, [CallerFilePath] string file = "")
{
MethodBase method = new StackTrace().GetFrame(1).GetMethod();
Fatal(method.DeclaringType, "{0}()\t{1} at Line# {2} in [{3}]".FormatWith(methodName, ex.Message.ToCSVFormat()), ex);
}
public static LogConfig Config
{
get
{
return new LogConfig(MethodBase.GetCurrentMethod().DeclaringType, Assembly.GetEntryAssembly());
}
set
{
_loggers[Assembly.GetEntryAssembly()][MethodBase.GetCurrentMethod().DeclaringType] = value.Update();
}
}
/* Log a message object */
private static void Debug(Type source, string message)
{
ILog logger = getLogger(source, Assembly.GetEntryAssembly());
if (logger.IsDebugEnabled)
logger.Debug(message);
}
private static void Info(Type source, object message)
{
ILog logger = getLogger(source, Assembly.GetEntryAssembly());
if (logger.IsInfoEnabled)
logger.Info(message);
}
private static void Warn(Type source, object message)
{
ILog logger = getLogger(source, Assembly.GetEntryAssembly());
if (logger.IsWarnEnabled)
logger.Warn(message);
}
private static void Error(Type source, object message)
{
ILog logger = getLogger(source, Assembly.GetEntryAssembly());
if (logger.IsErrorEnabled)
logger.Error(message);
}
private static void Fatal(Type source, object message)
{
ILog logger = getLogger(source, Assembly.GetEntryAssembly());
if (logger.IsFatalEnabled)
logger.Fatal(message);
}
/* Log a message object and exception */
private static void Debug(object source, object message, Exception exception)
{
Debug(source.GetType(), message, exception);
}
private static void Debug(Type source, object message, Exception exception)
{
getLogger(source, Assembly.GetEntryAssembly()).Debug(message, exception);
}
private static void Debug(Type source, string message, Exception exception)
{
getLogger(source, Assembly.GetEntryAssembly()).Debug(message, exception);
}
private static void Info(object source, object message, Exception exception)
{
Info(source.GetType(), message, exception);
}
private static void Info(Type source, object message, Exception exception)
{
getLogger(source, Assembly.GetEntryAssembly()).Info(message, exception);
}
private static void Info(Type source, string message, Exception exception)
{
getLogger(source, Assembly.GetEntryAssembly()).Info(message, exception);
}
private static void Warn(object source, object message, Exception exception)
{
Warn(source.GetType(), message, exception);
}
private static void Warn(Type source, object message, Exception exception)
{
getLogger(source, Assembly.GetEntryAssembly()).Warn(message, exception);
}
private static void Warn(Type source, string message, Exception exception)
{
getLogger(source, Assembly.GetEntryAssembly()).Warn(message, exception);
}
private static void Error(object source, object message, Exception exception)
{
Error(source.GetType(), message, exception);
}
private static void Error(Type source, object message, Exception exception)
{
getLogger(source, Assembly.GetEntryAssembly()).Error(message, exception);
}
private static void Error(Type source, string message, Exception exception)
{
getLogger(source, Assembly.GetEntryAssembly()).Error(message, exception);
}
private static void Fatal(object source, object message, Exception exception)
{
Fatal(source.GetType(), message, exception);
}
private static void Fatal(Type source, object message, Exception exception)
{
getLogger(source, Assembly.GetEntryAssembly()).Fatal(message, exception);
}
private static void Fatal(Type source, string message, Exception exception)
{
getLogger(source, Assembly.GetEntryAssembly()).Fatal(message, exception);
}
private static void initialize(Assembly assem, Type t)
{
_logInitialized = true;
}
private static void EnsureInitialized(Assembly assem, Type t)
{
if (!_logInitialized)
{
initialize(assem, t);
}
}
}
}
用法和输出
用法示例:
namespace Test.Sandbox
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
//Example 1:
private void button1_Click(object sender, RoutedEventArgs e)
{
Log.Debug();
Log.Debug("button1 Clicked");
Log.Debug("Fake Error Occured", new Exception("Fake Error"));
}
//Example 2:
private void button2_Click(object sender, RoutedEventArgs e)
{
Log.Config.Level = Level.Error; // Set error level (updates logconfig.xml)
Log.Debug(); //Does not get written because Level has been set to 'Error', above
Log.Error();
Log.Error("This is an Error");
Log.Error(new Exception("Fake Error 2"));
}
}
}
来自button1_Click的输出:
DEBUG 2015-12-07 13:11:05.618 [9] Test.Sandbox.MainWindow button1_Click()
DEBUG 2015-12-07 13:11:05.623 [9] Test.Sandbox.MainWindow button1_Click() button1 Clicked
DEBUG 2015-12-07 13:11:05.623 [9] Test.Sandbox.MainWindow button1_Click() Fake Error Occured
System.Exception: Fake Error
来自button2_Click的输出:
ERROR 2015-12-07 13:11:07.033 [9] Test.Sandbox.MainWindow button2_Click() in C:\Projects\Test.NET\Test.Sandbox\MainWindow.xaml.cs
ERROR 2015-12-07 13:11:07.034 [9] Test.Sandbox.MainWindow button2_Click() This is an Error in C:\Projects\Test.NET\Test.Sandbox\MainWindow.xaml.cs
ERROR 2015-12-07 13:11:07.034 [9] Test.Sandbox.MainWindow button2_Click() Fake Error 2 in C:\Projects\Test.NET\Test.Sandbox\MainWindow.xaml.cs
System.Exception: Fake Error 2
输出文件夹结构:
记录器可配置属性:
设置这些属性将自动更新与 Entry-Assembly(& 类型)关联的 logger.config.xml 文件
Log.Config.AppendToFile = true;
Log.Config.Level = Level.Debug;
Log.Config.LogPattern = "%-5level %date %message";
Log.Config.MaxLogFiles = 5;
Log.Config.MaxLogSizeInMB = 5;
待办事项:
- (性能改进)找到另一种获取
CallingMethod.DeclaringType
的替代方法,而无需在Log.Debug()
、Log.Info()
和Log.Warn()
消息上使用 StackTrace。 - 为
Log.Error()
和Log.Fatal()
收集额外的 StackTrace 信息
- 多 Appender 支持
- 可配置的输出文件夹路径
- 实施 PatternBuilder class & 接口