lunes, 9 de marzo de 2009

Adding new fields to the log4cxx logger.

Introduction

We've been using the log4cxx library for a while in our applications. Then we had this new requirement to add some new unsupported fields in the log (see this link for the supported fields). After some research we were unable to find any documentation on the subject so we had to look at the code and try to come up with a solution. We impose yet another requirement on ourselves that is; not to change the library code and make the code as generic as possible.

I don't know if this is the best way to do this but this solution has been good for us. Any alternative approach will be highly appreciated.

The design

The log4cxx::ExtendedLayout library has been derived from log4cxx::PatternLayout. This class has reserved the w1,w2,.. formats. Some new log4cxx::ExtendedPatternConverter have been created to support the new formats. These objects format their messages after ExtendedFormatter objects provided by FormatterFactory (a singleton).

If an extension is required the log4cxx::ExtendedLayout must be registered and then used in the configuration file. For example:
 <appender name="file" class="FileAppender">
<!--
This path is a valid configuration for the 'managed'
example since we set the directory as working dir
programmatically
-->
<param name="File" value="./engine.log" />
<param name="Append" value="true" />
<layout class="org.apache.log4j.ExtendedLayout">

<param name="ConversionPattern"
value="%d{yyyy-MM-dd HH:mm:ss:SSS} %w1 %w2 %w3 %c - %m%n" />
</layout>
</appender>


Then a FormatterFactory (example TestFactory) derived class must be created to generate and as many ExtendedFormatter derived classes as required (example ExtendedFormatter1).

The classes involved in the scheme can be seen in the following diagram:



The implementation

ExtendedLayout.cpp
/**
* \file
* \brief This file contains the definition of the log4cxx::ExtendedLayout class.
*
* The code in this file has been copied from the PatternLayout code.
*/
#include "ExtendedLayout.hpp"
#include "ExtendedPatternConverter.hpp"

using namespace log4cxx;
using namespace log4cxx::helpers;
using namespace log4cxx::pattern;
IMPLEMENT_LOG4CXX_OBJECT(ExtendedLayout)
ExtendedLayout::ExtendedLayout()
{
}
ExtendedLayout::ExtendedLayout(const LogString & pattern)
{
Pool pool;
activateOptions (pool);
}

#define RULES_PUT(spec, cls) \
specs.insert(PatternMap::value_type(LogString(LOG4CXX_STR(spec)), (PatternConstructor) cls ::newInstance))
log4cxx::pattern::PatternMap ExtendedLayout::getFormatSpecifiers()
{
PatternMap specs = PatternLayout::getFormatSpecifiers ();
RULES_PUT("w1", ExtendedPatternW1);
RULES_PUT("w2", ExtendedPatternW2);
RULES_PUT("w3", ExtendedPatternW3);
return (specs);
}
ExtendedLayout.hpp
#ifndef _EXTENDED_LAYOUT_HPP_
#define _EXTENDED_LAYOUT_HPP_
/**
* \file
* \brief This file contains the definition of the log4cxx::ExtendedLayout class.
*/
#include <&log4cxx/patternlayout.h>
namespace log4cxx
{
/**
* \class ExtendedLayout
* \brief This class allows the extension of the log4cxx pattern layout.
*/
class ExtendedLayout: public PatternLayout
{
public:
DECLARE_LOG4CXX_OBJECT(ExtendedLayout)
BEGIN_LOG4CXX_CAST_MAP()
LOG4CXX_CAST_ENTRY(ExtendedLayout)
LOG4CXX_CAST_ENTRY_CHAIN(PatternLayout)
END_LOG4CXX_CAST_MAP()
ExtendedLayout();
ExtendedLayout(const LogString & pattern);
virtual ~ExtendedLayout () {};
/**
The PatternLayout does not handle the throwable contained within
spi::LoggingEvent LoggingEvents. Thus, it returns
true.
*/
virtual bool ignoresThrowable() const { return true; }
protected:
virtual log4cxx::pattern::PatternMap getFormatSpecifiers();
};
}
#endif // _EXTENDED_LAYOUT_HPP_

