| /* |
| * Copyright 2011 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.google.ipc.invalidation.external.client.android.service; |
| |
| import com.google.ipc.invalidation.external.client.SystemResources; |
| import com.google.ipc.invalidation.external.client.SystemResources.Logger; |
| import com.google.ipc.invalidation.util.Formatter; |
| |
| import android.util.Log; |
| |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.logging.Level; |
| |
| |
| /** |
| * Provides the implementation of {@link Logger} for Android. The logging tag will be based upon the |
| * top-level class name containing the code invoking the logger (the outer class, not an inner or |
| * anonymous class name). For severe and warning level messages, the Android logger will also |
| * dump the stack trace of the first argument if it is a throwable. |
| */ |
| public class AndroidLogger implements Logger { |
| |
| /** Creates a new AndroidLogger that uses the provided value as the Android logging tag */ |
| public static AndroidLogger forTag(String tag) { |
| return new AndroidLogger(tag, null); |
| } |
| |
| /** Creates a new AndroidLogger that will compute a tag value dynamically based upon the class |
| * that calls into the logger and will prepend the provided prefix (if any) on all |
| * logged messages. |
| */ |
| public static AndroidLogger forPrefix(String prefix) { |
| return new AndroidLogger(null, prefix); |
| } |
| |
| /** |
| * If {@code false}, then Log.isLoggable() is called to filter log messages |
| */ |
| private static boolean filteringDisabled = false; |
| |
| /** |
| * Maps from a Java {@link Level} to the android {@link Log} priority value used to log |
| * messages at that level. |
| */ |
| private static Map<Level, Integer> levelToPriority = new HashMap<Level, Integer>(); |
| |
| static { |
| // Define the mappings for Java log levels to the associated Android log priorities |
| levelToPriority.put(Level.INFO, Log.INFO); |
| levelToPriority.put(Level.WARNING, Log.WARN); |
| levelToPriority.put(Level.SEVERE, Log.ERROR); |
| levelToPriority.put(Level.FINE, Log.DEBUG); |
| levelToPriority.put(Level.FINER, Log.VERBOSE); |
| levelToPriority.put(Level.FINEST, Log.VERBOSE); |
| levelToPriority.put(Level.CONFIG, Log.INFO); |
| } |
| |
| /** |
| * Disables log filtering so all logged messages will be captured. |
| */ |
| public static void disableFilteringForTest() { |
| filteringDisabled = true; |
| } |
| |
| /** |
| * The default minimum Android log level. We default to 0 to ensure everything is logged. |
| * This should be a value from the {@link Log} constants. |
| */ |
| private static int minimumLogLevel = 0; |
| |
| /** |
| * The maximum length of an Android logging tag. There's no formal constants but the constraint is |
| * mentioned in the Log javadoc |
| */ |
| private static final int MAX_TAG_LENGTH = 23; |
| |
| /** Constant tag to use for logged messages (or {@code null} to use topmost class on stack */ |
| private final String tag; |
| |
| /** Prefix added to Android logging messages */ |
| private final String logPrefix; |
| |
| /** Creates a logger that prefixes every logging stmt with {@code logPrefix}. */ |
| private AndroidLogger(String tag, String logPrefix) { |
| this.tag = tag; |
| this.logPrefix = logPrefix; |
| } |
| |
| @Override |
| public boolean isLoggable(Level level) { |
| return isLoggable(getTag(), levelToPriority(level)); |
| } |
| |
| @Override |
| public void log(Level level, String template, Object... args) { |
| int androidLevel = levelToPriority(level); |
| String tag = getTag(); |
| if (isLoggable(tag, androidLevel)) { |
| Log.println(androidLevel, tag, format(template, args)); |
| } |
| } |
| |
| @Override |
| public void severe(String template, Object...args) { |
| String tag = getTag(); |
| if (isLoggable(tag, Log.ERROR)) { |
| // If the first argument is an exception, use the form of Log that will dump a stack trace |
| if ((args.length > 0) && (args[0] instanceof Throwable)) { |
| Log.e(tag, format(template, args), (Throwable) args[0]); |
| } else { |
| Log.e(tag, format(template, args)); |
| } |
| } |
| } |
| |
| @Override |
| public void warning(String template, Object...args) { |
| String tag = getTag(); |
| if (isLoggable(tag, Log.WARN)){ |
| // If the first argument is an exception, use the form of Log that will dump a stack trace |
| if ((args.length > 0) && (args[0] instanceof Throwable)) { |
| Log.w(tag, format(template, args), (Throwable) args[0]); |
| } else { |
| Log.w(tag, format(template, args)); |
| } |
| } |
| } |
| |
| @Override |
| public void info(String template, Object...args) { |
| String tag = getTag(); |
| if (isLoggable(tag, Log.INFO)) { |
| Log.i(tag, format(template, args)); |
| } |
| } |
| |
| @Override |
| public void fine(String template, Object...args) { |
| String tag = getTag(); |
| if (isLoggable(tag, Log.DEBUG)) { |
| Log.d(tag, format(template, args)); |
| } |
| } |
| |
| @Override |
| public void setSystemResources(SystemResources resources) { |
| // No-op. |
| } |
| |
| /** Given a Java logging level, returns the corresponding Android log priority. */ |
| private static int levelToPriority(Level level) { |
| Integer priority = levelToPriority.get(level); |
| if (priority != null) { |
| return priority; |
| } |
| throw new IllegalArgumentException("Unsupported level: " + level); |
| } |
| |
| /** Formats the content of a logged messages for output, prepending the log prefix if any. */ |
| private String format(String template, Object...args) { |
| return (logPrefix != null) ? |
| ("[" + logPrefix + "] " + Formatter.format(template, args)) : |
| Formatter.format(template, args); |
| } |
| |
| /** Returns the Android logging tag that should be placed on logged messages */ |
| private String getTag() { |
| if (tag != null) { |
| return tag; |
| } |
| |
| StackTraceElement[] stackTrace = new Throwable().getStackTrace(); |
| String className = null; |
| for (int i = 0; i < stackTrace.length; i++) { |
| className = stackTrace[i].getClassName(); |
| |
| // Skip over this class's methods |
| if (!className.equals(AndroidLogger.class.getName())) { |
| break; |
| } |
| } |
| |
| // Compute the unqualified class name w/out any inner class, then truncate to the |
| // maximum tag length. |
| int unqualBegin = className.lastIndexOf('.') + 1; |
| if (unqualBegin < 0) { // should never happen, but be safe |
| unqualBegin = 0; |
| } |
| int unqualEnd = className.indexOf('$', unqualBegin); |
| if (unqualEnd < 0) { |
| unqualEnd = className.length(); |
| } |
| if ((unqualEnd - unqualBegin) > MAX_TAG_LENGTH) { |
| unqualEnd = unqualBegin + MAX_TAG_LENGTH; |
| } |
| return className.substring(unqualBegin, unqualEnd); |
| } |
| |
| /** |
| * Add additional constraint on logging. In addition to the normal check of |
| * {@link Log#isLoggable(String, int)} for logging, this also requires a minimum |
| * log level of the given value. This should be a value from the {@link Log} constants. |
| */ |
| public static void setMinimumAndroidLogLevel(int logLevel) { |
| minimumLogLevel = logLevel; |
| } |
| |
| /** |
| * Returns {@code true} is the provided tag/level will produce logged output. |
| */ |
| |
| boolean isLoggable(String tag, int priority) { |
| return filteringDisabled || (priority >= minimumLogLevel && Log.isLoggable(tag, priority)); |
| } |
| } |