欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android 自定义Log 多模式

程序员文章站 2022-05-12 20:25:51
...

先上个效果图:

模板一:

Android 自定义Log 多模式

模板二:

Android 自定义Log 多模式
LOG常用的方法有以下5个:
Log.v() Log.d() Log.i() Log.w() Log.e() 。分别对应下图,除Assert
Android 自定义Log 多模式

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
    Android 自定义Log 多模式

有个Log的工具类:https://github.com/orhanobut/logger
Android 自定义Log 多模式
用法很简单:

//在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
Android 自定义Log 多模式

BuildConfig 里面是一些常量
Logger 一些具体的执行方法,调用LoggerPrinter 中的具体实现供客户端调用
Utils 工具类

下面仿照大佬的代码写个简单的吧

既然是简单的,就简单到底,就3个核心类(实际就一个):
Android 自定义Log 多模式

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/xslt}indent-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适配器的功能):
(作者完美使用了适配器模式+策略模式)
Android 自定义Log 多模式
即另外一种情况下使用到的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变的卡顿

相关标签: log 日志 logger