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

发布于 2024-10-07 15:31:17 字数 375 浏览 5 评论 0原文

我喜欢为现有日志消息中的所有消息添加类别前缀。然而,将这个前缀一一添加到所有现有的日志消息中是很繁琐的。有没有一种方法可以在类级别添加一个属性,然后该类中的所有消息都将被记录到某个类别?

而不是像下面这样。

Log.Info("[Ref] Level 1 Starts ...");

我真的想要像这样或不同的方式来定义 log4net.ILog,

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

I like to add category prefix to all the messages on the existing logging messages. However it is tedious to add this prefix to all the existing logging messages one by one. Is there a way in I can just add an attribute to the class level then all the messages in this class will be logged for certain category?

Instead of the way right now as below,

Log.Info("[Ref] Level 1 Starts ...");

I really want to something like this or a different way to define the log4net.ILog.

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

挖鼻大婶 2024-10-14 15:31:17

您询问如何通过属性来做到这一点。 @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");
  }
}

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

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");
  }
}

在前面的示例中,类 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

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

%logger:2

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

NameSpaceA.NameSpaceB.NameSpaceC.Class

那么上述格式会将其输出为记录器名称:

NameSpaceC.Class

我对语法不是 100% 确定,因为我还没有使用过它,而且现在找不到一个好的示例。

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

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

也许您可以根据类的命名空间“分类”您的类。因此,您可能希望将类的直接父命名空间记录为其类别。

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

我不确定您是否可以使用 log4net 开箱即用地做到这一点,但您可以轻松编写一个 PatternLayoutConverter 来获取记录器名称并剥离类名称和任何“更高级别”名称空间。

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

再次

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

NameSpaceA.NameSpaceB.NameSpaceC.Class

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

%category

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

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);
    }
  }

或者,使用 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);
    }

  }

@Jonathan 的想法非常酷,但它确实在您的部分添加了一些额外的编码定义并维护一个新的记录器包装器(但很多人这样做并且并不认为这是一个特别繁重的负担)。当然,我的自定义 PatternLayoutConverter 想法也需要您自定义代码。

另一个缺点是 GetCategory 看起来在每次日志记录调用上调用都可能非常昂贵。

You are asking how to do this via an Attribute. @Jonathan's suggestion looks like it would probably work ok, but you might be able to achieve a good-enough result using log4net's built in capabilities.

If you want to group classes into "categories", then you could retrieve the logger based on the category name rather than on the classname. When you set up your output format, you can use the logger formatting token to tell log4net to write the logger name in the output.

Typically one would retrieve the logger based on the classname like this:

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");
  }
}

It is perfectly acceptable to retrieve loggers based on an arbitrary name like this:

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");
  }
}

In the preceding example, classes A and B are considered to be in the same "category", so they retrieved the logger with the same name. Class C is in a different category, so it retrieved the logger with a different name.

You could configure your loggers (in the config file) with your own "category" hierarchy:

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

You can also configure the logger output format to log only part of the fully qualified logger name. Something like this:

%logger:2

To get the last 2 parts of the fully qualified name. For example, if your class's fully qualified name is:

NameSpaceA.NameSpaceB.NameSpaceC.Class

Then the above format would output this as the logger name:

NameSpaceC.Class

I am not 100% sure about the syntax because I haven't used it and I can't find a good example right now.

One drawback to this approach is that you have to define and remember what your categories are and you must decide what the appropriate category is for each class (you also have this issue if you want to decorate each class with an attribute containing its category). Also, if you have several classes in the same category, you cannot turn logging on or off or change the logging level for a subset of those classes.

Maybe it would be useful to log a single namespace from within the namespace hierarchy:

Maybe you can "categorize" your classes based on their namespace. So, you might want to log the immediate parent namespace of a class as its category.

So, for the fully qualified class name above, you might want to log "NameSpaceC" as the "category" or logger name.

I'm not sure if you can do that out of the box with log4net, but you could easily write a PatternLayoutConverter to get the logger name and strip off the class name and any "higher level" namespaces.

Here is a link to an example of a custom PatternLayoutConverter. Takes a parameter that, in my case, I wanted to use to look up a value in a dictionary. In this case the parameter could represent the offset from the END of the fully-qualified logger name (the same interpretation as the parameter to log4net's built in logger name layout object), but additional code could be added to log ONLY the single namespace at that index.

Custom log4net property PatternLayoutConverter (with index)

Again, given this fully qualified class name:

NameSpaceA.NameSpaceB.NameSpaceC.Class

You might consider the immediate parent namespace to be the "category". If you defined a custom PatternLayoutConverter, category, and it took a parameter, then your configuration might look like this:

%category

By default it would return the substring between the last and next to last '.' characters. Given a parameter, it could return any discrete namespace up the chain.

The PatternLayoutConverter might look something like this (untested):

  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);
    }
  }

Or, using the Option property to get the Nth namespace name (relative to the end):

  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);
    }

  }

The idea from @Jonathan is pretty cool, but it does add some extra coding on your part to define and maintain a new logger wrapper (but a lot of people do that and don't find it to be a particularly onerous burden). Of course, my custom PatternLayoutConverter ideas also require custom code on your part.

One other drawback is that GetCategory looks like it could be pretty expensive to call on every logging call.

≈。彩虹 2024-10-14 15:31:17

有趣的问题,粗略的尝试...

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));
    }
}

LoggingCategoryAttribute - 适用于类

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

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

    public string Category { get { return _category; } }
}

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");
    }
}

Interesting problem, rough attempt...

Log4NetLogger - logging adapter

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));
    }
}

LoggingCategoryAttribute - applicable to classes

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

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

    public string Category { get { return _category; } }
}

LogTester - a test implementation

[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");
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文