Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發 >> 關於android開發 >> ULog遠程日志——讓Android調試更加方便直觀

ULog遠程日志——讓Android調試更加方便直觀

編輯:關於android開發

ULog遠程日志——讓Android調試更加方便直觀


在發布U8SDK之後,使用U8SDK做SDK接入的同學,反饋比較多的一個問題就是調試困難。這其實不是U8SDK的毛病,而是Android開發的通病。做Android開發,我們通常都是結合logcat來調試,如果是原生的應用,有時我們還可以直接通過Debug斷點來調試,但是,做游戲開發,我們一般采用U3D,Cocos2dx游戲引擎開發之後,發布到對應的移動平台。所以,斷點調試就不太好實現了。很多時候,我們都是打印一些日志,然後在logcat中定位對應的日志,來查找錯誤信息。

之前也一直是使用logcat,但是logcat中日志太多,過濾功能雖然有,但是有時候,有些機型,打印的日志太多,你還沒有看清楚,日志就被各種系統日志給頂掉了。

而且,很多做游戲開發的同學,可能也是游戲快要做好了,要接各個平台的渠道SDK的時候,才開始接觸Android開發。導致對logcat如何正確使用,就更找不到門路。

所以,我想干脆在原有日志的基礎上,再加一個遠程日志打印,再接將日志輸出到網頁上,這樣日志方便搜索,也不用理會各種亂七八糟的系統日志。

然後,我們在U8SDK中將原有日志簡單封裝了下,讓日志打印可以支持遠程打印。遠程打印,顧名思義,我們需要一個服務器端,來接收這些日志,並輸出到網頁上。

首先想到的是,用java寫,部署到tomcat,但是想想,如此簡單的一個邏輯,用java真是有點浪費不說,還有點臃腫。最後,因為U8SDK打包工具用python寫的,那麼想就用python搭建一個吧,了解了下python中搭建一個web服務器端,有幾種框架可以使用,最終選擇了一個最最簡單的web.py框架,這個框架要實現我們這個功能,真是再合適不過了,輕量的不能再輕量了。

我們只需要實現兩個功能接口, 一個是上報日志的接口,一個是顯示日志的接口。上報日志,我們采用Http Post方式, 顯示日志,采用Http Get浏覽器中直接訪問。

為了讓不同級別的日志顯示區別開來,我們給不同級別的日志,配上不同的顏色。

客戶端上報的日志,先在內存中放著,網頁上我們啟動一段js代碼,每隔1秒鐘自動刷新一次,並將滾動位置,始終保持在最後。這樣,我們的一個簡單的日志收集和展示的web 服務器就好了,代碼片段:

import json
import web

web.config.debug = False

urls = (
        '/','index'
    )

localLogs = ""


class index:

    def GET(self):

        htmlFormat = "
%s <script type=\"text/javascript\">function myrefresh(){window.location.reload();window.scrollTo(0,document.body.scrollHeight);}setTimeout('myrefresh()',1000); </script>" global localLogs localLogs = localLogs.encode('gbk') return htmlFormat % localLogs def POST(self): inputs=web.input() content = inputs.get('log') if content.startswith('{') and content.endswith('}'): content = '[' + content + ']' logs = json.loads(content) for log in logs: if 'stack' not in log: log['stack'] = " " color = '#808080' if log['level'] == 'INFO': color = '#008000' elif log['level'] == 'WARNING': color = '#FFA500' elif log['level'] == 'ERROR': color = '#FF0000' strLog = ' %s %s: [%s] %s ' % (color, log['time'],log['level'], log['tag'], log['msg']) stacks = log['stack'].split('\n') strLog = strLog + (' ' % color) for s in stacks: strLog = strLog + (' %s ' % (s.strip())) strLog = strLog + ' ' global localLogs localLogs = localLogs + strLog return "" if __name__ == '__main__': app = web.application(urls, globals()) app.run()

上面這段代碼,就是我們這個日志服務器的全部代碼了,我們將這個文件保存為uconsole.py,然後,打開命令終端,運行:

python uconsole.py

這樣,就可以啟動該服務器了,默認監聽的端口是8080。如果該端口被占用了,會啟動失敗,那麼我們可以換一個端口

python uconsole.py 8082

這樣,我們就變成監聽8082這個端口了。你可以打開http:localhost:8082/ 你會發現網頁是空白的,但是每隔一秒網頁會自動刷新一下,說明當前服務器已經啟動好了,正在等待日志的到來。

