package com.mcxiaoke.next.utils;
import android.content.Context;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.util.Log;
import com.mcxiaoke.next.Charsets;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
/**
* 日志工具类,支持记录到文件,支持针对单个TAG设定日志级别
* User: mcxiaoke
* Date: 13-9-10 14-04-08
* Time: 下午1:14
*/
public final class LogUtils {
/**
* 表示关闭LOG输出 *
*/
public static final int LEVEL_OFF = Integer.MAX_VALUE;
public static final String TAG_DEBUG = "LogUtils";
private static final String FILE_LOG_DIR = "logs";
private static Map<String, Long> sTraceMap = new HashMap<String, Long>();
private static FileLogger sFileLogger;
// 默认情况下log输出ERROR级别,file log不输出
private static int sLoggingLevel = Log.ERROR;
private static int sFileLoggingLevel = Log.ASSERT;
private LogUtils() {
}
private static boolean needLog(int level) {
return level >= sLoggingLevel;
}
public static void setLevel(int level) {
sLoggingLevel = level;
}
public static void setFileLoggingLevel(Context appContext, int level) {
sFileLoggingLevel = level;
openFileLogger(appContext);
}
public static void e(Throwable t) {
e(TAG_DEBUG, t);
}
public static void e(String tag, Throwable e) {
if (needLog(Log.ERROR)) {
Log.e(tag, "", e);
}
}
public static void e(String tag, String message) {
if (needLog(Log.ERROR)) {
Log.e(tag, message);
}
}
public static void w(String tag, String message) {
if (needLog(Log.WARN)) {
Log.w(tag, message);
}
}
public static void i(String tag, String message) {
if (needLog(Log.INFO)) {
Log.i(tag, message);
}
}
public static void d(String tag, String message) {
if (needLog(Log.DEBUG)) {
Log.d(tag, message);
}
}
public static void v(String tag, String message) {
if (needLog(Log.VERBOSE)) {
Log.v(tag, message);
}
}
public static void v(String format, Object... args) {
if (needLog(Log.VERBOSE)) {
Log.v(TAG_DEBUG, buildMessage(format, args));
}
}
public static void d(String format, Object... args) {
if (needLog(Log.DEBUG)) {
Log.d(TAG_DEBUG, buildMessage(format, args));
}
}
public static void i(String format, Object... args) {
if (needLog(Log.INFO)) {
Log.i(TAG_DEBUG, buildMessage(format, args));
}
}
public static void w(String format, Object... args) {
if (needLog(Log.WARN)) {
Log.w(TAG_DEBUG, buildMessage(format, args));
}
}
public static void e(String format, Object... args) {
if (needLog(Log.ERROR)) {
Log.e(TAG_DEBUG, buildMessage(format, args));
}
}
public static void e(Throwable tr, String format, Object... args) {
if (needLog(Log.ERROR)) {
Log.e(TAG_DEBUG, buildMessage(format, args), tr);
}
}
public static void e(Class<?> clz, String message) {
e(clz.getSimpleName(), message);
}
public static void w(Class<?> clz, String message) {
w(clz.getSimpleName(), message);
}
public static void i(Class<?> clz, String message) {
i(clz.getSimpleName(), message);
}
public static void d(Class<?> clz, String message) {
d(clz.getSimpleName(), message);
}
public static void v(Class<?> clz, String message) {
v(clz.getSimpleName(), message);
}
public static void e(Class<?> clz, Throwable t) {
e(clz.getSimpleName(), t);
}
public static void e(String message) {
if (needLog(Log.ERROR)) {
Log.e(TAG_DEBUG, getMethodInfo(4));
Log.e(TAG_DEBUG, "Message:\t" + message);
}
}
public static void w(String message) {
if (needLog(Log.WARN)) {
Log.w(TAG_DEBUG, getMethodInfo(4));
Log.w(TAG_DEBUG, "Message:\t" + message);
}
}
public static void i(String message) {
if (needLog(Log.INFO)) {
Log.i(TAG_DEBUG, getMethodInfo(4));
Log.i(TAG_DEBUG, "Message:\t" + message);
}
}
public static void d(String message) {
if (needLog(Log.DEBUG)) {
Log.d(TAG_DEBUG, getMethodInfo(4));
Log.d(TAG_DEBUG, "Message:\t" + message);
}
}
public static void v(String message) {
if (needLog(Log.VERBOSE)) {
Log.v(TAG_DEBUG, getMethodInfo(4));
Log.v(TAG_DEBUG, "Message:\t" + message);
}
}
private static String getMethodInfo(int index) {
final Thread current = Thread.currentThread();
final StackTraceElement[] stack = current.getStackTrace();
final StackTraceElement element = stack[index];
if (!element.isNativeMethod()) {
final String className = element.getClassName();
final String fileName = element.getFileName();
final int lineNumber = element.getLineNumber();
final String methodName = element.getMethodName();
return "Method:\t" + className + "." + methodName + "() (" + fileName + ":" + lineNumber + ")";
}
return "";
}
/**
* 获取StackTrace信息
*/
private static String buildMessage(String format, Object... args) {
String msg = (args == null) ? format : String.format(Locale.US, format, args);
StackTraceElement[] trace = new Throwable().fillInStackTrace().getStackTrace();
String caller = "<unknown>";
// Walk up the stack looking for the first caller outside of VolleyLog.
// It will be at least two frames up, so start there.
for (int i = 2; i < trace.length; i++) {
Class<?> clazz = trace[i].getClass();
if (!clazz.equals(LogUtils.class)) {
String callingClass = trace[i].getClassName();
callingClass = callingClass.substring(callingClass.lastIndexOf('.') + 1);
callingClass = callingClass.substring(callingClass.lastIndexOf('$') + 1);
caller = callingClass + "." + trace[i].getMethodName();
break;
}
}
return String.format(Locale.US, "[%d] %s: %s",
Thread.currentThread().getId(), caller, msg);
}
public static void startTrace(String operation) {
sTraceMap.put(operation, System.currentTimeMillis());
}
public static void stopTrace(String operation) {
Long start = sTraceMap.remove(operation);
if (start != null) {
long end = System.currentTimeMillis();
long interval = end - start;
Log.v(TAG_DEBUG, operation + " use time: " + interval + "ms");
}
}
public static void removeTrace(String key) {
sTraceMap.remove(key);
}
public static void clearTrace() {
sTraceMap.clear();
Log.v(TAG_DEBUG, "trace is cleared.");
}
/**
* 写log到文件
*/
public static void fe(String tag, Throwable e) {
fe(tag, "", e);
}
public static void fe(String tag, String message, Throwable e) {
if (needLog(Log.ERROR)) {
Log.e(tag, "", e);
}
if (isFileLoggable(Log.ERROR)) {
if (sFileLogger != null) {
sFileLogger.e(tag, message, e);
}
}
}
public static void fe(String tag, String message) {
if (needLog(Log.ERROR)) {
Log.e(tag, message);
}
if (isFileLoggable(Log.ERROR)) {
if (sFileLogger != null) {
sFileLogger.e(tag, message);
}
}
}
public static void fw(String tag, String message) {
if (needLog(Log.WARN)) {
Log.w(tag, message);
}
if (isFileLoggable(Log.WARN)) {
if (sFileLogger != null) {
sFileLogger.w(tag, message);
}
}
}
public static void fi(String tag, String message) {
if (needLog(Log.INFO)) {
Log.i(tag, message);
}
if (isFileLoggable(Log.INFO)) {
if (sFileLogger != null) {
sFileLogger.i(tag, message);
}
}
}
public static void fd(String tag, String message) {
if (needLog(Log.DEBUG)) {
Log.d(tag, message);
}
if (isFileLoggable(Log.DEBUG)) {
if (sFileLogger != null) {
sFileLogger.d(tag, message);
}
}
}
public static void fv(String tag, String message) {
if (needLog(Log.VERBOSE)) {
Log.v(tag, message);
}
if (isFileLoggable(Log.VERBOSE)) {
if (sFileLogger != null) {
sFileLogger.v(tag, message);
}
}
}
/**
* *****
* File Logger相关
*/
private static boolean isFileLoggable(int level) {
return level >= sFileLoggingLevel;
}
private static void closeFileLogger() {
if (sFileLogger != null) {
sFileLogger.close();
sFileLogger = null;
}
}
private static void openFileLogger(Context context) {
closeFileLogger();
if (sFileLoggingLevel < Log.ASSERT) {
sFileLogger = new FileLogger(TAG_DEBUG, createFileLogDirIfNeeded(context));
}
}
private static File createFileLogDirIfNeeded(Context context) {
File dir;
if (AndroidUtils.isMediaMounted()) {
dir = new File(context.getExternalCacheDir(), FILE_LOG_DIR);
} else {
dir = new File(context.getCacheDir(), FILE_LOG_DIR);
}
if (!dir.exists()) {
dir.mkdirs();
}
return dir;
}
public void clearLogFiles(Context context) {
File logDir = createFileLogDirIfNeeded(context);
IOUtils.delete(logDir.getPath());
}
public void clearLogFilesAsync(final Context context) {
new Thread() {
@Override
public void run() {
clearLogFiles(context);
}
}.start();
}
private static class LogEntry {
private static SimpleDateFormat dateFormat; // must always be used in the same thread
private static Date mDate;
private final long now;
private final char level;
private final String tag;
private final String threadName;
private final String msg;
private final Throwable cause;
private String date;
LogEntry(char lvl, String tag, String threadName, String msg, Throwable tr) {
this.now = System.currentTimeMillis();
this.level = lvl;
this.tag = tag;
this.threadName = threadName;
this.msg = msg;
this.cause = tr;
}
private void addCsvHeader(final StringBuilder csv) {
if (dateFormat == null)
dateFormat = new SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US);
if (date == null) {
if (null == mDate)
mDate = new Date();
mDate.setTime(now);
date = dateFormat.format(mDate);
}
csv.append(date);
csv.append(',');
csv.append(level);
csv.append(',');
csv.append(android.os.Process.myPid());
csv.append(',');
if (threadName != null)
csv.append(threadName);
csv.append(',');
csv.append(',');
if (tag != null)
csv.append(tag);
csv.append(',');
}
private void addException(final StringBuilder csv, Throwable tr) {
if (tr == null)
return;
final StringBuilder sb = new StringBuilder(256);
sb.append(cause.getClass());
sb.append(": ");
sb.append(cause.getMessage());
sb.append('\n');
for (StackTraceElement trace : cause.getStackTrace()) {
//addCsvHeader(csv);
sb.append(" at ");
sb.append(trace.getClassName());
sb.append('.');
sb.append(trace.getMethodName());
sb.append('(');
sb.append(trace.getFileName());
sb.append(':');
sb.append(trace.getLineNumber());
sb.append(')');
sb.append('\n');
}
addException(sb, tr.getCause());
csv.append(sb.toString().replace(';', '-').replace(',', '-').replace('"', '\''));
}
public CharSequence formatCsv() {
final StringBuilder csv = new StringBuilder(256);
addCsvHeader(csv);
csv.append('"');
if (msg != null) csv.append(msg.replace(';', '-').replace(',', '-').replace('"', '\''));
csv.append('"');
csv.append('\n');
if (cause != null) {
addCsvHeader(csv);
csv.append('"');
addException(csv, cause);
csv.append('"');
csv.append('\n');
}
return csv.toString();
}
}
private static class FileLogger implements Handler.Callback {
public static final String TAG = FileLogger.class.getSimpleName();
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyyMMdd-HH", Locale.US);
private static final String UTF_8 = Charsets.ENCODING_UTF_8;
private static final int MSG_OPEN = 0;
private static final int MSG_WRITE = 1;
private static final int MSG_CLEAR = 2;
public static long MAX_FILE_SIZE = 1024 * 1024 * 10;
private File mLogDir;
private File mLogFile;
private String mTag;
private HandlerThread mHandlerThread;
private Handler mAsyncHandler;
private Writer mWriter;
public FileLogger(String logTag, File logDir) {
mTag = logTag;
mLogDir = logDir;
mHandlerThread = new HandlerThread(TAG, android.os.Process.THREAD_PRIORITY_BACKGROUND);
mHandlerThread.start();
mAsyncHandler = new Handler(mHandlerThread.getLooper(), this);
sendOpenMessage();
}
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case MSG_OPEN:
onMessageOpen(msg);
break;
case MSG_WRITE:
onMessageWrite(msg);
break;
case MSG_CLEAR:
onMessageClear();
break;
}
return true;
}
public void close() {
Handler handler = mAsyncHandler;
mAsyncHandler = null;
if (handler != null) {
handler.removeCallbacksAndMessages(null);
}
if (mHandlerThread != null) {
mHandlerThread.quit();
mHandlerThread = null;
}
}
public void clear() {
sendClearMessage();
}
private void onMessageOpen(Message msg) {
closeWriter();
openWriter();
}
private void onMessageWrite(Message msg) {
try {
LogEntry logMessage = (LogEntry) msg.obj;
if (mWriter != null) {
mWriter.append(logMessage.formatCsv());
mWriter.flush();
}
} catch (IOException e) {
Log.e(TAG, e.getClass().getSimpleName() + " : " + e.getMessage());
}
verifyFileSize();
}
private void onMessageClear() {
if (mLogFile != null) {
closeWriter();
IOUtils.delete(mLogDir);
openWriter();
}
}
private void sendOpenMessage() {
if (mAsyncHandler != null) {
mAsyncHandler.sendEmptyMessage(MSG_OPEN);
}
}
private void sendWriteMessage(LogEntry log) {
if (mAsyncHandler != null) {
Message message = mAsyncHandler.obtainMessage(MSG_WRITE, log);
mAsyncHandler.sendMessage(message);
}
}
private void sendClearMessage() {
if (mAsyncHandler != null) {
mAsyncHandler.sendEmptyMessage(MSG_CLEAR);
}
}
private void createLogFile() {
if (mLogDir == null) {
return;
}
if (!mLogDir.exists()) {
mLogDir.mkdirs();
}
String fileName = String.format("log-%1$s.%2$s", DATE_FORMAT.format(new Date()), "txt");
File file = new File(mLogDir, fileName);
try {
file.createNewFile();
mLogFile = file;
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "createLogFile ex=" + e);
}
}
private void openWriter() {
createLogFile();
if (mLogFile == null) {
return;
}
if (mWriter == null)
try {
mWriter = new OutputStreamWriter(new FileOutputStream(mLogFile, true), UTF_8);
} catch (UnsupportedEncodingException e) {
Log.e(TAG, "can't get a writer for " + mLogFile + " : " + e.getMessage());
} catch (FileNotFoundException e) {
Log.e(TAG, "can't get a writer for " + mLogFile + " : " + e.getMessage());
}
}
private void closeWriter() {
if (mWriter != null) {
try {
mWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
mWriter = null;
}
}
private void verifyFileSize() {
if (mLogFile != null) {
long size = mLogFile.length();
if (size > MAX_FILE_SIZE) {
closeWriter();
mLogFile.delete();
openWriter();
}
}
}
public void d(String tag, String msg) {
write('d', tag, msg);
}
public void d(String msg) {
write('d', msg);
}
public void e(String tag, String msg, Throwable tr) {
write('e', tag, msg, tr);
}
public void e(String tag, String msg) {
write('e', tag, msg);
}
public void e(String msg) {
write('e', msg);
}
public void i(String msg, String tag) {
write('i', tag, msg);
}
public void i(String msg) {
write('i', msg);
}
public void v(String msg, String tag) {
write('v', tag, msg);
}
public void v(String msg) {
write('v', msg);
}
public void w(String tag, String msg, Throwable tr) {
write('w', tag, msg, tr);
}
public void w(String tag, String msg) {
write('w', tag, msg);
}
public void w(String msg) {
write('w', msg);
}
private void write(char lvl, String message) {
String tag;
if (mTag == null)
tag = TAG;
else
tag = mTag;
write(lvl, tag, message);
}
private void write(char lvl, String tag, String message) {
write(lvl, tag, message, null);
}
private void write(char lvl, String tag, String message, Throwable tr) {
if (tag == null) {
write(lvl, message);
return;
}
LogEntry log = new LogEntry(lvl, tag, Thread.currentThread().getName(), message, tr);
sendWriteMessage(log);
}
}
}