Jan 16 2008

LogLevel NAnt Task

Category: Applications | .NetRory Primrose @ 10:53

An ongoing issue that I am having with NAnt scripts is bloat in the log records being written to the screen and to disk. Tasks like xmlpeek and regex log messages that I am not interested in for the given logging threshold. It would be nice to not have these unwanted messages logged but for everything else to be logged as expected given the defined log threshold.

After doing some searching, I came across this post from Jay Flowers. Will Buttitta posted another version of the same idea in a comment against that post. Using tasks to change the logging threshold at runtime for a particular part of the script is a great idea. Unfortunately, I have found that neither of these implementations work.

After going through Reflector, I have found that classes that are derived from Task and call the Log method end up invoking Element.Log() which calls straight down to Project.Log(). The Project.Log method does not check the logging threshold before writing the log entry. What I haven't been able to figure out is why Task.Log is not invoked. If it was, then I think the two solutions in Jay's post would probably work as Task.Log() checks the current logging threshold before calling down to the base class.

There is however another way that will correctly modify the logging threshold at a much lower level.

Project.Log() invokes OnMessageLogged(). After searching for what is hooked up to the MessageLogged event, I found that a default listener is created and hooked up via Project.CreateDefaultLogger(). That pointed me to look at the Project.BuildListeners property and the IBuildLogger interface. The IBuildLogger has its own Threshold property.

This opens up a very simple solution. Regardless of how logging is invoked higher up the call chain, this implementation will always correctly define the log threshold at runtime because it is done directly on the loggers. This is of course assuming the logger implementation checks the threshold assigned to it.

using NAnt.Core;
using NAnt.Core.Attributes;
 
namespace Neovolve.NAnt.Tasks
{
    /// <summary>
    /// The <see cref="LogLevelTask"/>
    /// class is a NAnt task that is used to determine the logging level used to execute
    /// a set of child tasks.
    /// </summary>
    /// <remarks>This code was inspired from the blog post found at http://jayflowers.com/WordPress/?p=133</remarks>
    [TaskName("loglevel")]
    public class LogLevelTask : TaskContainer
    {
        #region Declarations
 
        /// <summary>
        /// Stores the LogLevel value.
        /// </summary>
        private Level _logLevel;
 
        #endregion
 
        #region Methods
 
        /// <summary>
        /// Executes the task.
        /// </summary>
        protected override void ExecuteTask()
        {
            Level OldLevel = Project.Threshold;
            AssignLogLevel(LogLevel);

            base.ExecuteTask();

            AssignLogLevel(OldLevel);
        }
 
        /// <summary>
        /// Assigns the log level.
        /// </summary>
        /// <param name="newLevel">The new level.</param>
        private void AssignLogLevel(Level newLevel)
        {
            // Loop through each logger
            foreach (IBuildListener listener in Project.BuildListeners)
            {
                IBuildLogger logger = listener as IBuildLogger;
 
                // Assign the new threshold
                if (logger != null)
                {
                    logger.Threshold = newLevel;
                }
            }
        }
 
        #endregion
 
        #region Properties
 
        /// <summary>
        /// Gets or sets the log level.
        /// </summary>
        /// <value>The log level.</value>
        [TaskAttribute("level", Required = true)]
        public Level LogLevel
        {
            get
            {
                return _logLevel;
            }
            set
            {
                _logLevel = value;
            }
        }
 
        #endregion
    }
}

To change the logging level, add the loglevel task around other tasks and set the level value. For example:

<loglevel level="None">
 
  <!-- Determine whether the template is a project or item related template -->
  <foreach item="Line"
          in="${Path.ProjectFileExpressions}"
          delim=","
          property="pathExpression">
 
    <if test="${ProjectTemplateFile == ''}">
      <regex pattern="${pathExpression}"
            input="${childFile}"
            options="IgnoreCase"
            failonerror="false" />
    </if>
 
  </foreach>
 
</loglevel>

Tags: ,

Comments

1.
David Jansen David Jansen says:

Awesome stuff! This works really well. Thanks for this.

2.
David Jansen David Jansen says:

Thanks for this! Awesome stuff. Works a treat.

3.
Jay Flowers Jay Flowers says:

Hi Rory,
I should have noted in my post that I am using a branch of NAnt in CI Factory and that in that branch I corrected a flaw in Project.  I felt that if you set the Threshold property it should update all the listeners as well.  So my Project.Threshold looks like:

public Level Threshold {
    get { return _threshold; }
    set {
        _threshold = value;
        foreach (IBuildListener Listener in this.BuildListeners)
        {
            if (typeof(IBuildLogger).IsAssignableFrom(Listener.GetType()))
            {
                ((IBuildLogger)Listener).Threshold = value;
            }
        }
    }
}

4.
Rory Primrose Rory Primrose says:

Thanks for the followup Jay. I think that is a good change.

5.
Declan Whelan Declan Whelan says:

Nice,

One related thing I would like to do is use the log level to control a verbosity setting passed on to an exec task. For example if log level is verbose I would like to add a "-v" command line arg to something beinf exec'd. Any suggestions?

Thanks

6.
Rory Primrose Rory Primrose says:

Hi Declan,

You would have to either:

1. use a set of if tasks each of which would test the current log level and then run an exec task with specific command line arguments according to the current level
2. define a set of exec tasks each of which defines an if attribute that does the same as the above option
3. define a custom function (script function) which will return the command line arguments based on the current log level

The best option is #3, followed by #2 then #1.
Option #3 is the best one as you won't have unnecessary duplication of tasks in your script.

7.
Declan Declan says:

Thanks Rory ... good advice and much appreciated.

Declan

8.
Robert Robert United Kingdom says:

Ahhh awesome bit of code. Just what I was looking for and it works a treat. Thanks for the great solution.

9.
waggi waggi United States says:

Great post.

10.
shakiro214 shakiro214 United States says:

for a little while, I wasn't sure how to get loglevel to work. i used .NET Reflector, but that didn't allow me to mess with the C# code.  and build 86 of NAnt has seperate dlls for the different tasks it uses. but because of the comments for this post, the 1 thing I could be sure about is that this code works, so what I did to get my build file to recognize the loglevel tag was simply embed it into the build file itself using script tags:

  <script language="C#">
          <code>
            <![CDATA[

               [TaskName("loglevel")]...

            ]]>
          </code>
  </script>

your post was very helpful. thank you Smile

Add comment


(Will show your Gravatar icon)

  Country flag

biuquote
  • Comment
  • Preview
Loading