# Tracing

The following information is not accurate anymore. JGroups switched to Apache commons-logging a long time ago, and can now use either log4j or the JDK logging subsystem, or any other implementation. I want to thank Jim Menard for this (now deprecated) Tracing subsystem anyway, this was incredibly useful in finding out bugs, especially when the only debugger available was jdb !

JGroups provides its own tracing module, located in the `org.javagroups.log` package. The reason for having its own tracing is that tracing was written before log4j or the JDK 1.4 logging API were available. We will probably switch to the JDK 1.4 logging API once JGroups is moved to 1.4, but in the meantime, the current tracing module is doing a fine job.

Tracing allows a programmer to write a message to some output, which can be a file, stdout/stderr or a socket. Outputs are associated with a module name and a trace level. A module name specifies a class name and optionally a method name. A trace level is an integer specifying a level of output verbosity. The Trace class defines trace level constants DEBUG, TEST, INFO, WARN, ERROR or FATAL (in increasing order of importance). Messages sent to an output are only printed if the trace level of the message is greater than or equal to the trace level set for the output.

Outputs are flushed when a program terminates. Outputs may be set to auto-flush after every call to print. By default, auto-flushing is turned off. As of Java 1.3, the `Runtime.addShutdownHook()` method allows us to register a thread that is used to flush and close all outputs when the program exits. Note that this will not be the case if a program is killed via `kill -9`.

If a module is not associated with any output, then a default output is used. The initial value of the default output is null. Messages sent to a null output are ignored.

## Module names

A module name is a string used to associate a message with a particular logging output. Module names should be of the form `classname[.methodname([args...])]]`: a class name optionally followed by a method name with parenthesis that optionally contain arguments. Here are some examples of legal module names:

- `MyClass` (classname)

- `JGroups.Common.MyClass` (class names can be fully qualified)

- `MyClass.method()` (classname and method, no args)

- `MyClass.method(int, String)` (classname and method, with args)

- `MyClass.method` (legal, but this is a fully qualified class name because there are no parenthesis)

Currently, method arguments are always ignored. In the future, we may want to be able to distinguish between methods with the same name and different arguments (for example, `MyClass.method(int)` and `MyClass.method(String)`).

## Output lookup

When calling `Trace.print()`, the first argument, the first argument is a module name. When looking for which output to use, Trace uses the following set of rules. If no output is found (the returned output is null), then nothing is printed.

1. If the module name contains no method name (there are no parenthesis), look for the output associated with this module name. If found, return it. Else, return the default output (initially `null`).

2. Look for the output associated with this module and method name, excluding arguments if any.

3. Strip off the method name and look for the output associated with the class name.

4. Use the default output (initially `null`).

For example,

        Trace.print("JChannel.foo()", Trace.FATAL, "ouch!");

would first look for `JChannel.foo()`, then `JChannel`, then use the default output (which may be `null`, which means no output will be produced).

## Using Trace

To use Trace in a program, the following things have to be done:

1. Define trace outputs

2. Use trace statements in your code

### Defining Trace outputs

There are two ways to define Trace outputs: programmatically and file-based. The `setOutput()` methods allow a programmer to add trace output as in the following example:

        Trace.setOutput("MyClass", Trace.WARN, "/tmp/logfile");

This means that all trace statements whose module names start with `MyClass` and whose level is WARN or greater will be written to `/tmp/logfile`. Any number of such statements can be given, and the one that matches most of the module name will be used. For example, we can have an additional statement like:

        Trace.setOutput("MyClass.foo", Trace.WARN, "/tmp/logfile2");

In this case, a trace statement whose module name is `MyClass.foo()` would be written to `/tmp/logfile2` rather than `/tmp/logfile`.

Tracing can also be configured through a property file which is read by `Trace.init()`. This has the advantage that tracing can be modified by changing a file rather than changing code. In order to configure Trace from a file, one of the two `Trace.init()` methods can be called:

       public static void init(String fname) throws IOException, SecurityException;
       public static void init();

The first method takes a filename as argument. It can either be a fully qualified filename or a relative filename. In the latter case, the file will be looked up in the directory from which the program was started. If `fname` refers to a directory, JGroups will automatically add a directory separator followed by `javagroups.properties`, e.g. if `fname` is `/tmp`, then the full filename will be `/tmp/javagroups.properties`. If the file is not found, or cannot be opened, an exception will be thrown.

The second `init()` method takes no parameters and throws no exceptions. The following algorithm is used to determine the filename:

- If there is a `javagroups.properties` file in the user's home directory (`user.home` property), then it will be used.

- Otherwise `javagroups.properties` will be searched in the `CLASSPATH` and the first one found will be used (the `Class.getResourceAsStream()` method call is used for this).