接下來,我們就要設計下客戶端部分的日志框架,讓日志以Http Post的方式上報到我們這個日志服務器端。

我們打算將Android原有的日志(com.android.util.Log)進行一個簡單的封裝,因為我們僅僅要加一個遠程日志接口,所以,我們不希望把它弄得過於復雜。

我們定義一個ILog接口,將日志接口提取出來,這樣,我們將會有這個兩個實現,一個LocalLog,一個RemoteLog。LocalLog中,我們依然調用Android自帶的Log進行打印,RemoteLog中,我們將日志放到一個隊列中,每隔一定的間隔,就將存儲的日志一次性上報到日志服務器。
ILog接口:

public interface ILog {

    public void d(String tag, String msg);
    public void i(String tag, String msg);
    public void w(String tag, String msg);
    public void w(String tag, String msg, Throwable e);
    public void e(String tag, String msg);
    public void e(String tag, String msg, Throwable e);

    public void destory();
}

ULocalLog實現:

/**
 * 
 * Android本地日志輸出
 *
 */
public class ULocalLog implements ILog{

    @Override
    public void d(String tag, String msg) {
        Log.d(tag, msg);
    }

    @Override
    public void i(String tag, String msg) {
        Log.i(tag, msg);
    }

    @Override
    public void w(String tag, String msg) {
        Log.w(tag, msg);
    }

    @Override
    public void e(String tag, String msg) {
        Log.e(tag, msg);
    }

    @Override
    public void w(String tag, String msg, Throwable e) {
        Log.w(tag, msg, e);
    }

    @Override
    public void e(String tag, String msg, Throwable e) {
        Log.e(tag, msg, e);

    }

    @Override
    public void destory() {

    }

}

URemoteLog實現:

public class URemoteLog implements ILog{

    private URemoteLogPrinter printer;

    public URemoteLog(String url, int interval){
        printer = new URemoteLogPrinter(url, interval);
    }

    @Override
    public void d(String tag, String msg) {
        printer.print(new ULog(ULog.L_DEBUG, tag, msg));
    }

    @Override
    public void i(String tag, String msg) {
        printer.print(new ULog(ULog.L_INFO, tag, msg));
    }

    @Override
    public void w(String tag, String msg) {
        printer.print(new ULog(ULog.L_WARN, tag, msg));
    }

    @Override
    public void w(String tag, String msg, Throwable e) {
        printer.print(new ULog(ULog.L_WARN, tag, msg, e));
    }

    @Override
    public void e(String tag, String msg) {
        printer.print(new ULog(ULog.L_ERROR, tag, msg));
    }

    @Override
    public void e(String tag, String msg, Throwable e) {
        printer.print(new ULog(ULog.L_ERROR, tag, msg, e));
    }

    @Override
    public void destory() {
        printer.stop();
    }

}

遠程日志實現中,我們采用一個URemoteLogPrinter來臨時存儲日志,並定時傳到日志服務器,該類實現如下:

public class URemoteLogPrinter {

    private List logs;
    private String url;
    private int interval = 1000; //單位 毫秒

    private Timer timer;
    private boolean running;

    public URemoteLogPrinter(){

    }

    public URemoteLogPrinter(String remoteUrl, int interval){
        this.logs = Collections.synchronizedList(new ArrayList());
        this.url = remoteUrl;
        this.interval = interval;
    }

    public void print(ULog log){

        start();
        synchronized (logs) {
            logs.add(log);
        }
    }

    public void printImmediate(String url, ULog log){

        Map params = new HashMap();
        params.put("log", log.toJSON());
        U8HttpUtils.httpPost(url, params);
    }

    public List getAndClear(){
        synchronized (logs) {
            List all = new ArrayList(logs);
            logs.clear();
            return all;
        }
    }

    public void start(){
        if(running){
            return;
        }

        running = true;
        TimerTask task = new LogPrintTask();
        timer = new Timer(true);
        timer.scheduleAtFixedRate(task, 100, interval);
    }

    public void stop(){
        if(timer != null){
            timer.cancel();
        }
        running = false;
    }

    class LogPrintTask extends TimerTask{

