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 多模式的主要内容,如果未能解决你的问题,请参考以下文章

最强自定义PHP集成环境,系统缺失dll和vc也能正常运行

log4j2配置不会加载自定义模式转换器

log日志应用 自定义的log

Android-线程池

Android-线程池

自定义 Android EditText