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 deadlock-prone. Use {@code Logger.getLogger(Logger.GLOBAL_LOGGER_NAME)} as
95     * a direct replacement, but see the discussion of how to use {@code 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     * @hide 1.7
392     */
393    public static Logger getGlobal() {
394        return global;
395    }
396
397    /**
398     * Adds a handler to this logger. The {@code name} will be fed with log
399     * records received by this logger.
400     *
401     * @param handler
402     *            the handler object to add, cannot be {@code null}.
403     */
404    public void addHandler(Handler handler) {
405        if (handler == null) {
406            throw new NullPointerException("handler == null");
407        }
408        // Anonymous loggers can always add handlers
409        if (this.isNamed) {
410            LogManager.getLogManager().checkAccess();
411        }
412        this.handlers.add(handler);
413        updateDalvikLogHandler();
414    }
415
416    /**
417     * Set the logger's manager and initializes its configuration from the
418     * manager's properties.
419     */
420    void setManager(LogManager manager) {
421        String levelProperty = manager.getProperty(name + ".level");
422        if (levelProperty != null) {
423            try {
424                manager.setLevelRecursively(Logger.this, Level.parse(levelProperty));
425            } catch (IllegalArgumentException invalidLevel) {
426                invalidLevel.printStackTrace();
427            }
428        }
429
430        String handlersPropertyName = name.isEmpty() ? "handlers" : name + ".handlers";
431        String handlersProperty = manager.getProperty(handlersPropertyName);
432        if (handlersProperty != null) {
433            for (String handlerName : handlersProperty.split(",|\\s")) {
434                if (handlerName.isEmpty()) {
435                    continue;
436                }
437
438                final Handler handler;
439                try {
440                    handler = (Handler) LogManager.getInstanceByClass(handlerName);
441                } catch (Exception invalidHandlerName) {
442                    invalidHandlerName.printStackTrace();
443                    continue;
444                }
445
446                try {
447                    String level = manager.getProperty(handlerName + ".level");
448                    if (level != null) {
449                        handler.setLevel(Level.parse(level));
450                    }
451                } catch (Exception invalidLevel) {
452                    invalidLevel.printStackTrace();
453                }
454
455                handlers.add(handler);
456            }
457        }
458
459        updateDalvikLogHandler();
460    }
461
462    /**
463     * Gets all the handlers associated with this logger.
464     *
465     * @return an array of all the handlers associated with this logger.
466     */
467    public Handler[] getHandlers() {
468        return handlers.toArray(EMPTY_HANDLERS_ARRAY);
469    }
470
471    /**
472     * Removes a handler from this logger. If the specified handler does not
473     * exist then this method has no effect.
474     *
475     * @param handler
476     *            the handler to be removed.
477     */
478    public void removeHandler(Handler handler) {
479        // Anonymous loggers can always remove handlers
480        if (this.isNamed) {
481            LogManager.getLogManager().checkAccess();
482        }
483        if (handler == null) {
484            return;
485        }
486        this.handlers.remove(handler);
487        updateDalvikLogHandler();
488    }
489
490    /**
491     * Gets the filter used by this logger.
492     *
493     * @return the filter used by this logger, may be {@code null}.
494     */
495    public Filter getFilter() {
496        return this.filter;
497    }
498
499    /**
500     * Sets the filter used by this logger.
501     *
502     * @param newFilter
503     *            the filter to set, may be {@code null}.
504     */
505    public void setFilter(Filter newFilter) {
506        // Anonymous loggers can always set the filter
507        if (this.isNamed) {
508            LogManager.getLogManager().checkAccess();
509        }
510        filter = newFilter;
511    }
512
513    /**
514     * Gets the logging level of this logger. A {@code null} level indicates
515     * that this logger inherits its parent's level.
516     *
517     * @return the logging level of this logger.
518     */
519    public Level getLevel() {
520        return levelObjVal;
521    }
522
523    /**
524     * Sets the logging level for this logger. A {@code null} level indicates
525     * that this logger will inherit its parent's level.
526     *
527     * @param newLevel
528     *            the logging level to set.
529     */
530    public void setLevel(Level newLevel) {
531        // Anonymous loggers can always set the level
532        LogManager logManager = LogManager.getLogManager();
533        if (this.isNamed) {
534            logManager.checkAccess();
535        }
536        logManager.setLevelRecursively(this, newLevel);
537    }
538
539    /**
540     * Gets the flag which indicates whether to use the handlers of this
541     * logger's parent to publish incoming log records, potentially recursively
542     * up the namespace.
543     *
544     * @return {@code true} if set to use parent's handlers, {@code false}
545     *         otherwise.
546     */
547    public boolean getUseParentHandlers() {
548        return this.notifyParentHandlers;
549    }
550
551    /**
552     * Sets the flag which indicates whether to use the handlers of this
553     * logger's parent, potentially recursively up the namespace.
554     *
555     * @param notifyParentHandlers
556     *            the new flag indicating whether to use the parent's handlers.
557     */
558    public void setUseParentHandlers(boolean notifyParentHandlers) {
559        // Anonymous loggers can always set the useParentHandlers flag
560        if (this.isNamed) {
561            LogManager.getLogManager().checkAccess();
562        }
563        this.notifyParentHandlers = notifyParentHandlers;
564        updateDalvikLogHandler();
565    }
566
567    /**
568     * Gets the nearest parent of this logger in the namespace, a {@code null}
569     * value will be returned if called on the root logger.
570     *
571     * @return the parent of this logger in the namespace.
572     */
573    public Logger getParent() {
574        return parent;
575    }
576
577    /**
578     * Sets the parent of this logger in the namespace. This method should be
579     * used by the {@code LogManager} object only.
580     *
581     * @param parent
582     *            the parent logger to set.
583     */
584    public void setParent(Logger parent) {
585        if (parent == null) {
586            throw new NullPointerException("parent == null");
587        }
588
589        // even anonymous loggers are checked
590        LogManager logManager = LogManager.getLogManager();
591        logManager.checkAccess();
592        logManager.setParent(this, parent);
593    }
594
595    /**
596     * Gets the name of this logger, {@code null} for anonymous loggers.
597     *
598     * @return the name of this logger.
599     */
600    public String getName() {
601        return this.name;
602    }
603
604    /**
605     * Gets the loaded resource bundle used by this logger to localize logging
606     * messages. If the value is {@code null}, the parent's resource bundle will be
607     * inherited.
608     *
609     * @return the loaded resource bundle used by this logger.
610     */
611    public ResourceBundle getResourceBundle() {
612        return this.resourceBundle;
613    }
614
615    /**
616     * Gets the name of the loaded resource bundle used by this logger to
617     * localize logging messages. If the value is {@code null}, the parent's resource
618     * bundle name will be inherited.
619     *
620     * @return the name of the loaded resource bundle used by this logger.
621     */
622    public String getResourceBundleName() {
623        return this.resourceBundleName;
624    }
625
626    /**
627     * This method is for compatibility. Tests written to the reference
628     * implementation API imply that the isLoggable() method is not called
629     * directly. This behavior is important because subclass may override
630     * isLoggable() method, so that affect the result of log methods.
631     */
632    private boolean internalIsLoggable(Level l) {
633        int effectiveLevel = levelIntVal;
634        if (effectiveLevel == Level.OFF.intValue()) {
635            // always return false if the effective level is off
636            return false;
637        }
638        return l.intValue() >= effectiveLevel;
639    }
640
641    /**
642     * Determines whether this logger will actually log messages of the
643     * specified level. The effective level used to do the determination may be
644     * inherited from its parent. The default level is {@code Level.INFO}.
645     *
646     * @param l
647     *            the level to check.
648     * @return {@code true} if this logger will actually log this level,
649     *         otherwise {@code false}.
650     */
651    public boolean isLoggable(Level l) {
652        return internalIsLoggable(l);
653    }
654
655    /**
656     * Sets the resource bundle and its name for a supplied LogRecord object.
657     * This method first tries to use this logger's resource bundle if any,
658     * otherwise try to inherit from this logger's parent, recursively up the
659     * namespace.
660     */
661    private void setResourceBundle(LogRecord record) {
662        for (Logger p = this; p != null; p = p.parent) {
663            String resourceBundleName = p.resourceBundleName;
664            if (resourceBundleName != null) {
665                record.setResourceBundle(p.resourceBundle);
666                record.setResourceBundleName(resourceBundleName);
667                return;
668            }
669        }
670    }
671
672    /**
673     * Logs a message indicating that a method has been entered. A log record
674     * with log level {@code Level.FINER}, log message "ENTRY", the specified
675     * source class name and source method name is submitted for logging.
676     *
677     * @param sourceClass
678     *            the calling class name.
679     * @param sourceMethod
680     *            the method name.
681     */
682    public void entering(String sourceClass, String sourceMethod) {
683        if (!internalIsLoggable(Level.FINER)) {
684            return;
685        }
686
687        LogRecord record = new LogRecord(Level.FINER, "ENTRY");
688        record.setLoggerName(this.name);
689        record.setSourceClassName(sourceClass);
690        record.setSourceMethodName(sourceMethod);
691        setResourceBundle(record);
692        log(record);
693    }
694
695    /**
696     * Logs a message indicating that a method has been entered. A log record
697     * with log level {@code Level.FINER}, log message "ENTRY", the specified
698     * source class name, source method name and one parameter is submitted for
699     * logging.
700     *
701     * @param sourceClass
702     *            the source class name.
703     * @param sourceMethod
704     *            the source method name.
705     * @param param
706     *            the parameter for the method call.
707     */
708    public void entering(String sourceClass, String sourceMethod, Object param) {
709        if (!internalIsLoggable(Level.FINER)) {
710            return;
711        }
712
713        LogRecord record = new LogRecord(Level.FINER, "ENTRY" + " {0}");
714        record.setLoggerName(this.name);
715        record.setSourceClassName(sourceClass);
716        record.setSourceMethodName(sourceMethod);
717        record.setParameters(new Object[] { param });
718        setResourceBundle(record);
719        log(record);
720    }
721
722    /**
723     * Logs a message indicating that a method has been entered. A log record
724     * with log level {@code Level.FINER}, log message "ENTRY", the specified
725     * source class name, source method name and array of parameters is
726     * submitted for logging.
727     *
728     * @param sourceClass
729     *            the source class name.
730     * @param sourceMethod
731     *            the source method name.
732     * @param params
733     *            an array of parameters for the method call.
734     */
735    public void entering(String sourceClass, String sourceMethod,
736            Object[] params) {
737        if (!internalIsLoggable(Level.FINER)) {
738            return;
739        }
740
741        String msg = "ENTRY";
742        if (params != null) {
743            StringBuilder msgBuffer = new StringBuilder("ENTRY");
744            for (int i = 0; i < params.length; i++) {
745                msgBuffer.append(" {").append(i).append("}");
746            }
747            msg = msgBuffer.toString();
748        }
749        LogRecord record = new LogRecord(Level.FINER, msg);
750        record.setLoggerName(this.name);
751        record.setSourceClassName(sourceClass);
752        record.setSourceMethodName(sourceMethod);
753        record.setParameters(params);
754        setResourceBundle(record);
755        log(record);
756    }
757
758    /**
759     * Logs a message indicating that a method is exited. A log record with log
760     * level {@code Level.FINER}, log message "RETURN", the specified source
761     * class name and source method name is submitted for logging.
762     *
763     * @param sourceClass
764     *            the calling class name.
765     * @param sourceMethod
766     *            the method name.
767     */
768    public void exiting(String sourceClass, String sourceMethod) {
769        if (!internalIsLoggable(Level.FINER)) {
770            return;
771        }
772
773        LogRecord record = new LogRecord(Level.FINER, "RETURN");
774        record.setLoggerName(this.name);
775        record.setSourceClassName(sourceClass);
776        record.setSourceMethodName(sourceMethod);
777        setResourceBundle(record);
778        log(record);
779    }
780
781    /**
782     * Logs a message indicating that a method is exited. A log record with log
783     * level {@code Level.FINER}, log message "RETURN", the specified source
784     * class name, source method name and return value is submitted for logging.
785     *
786     * @param sourceClass
787     *            the source class name.
788     * @param sourceMethod
789     *            the source method name.
790     * @param result
791     *            the return value of the method call.
792     */
793    public void exiting(String sourceClass, String sourceMethod, Object result) {
794        if (!internalIsLoggable(Level.FINER)) {
795            return;
796        }
797
798        LogRecord record = new LogRecord(Level.FINER, "RETURN" + " {0}");
799        record.setLoggerName(this.name);
800        record.setSourceClassName(sourceClass);
801        record.setSourceMethodName(sourceMethod);
802        record.setParameters(new Object[] { result });
803        setResourceBundle(record);
804        log(record);
805    }
806
807    /**
808     * Logs a message indicating that an exception is thrown. A log record with
809     * log level {@code Level.FINER}, log message "THROW", the specified source
810     * class name, source method name and the {@code Throwable} object is
811     * submitted for logging.
812     *
813     * @param sourceClass
814     *            the source class name.
815     * @param sourceMethod
816     *            the source method name.
817     * @param thrown
818     *            the {@code Throwable} object.
819     */
820    public void throwing(String sourceClass, String sourceMethod,
821            Throwable thrown) {
822        if (!internalIsLoggable(Level.FINER)) {
823            return;
824        }
825
826        LogRecord record = new LogRecord(Level.FINER, "THROW");
827        record.setLoggerName(this.name);
828        record.setSourceClassName(sourceClass);
829        record.setSourceMethodName(sourceMethod);
830        record.setThrown(thrown);
831        setResourceBundle(record);
832        log(record);
833    }
834
835    /**
836     * Logs a message of level {@code Level.SEVERE}; the message is transmitted
837     * to all subscribed handlers.
838     *
839     * @param msg
840     *            the message to log.
841     */
842    public void severe(String msg) {
843        log(Level.SEVERE, msg);
844    }
845
846    /**
847     * Logs a message of level {@code Level.WARNING}; the message is
848     * transmitted to all subscribed handlers.
849     *
850     * @param msg
851     *            the message to log.
852     */
853    public void warning(String msg) {
854        log(Level.WARNING, msg);
855    }
856
857    /**
858     * Logs a message of level {@code Level.INFO}; the message is transmitted
859     * to all subscribed handlers.
860     *
861     * @param msg
862     *            the message to log.
863     */
864    public void info(String msg) {
865        log(Level.INFO, msg);
866    }
867
868    /**
869     * Logs a message of level {@code Level.CONFIG}; the message is transmitted
870     * to all subscribed handlers.
871     *
872     * @param msg
873     *            the message to log.
874     */
875    public void config(String msg) {
876        log(Level.CONFIG, msg);
877    }
878
879    /**
880     * Logs a message of level {@code Level.FINE}; the message is transmitted
881     * to all subscribed handlers.
882     *
883     * @param msg
884     *            the message to log.
885     */
886    public void fine(String msg) {
887        log(Level.FINE, msg);
888    }
889
890    /**
891     * Logs a message of level {@code Level.FINER}; the message is transmitted
892     * to all subscribed handlers.
893     *
894     * @param msg
895     *            the message to log.
896     */
897    public void finer(String msg) {
898        log(Level.FINER, msg);
899    }
900
901    /**
902     * Logs a message of level {@code Level.FINEST}; the message is transmitted
903     * to all subscribed handlers.
904     *
905     * @param msg
906     *            the message to log.
907     */
908    public void finest(String msg) {
909        log(Level.FINEST, msg);
910    }
911
912    /**
913     * Logs a message of the specified level. The message is transmitted to all
914     * subscribed handlers.
915     *
916     * @param logLevel
917     *            the level of the specified message.
918     * @param msg
919     *            the message to log.
920     */
921    public void log(Level logLevel, String msg) {
922        if (!internalIsLoggable(logLevel)) {
923            return;
924        }
925        dalvikLogHandler.publish(this, androidTag, logLevel, msg);
926    }
927
928    /**
929     * Logs a message of the specified level with the supplied parameter. The
930     * message is then transmitted to all subscribed handlers.
931     *
932     * @param logLevel
933     *            the level of the given message.
934     * @param msg
935     *            the message to log.
936     * @param param
937     *            the parameter associated with the event that is logged.
938     */
939    public void log(Level logLevel, String msg, Object param) {
940        if (!internalIsLoggable(logLevel)) {
941            return;
942        }
943
944        LogRecord record = new LogRecord(logLevel, msg);
945        record.setLoggerName(this.name);
946        record.setParameters(new Object[] { param });
947        setResourceBundle(record);
948        log(record);
949    }
950
951    /**
952     * Logs a message of the specified level with the supplied parameter array.
953     * The message is then transmitted to all subscribed handlers.
954     *
955     * @param logLevel
956     *            the level of the given message
957     * @param msg
958     *            the message to log.
959     * @param params
960     *            the parameter array associated with the event that is logged.
961     */
962    public void log(Level logLevel, String msg, Object[] params) {
963        if (!internalIsLoggable(logLevel)) {
964            return;
965        }
966
967        LogRecord record = new LogRecord(logLevel, msg);
968        record.setLoggerName(this.name);
969        record.setParameters(params);
970        setResourceBundle(record);
971        log(record);
972    }
973
974    /**
975     * Logs a message of the specified level with the supplied {@code Throwable}
976     * object. The message is then transmitted to all subscribed handlers.
977     *
978     * @param logLevel
979     *            the level of the given message.
980     * @param msg
981     *            the message to log.
982     * @param thrown
983     *            the {@code Throwable} object associated with the event that is
984     *            logged.
985     */
986    public void log(Level logLevel, String msg, Throwable thrown) {
987        if (!internalIsLoggable(logLevel)) {
988            return;
989        }
990
991        LogRecord record = new LogRecord(logLevel, msg);
992        record.setLoggerName(this.name);
993        record.setThrown(thrown);
994        setResourceBundle(record);
995        log(record);
996    }
997
998    /**
999     * Logs a given log record. Only records with a logging level that is equal
1000     * or greater than this logger's level will be submitted to this logger's
1001     * handlers for logging. If {@code getUseParentHandlers()} returns {@code
1002     * true}, the log record will also be submitted to the handlers of this
1003     * logger's parent, potentially recursively up the namespace.
1004     * <p>
1005     * Since all other log methods call this method to actually perform the
1006     * logging action, subclasses of this class can override this method to
1007     * catch all logging activities.
1008     * </p>
1009     *
1010     * @param record
1011     *            the log record to be logged.
1012     */
1013    public void log(LogRecord record) {
1014        if (!internalIsLoggable(record.getLevel())) {
1015            return;
1016        }
1017
1018        // apply the filter if any
1019        Filter f = filter;
1020        if (f != null && !f.isLoggable(record)) {
1021            return;
1022        }
1023
1024        /*
1025         * call the handlers of this logger, throw any exception that occurs
1026         */
1027        Handler[] allHandlers = getHandlers();
1028        for (Handler element : allHandlers) {
1029            element.publish(record);
1030        }
1031        // call the parent's handlers if set useParentHandlers
1032        Logger temp = this;
1033        Logger theParent = temp.parent;
1034        while (theParent != null && temp.getUseParentHandlers()) {
1035            Handler[] ha = theParent.getHandlers();
1036            for (Handler element : ha) {
1037                element.publish(record);
1038            }
1039            temp = theParent;
1040            theParent = temp.parent;
1041        }
1042    }
1043
1044    /**
1045     * Logs a message of the given level with the specified source class name
1046     * and source method name.
1047     *
1048     * @param logLevel
1049     *            the level of the given message.
1050     * @param sourceClass
1051     *            the source class name.
1052     * @param sourceMethod
1053     *            the source method name.
1054     * @param msg
1055     *            the message to be logged.
1056     */
1057    public void logp(Level logLevel, String sourceClass, String sourceMethod,
1058            String msg) {
1059        if (!internalIsLoggable(logLevel)) {
1060            return;
1061        }
1062
1063        LogRecord record = new LogRecord(logLevel, msg);
1064        record.setLoggerName(this.name);
1065        record.setSourceClassName(sourceClass);
1066        record.setSourceMethodName(sourceMethod);
1067        setResourceBundle(record);
1068        log(record);
1069    }
1070
1071    /**
1072     * Logs a message of the given level with the specified source class name,
1073     * source method name and parameter.
1074     *
1075     * @param logLevel
1076     *            the level of the given message
1077     * @param sourceClass
1078     *            the source class name
1079     * @param sourceMethod
1080     *            the source method name
1081     * @param msg
1082     *            the message to be logged
1083     * @param param
1084     *            the parameter associated with the event that is logged.
1085     */
1086    public void logp(Level logLevel, String sourceClass, String sourceMethod,
1087            String msg, Object param) {
1088        if (!internalIsLoggable(logLevel)) {
1089            return;
1090        }
1091
1092        LogRecord record = new LogRecord(logLevel, msg);
1093        record.setLoggerName(this.name);
1094        record.setSourceClassName(sourceClass);
1095        record.setSourceMethodName(sourceMethod);
1096        record.setParameters(new Object[] { param });
1097        setResourceBundle(record);
1098        log(record);
1099    }
1100
1101    /**
1102     * Logs a message of the given level with the specified source class name,
1103     * source method name and parameter array.
1104     *
1105     * @param logLevel
1106     *            the level of the given message.
1107     * @param sourceClass
1108     *            the source class name.
1109     * @param sourceMethod
1110     *            the source method name.
1111     * @param msg
1112     *            the message to be logged.
1113     * @param params
1114     *            the parameter array associated with the event that is logged.
1115     */
1116    public void logp(Level logLevel, String sourceClass, String sourceMethod,
1117            String msg, Object[] params) {
1118        if (!internalIsLoggable(logLevel)) {
1119            return;
1120        }
1121
1122        LogRecord record = new LogRecord(logLevel, msg);
1123        record.setLoggerName(this.name);
1124        record.setSourceClassName(sourceClass);
1125        record.setSourceMethodName(sourceMethod);
1126        record.setParameters(params);
1127        setResourceBundle(record);
1128        log(record);
1129    }
1130
1131    /**
1132     * Logs a message of the given level with the specified source class name,
1133     * source method name and {@code Throwable} object.
1134     *
1135     * @param logLevel
1136     *            the level of the given message.
1137     * @param sourceClass
1138     *            the source class name.
1139     * @param sourceMethod
1140     *            the source method name.
1141     * @param msg
1142     *            the message to be logged.
1143     * @param thrown
1144     *            the {@code Throwable} object.
1145     */
1146    public void logp(Level logLevel, String sourceClass, String sourceMethod,
1147            String msg, Throwable thrown) {
1148        if (!internalIsLoggable(logLevel)) {
1149            return;
1150        }
1151
1152        LogRecord record = new LogRecord(logLevel, msg);
1153        record.setLoggerName(this.name);
1154        record.setSourceClassName(sourceClass);
1155        record.setSourceMethodName(sourceMethod);
1156        record.setThrown(thrown);
1157        setResourceBundle(record);
1158        log(record);
1159    }
1160
1161    /**
1162     * Logs a message of the given level with the specified source class name
1163     * and source method name, using the given resource bundle to localize the
1164     * message. If {@code bundleName} is null, the empty string or not valid then
1165     * the message is not localized.
1166     *
1167     * @param logLevel
1168     *            the level of the given message.
1169     * @param sourceClass
1170     *            the source class name.
1171     * @param sourceMethod
1172     *            the source method name.
1173     * @param bundleName
1174     *            the name of the resource bundle used to localize the message.
1175     * @param msg
1176     *            the message to be logged.
1177     */
1178    public void logrb(Level logLevel, String sourceClass, String sourceMethod,
1179            String bundleName, String msg) {
1180        if (!internalIsLoggable(logLevel)) {
1181            return;
1182        }
1183
1184        LogRecord record = new LogRecord(logLevel, msg);
1185        if (bundleName != null) {
1186            try {
1187                record.setResourceBundle(loadResourceBundle(bundleName));
1188            } catch (MissingResourceException e) {
1189                // ignore
1190            }
1191            record.setResourceBundleName(bundleName);
1192        }
1193        record.setLoggerName(this.name);
1194        record.setSourceClassName(sourceClass);
1195        record.setSourceMethodName(sourceMethod);
1196        log(record);
1197    }
1198
1199    /**
1200     * Logs a message of the given level with the specified source class name,
1201     * source method name and parameter, using the given resource bundle to
1202     * localize the message. If {@code bundleName} is null, the empty string
1203     * or not valid then the message is not localized.
1204     *
1205     * @param logLevel
1206     *            the level of the given message.
1207     * @param sourceClass
1208     *            the source class name.
1209     * @param sourceMethod
1210     *            the source method name.
1211     * @param bundleName
1212     *            the name of the resource bundle used to localize the message.
1213     * @param msg
1214     *            the message to be logged.
1215     * @param param
1216     *            the parameter associated with the event that is logged.
1217     */
1218    public void logrb(Level logLevel, String sourceClass, String sourceMethod,
1219            String bundleName, String msg, Object param) {
1220        if (!internalIsLoggable(logLevel)) {
1221            return;
1222        }
1223
1224        LogRecord record = new LogRecord(logLevel, msg);
1225        if (bundleName != null) {
1226            try {
1227                record.setResourceBundle(loadResourceBundle(bundleName));
1228            } catch (MissingResourceException e) {
1229                // ignore
1230            }
1231            record.setResourceBundleName(bundleName);
1232        }
1233        record.setLoggerName(this.name);
1234        record.setSourceClassName(sourceClass);
1235        record.setSourceMethodName(sourceMethod);
1236        record.setParameters(new Object[] { param });
1237        log(record);
1238    }
1239
1240    /**
1241     * Logs a message of the given level with the specified source class name,
1242     * source method name and parameter array, using the given resource bundle
1243     * to localize the message. If {@code bundleName} is null, the empty string
1244     * or not valid then the message is not localized.
1245     *
1246     * @param logLevel
1247     *            the level of the given message.
1248     * @param sourceClass
1249     *            the source class name.
1250     * @param sourceMethod
1251     *            the source method name.
1252     * @param bundleName
1253     *            the name of the resource bundle used to localize the message.
1254     * @param msg
1255     *            the message to be logged.
1256     * @param params
1257     *            the parameter array associated with the event that is logged.
1258     */
1259    public void logrb(Level logLevel, String sourceClass, String sourceMethod,
1260            String bundleName, String msg, Object[] params) {
1261        if (!internalIsLoggable(logLevel)) {
1262            return;
1263        }
1264
1265        LogRecord record = new LogRecord(logLevel, msg);
1266        if (bundleName != null) {
1267            try {
1268                record.setResourceBundle(loadResourceBundle(bundleName));
1269            } catch (MissingResourceException e) {
1270                // ignore
1271            }
1272            record.setResourceBundleName(bundleName);
1273        }
1274        record.setLoggerName(this.name);
1275        record.setSourceClassName(sourceClass);
1276        record.setSourceMethodName(sourceMethod);
1277        record.setParameters(params);
1278        log(record);
1279    }
1280
1281    /**
1282     * Logs a message of the given level with the specified source class name,
1283     * source method name and {@code Throwable} object, using the given resource
1284     * bundle to localize the message. If {@code bundleName} is null, the empty
1285     * string or not valid then the message is not localized.
1286     *
1287     * @param logLevel
1288     *            the level of the given message
1289     * @param sourceClass
1290     *            the source class name
1291     * @param sourceMethod
1292     *            the source method name
1293     * @param bundleName
1294     *            the name of the resource bundle used to localize the message.
1295     * @param msg
1296     *            the message to be logged.
1297     * @param thrown
1298     *            the {@code Throwable} object.
1299     */
1300    public void logrb(Level logLevel, String sourceClass, String sourceMethod,
1301            String bundleName, String msg, Throwable thrown) {
1302        if (!internalIsLoggable(logLevel)) {
1303            return;
1304        }
1305
1306        LogRecord record = new LogRecord(logLevel, msg);
1307        if (bundleName != null) {
1308            try {
1309                record.setResourceBundle(loadResourceBundle(bundleName));
1310            } catch (MissingResourceException e) {
1311                // ignore
1312            }
1313            record.setResourceBundleName(bundleName);
1314        }
1315        record.setLoggerName(this.name);
1316        record.setSourceClassName(sourceClass);
1317        record.setSourceMethodName(sourceMethod);
1318        record.setThrown(thrown);
1319        log(record);
1320    }
1321
1322    void reset() {
1323        levelObjVal = null;
1324        levelIntVal = Level.INFO.intValue();
1325
1326        for (Handler handler : handlers) {
1327            try {
1328                if (handlers.remove(handler)) {
1329                    handler.close();
1330                }
1331            } catch (Exception ignored) {
1332            }
1333        }
1334
1335        updateDalvikLogHandler();
1336    }
1337}
1338