blob: 301cabd6d7ea9a29c84a3ff6f676b3c542a3eb52 [file] [log] [blame]
/*
* 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));
}
}