You are viewing a plain text version of this content. The canonical link for it is here.
Posted to log4j-user@logging.apache.org by Benoit Voisin <be...@ergoidp.dk> on 2002/09/02 10:15:48 UTC

RE: Internationalization

We have implemented an internationalisation interface to Log4J.

We are using a special class, called MessageCode to handle that. We use the
category name to store the message's identifier and the text to store the
parameters ('|' seperated).

The localisation can then be done directly in an LocalisingAppender (using a
ResourceBundle) or at read time using a special reader.

The main problem is that it creates one Logger per type of message => two
class creation (the MessageCode helper object and the Logger) per message
type. For our project, this extra memory usage was not a big issue, and the
use of one Logger per MessageCode eases support and implementation.


We declare all the possible MessageCodes in interfaces:
public interface CatICParser
{
    public static final MessageCode INVALID_XML =
MessageCode.getMessageCode("2.1.0.1");
    public static final MessageCode INVALID_HML =
MessageCode.getMessageCode("2.1.0.2");
}

Then, usage in the code is then very easy:
        CatICParser.INVALID_XML.generate(fileName);
In case two parameters are required:
                CatICParser.INVALID_HML.generate(new
String[]{fileName,tagName});
        or
                CatICParser.INVALID_HML.generate(fileName,tagName);

There is currently no check for the number of parameters for each message
type. But this wont be a major issue, as the 'localizer' supports
missing/extra parameters. I am planning to have a small program looking in
the code and verifying the number of parameters used for each generate
message, but nothing is done yet.


We declare all the messages (code (identifier), list of required parameters,
and text in different languages) in a XML file. Some XSLT, then creates the
ResourceBundles and the Java interfaces declaring the MessageCode instance
(static final, named CatXXX).

I am working on a good documentation on this class, and if I get the
authorisation from management, I will provide full code for it, and if Ceki
is interested, it could be included as a Log4J Add-on. For the moment all I
can offer is this short introduction and an uggly copy paste from our
internal doc (see below).

If you are interested for more information, just contact me.

Benoit







1.     Logging Implementation usage


1.1     Introduction


v     All messages that can be send must be of a predeclared Message Code.
(There is one message code per message that can be sent). There is an
exception for pure tracing messages that are used for the developer at
development time.<?xml:namespace prefix = o ns =
"urn:schemas-microsoft-com:office:office" />

v     Message codes are defined in a centralised XML file and are identified
by a unique id

v     Message codes are grouped by categories which can inturn be grouped in
super-categories

v     A message code include different texts (title, description, trouble
shooting, ...), which can all be caracterised.

v     Messages are localised at read time (not at generation time): only the
message code's id, parameters and context info are stored in file or db.

 

There is a small sample project using this library and you should use it as
a base. It is situated in : W:\work\prototype\src\common\logging

 


1.2     Definition of messages


Messages are defined in a messageList.xml file, which contains all the
information about the messages.

 

This XML file is then passed through various XSLT to generate:

v     The Java interfaces defining the messages

v     The message_XX.properties, containing the localisation strings for
each message. 

v     SQL statements inserting the message localisation strings in the
database (to be implemented)

In a previous version, the Log4j configuration file was generated from this
file. As it increases loading time and does not give much extra flexibility,
this was forgotten.


1.2.1     The Message definition file: messageList.xml


This is the file that defines the list of messages. There should be one
messageList.xml file per module or even by sub module in certain cases. The
only limitation is that the messagecategory hierarchy (concatanation of
category codes) must not clash.

You will find the commented XML Schema in
W:\work\epostmessenger\src\common\logging\xslt and the generated
documentation in W:\work\epostmessenger\src\common\logging\doc (both in
PVCS).

 

Here is a sample file.

 

<?xml version="1.0" encoding="UTF-8"?>