        @Override
        public void run() {
            try{

                List logs = getAndClear();

                if(logs.size() > 0){
                    StringBuilder sb = new StringBuilder();
                    sb.append("[");
                    for(ULog log : logs){
                        sb.append(log.toJSON()).append(",");
                    }
                    sb.deleteCharAt(sb.length()-1).append("]");

                    Map params = new HashMap();
                    params.put("log", sb.toString());

                    U8HttpUtils.httpPost(url, params);

                }

            }catch(Exception e){
                e.printStackTrace();
                stop();
            }
        }

    }
}

這樣,我們整個日志封裝就可以,但是我們遠程日志中,有幾個參數我們需要設置下,比如,遠程打印的時間間隔,遠程日志服務器地址等參數。這些參數,我們後面放到AndroidManifest.xml中的meta-data中,同時,我們需要有一個調用的接口,給應用來調用日志。所以,我們封裝一個Log類:

public class Log{

    private static Log instance = new Log();

    private List logPrinters;
    private boolean isInited = false;

    private boolean enable = false;
    private String level = ULog.L_DEBUG;
    private boolean local = true;
    private boolean remote = true;
    private int remoteInterval = 1000;
    private String remoteUrl = "";

    private Log(){
        logPrinters = new ArrayList();
    }

    public static void d(String tag, String msg) {
        try{

            if(!ULog.L_DEBUG.equalsIgnoreCase(instance.level)){
                return;
            }

            for(ILog printer: instance.logPrinters){
                printer.d(tag, msg);
            }           

        }catch(Exception e){
            e.printStackTrace();
        }


    }