ExttendedPatternConverter.cpp
/**
* \file
* \brief This file contains the implementation of the log4cxx::ExtendedPatternConverter class.
*/
#include "ExtendedPatternConverter.hpp"
using namespace log4cxx;
ExtendedPatternConverter::ExtendedPatternConverter (ExtendedPatternType type):
log4cxx::pattern::LoggingEventPatternConverter ( LOG4CXX_STR("Extended"), LOG4CXX_STR("Extended")),
mType(type)
{
theFormatter = getFormatterFactoryInstance().getFormatter (type);
}
ExtendedPatternConverter::~ExtendedPatternConverter ()
{
}
void log4cxx::ExtendedPatternConverter::format (const log4cxx::spi::LoggingEventPtr& event,
log4cxx::LogString& toAppendTo,
log4cxx::helpers::Pool& p) const
{
std::string strFormat = theFormatter->format ();
toAppendTo.append(strFormat.c_str());
}
IMPLEMENT_LOG4CXX_OBJECT(ExtendedPatternW1)
ExtendedPatternW1::ExtendedPatternW1 (): ExtendedPatternConverter (EXTENDED_PATTERN_W1)
{
}
pattern::PatternConverterPtr ExtendedPatternW1::newInstance( const std::vector<LogString>& options)
{
static pattern::PatternConverterPtr def (new ExtendedPatternW1 ());
return (def);
}
IMPLEMENT_LOG4CXX_OBJECT(ExtendedPatternW2)
ExtendedPatternW2::ExtendedPatternW2 (): ExtendedPatternConverter (EXTENDED_PATTERN_W2)
{
}
pattern::PatternConverterPtr ExtendedPatternW2::newInstance( const std::vector<LogString>& options)
{
static pattern::PatternConverterPtr def (new ExtendedPatternW2 ());
return (def);
}
IMPLEMENT_LOG4CXX_OBJECT(ExtendedPatternW3)
ExtendedPatternW3::ExtendedPatternW3 (): ExtendedPatternConverter (EXTENDED_PATTERN_W3)
{
}
pattern::PatternConverterPtr ExtendedPatternW3::newInstance( const std::vector<LogString>& options)
{
static pattern::PatternConverterPtr def (new ExtendedPatternW3 ());
return (def);
}
ExtendedPatternConverter.hpp
#ifndef _EXTENDED_PATTERN_CONVERTER_HPP_
#define _EXTENDED_PATTERN_CONVERTER_HPP_
/**
* \file
* \brief This file contains the definition of the log4cxx::ExtendedPatternConverter class.
*/
#include <log4cxx/patternlayout.h>
#include <log4cxx/pattern/loggingeventpatternconverter.h>
#include "FormatterFactory.hpp"
namespace log4cxx
{
/**
* \class ExtendedPatternConverter
* \brief This class defines a generic extended pattern converter.
*
* A derived class will be created for every ExtendedPatternType.
*/
class ExtendedPatternConverter: public log4cxx::pattern::LoggingEventPatternConverter
{
/// The type of the extended pattern
ExtendedPatternType mType;
// The formatter to use
ExtendedFormatterPtr theFormatter;
protected:
/// ProtectedConstructor
ExtendedPatternConverter (ExtendedPatternType type);
public:
virtual ~ExtendedPatternConverter ();
virtual void format (const log4cxx::spi::LoggingEventPtr& event,
log4cxx::LogString& toAppendTo,
log4cxx::helpers::Pool& p) const;
};
/**
* \class ExtendedPatternW1
* \brief This class will be associated to the w1 pattern.
*/
class ExtendedPatternW1: public ExtendedPatternConverter
{
private:
/// Private constructor
ExtendedPatternW1 ();
public:
DECLARE_LOG4CXX_PATTERN(ExtendedPatternW1)
BEGIN_LOG4CXX_CAST_MAP()
LOG4CXX_CAST_ENTRY(ExtendedPatternW1)
LOG4CXX_CAST_ENTRY_CHAIN(LoggingEventPatternConverter)
END_LOG4CXX_CAST_MAP()
/**
* Obtains an instance of ExtendedPatternW1.
* @param options options, currently ignored, may be null.
* @return instance of ExtendedPatternW1.
*/
static log4cxx::pattern::PatternConverterPtr newInstance( const std::vector<LogString> & options);
};
/**
* \class ExtendedPatternW2
* \brief This class will be associated to the w3 pattern.
*/
class ExtendedPatternW2: public ExtendedPatternConverter
{
private:
/// Private constructor
ExtendedPatternW2 ();
public:
DECLARE_LOG4CXX_PATTERN(ExtendedPatternW2)
BEGIN_LOG4CXX_CAST_MAP()
LOG4CXX_CAST_ENTRY(ExtendedPatternW2)
LOG4CXX_CAST_ENTRY_CHAIN(LoggingEventPatternConverter)
END_LOG4CXX_CAST_MAP()
/**
* Obtains an instance of ExtendedPatternW2.
* @param options options, currently ignored, may be null.
* @return instance of ExtendedPatternW2.
*/
static log4cxx::pattern::PatternConverterPtr newInstance( const std::vector <LogString> & options);
};
/**
* \class ExtendedPatternW3
* \brief This class will be associated to the w3 pattern.
*/
class ExtendedPatternW3: public ExtendedPatternConverter
{
private:
/// Private constructor
ExtendedPatternW3 ();
public:
DECLARE_LOG4CXX_PATTERN(ExtendedPatternW3)
BEGIN_LOG4CXX_CAST_MAP()
LOG4CXX_CAST_ENTRY(ExtendedPatternW3)
LOG4CXX_CAST_ENTRY_CHAIN(LoggingEventPatternConverter)
END_LOG4CXX_CAST_MAP()
/**
* Obtains an instance of ExtendedPatternW3.
* @param options options, currently ignored, may be null.
* @return instance of ExtendedPatternW3.
*/
static log4cxx::pattern::PatternConverterPtr newInstance( const std::vector<logString> & options);
};
} // namespace log4cxx
#endif // _EXTENDED_PATTERN_CONVERTER_HPP_
FormatterFactory.cpp
/**
* \file
* \brief This file contains the implementation of the FormatterFactory class.
*/
#include <iostream>
#include "FormatterFactory.hpp"
ExtendedFormatterPtr FormatterFactory::getFormatter (ExtendedPatternType type)
{
switch (type)
{
case EXTENDED_PATTERN_W1:
case EXTENDED_PATTERN_W2:
case EXTENDED_PATTERN_W3:
return (ExtendedFormatterPtr)(new DefaultFormatter ());
default:
std::cerr << "Invalid pattern type " << type << std::endl;
return (ExtendedFormatterPtr)(new DefaultFormatter ());
}
}
FormatterFactory.hpp
#ifndef _FORMATTER_FACTORY_HPP_
#define _FORMATTER_FACTORY_HPP_
/**
* \file
* \brief This file contains the definition of the FormatterFactory class.
*/
#include "ExtendedFormatter.hpp"
#include <boost/shared_ptr.hpp>
/// Type definition of a shared pointer to an extended formatter.
typedef boost::shared_ptr <ExtendedFormatter> ExtendedFormatterPtr;
/**
* This type defines all the extended type that can be used, they are associated to the "w" letter in the
* format.
*/
typedef enum ExtendedPatternType
{
EXTENDED_PATTERN_W1 /// Associated to w1
,EXTENDED_PATTERN_W2 /// Associated to w2
,EXTENDED_PATTERN_W3 /// Associated to w3
} ExtendedPatternType;
/**
* \class FormatterFactory
* \brief This class generates objects of ExtendedFormatter type.
*
* This factory is used by the ExtendedLayout class.
*
* The project that needs to create new formatter should extend this class. This derived class should provide
* its own formatters.
*/
class FormatterFactory
{
protected:
/// The constructor is protected because it can't be called directly. A derived class is required.
FormatterFactory () {};
public:
/**
* \brief This function returns the formatter associated with the given type.
*
* \warning The return object is created with new. It's the caller responsibility to free it with
* delete.
* \param type The type of formatter being requested.
* \return The function returns a reference to the formatter. The function returns NULL is case of
* error.
*/
virtual ExtendedFormatterPtr getFormatter (ExtendedPatternType type);
};
/**
* \brief This function returns a reference to the formatter factory instance.
*
* This function should be defined by the derived factory.
*
*/
FormatterFactory & getFormatterFactoryInstance ();
#endif // _FORMATTER_FACTORY_HPP_
singleton.hpp
#ifndef _SINGLETON_H_
#define _SINGLETON_H_
/**
* @file
* @brief This file defines the Singleton class.
*/
#include <boost/shared_ptr.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/utility.hpp> // noncopyable
#include <iostream>

