开发者

How to configure java.util.logging on Android?

开发者 https://www.devze.com 2023-02-01 23:36 出处:网络
I want to use java.util.logging on Android. I want to configure the logging system with logging.properties开发者_高级运维. But how can I tell Android using the specific configure file? For example, I

I want to use java.util.logging on Android. I want to configure the logging system with logging.properties开发者_高级运维. But how can I tell Android using the specific configure file? For example, I placed the logging.properties in the classpath root of the application. How Android knows the location of logging.properties.

Thanks


This is now an FAQ for one of my projects, hopefully more people will find this here: java.util.logging works fine on Android. Please don't use anything else in your code, logging frameworks are like a pest in the Java world.

What is broken is the default logging handler shipped with Android, it ignores any log messages with level finer than INFO. You don't see DEBUG etc. messages.

The reason is the call to Log.isLoggable() in AndroidHandler.java:

https://github.com/android/platform_frameworks_base/blob/master/core/java/com/android/internal/logging/AndroidHandler.java

Here is how you fix it:

import android.util.Log;
import java.util.logging.*;

/**
 * Make JUL work on Android.
 */
public class AndroidLoggingHandler extends Handler {

    public static void reset(Handler rootHandler) {
        Logger rootLogger = LogManager.getLogManager().getLogger("");
        Handler[] handlers = rootLogger.getHandlers();
        for (Handler handler : handlers) {
            rootLogger.removeHandler(handler);
        }
        rootLogger.addHandler(rootHandler);
    }

    @Override
    public void close() {
    }

    @Override
    public void flush() {
    }

    @Override
    public void publish(LogRecord record) {
        if (!super.isLoggable(record))
            return;

        String name = record.getLoggerName();
        int maxLength = 30;
        String tag = name.length() > maxLength ? name.substring(name.length() - maxLength) : name;

        try {
            int level = getAndroidLevel(record.getLevel());
            Log.println(level, tag, record.getMessage());
            if (record.getThrown() != null) {
                Log.println(level, tag, Log.getStackTraceString(record.getThrown()));
            }
        } catch (RuntimeException e) {
            Log.e("AndroidLoggingHandler", "Error logging message.", e);
        }
    }

    static int getAndroidLevel(Level level) {
        int value = level.intValue();

        if (value >= Level.SEVERE.intValue()) {
            return Log.ERROR;
        } else if (value >= Level.WARNING.intValue()) {
            return Log.WARN;
        } else if (value >= Level.INFO.intValue()) {
            return Log.INFO;
        } else {
            return Log.DEBUG;
        }
    }
}

In the main activity/initialization code of your application:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    AndroidLoggingHandler.reset(new AndroidLoggingHandler());
    java.util.logging.Logger.getLogger("my.category").setLevel(Level.FINEST);
...

TL;DR: Yes, you could use some magic properties, or adb shell command, or even learn how the stupid built-in logging handler's DalvikLogging.loggerNameToTag converts category names to tags (which you would have to do for those magic properties and shell commands), but why bother? Isn't logging painful enough?


Generally one uses android.util.Log for logging on Android. There are some key advantages to using that logger, such as being able to use adb logcat to view logging output sent to those logs.

You can try put logging.properties in assets/ or res/raw/. If Android doesn't pick those up there, then one can use java.util.logging.LogManager.readConfiguration(java.io.InputStream) to force load it. (You can use the Resources and AssetManager classes to get the file as an InputStream).


At least on Android 4.3, with unchanged Handler, if you use

adb shell setprop log.tag.YOURTAG DEBUG

You can see messages logged with up to Level.FINE in logcat. I haven't figured out a way to log higher levels, but that's sufficient for me.


In case one of you only wants to route java.util.logging output from third party libraries, this can achieved pretty easily with SLF4J and its jul-to-slf4j bridge. This works for FINE and FINEST log statements, too, BTW.

Here's the maven dependency

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jul-to-slf4j</artifactId>
    <version>${slf4j.version}</version>
</dependency>

To bootstrap it, put the following in your Application class, Roboguice Module or somewhere else where it gets executed before your first log statement. (Configuring this in assets/logging.propertiesseems not to work, unfortunately).

/*
 * add SLF4JBridgeHandler to j.u.l's root logger, should be done once
 * during the initialization phase of your application
 */
SLF4JBridgeHandler.install();

You can then either

  • configure all your log statements from assets/logback.xml (using logback-android). See here for a mapping of log levels.

  • or just use SLF4J-android to forward the log statements to logcat. To do so, just put the following dependency to your classpath, no further config required:

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-android</artifactId>
        <version>${slf4j.version}</version>
    </dependency>
    

I implemented this successfully using

<properties>
    <slf4j.version>1.7.6</slf4j.version>
</properties>

Note:

  • Please read the part of the jul-to-slf4j doc that relates to performance carefully!
  • If you use the LogcatAppender make sure to keep in mind that the JUL log messages (except the ones with level finer than INFO) are routed to logcat by android. So make sure to apply appropriate filters that avoid duplicates.


Here is modified answer of Christian Bauer

If somewhere in any third party lib you will see something like this:

   final public class AnyClass {        
        private static final Logger logger = Logger.getLogger(AnyClass.class.getName());
         ...
            if (logger.isLoggable(Level.FINE)) {
                logger.fine("any log event");
            }
         ...
     }

Add to your code this after creating first instance of AnyClass.class:

SetupLogger.setup(Arrays.asList(
                new Pair<>(AnyClass.class.getName(), Level.FINEST),//AnyClass.class.getName() is because instance `logger` has his name
                new Pair<>(AnyOther.class.getName(), Level.FINEST)
        ));

SetupLogger .class

public class SetupLogger {

    static public void setup(List<Pair<String, Level>> loggerNames) {
        // suppress the logging output to the console
        AndroidLoggingHandler rootHandler = new AndroidLoggingHandler();
        AndroidLoggingHandler.reset(rootHandler );
        for (Pair<String, Level> pair : loggerNames) {
            Logger logger = Logger.getLogger(pair.first);
            logger.setLevel(pair.second);
        }
    }
}
0

精彩评论

暂无评论...
验证码 换一张
取 消