    public static void i(String tag, String msg) {
        try{

            if(!ULog.L_DEBUG.equalsIgnoreCase(instance.level) &&
                    !ULog.L_INFO.equalsIgnoreCase(instance.level)){
                return;
            }           

            for(ILog printer: instance.logPrinters){
                printer.i(tag, msg);
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    public static void w(String tag, String msg) {
        try{
            if(ULog.L_ERROR.equalsIgnoreCase(instance.level)){
                return;
            }           

            for(ILog printer: instance.logPrinters){
                printer.w(tag, msg);
            }           
        }catch(Exception e){
            e.printStackTrace();
        }
    }


    public static void w(String tag, String msg, Throwable e) {
        try{

            if(ULog.L_ERROR.equalsIgnoreCase(instance.level)){
                return;
            }           

            for(ILog printer: instance.logPrinters){
                printer.w(tag, msg, e);
            }

        }catch(Exception e2){
            e2.printStackTrace();
        }
    }


    public static void e(String tag, String msg) {
        try{
            for(ILog printer: instance.logPrinters){
                printer.e(tag, msg);
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }


    public static void e(String tag, String msg, Throwable e) {
        try{
            for(ILog printer: instance.logPrinters){
                printer.e(tag, msg, e);
            }
        }catch(Exception e2){
            e2.printStackTrace();
        }
    }


    /**
     * 在Application的attachBaseContext中調用
     * @param context
     */
    public static void init(Context context){

        try{

            if(instance.isInited){
                return;
            }

            instance.parseConfig(context);

            instance.logPrinters.clear();

            if(!instance.enable){
                android.util.Log.d("ULOG", "the log is not enabled.");
                return;
            }

            if(instance.local){
                instance.logPrinters.add(new ULocalLog());
            }

            if(instance.remote){
                instance.logPrinters.add(new URemoteLog(instance.remoteUrl, instance.remoteInterval));
            }

            Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {

                @Override
                public void uncaughtException(Thread t, final Throwable e) {

                    new Thread(new Runnable() {

                        @Override
                        public void run() {
                            try{
                                URemoteLogPrinter printer = new URemoteLogPrinter();
                                printer.printImmediate(instance.remoteUrl, new ULog(ULog.L_ERROR, "Crash", "Application Crashed!!!", e));

                            }catch(Exception e){
                                e.printStackTrace();
                            }finally{
                                System.exit(0);
                            }

                        }
                    }).start();

                    try {
                        Thread.sleep(500);

                    } catch (InterruptedException e1) {
                        e1.printStackTrace();
                    }


                }
            });

            instance.isInited = true;           

        }catch(Exception e){
            e.printStackTrace();
        }

    }


    /**
     * 在Application的onTerminate中調用銷毀
     */
    public static void destory(){

        try{
            if(instance.logPrinters != null){
                for(ILog printer : instance.logPrinters){
                    printer.destory();
                }
            }
        }catch(Exception e){
            e.printStackTrace();
        }


    }

    private void parseConfig(Context ctx){

        try{
            ApplicationInfo appInfo = ctx.getPackageManager().getApplicationInfo(ctx.getPackageName(), PackageManager.GET_META_DATA);
            if(appInfo != null && appInfo.metaData != null){

                if(appInfo.metaData.containsKey("ulog.enable")){
                    enable = appInfo.metaData.getBoolean("ulog.enable");
                }

                if(appInfo.metaData.containsKey("ulog.level")){
                    level = appInfo.metaData.getString("ulog.level");
                }   

                if(appInfo.metaData.containsKey("ulog.local")){
                    local = appInfo.metaData.getBoolean("ulog.local");
                }   

                if(appInfo.metaData.containsKey("ulog.remote")){
                    remote = appInfo.metaData.getBoolean("ulog.remote");
                }   

                if(appInfo.metaData.containsKey("ulog.remote_interval")){
                    remoteInterval = appInfo.metaData.getInt("ulog.remote_interval");
                }

                if(appInfo.metaData.containsKey("ulog.remote_url")){
                    remoteUrl = appInfo.metaData.getString("ulog.remote_url");
                }               

            }

        }catch(Exception e){
            e.printStackTrace();
        }       
    }   
}

除了日志打印的幾個接口,我們增加了兩個接口,一個是init,一個是destroy。我們需要在調用Log打印之前,調用init方法進行初始化,因為我們需要讀取配置參數,根據配置來設定對應的參數。同樣的,在應用退出的時候,我們需要調用destroy來回收資源。

一般init我們可以在Application的onCreate或者attachBaseContext中調用;destroy可以在Application的onTerminate中調用

一切准備好之後,我們還需要在應用的AndroidManifest.xml中增加幾個日志的配置參數:

<code class=" hljs xml">    <meta-data android:name="ulog.enable" android:value="true">  <!--{cke_protected}{C}%3C!%2D%2D%E6%98%AF%E5%90%A6%E5%BC%80%E5%90%AF%E6%97%A5%E5%BF%97%EF%BC%8C%E5%85%B3%E9%97%AD%E4%B9%8B%E5%90%8E%EF%BC%8C%E4%B8%8D%E4%BC%9A%E8%BE%93%E5%87%BA%E5%88%B0logcat%E4%B9%9F%E4%B8%8D%E4%BC%9A%E8%BE%93%E5%87%BA%E5%88%B0%E8%BF%9C%E7%A8%8B%2D%2D%3E-->
    <meta-data android:name="ulog.level" android:value="DEBUG">   <!--{cke_protected}{C}%3C!%2D%2D%E6%97%A5%E5%BF%97%E7%BA%A7%E5%88%AB(DEBUG%7CINFO%7CWARNING%7CERROR)%2D%2D%3E-->
    <meta-data android:name="ulog.local" android:value="true">    <!--{cke_protected}{C}%3C!%2D%2D%E6%98%AF%E5%90%A6%E5%9C%A8logcat%E4%B8%AD%E6%89%93%E5%8D%B0%2D%2D%3E-->
    <meta-data android:name="ulog.remote" android:value="true">   <!--{cke_protected}{C}%3C!%2D%2D%E6%98%AF%E5%90%A6%E8%BF%9C%E7%A8%8B%E6%89%93%E5%8D%B0%2D%2D%3E-->
    <meta-data android:name="ulog.remote_interval" android:value="500">   <!--{cke_protected}{C}%3C!%2D%2D%E8%BF%9C%E7%A8%8B%E6%89%93%E5%8D%B0%E6%97%B6%EF%BC%8C%E6%97%A5%E5%BF%97%E4%B8%8A%E6%8A%A5%E9%97%B4%E9%9A%94%EF%BC%8C%E5%8D%95%E4%BD%8D%E6%AF%AB%E7%A7%92%2D%2D%3E-->
    <meta-data android:name="ulog.remote_url" android:value="http://192.168.18.9:8080/">  <!--{cke_protected}{C}%3C!%2D%2D%E8%BF%9C%E7%A8%8B%E6%97%A5%E5%BF%97%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%9C%B0%E5%9D%80%EF%BC%8C%E5%B0%B1%E6%98%AFuconsole%E7%9B%91%E5%90%AC%E7%9A%84%E5%9C%B0%E5%9D%80%2D%2D%3E--></meta-data></meta-data></meta-data></meta-data></meta-data></meta-data></code>

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved