Android 自定义Log 多模式
Posted 大雄童鞋
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 自定义Log 多模式相关的知识,希望对你有一定的参考价值。
先上个效果图:
模板一:
模板二:
LOG常用的方法有以下5个:
Log.v() Log.d() Log.i() Log.w() Log.e() 。分别对应下图,除Assert
1、Verbose 的调试颜色为黑色的,任何消息都会输出,这里的v代表verbose啰嗦的意思,平时使用就是Log.v(“”,”“);
2、Debug 的输出颜色是蓝色的,仅输出debug调试的意思,但他会输出上层的信息,过滤起来可以通过DDMS的Logcat标签来选择.
3、Info 的输出为绿色,一般提示性的消息information,它不会输出Log.v和Log.d的信息,但会显示i、w和e的信息
4、Warn 的意思为橙色,可以看作为warning警告,一般需要我们注意优化android代码,同时选择它后还会输出Log.e的信息。
5、Error 为红色,可以想到error错误,这里仅显示红色的错误信息,这些错误就需要我们认真的分析,查看栈的信息了。
6、Assert 表示断言失败后的错误消息,这类错误原本是不可能出现的错误,现在却出现了,是极其严重的错误类型。
Debug属于调试日志
其他五类Log的重要程度排序如下。
Assert > Error > Warn > Info > Verbose
推荐颜色:
- Verbose:#000000
- Debug :#0000FF
- Info:#008700
- Warn:#878700
- Error:#FF0000
Assert:#8F0005
Android Studio设置颜色步骤:
File ->Setting ->Editor->Color&Fonts->Android Logcat
有个Log的工具类:https://github.com/orhanobut/logger
用法很简单:
//在Application中初始化一下
Logger.addLogAdapter(new AndroidLogAdapter());
//就可以用了
Logger.v("verbose");
Logger.d("debug");
Logger.i("information");
Logger.w("warning");
Logger.e("error");
Logger.json("\\"a\\": \\"Hello\\", \\"b\\": \\"World\\"");
Logger.xml("<note>\\n" +
"<to>George</to>\\n" +
"<from>John</from>\\n" +
"<heading>Reminder</heading>\\n" +
"<body>Don't forget the meeting!</body>\\n" +
"</note>");
好炫酷,好想自己写一个,看了看源码,试试写个简单的
先分析下源码的执行流程:
Logger.v
-> LoggerPrinter.log
-> AndroidLogAdapter.log
-> PrettyFormatStrategy.log
-> PrettyFormatStrategy.logChunk
-> LogcatLogStrategy.log 至此打印结束
多日志打印即循环执行此过程,此项目很好的使用了策略模式和Builder模式。
其中:
Printer 的实现类有:LoggerPrinter
LogAdapter 的实现类有:AndroidLogAdapter、DiskLogAdapter
FormatStrategy 的实现类有:CsvFormatStrategy、PrettyFormatStrategy
LogStrategy 的实现类有:DiskLogStrategy、LogcatLogStrategy
BuildConfig 里面是一些常量
Logger 一些具体的执行方法,调用LoggerPrinter 中的具体实现供客户端调用
Utils 工具类
下面仿照大佬的代码写个简单的吧
既然是简单的,就简单到底,就3个核心类(实际就一个):
1、IPrinter.java(定义一些接口)
package com.zx.logs;
public interface IPrinter
void v(String message, Object... args);
void d(String message, Object... args);
void i(String message, Object... args);
void w(String message, Object... args);
void e(String message, Object... args);
void json(String json);
void xml(String xml);
void log(int priority, String message, Throwable throwable, Object... args);
// priority 优先顺序
// message 消息
// tag 日志中tag,相当于Logcat Filter
// throwable 子线程中打印日志,提取发生行数
// args String格式化字符串
void log(int priority, String message, String tag, Throwable throwable, Object... args);
2、Logs.java(暴露出来方法)
package com.zx.logs;
public class Logs
private static IPrinter iPrinter = new LogsPrinter();
public static void v(String message, Object... args)
iPrinter.v(message, args);
public static void d(String message, Object... args)
iPrinter.d(message, args);
public static void i(String message, Object... args)
iPrinter.i(message, args);
public static void w(String message, Object... args)
iPrinter.w(message, args);
public static void e(String message, Object... args)
iPrinter.e(message, args);
public static void log(int priority, String tag, String message, Throwable throwable)
iPrinter.log(priority, tag, message, throwable);
public static void json(String json)
iPrinter.json(json);
public static void xml(String xml)
iPrinter.xml(xml);
3、LogsPrinter.java(大部分处理都在这里了)
package com.zx.logs;
import android.text.TextUtils;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.UnknownHostException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
public class LogsPrinter implements IPrinter
//Android对日志项的最大限制是4076字节,所以4000个字节作为块的大小从默认的字符集
private static final int CHUNK_SIZE = 4000;
//最小堆栈
private static final int MIN_STACK_OFFSET = 5;
public static final int VERBOSE = 2;
public static final int DEBUG = 3;
public static final int INFO = 4;
public static final int WARN = 5;
public static final int ERROR = 6;
public static final int ASSERT = 7;
private static final int JSON_INDENT = 2;
private String tag = "LogsPrinter";
Pattern mPattern = new Pattern();
public void setTag(String tag)
this.tag = tag;
@Override
public void v(String message, Object... args)
log(VERBOSE, message, args);
@Override
public void d(String message, Object... args)
log(DEBUG, message, args);
@Override
public void i(String message, Object... args)
log(INFO, message, args);
@Override
public void w(String message, Object... args)
log(WARN, message, args);
@Override
public void e(String message, Object... args)
log(ERROR, message, args);
@Override
public void json(String json)
if (TextUtils.isEmpty(json))
d("Empty/Null json content");
return;
try
json = json.trim();
if (json.startsWith(""))
JSONObject jsonObject = new JSONObject(json);
String message = jsonObject.toString(JSON_INDENT);
d(message);
return;
if (json.startsWith("["))
JSONArray jsonArray = new JSONArray(json);
String message = jsonArray.toString(JSON_INDENT);
d(message);
return;
e("Invalid Json");
catch (JSONException e)
e("Invalid Json");
@Override
public void xml(String xml)
if (TextUtils.isEmpty(xml))
d("Empty/Null xml content");
return;
try
Source xmlInput = new StreamSource(new StringReader(xml));
StreamResult xmlOutput = new StreamResult(new StringWriter());
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("http://xml.apache.org/xsltindent-amount", "2");
transformer.transform(xmlInput, xmlOutput);
d(xmlOutput.getWriter().toString().replaceFirst(">", ">\\n"));
catch (TransformerException e)
e("Invalid xml");
private String getStackTraceString(Throwable tr)
if (tr == null)
return "";
// This is to reduce the amount of log spew that apps do in the non-error
// condition of the network being unavailable.
Throwable t = tr;
while (t != null)
if (t instanceof UnknownHostException)
return "";
t = t.getCause();
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
tr.printStackTrace(pw);
pw.flush();
return sw.toString();
private synchronized void log(int priority, String message, Object... args)
log(priority, message, null, args);
@Override
public synchronized void log(int priority, String message, Throwable throwable, Object... args)
message = configMessage(message, throwable, args);
logNext(priority, tag, message);
@Override
public synchronized void log(int priority, String message, String tag, Throwable throwable, Object... args)
message = configMessage(message, throwable, args);
logNext(priority, tag, message);
private String configMessage(String message, Throwable throwable, Object... args)
message = createMessage(message, args);
if (throwable != null && message != null)
message += " : " + getStackTraceString(throwable);
if (throwable != null && message == null)
message = getStackTraceString(throwable);
return message;
//大于0就显示打印日志位置
int methodCount = 2;
//是否显示线程信息 Thread: main(主线程中打印)或Thread: Thread-4(子线程中打印)
boolean showThreadInfo = true;
int methodOffset = 0;
private synchronized void logNext(int priority, String tag, String message)
logTopBorder(priority, tag);
logHeaderContent(priority, tag, methodCount);
//得到系统的默认字符集的信息字节(Android是UTF-8)
byte[] bytes = message.getBytes();
int length = bytes.length;
if (length <= CHUNK_SIZE)
if (methodCount > 0)
logDivider(priority, tag);
logContent(priority, tag, message);
logBottomBorder(priority, tag);
return;
if (methodCount > 0)
logDivider(priority, tag);
for (int i = 0; i < length; i += CHUNK_SIZE)
int count = Math.min(length - i, CHUNK_SIZE);
//create a new String with system's default charset (which is UTF-8 for Android)
logContent(priority, tag, new String(bytes, i, count));
logBottomBorder(priority, tag);
//日志--顶部边框
private void logTopBorder(int logType, String tag)
logChunk(logType, tag, mPattern.getTopBorder());
//日志--底部边框
private void logBottomBorder(int logType, String tag)
logChunk(logType, tag, mPattern.getBottomBorder());
//日志--分割线
private void logDivider(int logType, String tag)
logChunk(logType, tag, mPattern.getMiddleBorder());
@SuppressWarnings("StringBufferReplaceableByString")
private void logHeaderContent(int logType, String tag, int methodCount)
//获取当前线程状态
StackTraceElement[] trace = Thread.currentThread().getStackTrace();
if (showThreadInfo)
logChunk(logType, tag, mPattern.getHorizontalLine() + " Thread: " + Thread.currentThread().getName());
logDivider(logType, tag);
String level = "";
int stackOffset = getStackOffset(trace) + methodOffset;
//corresponding method count with the current stack may exceeds the stack trace. Trims the count
if (methodCount + stackOffset > trace.length)
methodCount = trace.length - stackOffset - 1;
for (int i = methodCount; i > 0; i--)
int stackIndex = i + stackOffset;
if (stackIndex >= trace.length)
continue;
StringBuilder builder = new StringBuilder();
builder.append(mPattern.getHorizontalLine())
.append(' ')
.append(level)
.append(getSimpleClassName(trace[stackIndex].getClassName()))
.append(".")
.append(trace[stackIndex].getMethodName())
.append(" ")
.append(" (")
.append(trace[stackIndex].getFileName())
.append(":")
.append(trace[stackIndex].getLineNumber())
.append(")");
level += " ";
logChunk(logType, tag, builder.toString());
//日志--内容
private void logContent(int logType, String tag, String chunk)
String[] lines = chunk.split(System.getProperty("line.separator"));
for (String line : lines)
logChunk(logType, tag, mPattern.getHorizontalLine() + " " + line);
private void logChunk(int priority, String tag, String chunk)
Log.println(priority, tag, chunk);
//确定堆栈跟踪的起始索引
private int getStackOffset(StackTraceElement[] trace)
for (int i = MIN_STACK_OFFSET; i < trace.length; i++)
StackTraceElement e = trace[i];
String name = e.getClassName();
if (!name.equals(LogsPrinter.class.getName()) && !name.equals(Logs.class.getName()))
return --i;
return -1;
//截取最后一次出现的位置的下一个
private String getSimpleClassName(String name)
int lastIndex = name.lastIndexOf(".");
return name.substring(lastIndex + 1);
//字符串格式化
private String createMessage(String message, Object... args)
return args == null || args.length == 0 ? message : String.format(message, args);
4、Pattern.java(一个模式选择类)
package com.zx.logs;
public class Pattern
private static final int SINGLE_LINE = 1;
private static final int DOUBLE_LINE = 2;
//模式切换
private int type = SINGLE_LINE;
private char topLeftCorner;
private char bottomLeftCorner;
private char middleCorner;
private char horizontalLine;
private String doubleDivider;
private String singleDivider;
private String topBorder;
private String bottomBorder;
private String middleBorder;
private char getChar(char value1, char value2)
char corner = value1;
switch (type)
case SINGLE_LINE:
corner = value1;
break;
case DOUBLE_LINE:
corner = value2;
break;
return corner;
private String getString(String value1, String value2)
String corner = value1;
switch (type)
case SINGLE_LINE:
corner = value1;
break;
case DOUBLE_LINE:
corner = value2;
break;
return corner;
public char getTopLeftCorner()
topLeftCorner = getChar(SingleLine.TOP_LEFT_CORNER, DoubleLine.TOP_LEFT_CORNER);
return topLeftCorner;
public char getBottomLeftCorner()
bottomLeftCorner = getChar(SingleLine.BOTTOM_LEFT_CORNER, DoubleLine.BOTTOM_LEFT_CORNER);
return bottomLeftCorner;
public char getMiddleCorner()
middleCorner = getChar(SingleLine.MIDDLE_CORNER, DoubleLine.MIDDLE_CORNER);
return middleCorner;
public char getHorizontalLine()
horizontalLine = getChar(SingleLine.HORIZONTAL_LINE, DoubleLine.HORIZONTAL_LINE);
return horizontalLine;
public String getDoubleDivider()
doubleDivider = getString(SingleLine.DOUBLE_DIVIDER, DoubleLine.DOUBLE_DIVIDER);
return doubleDivider;
public String getSingleDivider()
singleDivider = getString(SingleLine.SINGLE_DIVIDER, DoubleLine.SINGLE_DIVIDER);
return singleDivider;
public String getTopBorder()
topBorder = getString(SingleLine.TOP_BORDER, DoubleLine.TOP_BORDER);
return topBorder;
public String getBottomBorder()
bottomBorder = getString(SingleLine.BOTTOM_BORDER, DoubleLine.BOTTOM_BORDER);
return bottomBorder;
public String getMiddleBorder()
middleBorder = getString(SingleLine.MIDDLE_BORDER, DoubleLine.MIDDLE_BORDER);
return middleBorder;
static class SingleLine
private static final char TOP_LEFT_CORNER = '┌';
private static final char BOTTOM_LEFT_CORNER = '└';
private static final char MIDDLE_CORNER = '├';
private static final char HORIZONTAL_LINE = '│';
private static final String DOUBLE_DIVIDER = "────────────────────────────────────────────────────────";
private static final String SINGLE_DIVIDER = "┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄";
private static final String TOP_BORDER = TOP_LEFT_CORNER + DOUBLE_DIVIDER + DOUBLE_DIVIDER;
private static final String BOTTOM_BORDER = BOTTOM_LEFT_CORNER + DOUBLE_DIVIDER + DOUBLE_DIVIDER;
private static final String MIDDLE_BORDER = MIDDLE_CORNER + SINGLE_DIVIDER + SINGLE_DIVIDER;
static class DoubleLine
private static final char TOP_LEFT_CORNER = '╔';
private static final char BOTTOM_LEFT_CORNER = '╚';
private static final char MIDDLE_CORNER = '╟';
private static final char HORIZONTAL_LINE = '║';
private static final String DOUBLE_DIVIDER = "════════════════════════════════════════════";
private static final String SINGLE_DIVIDER = "────────────────────────────────────────────";
private static final String TOP_BORDER = TOP_LEFT_CORNER + DOUBLE_DIVIDER + DOUBLE_DIVIDER;
private static final String BOTTOM_BORDER = BOTTOM_LEFT_CORNER + DOUBLE_DIVIDER + DOUBLE_DIVIDER;
private static final String MIDDLE_BORDER = MIDDLE_CORNER + SINGLE_DIVIDER + SINGLE_DIVIDER;
客户端使用(比较简单,无需初始化,直接使用):
public class MainActivity extends AppCompatActivity
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
public void log(View view)
doLog();
new Thread(new Runnable()
@Override
public void run()
doLog();
).start();
private void doLog()
Logs.v("vvvvvvvvvvvvvvvvvvvvvvv");
Logs.d("dddddddddddddddddddddddddd");
Logs.i("iiiiiiiiiiiiiiiiiiiiiiiiiiiii");
Logs.w("wwwwwwwwwwwwwwwwwwwwwwwwwwwwwww");
Logs.e("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeee");
Logs.json("\\"a\\": \\"Hello\\", \\"b\\": \\"World\\"");
Logs.xml("<note>\\n" +
"<to>George</to>\\n" +
"<from>John</from>\\n" +
"<heading>Reminder</heading>\\n" +
"<body>Don't forget the meeting!</body>\\n" +
"</note>");
大功告成啦!
附:
大佬的源码中有3个类我没用到(即只提取了AndroidLogAdapter适配器的功能):
(作者完美使用了适配器模式+策略模式)
即另外一种情况下使用到的Log打印,因为在使用Logger打印的时候,需要在Application中进行适配器的设置:
//正常情况打印(默认选此配置)
Logger.addLogAdapter(new AndroidLogAdapter());
//保存日志到本地
Logger.addLogAdapter(new DiskLogAdapter());
从 Logger.v-> LoggerPrinter.log 之后,会根据适配器的设置选择使用AndroidLogAdapter或者DiskLogAdapter->DiskLogStrategy->CsvFormatStrategy
CsvFormatStrategy:保存日志到本地sd卡中是在根目录下一个logger文件,是.cvs文件
源码下载:
http://download.csdn.net/download/u013277740/10244736
最后啰嗦一句,重点提醒
Log打印框架都仅仅适用于调试(Debug)模式,如果要发布,千万要关掉Log打印,至于怎么关闭方法很多:Log框架自带的方法关闭、代码注释、混淆等等。。。
正式发布如果不禁掉,返回的json数据会进行格式转换,很消耗时间的,如果打印的日志在主线程,则会使APP变的卡顿
以上是关于Android 自定义Log 多模式的主要内容,如果未能解决你的问题,请参考以下文章