Logging macros - vilinski/nemerle GitHub Wiki

Table of Contents

Example usage

This package sits in the Nemerle.Logging namespace. It is a mental shortcut for the following:

#define VERB

class A {
  SomeMethod () : void
  {
    #if VERB
    DoLog ("some stuff");
    #endif
    ...
    #if VERB
    def previous_value = GetValue ();
    #endif
    ...
    UpdateValue ();
    ...
    #if VERB
    DoLog ($ "value $(GetValue ()) --> $previous_value");
    #endif
  }
}

The above is replaced by:

using Nemerle.Logging;
[assembly: LogFunction (DoLog)]
[assembly: LogFlag (VERB, true)]

class A {
  SomeMethod () : void
  {
    log (VERB, "some stuff");
    ...
    whenlogging (VERB)
      def previous_value = GetValue ();
    ...
    UpdateValue ();
    ...
    log (VERB, $ "value change $previous_value --> $(GetValue ())");
  }
}

The flags set with LogFlag are global to the current compilation. Same goes to the LogFunction. It is an error to use log (or whenlogging) with an unknown flag (use LogFlag (FOO, false) to disable given flag). This should save you from typos.

Setting a flag or printing function several times is also an error.

Enabling given logging is a compile time operation -- without the flag the logging code is not compiled in. However, when some boolean expression are used instead of literal true/false, logging code is preceeded by check of whether this expression is true.

You can use 0/1 instead of true/false as an argument to LogFlag. This saves typing (I tend to disable/enable lots of debug flags during debuging ;-)

It is also allowed to pass several arguments to the logging function, provided it supports such overload. So the code like below is OK:

[LogFunction (DoLog)]
[LogFlag (SOME_FLAG, 1)]
...
DoLog (color : int, s : string) : void { ... }
...
log (SOME_FLAG, 12, "ble bla");
...

Specifying multiple logging functions

If you want to customize which logging function is used for every flag, you can specify mapping from each flag name to logging function. It is done by listing expressions of form FLAG => func_expr as arguments of LogFunction.

For example you may want to use log4net's functions for Debug and Info, so you specify:

using Nemerle.Logging;

[assembly: LogFunction (DEBUG => log4net_category.Debug, TRACE => log4net_category.Info)]

and then

log (DEBUG, "I'm here");
log (TRACE, "enter business functionality");

will use log4net_category.debug in first expression and log4net_category.info in second.

Conditions

It is sometimes needed to restrict logging to some particular case (for example when -d flag is passed to the program). This can be done with LogCondition macro. When you specify it, each call to the logging function is preceeded by check of the condition.

[assembly: LogCondition (EnableLogging),
           LogFlag (DEBUG, true)]

...
EnableLogging = true;
log (DEBUG, "i'm visible");
EnableLogging = false;
log (DEBUG, "and i'm not");

You could also check all the condition you want in the log printing function, but this way you can avoid computations needed for producing the log message (this is not high cost in most cases, but it is very significant in some).

Format

You can prepend logging flag to each message by using:

[assembly: LogFormat (PrependFlag)]

There are no other options controling format right now.

Using macro with log4net framework

There already was an example of using log4net's framework for logging. This article will demonstate integration with log4net in more particular. Let's imagine that we have a class library with logging based on log4net. The library has an aux module MyLib. This module has a property to assign logger from outside:

public module MyLib
{
    [Accessor(flags = WantSetter)]
    mutable _logger : ILog;
}
Standard package of log4net has 5 detail levels of messages that are logged. All of them will be used in our class library:
[assembly: LogFunction (Debug => MyLib.Logger.Debug, 
                        Info => MyLib.Logger.Info, 
                        Warn => MyLib.Logger.Warn, 
                        Error => MyLib.Logger.Error, 
                        Fatal => MyLib.Logger.Fatal)]

...
log (Debug, $"nothing serious, just debug variable value: $var");
...
log (Fatal, $"oh my god! unhandled exception: $ex; additional info: $addInfo");
The situation, when the logger isn't assigned, but logging method is called should be prevented:
[assembly: LogCondition (MyLib.Logger != null)]
Log's detail level can be set from a configuration file of application that uses our lib:
<logger name="SomeLogger">
  <level value="INFO"/> <!-- only logs with INFO level and higher will be saved -->
  <appender-ref ref="LogFileAppender" />
  ...
</logger>
So, setting compile-time flags isn't a good way. But, properties like MyLib.Logger.IsDebugEnabled can be used as flags:
[assembly: LogFlag (Debug, MyLib.Logger.IsDebugEnabled)]
[assembly: LogFlag (Info,  MyLib.Logger.IsInfoEnabled )]
[assembly: LogFlag (Warn,  MyLib.Logger.IsWarnEnabled )]
[assembly: LogFlag (Error, MyLib.Logger.IsErrorEnabled)]
[assembly: LogFlag (Fatal, MyLib.Logger.IsFatalEnabled)]
Usage:
log (Debug, $"nothing serious, just debug variable value: $var");

// Equivalent
when (MyLib.Logger != null && MyLib.Logger.IsDebugEnabled)
  MyLib.Logger.Debug($"nothing serious, just debug variable value: $var");
Moreover, using of MyLib.Logger.IsDebugEnabled as flag keep us from penalty caused by expanding of splice when logging detail level is higher than DEBUG.

Limitations

In the following example x and y will be local to the scope:

whenlogging (FOO) {
  def x = ...;
  def y = ...;
}

Source

Source is available at https://github.com/rsdn/nemerle/tree/master/macros/Logging.n

⚠️ **GitHub.com Fallback** ⚠️