template <class Type> class Singleton : public Type, boost::noncopyable
{
public:
static Type& getInstance ();
private:
static boost::shared_ptr<Type> mInstance;
static boost::mutex mInitMutex;
};
template <class Type> boost::shared_ptr<Type> Singleton<Type>::mInstance;
template <class Type> boost::mutex Singleton<Type>::mInitMutex;
template <class Type> inline Type& Singleton<Type>::getInstance()
{
if (!mInstance)
{
boost::mutex::scoped_lock lock (mInitMutex);
if (!mInstance)
{
mInstance= boost::shared_ptr <Type> (new Type);
}
}
return *mInstance;
}
#endif /* _SINGLETON_H_ */
ExtendedFormatter.hpp
#ifndef _EXTENDED_FORMATTER_HPP_
#define _EXTENDED_FORMATTER_HPP_
#include <string>
/**
* \file
* \brief This file contains the definition of the virtual ExtendedFormatter class.
*
* It also defines the default class DefaultFormatter class.
*/
/**
* \class ExtendedFormatter
* \brief This virtual class defines a formatter to be used with the log4cxx::ExtendedLayout.
*
* To create a formatter for the logger a new class of this type must be created and then registered in the
* pattern factory.
*/
class ExtendedFormatter
{
public:
ExtendedFormatter () {};
virtual ~ExtendedFormatter () {};

/**
* The function that does the actual formatting.
*
* @return The function returns the formatted message to be printed in the log.
*/
virtual std::string format () = 0;
};
/**
* \class DefaultFormatter
* \brief The formatter to be used by default.
*/
class DefaultFormatter: public ExtendedFormatter
{
public:
DefaultFormatter () {};
std::string format () { return std::string ("undefined");};
};
#endif // _EXTENDED_FORMATTER_HPP_
TestFactory.cpp
#include "TestFactory.hpp"
#include "Formatter1.hpp"
#include "singleton.hpp"
typedef Singleton<TestFactory> TestFactorySingleton;
FormatterFactory & getFormatterFactoryInstance ()
{
return ((FormatterFactory &)(TestFactorySingleton::getInstance ()));
}
/**
* \file
* This file contains the implementation fo the TestFactory class.
*/
ExtendedFormatterPtr TestFactory::getFormatter (ExtendedPatternType type)
{
switch (type)
{
case EXTENDED_PATTERN_W1:
return ((ExtendedFormatterPtr)(new ExtendedFormatter1));
case EXTENDED_PATTERN_W3:
return ((ExtendedFormatterPtr)(new ExtendedFormatter3));
default:
return (FormatterFactory::getFormatter (type));
}
}
TestFactory.hpp
#ifndef _TEST_FACTORY_HPP_
#define _TEST_FACTORY_HPP_
#include "FormatterFactory.hpp"
/**
* \file
* \brief This file contains the definition of the TestFactory class.
*/
class TestFactory: public FormatterFactory
{
public:
TestFactory () {};
ExtendedFormatterPtr getFormatter (ExtendedPatternType type);
};
#endif // _TEST_FACTORY_HPP_
Formatter1.hpp
#ifndef _EXTENDED_FORMATTER_1_HPP_
#define _EXTENDED_FORMATTER_1_HPP_
#include "ExtendedFormatter.hpp"
/**
* \file
* \brief This file contains the definition of the ExtendedFormatter1 class.
*/
class ExtendedFormatter1 : public ExtendedFormatter
{
std::string format () {return (std::string ("test1"));};
};
class ExtendedFormatter3 : public ExtendedFormatter
{
std::string format () {return (std::string ("test3"));};
};
#endif // _EXTENDED_FORMATTER_1_HPP_
The main function
#include 
#include <log4cxx/logger.h>
#include <log4cxx/basicconfigurator.h>
#include <log4cxx/xml/domconfigurator.h>
#include "ExtendedLayout.hpp"
int main (int argc, char **argv)
{
int retVal = 1;
if (argc == 2)
{
log4cxx::ExtendedLayout::registerClass ();
log4cxx::xml::DOMConfigurator::configureAndWatch (argv[1], 3000);
log4cxx::LoggerPtr _logger = log4cxx::Logger::getLogger ("APPLICATION");
LOG4CXX_ERROR (_logger, "message\n");
retVal = 0;
}
else
{
std::cerr << "The program expects exactly one parameter. " << std::endl;
}
return (retVal);
}
Makefile
I created a Makefile that probably wont work for most of the people but, hopefully, it'll be enough to get the idea. The code has been tested both for Linux and windows.

SRCS= ExtendedLayout.cpp FormatterFactory.cpp TestFactory.cpp ExtendedPatternConverter.cpp ppral.cpp
OBJS= ExtendedLayout.o FormatterFactory.o TestFactory.o ExtendedPatternConverter.o ppral.o
TARGET= layoutTest

CC= g++ -Wall -c -I/usr/local/include/log4cxx -I/usr/local/include/boost-1_35/
LN= g++ -Wall -L /usr/local/lib

all: $(TARGET)

%.o: %.cpp
$(CC) $< -o $@

$(TARGET): $(OBJS)
$(LN) $(OBJS) -llog4cxx -lboost_thread-gcc42-mt -o $@

clean:
rm -f $(OBJS) $(TARGET)

domingo, 8 de marzo de 2009

Introduction

I just needed some space to publish some work we've been doing so I created this blog. Because we've been using so public domain tools we wanted to make available to the community some work we made with them in the hope that somebody might found them useful.