如何在log4net消息中添加类别前缀?

ton*_*yjy 7 .net c# logging log4net attributes

我想为现有日志消息上的所有消息添加类别前缀.但是,将此前缀逐个添加到所有现有日志记录消息中非常繁琐.有没有办法可以在类级别添加一个属性,那么这个类中的所有消息都会记录到某个类别?

而不是现在的方式如下,

Log.Info("[Ref] Level 1 Starts ...");
Run Code Online (Sandbox Code Playgroud)

我真的想要这样或类似的方式来定义log4net.ILog.

[LoggingCategory("Ref")]
public class MyClass 
{
   public void MyMethod()
   {
        Log.Info("Level 1 Starts ...");
   }
}
Run Code Online (Sandbox Code Playgroud)

wag*_*ghe 9

您正在询问如何通过属性执行此操作.@ Jonathan的建议看起来可能会正常工作,但您可以使用log4net的内置功能获得足够好的结果.

如果要将类分组为"类别",则可以根据类别名称而不是类名检索记录器.设置输出格式时,可以使用记录器格式化标记告诉log4net在输出中写入记录器名称.

通常,人们会根据类名检索记录器,如下所示:

public class Typical
{
  private static readonly ILog logger = 
       LogManager.GetLogger
          (System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

  public void F()
  {
    logger.Info("this message will be tagged with the classname");
  }
}
Run Code Online (Sandbox Code Playgroud)

基于任意名称检索记录器是完全可以接受的,如下所示:

public class A
{
  private static readonly ILog logger = LogManager.GetLogger("REF");

  public void F()
  {
    logger.Info("this message will be tagged with REF");
  }
}

public class B
{
  private static readonly ILog logger = LogManager.GetLogger("REF");

  public void F()
  {
    logger.Info("this message will be tagged with REF");
  }
}

public class C
{
  private static readonly ILog logger = LogManager.GetLogger("UMP");

  public void F()
  {
    logger.Info("this message will be tagged with UMP");
  }
}
Run Code Online (Sandbox Code Playgroud)

在前面的示例中,类A和B被认为是在相同的"类别"中,因此它们检索具有相同名称的记录器.C类属于不同的类别,因此它使用不同的名称检索记录器.

您可以使用自己的"类别"层次结构配置记录器(在配置文件中):

App
App.DataAccess
App.DataAccess.Create
App.DataAccess.Read
App.DataAccess.Update
App.DataAccess.Delete
App.UI
App.UI.Login
App.UI.Query
App.UI.Options
Run Code Online (Sandbox Code Playgroud)

您还可以配置记录器输出格式以仅记录完全限定记录器名称的一部分.像这样的东西:

%logger:2
Run Code Online (Sandbox Code Playgroud)

获取完全限定名称的最后两部分.例如,如果您的类的完全限定名称是:

NameSpaceA.NameSpaceB.NameSpaceC.Class
Run Code Online (Sandbox Code Playgroud)

然后上面的格式将输出为记录器名称:

NameSpaceC.Class
Run Code Online (Sandbox Code Playgroud)

我不是100%肯定语法,因为我没有使用它,我现在找不到一个好的例子.

这种方法的一个缺点是你必须定义和记住你的类别是什么,你必须决定每个类的适当类别(如果你想用包含其类别的属性装饰每个类,你也有这个问题).此外,如果您在同一类别中有多个类,则无法打开或关闭日志记录或更改这些类的子集的日志记录级别.

也许在命名空间层次结构中记录单个命名空间会很有用:

也许你可以根据命名空间对你的类进行"分类".因此,您可能希望将类的直接父命名空间记录为其类别.

因此,对于上面的完全限定类名,您可能希望将"NameSpaceC"记录为"类别"或记录器名称.

我不确定你是否可以通过log4net开箱即用,但你可以轻松编写一个PatternLayoutConverter来获取记录器名称并去除类名和任何"更高级别"命名空间.

这是一个自定义PatternLayoutConverter示例的链接.采用一个参数,在我的例子中,我想用它来查找字典中的值.在这种情况下,参数可以表示从完全限定记录器名称的END开始的偏移量(与log4net内置的记录器名称布局对象的参数相同),但是可以添加其他代码以仅记录单个命名空间指数.

自定义log4net属性PatternLayoutConverter(带索引)

同样,给定这个完全限定的类名:

NameSpaceA.NameSpaceB.NameSpaceC.Class
Run Code Online (Sandbox Code Playgroud)

您可以将直接父命名空间视为"类别".如果您定义了一个自定义PatternLayoutConverter,category并且它接受了一个参数,那么您的配置可能如下所示:

%category
Run Code Online (Sandbox Code Playgroud)

默认情况下,它将返回最后一个和下一个到最后一个'.'字符之间的子字符串.给定一个参数,它可以返回链上的任何离散命名空间.

PatternLayoutConverter可能看起来像这样(未经测试):

