Skip to content

Logging

Jiaru Bai edited this page Nov 14, 2022 · 29 revisions

All codes within The World Avatar project (TWA) should ensure they adequately use logging to record their actions and the occurrences of any warnings/errors. To that end, a common logging approach is presented below. Unless a specialist approach is needed for a particular code base (if so, please consult senior developers), then it's expected that this approach is followed in all TWA projects.

Note: A previous logging framework is present in some older code bases; previously, the JPSBaseLogger class within the JPS Base Library was created with the intention that all codes would log through this central location, and that logs were remotely sent to a Servlet running using the JPS_BASE project. Whilst this JPSBaseLogger class still exists, it is now deprecated and should be avoided wherever possible. The logging Servlet provided by the JPS_BASE project is also not currently in use and should be avoided.


Logging in Java projects

Logging within all Java projects should now be handled via the use of Log4j2. Older versions of Log4j, and any use of SLF4J are now discouraged.

Log4j2 uses a configuration file to define the destination of logging statements, their format, and the minimum logging level. Two Log4j configuration files (one for development environments, one for production) have been created for use with TWA Java code bases and can be downloaded from the package repository using Maven. See the Packages page of the Wiki for details on how to connect to the package repository.

The configuration files for each environment are currently configured as such:

Development Production
Logs to Console Logs to Console
Logs to Rolling File Logs to Rolling File
Minimum level: DEBUG Minimum level: WARN

Log files will be written to ~/.jps/logs; if the JPSAgent class is initialised at any point (i.e. by referencing or inheriting from it), then the standard out and err streams will also be redirected to the logging system, developers can call methods like, System.out.println() as normal and the contents should get redirected to the logger (and to the console and log file in turn).

Setting up new code bases

If you're starting a new Java code base, it's highly suggested that you take a copy of the example Java agent. This has already been configured to include the required libraries, download the config files, and contains example logging statements. Comments are provided within the pom.xml, source code, and Docker configuration of this example agent; it's worth taking the time to read and understand what's going on before adding your own changes.

Updating existing code bases

If adding logging to (or updating logging in) an existing code base, simply follow the below steps.

  1. Add the parent pom to your project.
  2. Ensure the below dependencies are added to your project (any other logging libraries can be removed).
<dependency>
	<groupId>org.apache.logging.log4j</groupId>
	<artifactId>log4j-api</artifactId>
</dependency>
<dependency>
	<groupId>org.apache.logging.log4j</groupId>
	<artifactId>log4j-core</artifactId>
</dependency>
  1. If using a Java project that builds as a WAR file (i.e. a Servlet), also include the following dependency.
<dependency>
	<groupId>org.apache.logging.log4j</groupId>
	<artifactId>log4j-web</artifactId>
</dependency>
  1. Add the following plugins to your project's pom.xml file. If definitions of these plugins already exist in your project, their configuration should automatically be merged with the one declared in the parent pom (so you don't need to add these elements). If this causes issues, please contact senior developers.
<!-- Used to build into a WAR file and ensures everything in ./WEB-INF
gets copied into the final WAR file's internal WEB-INF directory. -->
<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-war-plugin</artifactId>
	<!-- Version, configuration, and executions should be pulled from the 
	parent POM unless overridden here. -->
</plugin>

<!-- Downloads and extracts ZIP archives from Maven repository -->
<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-dependency-plugin</artifactId>
	<!-- Version, configuration, and executions should be pulled from the 
	parent POM unless overridden here. -->
</plugin>
  1. Add the following profiles to your project's pom.xml file. This will allow you to choose which logging configuration file (development or production) will be used in your project via a command line argument.
<!-- Profiles are used to switch between building for development and production 
environments. Use "-P profile-id" within an mvn command to build with a profile -->
<profiles>
	<!-- This profile should be used for development builds. -->
	<profile>
		<id>dev-profile</id>
		<activation>
			<activeByDefault>true</activeByDefault>
		</activation>
		<properties>
			<!-- Set property to download development logging config -->
			<log.artifact>java-logging-dev</log.artifact>
		</properties>
	</profile>
	
	<!-- This profile should be used for production builds. -->
	<profile>
		<id>prod-profile</id>
		<properties>
			<!-- Set property to download production logging config -->
			<log.artifact>java-logging-prod</log.artifact>
		</properties>
	</profile>
</profiles>

Once these steps are completed, you should be able to initialise and call Log4j2 Logger objects directly within your classes. It's recommended that each class contains a private static final Logger instance with the input class/class name passed during construction.

An example of a simple Java class using a Log4j2 logger is shown below.

package uk.ac.cam.cares.jps.agent;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
 * Example logging usage.
 */
public class LoggingExample {

    /**
     * Logger for reporting info/errors.
     */
    private static final Logger LOGGER = LogManager.getLogger(LoggingExample.class);

    /**
     * Test the logging.
     */
    public void logSomething() {
        LOGGER.debug("This is a debug message.");
        LOGGER.info("This is an info message.");
        LOGGER.warn("This is a warn message.");
        LOGGER.error("This is an error message.");
        LOGGER.fatal("This is a fatal message.");
    }
}

Logging in Python projects

As of py4jps==1.0.29, the logging utilities can be found within the JPS_BASE_LIB/python_wrapper/py4jps/agentlogging directory.

Via the logging script within the py4jps.agentlogging module, developers can acquire a logger configured for development (dev) or production (prod) environments. Calls to this logger can then follow the standard Python logging framework. After importing the module, initialisation of the logging library will automatically take place, so developers only need to import it then uses its loggers.

The configurations for each logger:

Development Production
Logs to Console Logs to Console
Logs to Rolling File Logs to Rolling File
Minimum level: DEBUG Minimum level: WARN

Log files will be written to ~/.jps/logs; the standard out stream will also be redirected to the logging system, developers can call the print() function as normal and the contents should get redirected to the logger (and to the console and log file in turn).

Adding to your code bases

If adding logging to (or updating logging in) an existing code base, simply follow the below steps.

  1. Install the py4jps package directly from PyPI
    • This can be done with the following command (or by adding py4jps~=1.0.29 to your project's requirements.txt file).
pip install py4jps~=1.0.29
  1. Import the agentlogging module with from py4jps import agentlogging.
  2. Create a logger using the agentlogging.get_logger() function, passing either dev or prod.
  3. Log statements as usual.

An example of a Python script using the logging is provided below.

from py4jps import agentlogging

def demo():
    """
        Demo the logging functionality.
    """
    print("=== Development Logging ===")
    dev_logger = agentlogging.get_logger("dev")
    dev_logger.debug("This is a DEBUG statement")
    dev_logger.info("This is an INFO statement")
    dev_logger.warning("This is a WARNING statement.")
    dev_logger.error("This is an ERROR statement.")
    dev_logger.critical("This is a CRITICAL statement.")

    print("=== Production Logging ===")
    prod_logger = agentlogging.get_logger("prod")
    prod_logger.debug("This is a DEBUG statement")
    prod_logger.info("This is an INFO statement")
    prod_logger.warning("This is a WARNING statement.")
    prod_logger.error("This is an ERROR statement.")
    prod_logger.critical("This is a CRITICAL statement.")

    print("=== System Stream ===")
    print("This is a STANDARD OUT statement.")

demo()