OpenOfficeThe Logging Service

Draft (version 1.0)



As we have a strong demand for consistent logging I would like to present this draft describing the logging service. It should fulfill the following requirements:

We approached this task from the Java side and found 2 APIs mentionable:

The basic concepts used in these two APIs are nearly the same, although terminology and interfaces differ. log4j is older than JUL and more evolved, e.g. provides complete support for runtime configuration. Neither of the two APIs fulfills the above requirements, such as C++ support. Nevertheless, we decided to adopt the JUL interfaces because they will become the de-facto standard over time and because we want to avoid yet another logging API.

First of all I would like to introduce JUL as we will use it's syntax and terminology.


A short introduction to JUL

The most prominent class is Logger, where you call the methods for logging. A Logger object can be created like


     static Logger aLogger;
     aLogger = Logger.getLogger("name.Of.Logger");

and used like

     aLogger.logp(Level.INFO, "package.class", "method", "log string");

The Logger objects form a hierarchical tree based on the dotted notation of the name you assign to the logger with the parameter of getLogger (you could use any string there, but mirroring the package structure is a straight forward approach). All properties of the various logger objects are inherited in this tree structure. The most prominent property is the log level. JUL defines seven levels in the following order: SEVERE, WARNING, INFO, CONFIG, FINE, FINER, and FINEST. The table below should provide some guidelines (keeping close to the definitions given in JUL) when to use what level.

Level

Description

Example

User

Admin

Developer

SEVERE

serious failure that prevents normal program execution

cannot find computer

x

x


WARNING

potential problem but can be managed by the system

connection refused - retrying

x

x


INFO

informational messages

user logged in

x

x


CONFIG

static configuration messages

CPU: x86


x

x

FINE

tracing information (low volume / most important)

first login of user - initializing defaults



x

FINER

tracing information (entering, exiting, exceptions)

entering queryServiceLocation()



x

FINEST

tracing information (high volume / less important)

performing insert of x into table y



x



You can configure the logging framework via a configuration file. If this file contains a line like

     com.sun.star.foo.level = SEVERE

the above aLogger.logp(LogLevel::INFO, ...) call will have no effect because you raised the log threshold for this Logger higher than its log level. The same holds true if you write

     com.sun.level = SEVERE or .level = SEVERE

as all Logger properties are inherited and com.sun.star.foo is a child of com.sun is a child of root.


Logging with the XLogger component

The logging service contains the XLogger interface with the following signature:

module com { module sun { module star { module util { module logging {
interface XLogger: com::sun::star::uno::XInterface
{
    XLogger         getLogger( [in] string name );
    long            getLevel();
    string          getName();
    boolean         isLoggable( [in] long level );
    [oneway] void   logp( [in] long   level,
                          [in] string sourceClass,
                          [in] string sourceMethod,
                          [in] string msg );
};
}; }; }; }; };

A piece of code using XLogger in C++ could look like

Sequence< Any > aNameSequence(1);
aNameSequence[0] <<= OUString::createFromAscii("MyNamespace.MyClass");
Reference< XInterface > r = m_rxServiceManager->createInstanceWithArguments(
                                OUString::createFromAscii("com.sun.star.util.logging.Logger"),
                                aNameSequence );
Reference< XLogger > rxLogger( r , UNO_QUERY );

rxLogger->logp( LogLevel::INFO,
                OUString::createFromAscii("MyNamespace"),
                OUString::createFromAscii("MyClass::myMethod(myParameterType)"),
                OUString::createFromAscii("") );

(The actual location of the objects is transparent to the client in UNO. This imposes heavy and (according to the log threshold used) maybe useless net traffic if a XLogger object is hosted on a remote server. So there is a XLoggerRemote interface which provides some additional methods capable of coping with this problem. However there exists no implementation for this interface.)



Configuration

The configuration file for the logging service is found by either evaluating the content of the environment variable UTIL_LOGGING_CONFIG_FILE specifying the fully qualified path and filename or by looking for a file named logging.properties in the current working directory. The syntax of this file is inspired by one used by JUL. If the configuration file cannot be parsed (because it does not exist or it containes syntax errors) no logging is done until the logging framework is reconfigured. I will explain the syntax line by line based on the following example of a complete valid configuration file:
.watch=10
.level=SEVERE
.handler=com.sun.star.util.logging.ConsoleHandler
com.sun.star.util.logging.ConsoleHandler.target=stderr
com.sun.star.util.logging.ConsoleHandler.layout=%d %-7p %c %M %m %n

As all properties of the configuration can be changed during runtime the logging framework monitors the configuration file for changes. The interval in seconds for this monitoring is specified by the .watch property. You should provide this property. If you omit it the watchdog thread is not started at all and you loose the feature of runtime configuration. The value should be greater than 0. If the startup value is less than 1 the watchdog thread is not started. If you change the interval to a value less than 1 the watchdog thread stops checking for changes.

.level specifies the log threshold for a Logger. It can be prepended by the name of a Logger (or dot-seperated parts of it) to log more selectively. A line like com.sun.level=FINEST will overwrite the level SEVERE of the root logger for the logger with the name com.sun and all its children. If you don't prepend a name you configure the root logger from which all other loggers inherit their properties. This root .level must be provided.


