/*
* Copyright (C) 2014 SCVNGR, Inc. d/b/a LevelUp
*
* 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.scvngr.levelup.core.util;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import com.scvngr.levelup.core.BuildConfig;
import com.scvngr.levelup.core.annotation.LevelUpApi;
import com.scvngr.levelup.core.annotation.LevelUpApi.Contract;
import net.jcip.annotations.ThreadSafe;
import java.util.Locale;
/**
* This is a utility class to wrap the Android {@link Log} class.
* <p>
* Because logging is expensive, it is recommended that clients of this class strip out logging
* statements at compile time using ProGuard.
*/
@LevelUpApi(contract = Contract.DRAFT)
@ThreadSafe
public final class LogManager {
/**
* Where in the array of stack trace elements returned by {@link Thread#getStackTrace()} to find
* the source class/method reference.
*/
private static final int STACKTRACE_SOURCE_FRAME_INDEX = 4;
/**
* Format string for log messages.
* <p>
* The format is: <Thread> <Class>.<method>(): <message>
*/
private static final String FORMAT = "%-30s%s.%s(): %s";
/**
* Log tag for use with {@link Log}.
*/
private static volatile String sLogTag = "LevelUp";
/**
* Initializes logging.
* <p>
* This should be called from {@link android.app.Application#onCreate()}. (It may also need to
* be called from {@link android.content.ContentProvider#onCreate()} due to when the provider is
* initialized.)
*
* @param context Application context.
*/
public static void init(@NonNull final Context context) {
PreconditionUtil.assertNotNull(context, "context");
// Removing whitespace fixes issues with logcat filters
sLogTag = BuildUtil.getLabel(context).replace(" ", "");
if (BuildConfig.DEBUG) {
StrictModeUtil.setStrictMode(true);
android.support.v4.app.FragmentManager.enableDebugLogging(true);
android.support.v4.app.LoaderManager.enableDebugLogging(true);
if (EnvironmentUtil.isSdk11OrGreater()) {
enableDebugLoggingHoneycomb();
}
}
}
/**
* Enables debug logging in Android 3.0+ classes.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private static void enableDebugLoggingHoneycomb() {
android.app.FragmentManager.enableDebugLogging(true);
android.app.LoaderManager.enableDebugLogging(true);
}
/**
* Log a message.
*
* @param msg message to log. This message is expected to be a format string if varargs are
* passed in.
* @param args optional arguments to be formatted into {@code msg}.
*/
public static void v(@NonNull final String msg, @Nullable final Object... args) {
logMessage(Log.VERBOSE, msg, args, null);
}
/**
* Log a message.
*
* @param msg message to log.
* @param err an exception that occurred, whose trace will be printed with the log message.
*/
public static void v(@NonNull final String msg, @NonNull final Throwable err) {
logMessage(Log.VERBOSE, msg, null, err);
}
/**
* Log a message.
*
* @param msg message to log. This message is expected to be a format string if varargs are
* passed in.
* @param args optional arguments to be formatted into {@code msg}.
*/
public static void d(@NonNull final String msg, @Nullable final Object... args) {
logMessage(Log.DEBUG, msg, args, null);
}
/**
* Log a message.
*
* @param msg message to log.
* @param err an exception that occurred, whose trace will be printed with the log message.
*/
public static void d(@NonNull final String msg, @NonNull final Throwable err) {
logMessage(Log.DEBUG, msg, null, err);
}
/**
* Log a message.
*
* @param msg message to log. This message is expected to be a format string if varargs are
* passed in.
* @param args optional arguments to be formatted into {@code msg}.
*/
public static void i(@NonNull final String msg, @Nullable final Object... args) {
logMessage(Log.INFO, msg, args, null);
}
/**
* Log a message.
*
* @param msg message to log.
* @param err an exception that occurred, whose trace will be printed with the log message.
*/
public static void i(@NonNull final String msg, @NonNull final Throwable err) {
logMessage(Log.INFO, msg, null, err);
}
/**
* Log a message.
*
* @param msg message to log. This message is expected to be a format string if varargs are
* passed in.
* @param args optional arguments to be formatted into {@code msg}.
*/
public static void w(@NonNull final String msg, @Nullable final Object... args) {
logMessage(Log.WARN, msg, args, null);
}
/**
* Log a message.
*
* @param msg message to log.
* @param err an exception that occurred, whose trace will be printed with the log message.
*/
public static void w(@NonNull final String msg, @NonNull final Throwable err) {
logMessage(Log.WARN, msg, null, err);
}
/**
* Log a message.
*
* @param msg message to log. This message is expected to be a format string if varargs are
* passed in.
* @param args optional arguments to be formatted into {@code msg}.
*/
public static void e(@NonNull final String msg, @Nullable final Object... args) {
logMessage(Log.ERROR, msg, args, null);
}
/**
* Log a message.
*
* @param msg message to log.
* @param err an exception that occurred, whose trace will be printed with the log message.
*/
public static void e(@NonNull final String msg, @Nullable final Throwable err) {
logMessage(Log.ERROR, msg, null, err);
}
/**
* Helper for varargs in log messages.
*
* @param msg The format string.
* @param args The format arguments.
* @return A string formatted with the arguments.
*/
@NonNull
private static String formatMessage(final String msg, @Nullable final Object[] args) {
String output = msg;
try {
for (final Object x : args) {
output = String.format(Locale.US, output, x);
}
} catch (final Exception e) {
output = String.format(Locale.US, msg, args);
}
return output;
}
/**
* Logs a message to the Android log.
*
* @param logLevel {@link Log#VERBOSE}, {@link Log#DEBUG}, {@link Log#INFO}, {@link Log#WARN},
* or {@link Log#ERROR}.
* @param message the message to be logged. This message is expected to be a format string if
* messageFormatArgs is not null.
* @param messageFormatArgs formatting arguments for the message, or null if the string is to be
* handled without formatting.
* @param err an optional error to log with a stacktrace.
*/
private static void logMessage(final int logLevel, @NonNull final String message,
@Nullable final Object[] messageFormatArgs, @Nullable final Throwable err) {
final String preppedMessage = formatMessage(message, messageFormatArgs);
final StackTraceElement[] trace = Thread.currentThread().getStackTrace();
final String sourceClass = trace[STACKTRACE_SOURCE_FRAME_INDEX].getClassName();
final String sourceMethod = trace[STACKTRACE_SOURCE_FRAME_INDEX].getMethodName();
final String logcatLogLine =
String.format(Locale.US, FORMAT, Thread.currentThread().getName(), sourceClass,
sourceMethod, preppedMessage);
switch (logLevel) {
case Log.VERBOSE: {
if (null == err) {
Log.v(sLogTag, logcatLogLine);
} else {
Log.v(sLogTag, logcatLogLine, err);
}
break;
}
case Log.DEBUG: {
if (null == err) {
Log.d(sLogTag, logcatLogLine);
} else {
Log.d(sLogTag, logcatLogLine, err);
}
break;
}
case Log.INFO: {
if (null == err) {
Log.i(sLogTag, logcatLogLine);
} else {
Log.i(sLogTag, logcatLogLine, err);
}
break;
}
case Log.WARN: {
if (null == err) {
Log.w(sLogTag, logcatLogLine);
} else {
Log.w(sLogTag, logcatLogLine, err);
}
break;
}
case Log.ERROR: {
if (null == err) {
Log.e(sLogTag, logcatLogLine);
} else {
Log.e(sLogTag, logcatLogLine, err);
}
break;
}
case Log.ASSERT: {
if (null == err) {
Log.wtf(sLogTag, logcatLogLine);
} else {
Log.wtf(sLogTag, logcatLogLine, err);
}
break;
}
default: {
throw new AssertionError();
}
}
}
/**
* Private constructor prevents instantiation.
*
* @throws UnsupportedOperationException because this class cannot be instantiated.
*/
private LogManager() {
throw new UnsupportedOperationException("This class is non-instantiable");
}
}