Introduction
The JBoss logging tools are used to create internationalized log statements and exceptions.
Getting Started
To get started you need to include three dependencies in your project.
Required at compile time and runtime. |
|
Required at compile time only. |
|
Required at compile time only. |
If you’re using maven here is an example pom.xml
snippet:
<project>
<dependencies>
<!-- Required by the annotation processor and will be used at runtime -->
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging-annotations</artifactId>
<version>3.0.2.Final</version>
<!-- This is a compile-time dependency of this project, but is not needed at compile or runtime by other
projects that depend on this project.-->
<scope>provided</scope>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<!-- Define the annotation processor path. This is required on Java 22+. The other option is to
pass the -proc:full javac argument. -->
<annotationProcessorPaths>
<path>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging-processor</artifactId>
<version>3.0.2.Final</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
Once your project is configured you can create either a message bundle interface or a message logger interface. For detailed information see the JavaDocs for the annotations.
Message Bundle Interfaces
Message bundle interfaces provide a way to internationalize exceptions or strings. A message bundle interface is
annotated with @MessageBundle
. Each method in must be annotated with @Message
which will be used to determine the String used for either the return type or the message for the exception being
returned.
The value for a @Message
may contain an expression.
Expressions are in the form of ${key:defaultValue} . If the key is prefixed with sys. a system property is
used. If the key is prefixed with env. an environment variable is used. In all other cases the properties are resolved
from the org.jboss.logging.tools.expressionProperties path. If the key is not found in the properties the default
value will be used.
|
Expressions are processed at compile time. The values will be hard-coded in the generated source files. |
The following constraints are placed on methods in a message bundle:
-
The return type must be one of the follow
-
A
java.lang.String
-
A
java.lang.Throwable
or a subtype ofjava.lang.Throwable
-
A
java.lang.function.Supplier
who’s return type fits one of the other two constraints above.
-
-
The method must be annotated with
@Message
or a message must be inheritable. -
A method can have only one
@Cause
parameter. -
A method can only have one
@Producer
parameter.
A message is inheritable if the two methods have the same name and same number of format parameters. |
A format parameter is a parameter that has no annotations or is annotated with one of the following annotations; @FormatWith , @Pos or @Transform .
|
Example Message Bundle
@MessageBundle(projectCode = "CW") (1)
public interface ErrorMessages {
ErrorMessages MESSAGES = Messages.getBundle(ErrorMessages.class);
@Message("Version %d.%d.%d.%s") (2)
String version(int major, int minor, int macro, String rel);
@Message(id = 1, value = "Value '%s' is invalid")
IllegalArgumentException invalidValue(Object value);
@Message(id = 2, value = "Failure closing %s") (3)
CloseException closeFailure(@Cause Throwable cause, @Param @Pos(1) Closeable c);
CloseException closeFailure(@Cause Throwable cause, @Param @Pos(1) Closeable c, @Suppressed Throwable... errors);
@Message(id = 3, value = "Parameter %s cannot be null")
Supplier<String> nullParam(String name);
@Message(id = 4, value = "Operation %s failed.")
<T extends RuntimeException> T operationFailed(@Producer Function<String, T> fn, String name); (4)
<T extends RuntimeException> T operationFailed(@Producer BiFunction<String, IOException, T> fn, @Cause IOException cause, String name);
}
1 | The projectCode will be prepended to messages which have an id specified. For example with id = 1 the message will be prepended with CW000001 . You can control the number padding with the length property on the annotation. |
2 | No id is specified for this message which means no id will be prepended on this message. |
3 | The @Param annotation tells the generator that the parameter should be used to construct the CloseException . The @Pos(1) annotation indicates the parameter should also be used when formatting the message. |
4 | The @Producer annotation indicates that the Function should be used to create the exception being returned. |
Message bundle interfaces can also contain valid message logger methods. |
Message Logger Interfaces
Logger interfaces provide a way to internationalize log messages. A logger interface is an interface annotated with
@MessageLogger
. Each method in must be annotated with @Message
which will
be used to determine the message to be logged.
The value for a @Message
may contain an expression.
Expressions are in the form of ${key:defaultValue} . If the key is prefixed with sys. a system property is
used. If the key is prefixed with env. an environment variable is used. In all other cases the properties are resolved
from the org.jboss.logging.tools.expressionProperties path. If the key is not found in the properties the default
value will be used.
|
Expressions are processed at compile time. The values will be hard-coded in the generated source files. |
The following constraints are placed on methods in a message logger:
-
The method must have a
void
return type -
The method must be annotated with
@LogMessage
-
The method must be annotated with
@Message
or a message must be inheritable. -
A method can have only one
@Cause
parameter.
A message is inheritable if the two methods have the same name and same number of format parameters. |
A format parameter is a parameter that has no annotations or is annotated with one of the following annotations; @FormatWith , @Pos or @Transform .
|
A message logger interface may also extend the org.jboss.logging.BasicLogger
. This allows the logging interface to be usable for standard logging. For example AppLogger.LOGGER.debugf("Initializing %s", this);
Example Message Logger
@MessageLogger(projectCode = "CW") (1)
public interface AppLogger extends BasicLogger {
AppLogger LOGGER = Logger.getMessageLogger(AppLogger.class, AppLogger.class.getPackage().getName());
@LogMessage
@Once (2)
@Message("%s version %d.%d.%d.%s") (3)
void appVersion(CharSequence name, int major, int minor, int macro, String rel);
@LogMessage(level = Logger.Level.ERROR) (4)
@Message(id = 100, value = "Failure while closing %s")
void closeFailure(@Cause Throwable cause, Object obj);
@LogMessage(level = Logger.Level.WARN)
@Message(id = 101, value = "Encoding %s could not be found. Defaulting to %s.")
void encodingNotFound(String encoding, Charset dft);
@LogMessage
@Message(id = 102, value = "Cache size changed to '%d'")
void cacheSizeChanged(@Transform(Transform.TransformType.SIZE) Collection<String> c);
@LogMessage
void cacheSizeChanged(@Transform(Transform.TransformType.SIZE) String... array);
@LogMessage
void cacheSizeChanged(@Transform(Transform.TransformType.SIZE) Map<String, Object> map);
}
1 | The projectCode will be prepended to messages which have an id specified. For example with id = 100 the message will be prepended with CW000100 . You can control the number padding with the length property on the annotation. |
2 | Ensures the log message is only written once. |
3 | No id is specified for this message which means no id will be prepended on this message. |
4 | Overrides the default level to ERROR to indicate an error message should be logged. |
Reports
There are currently two types of reports that can be generated. The options are adoc
or asciidoc
for asciidoc and xml
for XML.
The reports contain the following data:
-
The formatted message id.
-
A possible link to a resolution document for the error.
-
The unformatted message.
-
The log level, if applicable.
-
The return type, if applicable.
Annotations
Two annotations can be used to assist withing linking resolution documents for messages.
-
-
This annotation can be used on a type to define the base URL for linking resolution documents. This annotation is not required for links to be created on reports.
-
-
-
This annotation is used to define information about the resolution document for creating links. If placed on an interface all methods that have a defined id will have a link generated. This can also be placed individually on the method to only generate links for specific id’s.
-
Example Use Cases
Below are some example use case snippets from the examples.
/**
* Writes the value of the object to the file.
*
* @param value the value to write, cannot be {@code null}
*
* @throws UncheckedIOException if an error occurs writing the data
*/
public void write(final Object value) {
AppLogger.LOGGER.appVersion("ContentWriter", 1, 0, 0, "Beta1"); (1)
Objects.requireNonNull(value, ErrorMessages.MESSAGES.nullParam("value")); (2)
write(Objects.requireNonNull(value, ErrorMessages.MESSAGES.nullParam("value")).toString());
}
/**
* Writes the value to the file.
*
* @param value the value to write, cannot be {@code null} or an {@linkplain String#isEmpty() empty string}.
*
* @throws UncheckedIOException if an error occurs writing the data
*/
public void write(final String value) {
AppLogger.LOGGER.appVersion("ContentWriter", 1, 0, 0, "Beta1");
if (Objects.requireNonNull(value, ErrorMessages.MESSAGES.nullParam("value")).isEmpty()) {
throw ErrorMessages.MESSAGES.invalidValue(value); (3)
}
try {
synchronized (outputLock) {
writer.write(value);
writer.newLine();
if (autoFlush) {
flush();
}
}
} catch (IOException e) {
throw ErrorMessages.MESSAGES.operationFailed(UncheckedIOException::new, e, "write"); (4)
}
}
@Override
public void close() {
try {
synchronized (outputLock) {
writer.close();
}
AppLogger.LOGGER.tracef("ContentWriter %s was successfully closed.", this);
} catch (Exception e) {
throw ErrorMessages.MESSAGES.closeFailure(e, this);
}
}
/**
* Safely close this writer logging any errors that occur during closing.
*/
public void safeClose() {
try {
synchronized (outputLock) {
writer.close();
}
AppLogger.LOGGER.tracef("ContentWriter %s was successfully closed.", this);
} catch (Exception e) {
AppLogger.LOGGER.closeFailure(e, this); (5)
}
}
1 | Logs the application version. Note this uses the @Once annotation to indicate this should only be logged once regardless of which write method is used. |
2 | The ErrorMessages.nullParam() returns a java.lang.function.Supplier . This allows the message to be lazily formatted only if the value is null . |
3 | Throws a message if the value is an empty string. |
4 | Uses a java.lang.function.BiFunction to create a new UncheckedIOException with the caught exception set as the cause. |
5 | Logs the caught exception instead of throwing a new exception. |
Translation Property Files
The translation properties files must exist in the same directory structure as the interface. The name of the properties file InterfaceName.i18n_language_country_variant.properties
. For example if you have a class named org.jboss.logging.tools.examples.ErrorMessages
and you want to translate this into French you create a properties file called ErrorMessages.i18n_fr.properties
in a directory org/jboss/logging/tools/examples
.
Annotation Processor Options
You can pass options to an annotation processor with the -A
compiler option. For example to disable generating the @Generated
annotation from being placed on the generated source files you would pass -Aorg.jboss.logging.tools.addGeneratedAnnotation=false
to the compiler command.
General
Option | Description |
---|---|
|
This option turns on debug logging for the processor |
|
This option allows you to define a path where, at compile-time, expressions in messages can be resolved. |
|
If set to |
In Java 9 the @javax.annotation.Generated was moved to @javax.annotation.processor.Generated . The processor attempts to determine which annotation to use by attempting to find the @javax.annotation.Generated first. If it fails the @javax.annotation.processor.Generated is attempted. If neither can be found no annotation will be placed on the generated implementations.
|
Expressions are in the form of ${key:defaultValue} . If the key is prefixed with sys. a system property is
used. If the key is prefixed with env. an environment variable is used. In all other cases the properties are resolved
from the org.jboss.logging.tools.expressionProperties path. If the key is not found in the properties the default
value will be used.
|
Expressions are processed at compile time. The values will be hard-coded in the generated source files. |
Translation Options
Option | Description |
---|---|
|
The base path for the translated properties files. This defaults to the location where new class files are placed. |
|
If set to |
|
If defined this indicates the path a skeleton file should be generated for the interface. The generated skeleton file will be placed in a directory that matches the package with a name that matches the interface with a |
|
Sets the maximum level to include in the generated skeleton files. For example if set to |
|
By default when generating a skeleton translation file an index will be appended to the format pattern. For example |
Report Options
Option | Description |
---|---|
|
Indicates the type of report that should be generated. The current report types are |
|
The path where the generated reports should be placed. This defaults to the location where new class files are placed. |
|
An optional title for the report. For asciidoc this defaults to |