1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements.  See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package java.util.logging;
19
20import dalvik.system.DalvikLogHandler;
21import dalvik.system.DalvikLogging;
22import java.util.ArrayList;
23import java.util.Iterator;
24import java.util.List;
25import java.util.Locale;
26import java.util.MissingResourceException;
27import java.util.ResourceBundle;
28import java.util.concurrent.CopyOnWriteArrayList;
29
30/**
31 * Loggers are used to log records to a variety of destinations such as log files or
32 * the console. They use instances of {@link Handler} to actually do the destination-specific
33 * operations.
34 *
35 * <p>Client applications can get named loggers by calling the {@code getLogger}
36 * methods. They can also get anonymous loggers by calling the
37 * {@code getAnonymousLogger} methods. Named loggers are organized in a
38 * namespace hierarchy managed by a log manager. The naming convention is
39 * usually the Java package naming convention. Anonymous loggers do not belong to any namespace.
40 *
41 * <p>Developers should use named loggers to enable logging to be controlled on a
42 * per-{@code Logger} granularity. The recommended idiom is to create and assign the logger to
43 * a {@code static final} field. This ensures that there's always a strong reference to the logger,
44 * preventing it from being garbage collected. In particular, {@link LogManager#addLogger(Logger)}
45 * will <i>not</i> keep your logger live.
46 *
47 * <p>Loggers "inherit" log level setting from their parent if their own level is
48 * set to {@code null}. This is also true for the resource bundle. The logger's
49 * resource bundle is used to localize the log messages if no resource bundle
50 * name is given when a log method is called. If {@code getUseParentHandlers()}
51 * returns {@code true}, loggers also inherit their parent's handlers. In this
52 * context, "inherit" only means that "behavior" is inherited. The internal
53 * field values will not change, for example, {@code getLevel()} still returns
54 * {@code null}.
55 * <p>
56 * When loading a given resource bundle, the logger first tries to use the
57 * context {@code ClassLoader}. If that fails, it tries the system {@code ClassLoader}. And if
58 * that still fails, it searches up the class stack and uses each class's
59 * {@code ClassLoader} to try to locate the resource bundle.
60 * <p>
61 * Some log methods accept log requests that do not specify the source class and
62 * source method. In these cases, the logging framework will automatically infer
63 * the calling class and method, but this is not guaranteed to be accurate.
64 * <p>
65 * Once a {@code LogRecord} object has been passed into the logging framework,
66 * it is owned by the logging framework and the client applications should not
67 * use it any longer.
68 * <p>
69 * All methods of this class are thread-safe.
70 *
71 * @see LogManager
72 */
73public class Logger {
74
75    /** A handler for use when no handler optimization is possible. */
76    private static final DalvikLogHandler GENERAL_LOG_HANDLER = new DalvikLogHandler() {
77        public void publish(Logger source, String tag, Level level, String message) {
78            LogRecord record = new LogRecord(level, message);
79            record.setLoggerName(source.name);
80            source.setResourceBundle(record);
81            source.log(record);
82        }
83    };
84
85    /**
86     * The name of the global logger. Before using this, see the discussion of how to use
87     * {@code Logger} in the class documentation.
88     * @since 1.6
89     */
90    public static final String GLOBAL_LOGGER_NAME = "global";
91
92    /**
93     * The global logger is provided as convenience for casual use.
94     * @deprecated This is deadlock-prone. Use {@code Logger.getLogger(Logger.GLOBAL_LOGGER_NAME)}
95     * as a direct replacement, but read the discussion of how to use {@link Logger} in the class
96     * documentation.
97     */
98    @Deprecated
99    public static final Logger global = new Logger(GLOBAL_LOGGER_NAME, null);
100
101    /**
102     * When converting the concurrent collection of handlers to an array, we
103     * always pass a zero-length array to avoid size miscalculations. Passing
104     * properly-sized arrays is non-atomic, and risks a null element in the
105     * result.
106     */
107    private static final Handler[] EMPTY_HANDLERS_ARRAY = new Handler[0];
108
109    /** The name of this logger. */
110    private volatile String name;
111
112    /** The parent logger of this logger. */
113    Logger parent;
114
115    /** The logging level of this logger, or null if none is set. */
116    volatile Level levelObjVal;
117
118    /**
119     * The effective logging level of this logger. In order of preference this
120     * is the first applicable of:
121     * <ol>
122     * <li>the int value of this logger's {@link #levelObjVal}
123     * <li>the logging level of the parent
124     * <li>the default level ({@link Level#INFO})
125     * </ol>
126     */
127    volatile int levelIntVal = Level.INFO.intValue();
128
129    /** The filter. */
130    private Filter filter;
131
132    /**
133     * The resource bundle used to localize logging messages. If null, no
134     * localization will be performed.
135     */
136    private volatile String resourceBundleName;
137
138    /** The loaded resource bundle according to the specified name. */
139    private volatile ResourceBundle resourceBundle;
140
141    /**
142     * The handlers attached to this logger. Eagerly initialized and
143     * concurrently modified.
144     */
145    private final List<Handler> handlers = new CopyOnWriteArrayList<Handler>();
146
147    /** True to notify the parent's handlers of each log message. */
148    private boolean notifyParentHandlers = true;
149
150    /**
151     * Indicates whether this logger is named. Only {@link #getAnonymousLogger
152     * anonymous loggers} are unnamed.
153     */
154    private boolean isNamed = true;
155
156    /**
157     * Child loggers. Should be accessed only while synchronized on {@code
158     * LogManager.getLogManager()}.
159     */
160    final List<Logger> children = new ArrayList<Logger>();
161
162    /** the tag used for optimized logging. Derived from the logger name. */
163    private final String androidTag;
164
165    /** Handler delegate for either optimized or standard logging. */
166    private volatile DalvikLogHandler dalvikLogHandler = GENERAL_LOG_HANDLER;
167
168    /**
169     * We've optimized for the common case: logging to a single handler that
170     * implements {@link DalvikLogHandler}. This is how Android framework
171     * applications are configured by default.
172     *
173     * <p>This optimization has been measured to show a 2.75x improvement in
174     * throughput in the common case: 154ns vs. 56ns per message on a Cortex-A8.
175     * Direct use of {@code android.util.Log} takes 29ns per message.
176     *
177     * <p>Each time the handler configuration changes, either directly or
178     * indirectly, it's necessary to either turn on or off this optimization.
179     * When the optimization is off, {@link #dalvikLogHandler} is assigned to
180     * {@link #GENERAL_LOG_HANDLER} which can satisfy arbitrary configuration.
181     * When the optimization is possible, {@link #dalvikLogHandler} is assigned
182     * to the user's efficient implementation. In pratice this is usually the
183     * {@code com.android.internal.logging.AndroidHandler}.
184     */
185    void updateDalvikLogHandler() {
186        DalvikLogHandler newLogHandler = GENERAL_LOG_HANDLER;
187
188        Logger parent = this.parent;
189
190        if (getClass() != Logger.class) {
191            /*
192             * Do nothing. Subclasses aren't eligible for the optimization
193             * because they may override methods like getHandlers() or
194             * log(LogRecord).
195             */
196
197        } else if (parent == null) {
198            // we use an iterator rather than size()+get() for safe concurrency
199            Iterator<Handler> h = handlers.iterator();
200            if (h.hasNext()) {
201                Handler firstHandler = h.next();
202                if (!h.hasNext() && firstHandler instanceof DalvikLogHandler) {
203                    /*
204                     * At this point, we're eligible for the optimization. We've
205                     * satisfied these constraints:
206                     *   1. This is not a subclass of logger
207                     *   2. This is a root logger (no parent)
208                     *   3. There is exactly one handler installed
209                     *   4. That handler is a DalvikLogHandler
210                     */
211                    newLogHandler = (DalvikLogHandler) firstHandler;
212                }
213            }
214        } else if (handlers.isEmpty() && notifyParentHandlers) {
215            /*
216             * At this point, we're eligible for the optimization if our parent
217             * logger is eligible. We've satisfied these constraints:
218             *   1. This is not a subclass of logger
219             *   2. our parent exists
220             *   3. we have no handlers of our own
221             *   4. we notify our parent's handlers
222             */
223            newLogHandler = parent.dalvikLogHandler;
224        }
225
226        if (newLogHandler == this.dalvikLogHandler) {
227            return;
228        }
229
230        this.dalvikLogHandler = newLogHandler;
231
232        for (Logger logger : children) {
233            logger.updateDalvikLogHandler();
234        }
235    }
236
237    /**
238     * Constructs a {@code Logger} object with the supplied name and resource
239     * bundle name; {@code notifiyParentHandlers} is set to {@code true}.
240     * <p>
241     * Notice : Loggers use a naming hierarchy. Thus "z.x.y" is a child of "z.x".
242     *
243     * @param name
244     *            the name of this logger, may be {@code null} for anonymous
245     *            loggers.
246     * @param resourceBundleName
247     *            the name of the resource bundle used to localize logging
248     *            messages, may be {@code null}.
249     * @throws MissingResourceException
250     *             if the specified resource bundle can not be loaded.
251     */
252    protected Logger(String name, String resourceBundleName) {
253        this.name = name;
254        initResourceBundle(resourceBundleName);
255        this.androidTag = DalvikLogging.loggerNameToTag(name);
256        updateDalvikLogHandler();
257    }
258
259    /**
260     * Load the specified resource bundle, use privileged code.
261     *
262     * @param resourceBundleName
263     *            the name of the resource bundle to load, cannot be {@code null}.
264     * @return the loaded resource bundle.
265     * @throws MissingResourceException
266     *             if the specified resource bundle can not be loaded.
267     */
268    static ResourceBundle loadResourceBundle(String resourceBundleName) {
269        // try context class loader to load the resource
270        ClassLoader cl = Thread.currentThread().getContextClassLoader();
271        if (cl != null) {
272            try {
273                return ResourceBundle.getBundle(resourceBundleName, Locale.getDefault(), cl);
274            } catch (MissingResourceException ignored) {
275                // Failed to load using context class loader, ignore
276            }
277        }
278        // try system class loader to load the resource
279        cl = ClassLoader.getSystemClassLoader();
280        if (cl != null) {
281            try {
282                return ResourceBundle.getBundle(resourceBundleName, Locale.getDefault(), cl);
283            } catch (MissingResourceException ignored) {
284                // Failed to load using system class loader, ignore
285            }
286        }
287        throw new MissingResourceException("Failed to load the specified resource bundle \"" +
288                resourceBundleName + "\"", resourceBundleName, null);
289    }
290
291    /**
292     * Gets an anonymous logger to use internally in a thread. Anonymous loggers
293     * are not registered in the log manager's namespace. No security checks
294     * will be performed when updating an anonymous logger's control settings.
295     * <p>
296     * The anonymous loggers' parent is set to be the root logger. This way it
297     * inherits the default logging level and handlers from the root logger.
298     *
299     * @return a new instance of anonymous logger.
300     */
301    public static Logger getAnonymousLogger() {
302        return getAnonymousLogger(null);
303    }
304
305    /**
306     * Gets an anonymous logger to use internally in a thread. Anonymous loggers
307     * are not registered in the log manager's namespace. No security checks
308     * will be performed when updating an anonymous logger's control settings.
309     * <p>
310     * The anonymous loggers' parent is set to be the root logger. This way it
311     * inherits default logging level and handlers from the root logger.
312     *
313     * @param resourceBundleName
314     *            the name of the resource bundle used to localize log messages.
315     * @return a new instance of anonymous logger.
316     * @throws MissingResourceException
317     *             if the specified resource bundle can not be loaded.
318     */
319    public static Logger getAnonymousLogger(String resourceBundleName) {
320        Logger result = new Logger(null, resourceBundleName);
321        result.isNamed = false;
322        LogManager logManager = LogManager.getLogManager();
323        logManager.setParent(result, logManager.getLogger(""));
324        return result;
325    }
326
327    /**
328     * Initializes this logger's resource bundle.
329     *
330     * @throws IllegalArgumentException if this logger's resource bundle already
331     *      exists and is different from the resource bundle specified.
332     */
333    private synchronized void initResourceBundle(String resourceBundleName) {
334        String current = this.resourceBundleName;
335
336        if (current != null) {
337            if (current.equals(resourceBundleName)) {
338                return;
339            } else {
340                throw new IllegalArgumentException("Resource bundle name '" + resourceBundleName + "' is inconsistent with the existing '" + current + "'");
341            }
342        }
343
344        if (resourceBundleName != null) {
345            this.resourceBundle = loadResourceBundle(resourceBundleName);
346            this.resourceBundleName = resourceBundleName;
347        }
348    }
349
350    /**
351     * Gets a named logger. The returned logger may already exist or may be
352     * newly created. In the latter case, its level will be set to the
353     * configured level according to the {@code LogManager}'s properties.
354     *
355     * @param name
356     *            the name of the logger to get, cannot be {@code null}.
357     * @return a named logger.
358     * @throws MissingResourceException
359     *             If the specified resource bundle can not be loaded.
360     */
361    public static Logger getLogger(String name) {
362        return LogManager.getLogManager().getOrCreate(name, null);
363    }
364
365    /**
366     * Gets a named logger associated with the supplied resource bundle. The
367     * resource bundle will be used to localize logging messages.
368     *
369     * @param name
370     *            the name of the logger to get, cannot be {@code null}.
371     * @param resourceBundleName
372     *            the name of the resource bundle, may be {@code null}.
373     * @throws IllegalArgumentException
374     *             if the logger identified by {@code name} is associated with a
375     *             resource bundle and its name is not equal to
376     *             {@code resourceBundleName}.
377     * @throws MissingResourceException
378     *             if the name of the resource bundle cannot be found.
379     * @return a named logger.
380     */
381    public static Logger getLogger(String name, String resourceBundleName) {
382        Logger result = LogManager.getLogManager()
383                .getOrCreate(name, resourceBundleName);
384        result.initResourceBundle(resourceBundleName);
385        return result;
386    }
387
388    /**
389     * Returns the global {@code Logger}.
390     * @since 1.7
391     */
392    public static Logger getGlobal() {
393        return global;
394    }
395
396    /**
397     * Adds a handler to this logger. The {@code name} will be fed with log
398     * records received by this logger.
399     *
400     * @param handler
401     *            the handler object to add, cannot be {@code null}.
402     */
403    public void addHandler(Handler handler) {
404        if (handler == null) {
405            throw new NullPointerException("handler == null");
406        }
407        // Anonymous loggers can always add handlers
408        if (this.isNamed) {
409            LogManager.getLogManager().checkAccess();
410        }
411        this.handlers.add(handler);
412        updateDalvikLogHandler();
413    }
414
415    /**
416     * Set the logger's manager and initializes its configuration from the
417     * manager's properties.
418     */
419    void setManager(LogManager manager) {
420        String levelProperty = manager.getProperty(name + ".level");
421        if (levelProperty != null) {
422            try {
423                manager.setLevelRecursively(Logger.this, Level.parse(levelProperty));
424            } catch (IllegalArgumentException invalidLevel) {
425                invalidLevel.printStackTrace();
426            }
427        }
428
429        String handlersPropertyName = name.isEmpty() ? "handlers" : name + ".handlers";
430        String handlersProperty = manager.getProperty(handlersPropertyName);
431        if (handlersProperty != null) {
432            for (String handlerName : handlersProperty.split(",|\\s")) {
433                if (handlerName.isEmpty()) {
434                    continue;
435                }
436
437                final Handler handler;
438                try {
439                    handler = (Handler) LogManager.getInstanceByClass(handlerName);
440                } catch (Exception invalidHandlerName) {
441                    invalidHandlerName.printStackTrace();
442                    continue;
443                }
444
445                try {
446                    String level = manager.getProperty(handlerName + ".level");
447                    if (level != null) {
448                        handler.setLevel(Level.parse(level));
449                    }
450                } catch (Exception invalidLevel) {
451                    invalidLevel.printStackTrace();
452                }
453
454                handlers.add(handler);
455            }
456        }
457
458        updateDalvikLogHandler();
459    }
460
461    /**
462     * Gets all the handlers associated with this logger.
463     *
464     * @return an array of all the handlers associated with this logger.
465     */
466    public Handler[] getHandlers() {
467        return handlers.toArray(EMPTY_HANDLERS_ARRAY);
468    }
469
470    /**
471     * Removes a handler from this logger. If the specified handler does not
472     * exist then this method has no effect.
473     *
474     * @param handler
475     *            the handler to be removed.
476     */
477    public void removeHandler(Handler handler) {
478        // Anonymous loggers can always remove handlers
479        if (this.isNamed) {
480            LogManager.getLogManager().checkAccess();
481        }
482        if (handler == null) {
483            return;
484        }
485        this.handlers.remove(handler);
486        updateDalvikLogHandler();
487    }
488
489    /**
490     * Gets the filter used by this logger.
491     *
492     * @return the filter used by this logger, may be {@code null}.
493     */
494    public Filter getFilter() {
495        return this.filter;
496    }
497
498    /**
499     * Sets the filter used by this logger.
500     *
501     * @param newFilter
502     *            the filter to set, may be {@code null}.
503     */
504    public void setFilter(Filter newFilter) {
505        // Anonymous loggers can always set the filter
506        if (this.isNamed) {
507            LogManager.getLogManager().checkAccess();
508        }
509        filter = newFilter;
510    }
511
512    /**
513     * Gets the logging level of this logger. A {@code null} level indicates
514     * that this logger inherits its parent's level.
515     *
516     * @return the logging level of this logger.
517     */
518    public Level getLevel() {
519        return levelObjVal;
520    }
521
522    /**
523     * Sets the logging level for this logger. A {@code null} level indicates
524     * that this logger will inherit its parent's level.
525     *
526     * @param newLevel
527     *            the logging level to set.
528     */
529    public void setLevel(Level newLevel) {
530        // Anonymous loggers can always set the level
531        LogManager logManager = LogManager.getLogManager();
532        if (this.isNamed) {
533            logManager.checkAccess();
534        }
535        logManager.setLevelRecursively(this, newLevel);
536    }
537
538    /**
539     * Gets the flag which indicates whether to use the handlers of this
540     * logger's parent to publish incoming log records, potentially recursively
541     * up the namespace.
542     *
543     * @return {@code true} if set to use parent's handlers, {@code false}
544     *         otherwise.
545     */
546    public boolean getUseParentHandlers() {
547        return this.notifyParentHandlers;
548    }
549
550    /**
551     * Sets the flag which indicates whether to use the handlers of this
552     * logger's parent, potentially recursively up the namespace.
553     *
554     * @param notifyParentHandlers
555     *            the new flag indicating whether to use the parent's handlers.
556     */
557    public void setUseParentHandlers(boolean notifyParentHandlers) {
558        // Anonymous loggers can always set the useParentHandlers flag
559        if (this.isNamed) {
560            LogManager.getLogManager().checkAccess();
561        }
562        this.notifyParentHandlers = notifyParentHandlers;
563        updateDalvikLogHandler();
564    }
565
566    /**
567     * Gets the nearest parent of this logger in the namespace, a {@code null}
568     * value will be returned if called on the root logger.
569     *
570     * @return the parent of this logger in the namespace.
571     */
572    public Logger getParent() {
573        return parent;
574    }
575
576    /**
577     * Sets the parent of this logger in the namespace. This method should be
578     * used by the {@code LogManager} object only.
579     *
580     * @param parent
581     *            the parent logger to set.
582     */
583    public void setParent(Logger parent) {
584        if (parent == null) {
585            throw new NullPointerException("parent == null");
586        }
587
588        // even anonymous loggers are checked
589        LogManager logManager = LogManager.getLogManager();
590        logManager.checkAccess();
591        logManager.setParent(this, parent);
592    }
593
594    /**
595     * Gets the name of this logger, {@code null} for anonymous loggers.
596     *
597     * @return the name of this logger.
598     */
599    public String getName() {
600        return this.name;
601    }
602
603    /**
604     * Gets the loaded resource bundle used by this logger to localize logging
605     * messages. If the value is {@code null}, the parent's resource bundle will be
606     * inherited.
607     *
608     * @return the loaded resource bundle used by this logger.
609     */
610    public ResourceBundle getResourceBundle() {
611        return this.resourceBundle;
612    }
613
614    /**
615     * Gets the name of the loaded resource bundle used by this logger to
616     * localize logging messages. If the value is {@code null}, the parent's resource
617     * bundle name will be inherited.
618     *
619     * @return the name of the loaded resource bundle used by this logger.
620     */
621    public String getResourceBundleName() {
622        return this.resourceBundleName;
623    }
624
625    /**
626     * This method is for compatibility. Tests written to the reference
627     * implementation API imply that the isLoggable() method is not called
628     * directly. This behavior is important because subclass may override
629     * isLoggable() method, so that affect the result of log methods.
630     */
631    private boolean internalIsLoggable(Level l) {
632        int effectiveLevel = levelIntVal;
633        if (effectiveLevel == Level.OFF.intValue()) {
634            // always return false if the effective level is off
635            return false;
636        }
637        return l.intValue() >= effectiveLevel;
638    }
639
640    /**
641     * Determines whether this logger will actually log messages of the
642     * specified level. The effective level used to do the determination may be
643     * inherited from its parent. The default level is {@code Level.INFO}.
644     *
645     * @param l
646     *            the level to check.
647     * @return {@code true} if this logger will actually log this level,
648     *         otherwise {@code false}.
649     */
650    public boolean isLoggable(Level l) {
651        return internalIsLoggable(l);
652    }
653
654    /**
655     * Sets the resource bundle and its name for a supplied LogRecord object.
656     * This method first tries to use this logger's resource bundle if any,
657     * otherwise try to inherit from this logger's parent, recursively up the
658     * namespace.
659     */
660    private void setResourceBundle(LogRecord record) {
661        for (Logger p = this; p != null; p = p.parent) {
662            String resourceBundleName = p.resourceBundleName;
663            if (resourceBundleName != null) {
664                record.setResourceBundle(p.resourceBundle);
665                record.setResourceBundleName(resourceBundleName);
666                return;
667            }
668        }
669    }
670
671    /**
672     * Logs a message indicating that a method has been entered. A log record
673     * with log level {@code Level.FINER}, log message "ENTRY", the specified
674     * source class name and source method name is submitted for logging.
675     *
676     * @param sourceClass
677     *            the calling class name.
678     * @param sourceMethod
679     *            the method name.
680     */
681    public void entering(String sourceClass, String sourceMethod) {
682        if (!internalIsLoggable(Level.FINER)) {
683            return;
684        }
685
686        LogRecord record = new LogRecord(Level.FINER, "ENTRY");
687        record.setLoggerName(this.name);
688        record.setSourceClassName(sourceClass);
689        record.setSourceMethodName(sourceMethod);
690        setResourceBundle(record);
691        log(record);
692    }
693
694    /**
695     * Logs a message indicating that a method has been entered. A log record
696     * with log level {@code Level.FINER}, log message "ENTRY", the specified
697     * source class name, source method name and one parameter is submitted for
698     * logging.
699     *
700     * @param sourceClass
701     *            the source class name.
702     * @param sourceMethod
703     *            the source method name.
704     * @param param
705     *            the parameter for the method call.
706     */
707    public void entering(String sourceClass, String sourceMethod, Object param) {
708        if (!internalIsLoggable(Level.FINER)) {
709            return;
710        }
711
712        LogRecord record = new LogRecord(Level.FINER, "ENTRY" + " {0}");
713        record.setLoggerName(this.name);
714        record.setSourceClassName(sourceClass);
715        record.setSourceMethodName(sourceMethod);
716        record.setParameters(new Object[] { param });
717        setResourceBundle(record);
718        log(record);
719    }
720
721    /**
722     * Logs a message indicating that a method has been entered. A log record
723     * with log level {@code Level.FINER}, log message "ENTRY", the specified
724     * source class name, source method name and array of parameters is
725     * submitted for logging.
726     *
727     * @param sourceClass
728     *            the source class name.
729     * @param sourceMethod
730     *            the source method name.
731     * @param params
732     *            an array of parameters for the method call.
733     */
734    public void entering(String sourceClass, String sourceMethod,
735            Object[] params) {
736        if (!internalIsLoggable(Level.FINER)) {
737            return;
738        }
739
740        String msg = "ENTRY";
741        if (params != null) {
742            StringBuilder msgBuffer = new StringBuilder("ENTRY");
743            for (int i = 0; i < params.length; i++) {
744                msgBuffer.append(" {").append(i).append("}");
745            }
746            msg = msgBuffer.toString();
747        }
748        LogRecord record = new LogRecord(Level.FINER, msg);
749        record.setLoggerName(this.name);
750        record.setSourceClassName(sourceClass);
751        record.setSourceMethodName(sourceMethod);
752        record.setParameters(params);
753        setResourceBundle(record);
754        log(record);
755    }
756
757    /**
758     * Logs a message indicating that a method is exited. A log record with log
759     * level {@code Level.FINER}, log message "RETURN", the specified source
760     * class name and source method name is submitted for logging.
761     *
762     * @param sourceClass
763     *            the calling class name.
764     * @param sourceMethod
765     *            the method name.
766     */
767    public void exiting(String sourceClass, String sourceMethod) {
768        if (!internalIsLoggable(Level.FINER)) {
769            return;
770        }
771
772        LogRecord record = new LogRecord(Level.FINER, "RETURN");
773        record.setLoggerName(this.name);
774        record.setSourceClassName(sourceClass);
775        record.setSourceMethodName(sourceMethod);
776        setResourceBundle(record);
777        log(record);
778    }
779
780    /**
781     * Logs a message indicating that a method is exited. A log record with log
782     * level {@code Level.FINER}, log message "RETURN", the specified source
783     * class name, source method name and return value is submitted for logging.
784     *
785     * @param sourceClass
786     *            the source class name.
787     * @param sourceMethod
788     *            the source method name.
789     * @param result
790     *            the return value of the method call.
791     */
792    public void exiting(String sourceClass, String sourceMethod, Object result) {
793        if (!internalIsLoggable(Level.FINER)) {
794            return;
795        }
796
797        LogRecord record = new LogRecord(Level.FINER, "RETURN" + " {0}");
798        record.setLoggerName(this.name);
799        record.setSourceClassName(sourceClass);
800        record.setSourceMethodName(sourceMethod);
801        record.setParameters(new Object[] { result });
802        setResourceBundle(record);
803        log(record);
804    }
805
806    /**
807     * Logs a message indicating that an exception is thrown. A log record with
808     * log level {@code Level.FINER}, log message "THROW", the specified source
809     * class name, source method name and the {@code Throwable} object is
810     * submitted for logging.
811     *
812     * @param sourceClass
813     *            the source class name.
814     * @param sourceMethod
815     *            the source method name.
816     * @param thrown
817     *            the {@code Throwable} object.
818     */
819    public void throwing(String sourceClass, String sourceMethod,
820            Throwable thrown) {
821        if (!internalIsLoggable(Level.FINER)) {
822            return;
823        }
824
825        LogRecord record = new LogRecord(Level.FINER, "THROW");
826        record.setLoggerName(this.name);
827        record.setSourceClassName(sourceClass);
828        record.setSourceMethodName(sourceMethod);
829        record.setThrown(thrown);
830        setResourceBundle(record);
831        log(record);
832    }
833
834    /**
835     * Logs a message of level {@code Level.SEVERE}; the message is transmitted
836     * to all subscribed handlers.
837     *
838     * @param msg
839     *            the message to log.
840     */
841    public void severe(String msg) {
842        log(Level.SEVERE, msg);
843    }
844
845    /**
846     * Logs a message of level {@code Level.WARNING}; the message is
847     * transmitted to all subscribed handlers.
848     *
849     * @param msg
850     *            the message to log.
851     */
852    public void warning(String msg) {
853        log(Level.WARNING, msg);
854    }
855
856    /**
857     * Logs a message of level {@code Level.INFO}; the message is transmitted
858     * to all subscribed handlers.
859     *
860     * @param msg
861     *            the message to log.
862     */
863    public void info(String msg) {
864        log(Level.INFO, msg);
865    }
866
867    /**
868     * Logs a message of level {@code Level.CONFIG}; the message is transmitted
869     * to all subscribed handlers.
870     *
871     * @param msg
872     *            the message to log.
873     */
874    public void config(String msg) {
875        log(Level.CONFIG, msg);
876    }
877
878    /**
879     * Logs a message of level {@code Level.FINE}; the message is transmitted
880     * to all subscribed handlers.
881     *
882     * @param msg
883     *            the message to log.
884     */
885    public void fine(String msg) {
886        log(Level.FINE, msg);
887    }
888
889    /**
890     * Logs a message of level {@code Level.FINER}; the message is transmitted
891     * to all subscribed handlers.
892     *
893     * @param msg
894     *            the message to log.
895     */
896    public void finer(String msg) {
897        log(Level.FINER, msg);
898    }
899
900    /**
901     * Logs a message of level {@code Level.FINEST}; the message is transmitted
902     * to all subscribed handlers.
903     *
904     * @param msg
905     *            the message to log.
906     */
907    public void finest(String msg) {
908        log(Level.FINEST, msg);
909    }
910
911    /**
912     * Logs a message of the specified level. The message is transmitted to all
913     * subscribed handlers.
914     *
915     * @param logLevel
916     *            the level of the specified message.
917     * @param msg
918     *            the message to log.
919     */
920    public void log(Level logLevel, String msg) {
921        if (!internalIsLoggable(logLevel)) {
922            return;
923        }
924        dalvikLogHandler.publish(this, androidTag, logLevel, msg);
925    }
926
927    /**
928     * Logs a message of the specified level with the supplied parameter. The
929     * message is then transmitted to all subscribed handlers.
930     *
931     * @param logLevel
932     *            the level of the given message.
933     * @param msg
934     *            the message to log.
935     * @param param
936     *            the parameter associated with the event that is logged.
937     */
938    public void log(Level logLevel, String msg, Object param) {
939        if (!internalIsLoggable(logLevel)) {
940            return;
941        }
942
943        LogRecord record = new LogRecord(logLevel, msg);
944        record.setLoggerName(this.name);
945        record.setParameters(new Object[] { param });
946        setResourceBundle(record);
947        log(record);
948    }
949
950    /**
951     * Logs a message of the specified level with the supplied parameter array.
952     * The message is then transmitted to all subscribed handlers.
953     *
954     * @param logLevel
955     *            the level of the given message
956     * @param msg
957     *            the message to log.
958     * @param params
959     *            the parameter array associated with the event that is logged.
960     */
961    public void log(Level logLevel, String msg, Object[] params) {
962        if (!internalIsLoggable(logLevel)) {
963            return;
964        }
965
966        LogRecord record = new LogRecord(logLevel, msg);
967        record.setLoggerName(this.name);
968        record.setParameters(params);
969        setResourceBundle(record);
970        log(record);
971    }
972
973    /**
974     * Logs a message of the specified level with the supplied {@code Throwable}
975     * object. The message is then transmitted to all subscribed handlers.
976     *
977     * @param logLevel
978     *            the level of the given message.
979     * @param msg
980     *            the message to log.
981     * @param thrown
982     *            the {@code Throwable} object associated with the event that is
983     *            logged.
984     */
985    public void log(Level logLevel, String msg, Throwable thrown) {
986        if (!internalIsLoggable(logLevel)) {
987            return;
988        }
989
990        LogRecord record = new LogRecord(logLevel, msg);
991        record.setLoggerName(this.name);
992        record.setThrown(thrown);
993        setResourceBundle(record);
994        log(record);
995    }
996
997    /**
998     * Logs a given log record. Only records with a logging level that is equal
999     * or greater than this logger's level will be submitted to this logger's
1000     * handlers for logging. If {@code getUseParentHandlers()} returns {@code
1001     * true}, the log record will also be submitted to the handlers of this
1002     * logger's parent, potentially recursively up the namespace.
1003     * <p>
1004     * Since all other log methods call this method to actually perform the
1005     * logging action, subclasses of this class can override this method to
1006     * catch all logging activities.
1007     * </p>
1008     *
1009     * @param record
1010     *            the log record to be logged.
1011     */
1012    public void log(LogRecord record) {
1013        if (!internalIsLoggable(record.getLevel())) {
1014            return;
1015        }
1016
1017        // apply the filter if any
1018        Filter f = filter;
1019        if (f != null && !f.isLoggable(record)) {
1020            return;
1021        }
1022
1023        /*
1024         * call the handlers of this logger, throw any exception that occurs
1025         */
1026        Handler[] allHandlers = getHandlers();
1027        for (Handler element : allHandlers) {
1028            element.publish(record);
1029        }
1030        // call the parent's handlers if set useParentHandlers
1031        Logger temp = this;
1032        Logger theParent = temp.parent;
1033        while (theParent != null && temp.getUseParentHandlers()) {
1034            Handler[] ha = theParent.getHandlers();
1035            for (Handler element : ha) {
1036                element.publish(record);
1037            }
1038            temp = theParent;
1039            theParent = temp.parent;
1040        }
1041    }
1042
1043    /**
1044     * Logs a message of the given level with the specified source class name
1045     * and source method name.
1046     *
1047     * @param logLevel
1048     *            the level of the given message.
1049     * @param sourceClass
1050     *            the source class name.
1051     * @param sourceMethod
1052     *            the source method name.
1053     * @param msg
1054     *            the message to be logged.
1055     */
1056    public void logp(Level logLevel, String sourceClass, String sourceMethod,
1057            String msg) {
1058        if (!internalIsLoggable(logLevel)) {
1059            return;
1060        }
1061
1062        LogRecord record = new LogRecord(logLevel, msg);
1063        record.setLoggerName(this.name);
1064        record.setSourceClassName(sourceClass);
1065        record.setSourceMethodName(sourceMethod);
1066        setResourceBundle(record);
1067        log(record);
1068    }
1069
1070    /**
1071     * Logs a message of the given level with the specified source class name,
1072     * source method name and parameter.
1073     *
1074     * @param logLevel
1075     *            the level of the given message
1076     * @param sourceClass
1077     *            the source class name
1078     * @param sourceMethod
1079     *            the source method name
1080     * @param msg
1081     *            the message to be logged
1082     * @param param
1083     *            the parameter associated with the event that is logged.
1084     */
1085    public void logp(Level logLevel, String sourceClass, String sourceMethod,
1086            String msg, Object param) {
1087        if (!internalIsLoggable(logLevel)) {
1088            return;
1089        }
1090
1091        LogRecord record = new LogRecord(logLevel, msg);
1092        record.setLoggerName(this.name);
1093        record.setSourceClassName(sourceClass);
1094        record.setSourceMethodName(sourceMethod);
1095        record.setParameters(new Object[] { param });
1096        setResourceBundle(record);
1097        log(record);
1098    }
1099
1100    /**
1101     * Logs a message of the given level with the specified source class name,
1102     * source method name and parameter array.
1103     *
1104     * @param logLevel
1105     *            the level of the given message.
1106     * @param sourceClass
1107     *            the source class name.
1108     * @param sourceMethod
1109     *            the source method name.
1110     * @param msg
1111     *            the message to be logged.
1112     * @param params
1113     *            the parameter array associated with the event that is logged.
1114     */
1115    public void logp(Level logLevel, String sourceClass, String sourceMethod,
1116            String msg, Object[] params) {
1117        if (!internalIsLoggable(logLevel)) {
1118            return;
1119        }
1120
1121        LogRecord record = new LogRecord(logLevel, msg);
1122        record.setLoggerName(this.name);
1123        record.setSourceClassName(sourceClass);
1124        record.setSourceMethodName(sourceMethod);
1125        record.setParameters(params);
1126        setResourceBundle(record);
1127        log(record);
1128    }
1129
1130    /**
1131     * Logs a message of the given level with the specified source class name,
1132     * source method name and {@code Throwable} object.
1133     *
1134     * @param logLevel
1135     *            the level of the given message.
1136     * @param sourceClass
1137     *            the source class name.
1138     * @param sourceMethod
1139     *            the source method name.
1140     * @param msg
1141     *            the message to be logged.
1142     * @param thrown
1143     *            the {@code Throwable} object.
1144     */
1145    public void logp(Level logLevel, String sourceClass, String sourceMethod,
1146            String msg, Throwable thrown) {
1147        if (!internalIsLoggable(logLevel)) {
1148            return;
1149        }
1150
1151        LogRecord record = new LogRecord(logLevel, msg);
1152        record.setLoggerName(this.name);
1153        record.setSourceClassName(sourceClass);
1154        record.setSourceMethodName(sourceMethod);
1155        record.setThrown(thrown);
1156        setResourceBundle(record);
1157        log(record);
1158    }
1159
1160    /**
1161     * Logs a message of the given level with the specified source class name
1162     * and source method name, using the given resource bundle to localize the
1163     * message. If {@code bundleName} is null, the empty string or not valid then
1164     * the message is not localized.
1165     *
1166     * @param logLevel
1167     *            the level of the given message.
1168     * @param sourceClass
1169     *            the source class name.
1170     * @param sourceMethod
1171     *            the source method name.
1172     * @param bundleName
1173     *            the name of the resource bundle used to localize the message.
1174     * @param msg
1175     *            the message to be logged.
1176     */
1177    public void logrb(Level logLevel, String sourceClass, String sourceMethod,
1178            String bundleName, String msg) {
1179        if (!internalIsLoggable(logLevel)) {
1180            return;
1181        }
1182
1183        LogRecord record = new LogRecord(logLevel, msg);
1184        if (bundleName != null) {
1185            try {
1186                record.setResourceBundle(loadResourceBundle(bundleName));
1187            } catch (MissingResourceException e) {
1188                // ignore
1189            }
1190            record.setResourceBundleName(bundleName);
1191        }
1192        record.setLoggerName(this.name);
1193        record.setSourceClassName(sourceClass);
1194        record.setSourceMethodName(sourceMethod);
1195        log(record);
1196    }
1197
1198    /**
1199     * Logs a message of the given level with the specified source class name,
1200     * source method name and parameter, using the given resource bundle to
1201     * localize the message. If {@code bundleName} is null, the empty string
1202     * or not valid then the message is not localized.
1203     *
1204     * @param logLevel
1205     *            the level of the given message.
1206     * @param sourceClass
1207     *            the source class name.
1208     * @param sourceMethod
1209     *            the source method name.
1210     * @param bundleName
1211     *            the name of the resource bundle used to localize the message.
1212     * @param msg
1213     *            the message to be logged.
1214     * @param param
1215     *            the parameter associated with the event that is logged.
1216     */
1217    public void logrb(Level logLevel, String sourceClass, String sourceMethod,
1218            String bundleName, String msg, Object param) {
1219        if (!internalIsLoggable(logLevel)) {
1220            return;
1221        }
1222
1223        LogRecord record = new LogRecord(logLevel, msg);
1224        if (bundleName != null) {
1225            try {
1226                record.setResourceBundle(loadResourceBundle(bundleName));
1227            } catch (MissingResourceException e) {
1228                // ignore
1229            }
1230            record.setResourceBundleName(bundleName);
1231        }
1232        record.setLoggerName(this.name);
1233        record.setSourceClassName(sourceClass);
1234        record.setSourceMethodName(sourceMethod);
1235        record.setParameters(new Object[] { param });
1236        log(record);
1237    }
1238
1239    /**
1240     * Logs a message of the given level with the specified source class name,
1241     * source method name and parameter array, using the given resource bundle
1242     * to localize the message. If {@code bundleName} is null, the empty string
1243     * or not valid then the message is not localized.
1244     *
1245     * @param logLevel
1246     *            the level of the given message.
1247     * @param sourceClass
1248     *            the source class name.
1249     * @param sourceMethod
1250     *            the source method name.
1251     * @param bundleName
1252     *            the name of the resource bundle used to localize the message.
1253     * @param msg
1254     *            the message to be logged.
1255     * @param params
1256     *            the parameter array associated with the event that is logged.
1257     */
1258    public void logrb(Level logLevel, String sourceClass, String sourceMethod,
1259            String bundleName, String msg, Object[] params) {
1260        if (!internalIsLoggable(logLevel)) {
1261            return;
1262        }
1263
1264        LogRecord record = new LogRecord(logLevel, msg);
1265        if (bundleName != null) {
1266            try {
1267                record.setResourceBundle(loadResourceBundle(bundleName));
1268            } catch (MissingResourceException e) {
1269                // ignore
1270            }
1271            record.setResourceBundleName(bundleName);
1272        }
1273        record.setLoggerName(this.name);
1274        record.setSourceClassName(sourceClass);
1275        record.setSourceMethodName(sourceMethod);
1276        record.setParameters(params);
1277        log(record);
1278    }
1279
1280    /**
1281     * Logs a message of the given level with the specified source class name,
1282     * source method name and {@code Throwable} object, using the given resource
1283     * bundle to localize the message. If {@code bundleName} is null, the empty
1284     * string or not valid then the message is not localized.
1285     *
1286     * @param logLevel
1287     *            the level of the given message
1288     * @param sourceClass
1289     *            the source class name
1290     * @param sourceMethod
1291     *            the source method name
1292     * @param bundleName
1293     *            the name of the resource bundle used to localize the message.
1294     * @param msg
1295     *            the message to be logged.
1296     * @param thrown
1297     *            the {@code Throwable} object.
1298     */
1299    public void logrb(Level logLevel, String sourceClass, String sourceMethod,
1300            String bundleName, String msg, Throwable thrown) {
1301        if (!internalIsLoggable(logLevel)) {
1302            return;
1303        }
1304
1305        LogRecord record = new LogRecord(logLevel, msg);
1306        if (bundleName != null) {
1307            try {
1308                record.setResourceBundle(loadResourceBundle(bundleName));
1309            } catch (MissingResourceException e) {
1310                // ignore
1311            }
1312            record.setResourceBundleName(bundleName);
1313        }
1314        record.setLoggerName(this.name);
1315        record.setSourceClassName(sourceClass);
1316        record.setSourceMethodName(sourceMethod);
1317        record.setThrown(thrown);
1318        log(record);
1319    }
1320
1321    void reset() {
1322        levelObjVal = null;
1323        levelIntVal = Level.INFO.intValue();
1324
1325        for (Handler handler : handlers) {
1326            try {
1327                if (handlers.remove(handler)) {
1328                    handler.close();
1329                }
1330            } catch (Exception ignored) {
1331            }
1332        }
1333
1334        updateDalvikLogHandler();
1335    }
1336}
1337