The .handler property is used to specify the output device to be used. For the time being there are two handlers available: com.sun.star.util.logging.ConsoleHandler prints to the console and com.sun.star.util.logging.FileHandler prints to a file.


The target property of a handler is used to specify the output device more closely. FileHandler.target accepts any fully qualified path with a filename denoting the file to which the log lines should be apended (if the files doesn't exist it is created). ConsoleHandler.target accepts either stdout or stderr thus printing to the standard output or standard error stream respectively.


The layout property of a handler is used to specify the actual layout of the log string. The value of this property is a string very similiar to the format specification string known from printf:

%[flags][width][.precision][{h | l | I64 | L}]type

All fields have the same semantics as known from printf except for the type field. It does not denote the data type but the content type to be displayed (data type is always string here). Valid values for type are:


Type

Description

c

name of the logger

p

level of the logger

C

caller class

M

caller method

m

log message

H

caller host

P

caller PID

T

caller thread

d

current date (ISO8601)

n

linefeed



So the above configuration file configures the logging service to print the following line on standard error on console

2002-03-14 12:20:31,683 INFO    com.sun.star.Foo myMethod(int) some message

if

aLogger.logp(Level.INFO, "com.sun.star.Foo" , "myMetod(int)", "some message");

is called.


Instantiation / Naming

It is recommended that each class in your code that requires logging should instantiate its own logging object. Despite the fact that you can instanciate any logger with any name you want you should choose <package>.<class> for this (nevertheless there might be some exceptions to this in special cases). It is quite straightforward and gives you some nice configuration possibilities in combination with the inherit-feature of the logging API, e.g. prepending some name before .level.


Using a static CLASS variable proved being a good helper for clarifiying the code. You define it once and provide it as second parameter in every logp call.

You should always pass the complete signature of a method as third parameter. This eases evaluating a log if you use many methods with different signatures but with the same name (overloading).


Log Levels

There are no explicit laws when to use which log levels. The table in some chapters above gives some rough frame for using them. It is directly derived from the official JUL documentation and should be percieved as a general guideline. But in real life you may discover it somewhat difficult to decide which log level to choose. This holds especially true for the INFO level and the FINE/FINER/FINEST group.

You should keep in mind that every log level has a recipient: If you log a message that is directed to a user and you are using the FINE level - something is clearly rotten.

Exceptions should be instrumented with two lines of log code like

catch(Exception &e) { 

  aLogger.logp(Level::SEVERE, CLASS, "myMethod(int)", "EXCEPTION: " + e.getSomeName()); 
 
  aLogger.logp(Level::FINEST, CLASS, "myMethod(int)", e.getArbitraryInfo()); 

The level of the latter log call should be in any case FINEST to allow dumping the complete stack trace only if runtime considerations are neglectable. The level of the first log call should be choosen according to the importance of the exception thrown.


IsLoggable

The method isLoggable can be used if the evaluation of any of the parameters of the logp method will inflict unproportional computing effort. This will minimize evaluation costs if the log threshold of the logger is higher than its log level:

if (aLogger.isLoggable(LogLevel::INFO))
aLogger.logp(LogLevel::INFO, "com.sun.star.foo.Bar", "Bar", veryExpensiveMethod());

If the threshold is lower you will have a loss of performance as isLoggable is executed twice: in your code to avoid unnecessary parameter evaluation and in the log method itself. But isLoggable does not consume too much computing power; it just returns a comparison between two integers.


Another issue: you decrease the clarity of code if you use isLoggable very often. It is probably best to use isLoggable only if you have a parameter method that is outstanding costly. Making that kind of decisions requires some knowledge of the performance implications of the different code parts.

Using the UNO context

If you use or code a UNO component you have the possibility to use UNO contexts. I don't want to go in too much detail here (see: http://udk.openoffice.org/common/man/concept/uno_contexts.html ), but these contexts will allow you to save data in this context even across machine boundaries. This provides a solution for the problem when you want to add, let's say, the name of a user in every log message but the username is not available in all classes. You also can add arbitrary XLogger objects to the context for reusing them thoughout the system. The code for using some UNO context could look like:

private Logger m_aLogger;
private XComponentContext m_aContex;

// UNO component 
Bar(XComponentContext aContext)
{ 
  m_aContext = aContext;
  m_aLogger  = m_aContext.getValueByName("some.Logger");
  m_aLogger  = m_aLogger.getLogger("com.sun.star.foo.Bar");
}

// if don't code a UNO component but need the context
// you need to init UNO in Java like this
// (and are able to init components afterwards):
XMyObject bootstrapUno()
{ 
  XComponentContext    context  = Bootstrap.createInitialContext();
  XMultiServiceFactory smgr     = context.getServiceManager(); 
  XMyObject            myObject = smgr.createInstance("com.sun.star.myObject");

  return myObject;
}

(The context will be used to configure the XLogger components themselves as well. But this will be coded when the UNO context can be made persistent which isn't the case just right now.)



Author: Thomas Pfohe ($Date: 2002/03/26 11:14:25 $)
Copyright 2002 Sun Microsystems, Inc., 901 San Antonio Road, Palo Alto, CA 94303 USA.