1package de.duenndns.mtmexample;
2
3import java.io.IOException;
4import java.io.InputStream;
5import java.io.PrintWriter;
6import java.io.StringBufferInputStream;
7import java.io.StringWriter;
8import java.util.logging.Formatter;
9import java.util.logging.Handler;
10import java.util.logging.Level;
11import java.util.logging.LogManager;
12import java.util.logging.LogRecord;
13import java.util.logging.Logger;
14
15import android.util.Log;
16
17/**
18 * A <code>java.util.logging</code> (JUL) Handler for Android.
19 * <p>
20 * If you want fine-grained control over MTM's logging, you can copy this
21 * class to your code base and call the static {@link #initialize()} method.
22 * </p>
23 * <p>
24 * This JUL Handler passes log messages sent to JUL to the Android log, while
25 * keeping the format and stack traces of optionally supplied Exceptions. It
26 * further allows to install a {@link DebugLogSettings} class via
27 * {@link #setDebugLogSettings(DebugLogSettings)} that determines whether JUL log messages of
28 * level {@link java.util.logging.Level#FINE} or lower are logged. This gives
29 * the application developer more control over the logged messages, while
30 * allowing a library developer to place debug log messages without risking to
31 * spam the Android log.
32 * </p>
33 * <p>
34 * If there are no {@code DebugLogSettings} configured, then all messages sent
35 * to JUL will be logged.
36 * </p>
37 *
38 * @author Florian Schmaus
39 *
40 */
41@SuppressWarnings("deprecation")
42public class JULHandler extends Handler {
43
44 /** Implement this interface to toggle debug logging.
45 */
46 public interface DebugLogSettings {
47 public boolean isDebugLogEnabled();
48 }
49
50 private static final String CLASS_NAME = JULHandler.class.getName();
51
52 /**
53 * The global LogManager configuration.
54 * <p>
55 * This configures:
56 * <ul>
57 * <li> JULHandler as the default handler for all log messages
58 * <li> A default log level FINEST (300). Meaning that log messages of a level 300 or higher a
59 * logged
60 * </ul>
61 * </p>
62 */
63 private static final InputStream LOG_MANAGER_CONFIG = new StringBufferInputStream(
64// @formatter:off
65"handlers = " + CLASS_NAME + '\n' +
66".level = FINEST"
67);
68// @formatter:on
69
70 // Constants for Android vs. JUL debug level comparisons
71 private static final int FINE_INT = Level.FINE.intValue();
72 private static final int INFO_INT = Level.INFO.intValue();
73 private static final int WARN_INT = Level.WARNING.intValue();
74 private static final int SEVE_INT = Level.SEVERE.intValue();
75
76 private static final Logger LOGGER = Logger.getLogger(CLASS_NAME);
77
78 /** A formatter that creates output similar to Android's Log.x. */
79 private static final Formatter FORMATTER = new Formatter() {
80 @Override
81 public String format(LogRecord logRecord) {
82 Throwable thrown = logRecord.getThrown();
83 if (thrown != null) {
84 StringWriter sw = new StringWriter();
85 PrintWriter pw = new PrintWriter(sw, false);
86 pw.write(logRecord.getMessage() + ' ');
87 thrown.printStackTrace(pw);
88 pw.flush();
89 return sw.toString();
90 } else {
91 return logRecord.getMessage();
92 }
93 }
94 };
95
96 private static DebugLogSettings sDebugLogSettings;
97 private static boolean initialized = false;
98
99 public static void initialize() {
100 try {
101 LogManager.getLogManager().readConfiguration(LOG_MANAGER_CONFIG);
102 initialized = true;
103 } catch (IOException e) {
104 Log.e("JULHandler", "Can not initialize configuration", e);
105 }
106 if (initialized) LOGGER.info("Initialzied java.util.logging logger");
107 }
108
109 public static void setDebugLogSettings(DebugLogSettings debugLogSettings) {
110 if (!isInitialized()) initialize();
111 sDebugLogSettings = debugLogSettings;
112 }
113
114 public static boolean isInitialized() {
115 return initialized;
116 }
117
118 public JULHandler() {
119 setFormatter(FORMATTER);
120 }
121
122 @Override
123 public void close() {}
124
125 @Override
126 public void flush() {}
127
128 @Override
129 public boolean isLoggable(LogRecord record) {
130 final boolean debugLog = sDebugLogSettings == null ? true : sDebugLogSettings
131 .isDebugLogEnabled();
132
133 if (record.getLevel().intValue() <= FINE_INT) {
134 return debugLog;
135 }
136 return true;
137 }
138
139 /** JUL method that forwards log records to Android's LogCat. */
140 @Override
141 public void publish(LogRecord record) {
142 if (!isLoggable(record)) return;
143
144 final int priority = getAndroidPriority(record.getLevel());
145 final String tag = substringAfterLastDot(record.getSourceClassName());
146 final String msg = getFormatter().format(record);
147
148 Log.println(priority, tag, msg);
149 }
150
151 /** Helper to convert JUL verbosity levels to Android's Log. */
152 private static int getAndroidPriority(Level level) {
153 int value = level.intValue();
154 if (value >= SEVE_INT) {
155 return Log.ERROR;
156 } else if (value >= WARN_INT) {
157 return Log.WARN;
158 } else if (value >= INFO_INT) {
159 return Log.INFO;
160 } else {
161 return Log.DEBUG;
162 }
163 }
164
165 /** Helper to extract short class names. */
166 private static String substringAfterLastDot(String s) {
167 return s.substring(s.lastIndexOf('.') + 1).trim();
168 }
169}