google / flogger

A Fluent Logging API for Java

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Support for java.util.ResourceBundle

FWiesner opened this issue · comments

First, we love Flogger in our project and use it with standard Java logging.

What would be really cool is, if you could add overloads to the log methods, where you can use a java.utilResourceBundle as first and a bundle key as second argument, as the ResourceBundle key lookup should itself be skipped in case the log level is not enabled. Wrapping each log statement with a guard is possible, but defeats the beauty of Flogger log statements.

Keep up with the great work!

Rather than describe the change you think you want, can you describe the effect you wish to see?
There may be many ways to achieve what you want, and it's unlikely we would ever override the log methods to do any new feature (it would incur a prohibitive number of additional overloads).

As regards ResourceBundle, Flogger has so far avoided supporting them. The reason is that Flogger is designed for debug logging (i.e. logging messages added by developers during development for debugging purposes). Since ResourceBundle is about translated strings and internationalization, we didn't feel it added much value to Flogger and would have increased the API and code complexity a fair bit, as well as impact performance for formatting log messages with arguments.

If you mean that you want to defer the work for using a ResourceBundle for a logged argument then I'd recommend using either LazyArgs ( log("message=%s", lazy(() -> bundle.getString(key))) ) or maybe look at a custom MetadataKey<> if the resource bundle is a static constant.

For us the benefit of supporting ResourceBundle would be that we ship libraries that are then used by developers not necessarily being well proficient in English or ops folks who require debug logs in their native languages.

The approach with the LazyArg of course works, but then the log message is "%s", which does not look nice either ;) I'll have a look at the MetadataKey approach

I'm in the middle of doing a doc about this sort of customization, but it's not ready yet so I can try to summarize.

If the resource key is just the format message in the log string, you can do this by just having a custom backend that knows how to read that and look up the alternative. However that's a bit fragile since software engineers might tweak debug messages without knowing it can break the resource bundle mapping.

If each translatable message can have a stable key string or enum or some other strongly typed key, then you can do this by writing a custom backend and passing the metadata to it. It would look something like:

logger.atWarning().with(USER_MESSAGE, BAD_FILENAME).log("Bad filename: %s);

Where:

public final class UserMessageKey {
  // Public singleton.
  public static final MetadataKey<KeyType> USER_MESSAGE = MetadataKey.single("user_message", KeyType.class);
  // Public constants (can be made in one place or where needed).
  public static final KeyType BAD_FILENAME = new UserMessageKey("<resource bundle key string>");
}

Then the backend can look for this, extract the resource bundle string from the UserMessageKey's value, and lookup the translation string in a resource bundle it knows about. You can also have the backend ignore this key when formatting the message (since it shouldn't go into the logs itself).

And log statements not using this metadata will just be output as normal (including log messages from libraries you use which use Flogger).

You can obviously add more cleverness to handle how many arguments get passed (e.g. having MetadataKeys like MESSAGE_1ARG, MESSAGE_2ARG etc.) but I don't know enough about your code to be sure if that's worth it.

HTH, and I'll see about getting this doc finished soon.

ah, thank you. Well, it seems that I still need to use guards as my localized strings actually contain the format (of course retaining the order over different languages).

Example (made up and not from my code):

  • English: system component %s has failed initialization as the configuration file failed loading
  • German : Die Systemkomponente %s konnte nicht initialisiert werden, da die Konfigurationsdatei nicht geladen werden konnte

That would be doable in the way I stated, you just need to NOT do formatting via the default mechanism when you get a resource key. Your backend will have to format these itself (e.g. via an exception wrapped String.format()).
The raw message can be got from the "Template Context" of the LogData when there are arguments (without arguments it's just the literal message).

Oh yes, you don't need to retain order if you use the "%1$s" syntax (supported by Flogger's formatter and String.format).

Hmm, I've been thinking about this more and I think that having the backend know about the resource bundle makes less sense than I originally thought (since if you did this you need to have different loggers in different classes use different backend factories). I'll have more of a think about this over the next few days and see if I can come up with a better response.

I have a question for you. Do you expect users of your library to be able to change settings in their Java environment as part of using your library, or do you want the resource bundle stuff to just work without explicit configuration for anyone who depends on it? Both are possible, but with different approaches.
Also, are you using Java modules?

Well, my expectation is that users of our library can of course set the locale to use for messages (but we would have to seed the strings for new languages). Also, I would like to see that users of our library don't get bothered about with how we internally do our logging. So, that we use resource bundles should be mostly opaque and I therefore don't expect users to fiddle with anything else other than setting the default system locale perhaps.

Regarding Jigsaw modules... yes we use them and our own libraries are not supporting the classpath model anymore. For example we have a bunch of custom service loaders and there we only use the registration via module descriptors.

In this case I think the best approach would be to make you own fluent logger subclass to control how resource bundles are loaded and then you can pass a modified/wrapped log data instance to the same underlying backend anyone else is using.
I can provide more details when I'm back from vacation, but it shouldn't be too difficult. The only difference in code would be the logger declaration, all usage should be the same