6 Replies Latest reply on Jul 18, 2006 12:44 PM by mazz

    I18N logging messages

    mazz

      Has anyone considered hooking in I18N resource bundles into the logging framework?

      Something like:

      log.debug("file.not.found");

      where "file.not.found" is a key into a resource bundle and the message that actually gets logged is language/locale specific?

      for backward compatibility, we may have to wrap the key in a special object:

      log.debug(new I18N("file.not.found").toString());

      and have that I18N class's toString perform the resource bundle lookup.

      This would also enable parameters to be passed so the resource bundle's {0}, {1}, ...{n} arguments get replaced:

      log.debug(new I18N("file.not.found", new File("/etc/hosts"));

      where the resource looks like this:

      file.not.found=The file named {0} was not found

      There are other issues, such as how do you tell I18N what resource bundle to actually read from (I18N may actually need to be a class static field to define something like the commons-logging Logger - or the JBoss logger can use its category name to determine what resource bundle to use).

      Just some off-the-cuff thoughts - was wondering if anyone else was thinking about I18N logging and/or if anyone is working on something like this?

        • 1. Re: I18N logging messages
          mazz

          Another thought on this... can we AOP here?

          new I18N("file.not.found") could be all that we need. We can use AOP to determine what resource bundle the logger should use (dependency injection? an aspect on the log.debug method? an aspect on the I18N constructor?)

          One thing is that I'd like for this to be backward compatible to JBoss 3.x

          • 2. Re: I18N logging messages
            starksm64

            We have discussed this somewhat in the past but never developed sufficient motivation. The least intrusive change would be to update the Logger factory to use the message as a key with the resource bundles located in the classpath say under conf/logging-i18n

            It should emit a default resource bundle for the messages that don't have a matching key to dynamically build the keys which can be localized. This will allow one to gradually localize output as needed. There does need to be a varargs like I18N encapsulation object for the cases where the log message cannot really be used as a key due to it being composed of runtime values.

            The aop approach is not going to work with 3.2 due to the jdk1.3 requirements imposed by the 3.2 branch.

            • 3. Re: I18N logging messages
              mazz

               

              The least intrusive change would be to update the Logger factory to use the message as a key with the resource bundles located in the classpath say under conf/logging-i18n


              I was thinking we pass in an I18N class as the first arg. to log() and the Logger would handle it normally (that is, just call toString() on it like it would do for any Object it gets passed). The I18N would then, in its toString() lookup the message in the bundle. This would allow the resource bundle lookup to only occur when it needs to (that is, if the message actually needs to get logged, not if the log level isn't enabled, for example). I18N would provide a varargs type of functionality (to support < JDK 1.5, we'd have to have just a series of constructors) to allow for placeholder replacement values. This is how we can support dynamic messages with runtime info.

              The question is, how do you tell the I18N object what resource bundle to look in? We could add a factory method to Logger to create an I18N object that has the same category as the Logger and we use that as the name (or part of the name) of the resource bundle:

              private static final Logger LOG = LoggerFactory.getLogger(MyClass.class);
              ...
              LOG.debug(LOG.createI18N("file.not.found", myFile));


              Or we provide a public method I18N.setBundleName() that the Logger would have to call, but that would break the abstraction in which the Logger doesn't know its a special I18N object - it would be ideal for it to just call toString() and have it magically work without the Logger doing anything special (like "if (msg instanceof I18N) msg.setBundleName(...)).

              I've also seen I18N logging work where each class with I18N messages would create a sister object to the public static final Logger and provides I18N factory building:

              private static final Logger LOG = LoggerFactory.getLogger(MyClass.class);
              private static final I18N MSG = LOG.getI18NFactory();
              ...
              LOG.debug(I18N.msg("file.not.found", myfile));
              


              Using the message key string passed to log() as the resource bundle key would be a way to capture the messages that don't have dynamic info - but that would mean

              A) the resource bundle lookup would be performed on every log, even if the message isn't in a resource bundle yet - not sure how that exception condition would affect logging performance

              B) the resource bundle key would be really long in some cases (not sure if this is a problem, unless there turns out to be invalid .properties file characters in there like '=')

              C) depending on the ratio of static messages to messages with dynamic content, it may not prove to be valuable enough to offset the performance hit

              It should emit a default resource bundle for the messages that don't have a matching key to dynamically build the keys which can be localized. This will allow one to gradually localize output as needed.


              That's a good idea - we could spit out classname, line number type info with it - then you just perform a logging run to capture messages. Obviously, as you said, this would be a gradual process and wouldn't help with the dynamic log messages in which an I18N encapsulation object would have to be used.

              Then the question comes up with, how do you encapsulate the key string as a constant (to avoid typos in logging like "file.not.fond") and how do you auto-create the resource bundles to free the developer from having to do it? We can introduce an XDoclet tag that would define the english version of the message and the message key:

              /**
               * This class does something. Blah. Blah.
               * @author Joe Bling
               *
               * @msg FILE_NOT_FOUND File named {0} was not found
               */


              We just run this through a special xdoclet ant task to extract all the message keys, and create an interface that contains the message key (constant name and message key are the same name). Then it would create a resource bundle file with the english version of the message. We then hand off the resource bundles to translators to translate to different languages. This XDoclet approach gives the added benefit of allowing the developer to see the actual error message at the top of the .java file in the @msg tag. (conceivably, we could allow @msg anywhere - in methods for example - to allow for more localized definitions of @msg's. That is define @msg on the method in which it is used).

              An example interface would be:

              package where.my.class.is;
              
              public interface I18NMessages
              {
               /** File named {0} was not found */
               String FILE_NOT_FOUND = "FILE_NOT_FOUND";
              }
              


              Note that generating code like this with the javadoc command containing the actual message has an added benefit - tools like Eclipse that provide hover help will show you the actual message when you hover over the constant (nice for developers to make sure they know what placeholders are in the message right there while writing the log call).

              The resource bundle:

              FILE_NOT_FOUND=File named {0} was not found


              • 4. Re: I18N logging messages
                mazz

                Anyone ever use the XDoclet @msg tags? Looks like they've already got the ability to create message bundles. Not sure what they mean when they say they generate "translator classes", too.

                See: http://xdoclet.sourceforge.net/xdoclet/tags/xdoclet-tags.html[/url]

                • 5. Re: I18N logging messages
                  marksigep

                  Very good thoughts, but one important aspect has not been discussed yet. You'll need to tell the i18N object which Locale to use or specify a particular ResourceBundle with the correct Locale already set on it. Perhaps a new FileWriter would be appropriate in this case with XML attributes in log4j.xml defining the ResourceBundle basename (required) and the Locale country, language, and variant (all optional, defaults to Locale.getDefault()). Migfht also need to specify the charset for UTF-16 languages such as Chinese, Arabic, etc. You could then use Log4J as usual, but just passing in the key instead of the actual message. Heck at that point you could define mutiple i18N appenders of this new subclass then define a Logger with refs to all the internationalized appenders. Then you could be logging the same message in multiple languages simultaneously and the logging API would not change.

                  • 6. Re: I18N logging messages
                    mazz

                    Just an FYI regarding I18N messages and logging those messages.

                    A new project called i18nlog now exists (I grew this out of another project I've been working on at home). It includes:

                    * a way to auto-generate resource bundle properties files (via a custom ant task)
                    * an API to get I18N messages (for use within user interfaces for example)
                    * an API to create localized exceptions (where the exception message is an I18N message)
                    * an API to log I18N messages (so really, logging is just a part of this project).

                    It requires JDK5 because it uses varargs to get runtime values that replace the {0} type placeholders in the messages and it uses Java5 annotations to declare your resource bundles, messages and their locales.

                    See http://i18nlog.sourceforge.net for more information.

                    The API is here: http://i18nlog.sourceforge.net/api