A property file looks as follows:

        trace=true

        timestamp_format=HH:mm:ss[SSS]

        default_output=WARN STDERR

        # trace2=ClientGmsImpl.join      DEBUG STDOUT
        # trace2=CoordGmsImpl.handleJoin DEBUG STDOUT
        # trace3=GMS.castViewChange      DEBUG STDOUT

        trace4=SMACK                     DEBUG STDOUT
        trace5=AckMcastSenderWindow      DEBUG /tmp/trace.ack

        trace8=NAKACK.up                 DEBUG STDOUT

The `trace=true` statement enables or disabled tracing. This sets the public static `Trace.trace` variable, which should be queried by trace statements before executing a trace statement (see trace-statements. This flag is on by default. If tracing should be disabled, set it to false.

The next

Note that statements can occur in any order.

statement is `timestamp_format`. It defines the format of the timestamp which occurs before every line of output. The format used is the one defined by `java.text.SimpleDateFormat`. In the above example, we will print the hour, minute, second and millisecond when the event occurred. For example, a trace statement

        Trace.info("NAKACK.up()", "hello world")

will yield the following output:

    [14:07:07[539]] [INFO] NAKACK.up(): hello world].

The next statement is `default_output`. It defines an output that is used as a fallback, if there are no other outputs defined. In the example, all warnings or higher (error and fatal messages) will by default be printed to stderr. To enable all output on all levels to go to file `c:\tmp\trace.all`, the following statement can be used:

    default_ouput=DEBUG c:\tmp\trace.all

This is not recommended, as all output for all levels (DEBUG is the lowest level) will be dumped to `c:\tmp\trace.all`.

Next we have a couple of comments, indicated by a line starting with a hash (#). Note that this is the only accepted comment sign; C- or Java-like comments will not be accepted.

Next come zero or more trace statements. In the example we're tracing class `SMACK` to stdout, `AckMcastSenderWindow` to file `/tmp/trace.ack` and method `NAKACK.up()` to stdout. All of these statements have to start with the `trace` keyword, followed by a unique ID. Any ID can be used, e.g. `traceA`, `trace-01` and `traceMyTrace` are all valid trace statements. After the equals sign, the module name has to be given. This can be either a class name, or a class name followed by a method name (no parameters must be given !). Note that an inner class method to be traced might look like `MyClass.MyInnerClass.method1()`. The next entry defines the trace level: all statements with a level equal or higher to the one given are printed. The last entry in the trace statement is the output: it can be either

STDERR, STDOUT

- The standard error or output streams

<filename>

- A fully qualified file name

<host:port>

- The name and port of a host. The output will be sent to a host on which a listener has to be running (similar to UNIX syslogd).

### Using Trace statements

Trace statements can be used anywhere in the code (after `org.javagroups.log.*` has been imported). There are several public static methods in the Trace class which can be used:

       public static void println(String module, int level, String message);
       public static void debug(String module, String message);
       public static void test(String module, String message);
       public static void info(String module, String message);
       public static void warn(String module, String message);
       public static void error(String module, String message);
       public static void fatal(String module, String message);

Method `print()` takes the module, level and message to be printed as arguments. Methods `debug()`, `test()`, `info()`, `warn()`, `error()` and `fatal()` are mere convenience methods and all use `println()` internally.

### Conditional tracing

Naive tracing statements can lead to bad performance and a lot of memory used. Consider the following case:

        for(int i=0; i < 1000; i++)
            Trace.info("MyClass.foo()", "hello world");

When tracing is enabled, this will print the trace statement 1000 times. When tracing is disabled, it will not be printed. However, `Trace.info()` will still be evaluated, resulting in 1000 superfluous string creations

Actually, there will be more than 1000 strings created because of concatenation.

. Therefore to prevent evaluating trace statements when tracing is disabled, we can add a check before executing the trace statement:

        for(int i=0; i < 1000; i++) {
            if(Trace.trace)
                Trace.info("MyClass.foo()", "hello world");
        }

If trace is disabled, the ony thing we do here is the check for `Trace.trace`; the trace statement will not be evaluated. Note that it is very important to use such checks in code that is executed frequently. For example, the UDP protocol has such conditional trace statements whenever it receives or sends a datagram packet. Otherwise, if tracing was disabled, a lot of memory would be used for superfluous temporary string creation and subsequent deletion.

### Removing tracing altogether

The `Trace.trace` flag can be used to enable/disable tracing of certain statements. However, if we want to add trace statements for testing purposes that are not supposed to be shipped in the code for the official release, we can use the `Trace.debug` flag. This flag is defined as public static final in Trace and is set to true by default. A trace statement can use it as follows:

        for(int i=0; i < 1000; i++) {
            if(Trace.debug)
               Trace.info("MyClass.foo()", "hello world");
        }

In this case if `Trace.debug` is set to true, the trace statement will be printed. However, to remove all code using `Trace.debug` altogether, one has to set `Trace.debug` to false (in `Trace.java`) and recompile JGroups. The above example would not include the conditional trace statement in the generated code.