<!-- edited with XML Spy v4.3 U (http://www.xmlspy.com) by Benoit Voisin
(ErgoIDP) -->

<!--  

  * XML file containing the initial Log4J configuration and the list of
messages

  * It is used by: 

  *   - genJavaClasses.xsl to generate the CatXXX interface defining the
legal messages

  *   - genResourceBundle.xsl to generate the Message_en.properties
containing the localised text of the messages in a ResourceBundle 

  */

-->

<messagelist javaSrcDir="." javaPackageName="logging.logcat"
categoryPrefix="Id" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../../../epostmessenger/src/common/logg
ing/xslt/messageList.xsd">

       <!--javaSrcDir: root src  where the java interfaces should be
stored(usually "." as this file is stored in the src directory)

              javaPackageName: package to which all java interface should be
placed in

              categoryPrefix: Prefix to add to the Category code (Usually
"Id")-->

       <messagecategory code="1">

             <!--

             code: identifier of this category within its mother category

             javaname: the name of the Java interface that will represent
this category

             threshold: Log4J Level below which messages will be
forgotten-->

              <javaname>CatIC</javaname>

              <threshold>INFO</threshold>

             <text lang="en">

                    <!--Localised text, in the language in lang-->

                    <displayname>General IC</displayname>

             </text>

              <messagecategory code="1">

                    <javaname>CatICParser</javaname>

                    <threshold>INFO</threshold>

                    <text lang="en">

                           <displayname>IC Parser</displayname>

                    </text>

                    <message code="1">

                           <javaname>INVALID_XML</javaname>

                           <messagelevel>WARN</messagelevel>

                           <nbparams>1</nbparams>

                           <text lang="en">

                                  <!--Localised text, in the language in
lang-->

                                  <displayname>Invalid XML</displayname>

                                  <displaymessage>The file provided is not
valid XML :{0}</displaymessage>

                           </text>

                           <!--code: identifier of this message within its
mother category

                                  javaname: the name of the constant in the
Java interface that will represent this message(in capital letters)

                                  messagelevel: the Log4J Level of this
message-->

                    </message>

                    <message code="2">

                           <javaname>INVALID_HML</javaname>

                           <messagelevel>INFO</messagelevel>

                           <nbparams>2</nbparams>

                           <text lang="en">

                                  <displayname>Invalid HML</displayname>

                                  <displaymessage>The file provided is not
valid HML. There is an error on line {1} of file {0}</displaymessage>

                           </text>

                    </message>

              </messagecategory>

       </messagecategory>

</messagelist>


1.2.2     The Java interfaces CatXXX.java


A series of Java interfaces acting as enumeration of MessageCodes will be
created from this XML file:

 

/**

  * file generated by genJavaClasses.xsl from a message.xml file

 */

 

package dk.ergoidp.epostmessenger.prototype.util.logging.category;

 

import dk.ergoidp.epostmessenger.prototype.util.logging.MessageCode;

 

public interface CatICParser

{

    MessageCode INVALID_XML = MessageCode.getMessageCode("2.1.0.1");

    MessageCode INVALID_HML = MessageCode.getMessageCode("2.1.0.2");

}

Then, when a developer needs to generate a message stating the XML is
invalid, he just has to write this line:

CatICParser.INVALID_XML.generate(fileName);

In case two parameters are required:

CatICParser.INVALID_HML.generate(new String[]{fileName,tagName});
or    CatICParser.INVALID_HML.generate(fileName,tagName);

Generate methods exists with 1,2, or 3 parameters.


1.2.3     Generating the Java interfaces and the ResourceBundle


They are generated using XSLTs over the messageList.xml file:

v     genJavaClasses.xsl à Cat*.java

v     genResourceBundle.xsl à Messages_en.properties (for development time
translation of messages)

 

Soon, other will generate SQL statements to insert the localized texts into
the database

The easiest way is to launch theses transformations is to use Ant. Below is
a sample build.xml file: 

<?xml version="1.0" encoding="windows-1252"?>

<project name="LoggingImplementation" default="genAllLogging" basedir=".">

       <description>

        Ant file used to launch several actions. 

        Compiling and running is still done within JDevelopper, and not
here.

        For the moment:

            - Doxygen : generate Doxygen documentation on ./src

            - JavaDoc : generate JavaDoc documentation on ./src

            - Jalopy  : format the code according to certain standards

            - genAllLogging : Generates : 

             - genJavaClasses    : the CatXXX interfaces file (enumeration
of the different messages) 

             - genLog4J          : classes/Log4J.xml 

             - genResourceBundle : Resource_en.properties (the list of
localized messages)

    </description>

       <!-- set global properties for this build -->

       <taskdef name="jalopy"
classname="de.hunsicker.jalopy.plugin.ant.AntPlugin"/>

       <property name="messageListFile"
location="src/prototypeMessageList.xml"/>

       <property name="srcDir" location="src"/>

       <property name="ePostMessengerRoot"
location="../../../../epostmessenger"/>

       <property name="libDir" location="${ePostMessengerRoot}/src/lib"/>

       <property name="tempDir" location="temp"/>

       <property name="buildDir" location="classes"/>

       <property name="messageXsltDir"
location="${ePostMessengerRoot}/src/common/logging/xslt"/>

       <property name="dist" location="dist"/>

       <target name="init">

             <!-- Create the time stamp -->

             <tstamp/>

             <!-- Create the build directory structure used by compile -->

             <mkdir dir="${buildDir}"/>

             <mkdir dir="${tempDir}"/>

       </target>

       <!--
==================================================================== -->

       <!-- Logging code + resource generation
-->

       <!--
==================================================================== -->

       <target name="genAllLogging" description="generates the Java
interfaces enumarating the MessageCode and the ResourceBundle"
depends="init,genJavaClasses,genResourceBundle"/>

 

       <target name="genJavaClasses" description="run genJavaClasses.xsl on
log4j_pre.xml to generate the Java classes. A temporary file is created and
cut into the .java files using bvo.utils.FileCutter" depends="init">

             <xslt in="${messageListFile}" out="${tempDir}/JavaFiles.txt"
style="${messageXsltDir}/genJavaClasses.xsl"/>

             <echo message="XSLT done"/>

             <java fork="true" dir="${srcDir}"
classname="dk.ergoidp.util.FileCutter"
classpath="${libDir}/ErgoIdpUtils.jar">

                    <arg value="${tempDir}/JavaFiles.txt"/>

                    <arg value="///Start of:"/>

                    <arg value="///EOF"/>

             </java>

       </target>

       

       <target name="genResourceBundle" description="run
genResourceBundle.xsl on log4j_pre.xml to generate the
Messages_en.properties resource file" depends="init">

             <xslt in="${messageListFile}"
out="${buildDir}/Messages_en.properties"
style="${messageXsltDir}/genResourceBundle.xsl"/>

       </target>

</project>

Note that genJavaClasses.xsl is not able to cut in different files. Instead
it generates one single JavaFiles.txt, that is cut by a home made FileCutter
class.

Also, Ant 1.5 or later is required for the xslt task used in this build.xml
file.


1.2.4     The Message Lookup Addin


To ease developers knowing the parameters to include in each message (this
cannot be checked at compile time), an JDeveloper addin can help them find
the related display text:

When the cursor is on a line containing the start of a generate call, this
Addin finds the closest messageList.xml file and shows the display text (for
english) of the message code.

On the line:      CatICParser.INVALID_HML ...

a call to the Addin (Default Ctrl-L) displays in the status bar:

CatICParser.INVALID_HML=>The file provided is not valid HML. There is an
error on line {1} of file {0} [logged at:INFO]


1.2.5     The Localising appender - Messages_en.properties


To ease development and understanding of the messages generated by the code,
Log4J can be configured to copy localised versions of the messages in the
standard out.

Look at the Log4J configuration file below for the LOCALISED_STDOUT appender
or at the 

the dk.ergoidp.epostmessenger.prototype.util.logging.LocalisingPatternLayout
API documentation for more information.

Note that you need to have generated the Messages_en.properties (see above)
and that it should be accessible as a resource by the JVM (in the class
path).


1.2.6     Context information


Context information, such as, the main request, batch Id, ... are to be set
in the code using Log4J's MDC. This context information will be stored along
with the message code and parameters.

 

The keys are to be defined, depending on the needs.


1.2.7     Pure tracing (debugging)


For pure tracing statements, it is not required to declare MessageCodes. It
will be too heavy to use and not very usefull, as pure tracing statements
never have to be translated. In this case, pure Log4J calls can be used :

In each class where tracing has to be used, insert the line: 

static final org.apache.log4j.Logger debugLogger =
org.apache.log4j.Logger.getLogger(CLASS_NAME.class);

And trace using for example: 

debugLogger.error("Should never happen",exception);

 

debugLogger.debug("Entered myProcedure(" + param1 + ")");

 

In a development configuration, these messages will be logged in the
Standard Output. It should still be much prefered over System.out.println,
since at config or run time, the tracing level for each class can be
adjusted. System.out.println should not be used in the code.


1.3     The Log4J configuration file Log4j.xml


It defines the different Log4J Loggers corresponding to the message codes
and message categories, respecting the category hierarchy. There is one
Log4J logger per message code and per message category, but they do not have
to be defined in this file. Only the root category and the one with specific
configuration have to.

Here is a sample file:

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE log4j:configuration SYSTEM
"H:\Tech\Documentation\JavaLibs\jakarta-log4j-1.2rc1\src\java\org\apache\log
4j\xml\log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

       <!-- appender writing to the STD OUT (System.out)-->

       <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">

             <layout class="org.apache.log4j.PatternLayout">

                    <param name="ConversionPattern" value="%d %-5p [%t] %c
'%m'\n"/>

             </layout>

       </appender>

       <!-- appender writing to the STD OUT but translating the message
codes into english text -->

       <appender name="LOCALISED_STDOUT"
class="org.apache.log4j.ConsoleAppender">

             <layout
class="dk.ergoidp.epostmessenger.common.logging.LocalisingPatternLayout">

                    <param name="ConversionPattern" value="%d %-5p [%t] %c:
%z\n"/>

                    <!-- the %z represents the localized text -->

                    <param name="LocaleLanguage" value="en"/>

                    <!-- language -->

                    <param name="ResourceName" value="Messages"/>

                    <!-- ResourceBundle of messages -->

             </layout>

       </appender>

       <!--
=================================================================

         Root Logger for all MessageCodes. 

         Additivity =false so that it does not take into account the
appender 

         of the rot category(below) 

         =================================================================
-->

       <logger name="Id" additivity="false">

             <!-- all message codes starting with 'Id.' they will be taken
into account here -->

             <appender-ref ref="LOCALISED_STDOUT"/>

       </logger>

       <!-- if uncommented, will specialize tracing for the package logging 

        <logger name="logging" >         

            <level value="DEBUG"/>

        </logger>

    -->

       <!-- if uncommented, will specialize tracing for the class
logging.LogTester  

    <logger name="logging.LogTester" >         

        <level value="DEBUG"/>

    </logger>

    -->

       <!--
================================================================= 

         Global Root category

         We ask it to be forget any message below INFO level.        

         =================================================================
-->

       <root>

             <level value="INFO"/>

             <appender-ref ref="STDOUT"/>

       </root>

</log4j:configuration>