  class CategoryLookupPatternConverter : PatternLayoutConverter
  {
    protected override void Convert(System.IO.TextWriter writer, LoggingEvent loggingEvent)
    {
      //Assumes logger name is fully qualified classname.  Need smarter code to handle
      //arbitrary logger names.
      string [] names = loggingEvent.LoggerName.Split('.');
      string cat = names[names.Length - 1];
      writer.Write(setting);
    }
  }
Run Code Online (Sandbox Code Playgroud)

或者,使用Option属性获取第N个命名空间名称(相对于结尾):

  class CategoryLookupPatternConverter : PatternLayoutConverter
  {
    protected override void Convert(System.IO.TextWriter writer, LoggingEvent loggingEvent)
    {
      //Assumes logger name is fully qualified classname.  Need smarter code to handle
      //arbitrary logger names.
      string [] names = loggingEvent.LoggerName.Split('.');
      string cat;
      if (Option > 0 && Option < names.Length)
      {
        cat = names[names.Length - Option];
      }
      else
      {
        string cat = names[names.Length - 1];
      }
      writer.Write(setting);
    }

  }
Run Code Online (Sandbox Code Playgroud)

来自@Jonathan的想法非常酷,但它确实增加了一些额外的编码来定义和维护一个新的记录器包装器(但是很多人这样做并且没有发现它是一个特别繁重的负担).当然,我的自定义PatternLayoutConverter想法也需要您自定义代码.

另一个缺点是GetCategory看起来调用每个日志记录调用可能相当昂贵.


Jon*_*han 5

有趣的问题,艰难的尝试...

Log4NetLogger -记录适配器

public class Log4NetLogger
{
    private readonly ILog _logger;
    private readonly string _category;

    public Log4NetLogger(Type type)
    {
        _logger = LogManager.GetLogger(type);
        _category = GetCategory();
    }

    private string GetCategory()
    {
        var attributes = new StackFrame(2).GetMethod().DeclaringType.GetCustomAttributes(typeof(LoggingCategoryAttribute), false);
        if (attributes.Length == 1)
        {
            var attr = (LoggingCategoryAttribute)attributes[0];
            return attr.Category;
        }
        return string.Empty;
    }

    public void Debug(string message)
    {
        if(_logger.IsDebugEnabled) _logger.Debug(string.Format("[{0}] {1}", _category, message));
    }
}
Run Code Online (Sandbox Code Playgroud)

LoggingCategoryAttribute -适用于班级

[AttributeUsage(AttributeTargets.Class)]
public class LoggingCategoryAttribute : Attribute
{
    private readonly string _category;

    public LoggingCategoryAttribute(string category)
    {
        _category = category;
    }

    public string Category { get { return _category; } }
}
Run Code Online (Sandbox Code Playgroud)

LogTester -测试实施

[LoggingCategory("LT")]
public class LogTester
{
    private static readonly Log4NetLogger Logger = new Log4NetLogger(typeof(LogTester));

    public void Test()
    {
        Logger.Debug("This log message should have a prepended category");
    }
}
Run Code Online (Sandbox Code Playgroud)