Download Reference Manual
The Developer's Library for D
About Wiki Forums Source Search Contact

Logging: RollingFileAppender variant

Moderators: kris

Posted: 03/18/08 17:42:46

I had a couple of gripes with RollingFileAppender?: it doesn't usually make much sense to split a log file in two at an arbitrary point, and it's useful to have a clear indication of where a program starts (e.g. a couple of blank lines). So I had a think, and decided the easiest thing would be to start a new log file each time the program runs (well, assuming one FileAppender? per run) and rotate files.

I've created a blunt modification of RollingFileAppender? doing this (blunt because I just did something that works rather than thought it through, and it doesn't check for unexpected errors). It works (for me at least), so it's testable.

I'm posting it here, rather than as a ticket, to get peoples' feedback (maybe it's not so useful, maybe it should be adjusted a bit...); so comments?

/*******************************************************************************

        copyright:      Copyright (c) 2004 Kris Bell. All rights reserved

        license:        BSD style: $(LICENSE)
      
        version:        Initial release: May 2004
        
        author:         Kris, Diggory Hardy

*******************************************************************************/

module tango.util.log.SwitchingFileAppender;

private import  tango.io.FilePath,
                tango.io.FileConst,
                tango.io.FileConduit;

private import  tango.io.model.IBuffer;

private import  tango.util.log.Appender,
                tango.util.log.FileAppender;

/*******************************************************************************

        Append log messages to a file set. 

*******************************************************************************/

public class SwitchingFileAppender : FileAppender
{
        private Mask            mask;
        private IBuffer         buffer;

        /***********************************************************************
                
                Create a SwitchingFileAppender upon a file-set with the 
                specified path and optional layout.

                Where a file set already exists, we rename each file to the name
                of the next oldest file, removing the oldest when the limit
                is reached. Logging is always done to the first file in the set.

        ***********************************************************************/

        this (char[] path, int count, EventLayout layout = null)
        {
                assert (path);
                assert (count > 0 && count < 10);
                
                // Get a unique fingerprint for this instance
                mask = register (path);

                FilePath[] paths;
                paths.length = count;
                char[1] x;
                
                for (int i = count-1; i >= 0; --i) {
                    // Produce the name:
                    x[0] = '0' + i;

                    auto p = new FilePath (path);
                    p.name = p.name ~ x;
                    paths[i] = p;
                    
                    // Migrate existing files:
                    if (p.exists) {
                        if (i >= count-1) p.remove;
                        else p.dup.rename (paths[i+1]);
                    }
                }

                // Open the conduit (will be on a new file):
                auto style = FileConduit.WriteAppending;
                style.share = FileConduit.Share.Read;
                auto conduit = new FileConduit (paths[0], style);

                buffer = setConduit (conduit);
                
                // set provided layout (ignored when null)
                setLayout (layout);
        }

        /***********************************************************************
                
                Return the fingerprint for this class

        ***********************************************************************/

        Mask getMask ()
        {
                return mask;
        }

        /***********************************************************************
                
                Return the name of this class

        ***********************************************************************/

        char[] getName ()
        {
                return this.classinfo.name;
        }

        /***********************************************************************
                
                Append an event to the output.
                 
        ***********************************************************************/

        synchronized void append (Event event)
        {
                auto layout = getLayout;
                buffer.append (layout.header  (event));
                buffer.append (layout.content (event));
                buffer.append (layout.footer  (event))
                      .append (FileConst.NewlineString)
                      .flush  ();
        }
}

There are no responses to display.