How to add category prefix to log4net message?

I like to add a category prefix to all posts in existing postings. However, it is tedious to add this prefix to all existing log messages one by one. Is there any way that I can simply add an attribute to the class level, then all messages in this class will be logged for a certain category?

Instead of right now, as shown below,

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

I really want something like this or otherwise define log4net.ILog.

 [LoggingCategory("Ref")] public class MyClass { public void MyMethod() { Log.Info("Level 1 Starts ..."); } } 
+6
c # logging attributes log4net
Dec 15 '10 at 20:52
source share
2 answers

An interesting problem, a rude attempt ...

Log4NetLogger - Log4NetLogger 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 - 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"); } } 
+4
Dec 16 '10 at 2:27
source share

You are asking how to do this with an attribute. The @Jonathan suggestion looks like it will probably work fine, but you could achieve a reasonably good result using the built-in features of log4net.

If you want to group classes into "categories", you can get a log based on the name of the category, not the name of the class. When you customize your output format, you can use the log formatting token to tell log4net to write the log name to the output file.

Normally, you could get a registrar based on the class name as follows:

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

This is perfectly acceptable for retrieving registrars based on an arbitrary name:

 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 previous example, classes A and B are considered to be in the same category, so they retrieve the logger with the same name. Class C is in a different category, so it got a registrar with a different name.

You can configure your registrars (in the configuration 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 log output format to register only part of the registrar’s full name. Something like that:

 %logger:2 

Get the last 2 parts of the full name. For example, if your full class name is:

 NameSpaceA.NameSpaceB.NameSpaceC.Class 

Then the above format displays this as the log name:

 NameSpaceC.Class 

I am not 100% sure about the syntax, because I have not used it, and now I can not find a good example.

One of the drawbacks of this approach is that you need to define and remember what your categories are, and you must decide which category is appropriate for each class (you also have this problem if you want to decorate each class with an attribute containing its category). In addition, if you have several classes in the same category, you cannot enable or disable logging or change the level of logging for a subset of these classes.

It might be useful to write a single namespace from the namespace hierarchy:

Perhaps you can "classify" your classes based on their namespace. Thus, you might want to register the immediate parent class namespace as your category.

So, for the full class name above, you can write "NameSpaceC" as a "category" or log name.

I'm not sure you can do this out of the box using log4net, but you can easily write PatternLayoutConverter to get the registrar name and remove 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 search for values ​​in a dictionary. In this case, the parameter can represent the offset from END from the full name of the registrar (the same interpretation as the log4net parameter embedded in the log name layout object), but additional code can be added to register ONLY one namespace on this index.

Custom property log4net PatternLayoutConverter (with index)

Again, given this full class name:

 NameSpaceA.NameSpaceB.NameSpaceC.Class 

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

 %category 

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

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

@Jonathan’s idea is pretty cool, but it adds some extra coding on your part to define and support the new logger shell (but many people do this and don’t think this is a particularly burdensome burden). Of course, my own ideas for PatternLayoutConverter also require custom code on your part.

Another drawback is that GetCategory looks like it can be quite expensive to call every time the log is called.

+7
Dec 16 2018-10-16
source share



All Articles