ULog远程日志——让Android调试更加方便直观
2016-02-02 12:02
411 查看
在发布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 服务器就好了,代码片段:
上面这段代码,就是我们这个日志服务器的全部代码了,我们将这个文件保存为uconsole.py,然后,打开命令终端,运行:
这样,就可以启动该服务器了,默认监听的端口是8080。如果该端口被占用了,会启动失败,那么我们可以换一个端口
这样,我们就变成监听8082这个端口了。你可以打开http:localhost:8082/ 你会发现网页是空白的,但是每隔一秒网页会自动刷新一下,说明当前服务器已经启动好了,正在等待日志的到来。
接下来,我们就要设计下客户端部分的日志框架,让日志以Http Post的方式上报到我们这个日志服务器端。
我们打算将Android原有的日志(com.android.util.Log)进行一个简单的封装,因为我们仅仅要加一个远程日志接口,所以,我们不希望把它弄得过于复杂。
我们定义一个ILog接口,将日志接口提取出来,这样,我们将会有这个两个实现,一个LocalLog,一个RemoteLog。LocalLog中,我们依然调用Android自带的Log进行打印,RemoteLog中,我们将日志放到一个队列中,每隔一定的间隔,就将存储的日志一次性上报到日志服务器。
ILog接口:
ULocalLog实现:
URemoteLog实现:
远程日志实现中,我们采用一个URemoteLogPrinter来临时存储日志,并定时传到日志服务器,该类实现如下:
这样,我们整个日志封装就可以,但是我们远程日志中,有几个参数我们需要设置下,比如,远程打印的时间间隔,远程日志服务器地址等参数。这些参数,我们后面放到AndroidManifest.xml中的meta-data中,同时,我们需要有一个调用的接口,给应用来调用日志。所以,我们封装一个Log类:
除了日志打印的几个接口,我们增加了两个接口,一个是init,一个是destroy。我们需要在调用Log打印之前,调用init方法进行初始化,因为我们需要读取配置参数,根据配置来设定对应的参数。同样的,在应用退出的时候,我们需要调用destroy来回收资源。
一般init我们可以在Application的onCreate或者attachBaseContext中调用;destroy可以在Application的onTerminate中调用
一切准备好之后,我们还需要在应用的AndroidManifest.xml中增加几个日志的配置参数:
整个日志框架和服务器代码,已经放在github上,有需要的同学可以直接使用:
ULog源码地址
之前也一直是使用logcat,但是logcat中日志太多,过滤功能虽然有,但是有时候,有些机型,打印的日志太多,你还没有看清楚,日志就被各种系统日志给顶掉了。
而且,很多做游戏开发的同学,可能也是游戏快要做好了,要接各个平台的渠道SDK的时候,才开始接触Android开发。导致对logcat如何正确使用,就更找不到门路。
所以,我想干脆在原有日志的基础上,再加一个远程日志打印,再接将日志输出到网页上,这样日志方便搜索,也不用理会各种乱七八糟的系统日志。
然后,我们在U8SDK中将原有日志简单封装了下,让日志打印可以支持远程打印。远程打印,顾名思义,我们需要一个服务器端,来接收这些日志,并输出到网页上。
首先想到的是,用java写,部署到tomcat,但是想想,如此简单的一个逻辑,用java真是有点浪费不说,还有点臃肿。最后,因为U8SDK打包工具用python写的,那么想就用python搭建一个吧,了解了下python中搭建一个web服务器端,有几种框架可以使用,最终选择了一个最最简单的web.py框架,这个框架要实现我们这个功能,真是再合适不过了,轻量的不能再轻量了。
我们只需要实现两个功能接口, 一个是上报日志的接口,一个是显示日志的接口。上报日志,我们采用Http Post方式, 显示日志,采用Http Get浏览器中直接访问。
为了让不同级别的日志显示区别开来,我们给不同级别的日志,配上不同的颜色。
客户端上报的日志,先在内存中放着,网页上我们启动一段js代码,每隔1秒钟自动刷新一次,并将滚动位置,始终保持在最后。这样,我们的一个简单的日志收集和展示的web 服务器就好了,代码片段:
[code]import json import web web.config.debug = False urls = ( '/','index' ) localLogs = "" class index: def GET(self): htmlFormat = "<html><head><title></title></head><body>%s <script type=\"text/javascript\">function myrefresh(){window.location.reload();window.scrollTo(0,document.body.scrollHeight);}setTimeout('myrefresh()',1000); </script></body></html>" 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 = '<div style="color:%s">%s %s: [%s] %s </div>' % (color, log['time'],log['level'], log['tag'], log['msg']) stacks = log['stack'].split('\n') strLog = strLog + ('<div color="%s">' % color) for s in stacks: strLog = strLog + ('<div>%s</div>' % (s.strip())) strLog = strLog + '</div>' global localLogs localLogs = localLogs + strLog return "" if __name__ == '__main__': app = web.application(urls, globals()) app.run()
上面这段代码,就是我们这个日志服务器的全部代码了,我们将这个文件保存为uconsole.py,然后,打开命令终端,运行:
[code]python uconsole.py
这样,就可以启动该服务器了,默认监听的端口是8080。如果该端口被占用了,会启动失败,那么我们可以换一个端口
[code]python uconsole.py 8082
这样,我们就变成监听8082这个端口了。你可以打开http:localhost:8082/ 你会发现网页是空白的,但是每隔一秒网页会自动刷新一下,说明当前服务器已经启动好了,正在等待日志的到来。
接下来,我们就要设计下客户端部分的日志框架,让日志以Http Post的方式上报到我们这个日志服务器端。
我们打算将Android原有的日志(com.android.util.Log)进行一个简单的封装,因为我们仅仅要加一个远程日志接口,所以,我们不希望把它弄得过于复杂。
我们定义一个ILog接口,将日志接口提取出来,这样,我们将会有这个两个实现,一个LocalLog,一个RemoteLog。LocalLog中,我们依然调用Android自带的Log进行打印,RemoteLog中,我们将日志放到一个队列中,每隔一定的间隔,就将存储的日志一次性上报到日志服务器。
ILog接口:
[code]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实现:
[code]/** * * 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实现:
[code]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来临时存储日志,并定时传到日志服务器,该类实现如下:
[code]public class URemoteLogPrinter { private List<ULog> 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<ULog>()); 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<String, String> params = new HashMap<String,String>(); params.put("log", log.toJSON()); U8HttpUtils.httpPost(url, params); } public List<ULog> getAndClear(){ synchronized (logs) { List<ULog> all = new ArrayList<ULog>(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<ULog> 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<String, String> params = new HashMap<String,String>(); params.put("log", sb.toString()); U8HttpUtils.httpPost(url, params); } }catch(Exception e){ e.printStackTrace(); stop(); } } } }
这样,我们整个日志封装就可以,但是我们远程日志中,有几个参数我们需要设置下,比如,远程打印的时间间隔,远程日志服务器地址等参数。这些参数,我们后面放到AndroidManifest.xml中的meta-data中,同时,我们需要有一个调用的接口,给应用来调用日志。所以,我们封装一个Log类:
[code]public class Log{ private static Log instance = new Log(); private List<ILog> 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<ILog>(); } 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] <meta-data android:name="ulog.enable" android:value="true" /> <!--是否开启日志,关闭之后,不会输出到logcat也不会输出到远程--> <meta-data android:name="ulog.level" android:value="DEBUG" /> <!--日志级别(DEBUG|INFO|WARNING|ERROR)--> <meta-data android:name="ulog.local" android:value="true" /> <!--是否在logcat中打印--> <meta-data android:name="ulog.remote" android:value="true" /> <!--是否远程打印--> <meta-data android:name="ulog.remote_interval" android:value="500" /> <!--远程打印时,日志上报间隔,单位毫秒--> <meta-data android:name="ulog.remote_url" android:value="http://192.168.18.9:8080/" /> <!--远程日志服务器地址,就是uconsole监听的地址-->
整个日志框架和服务器代码,已经放在github上,有需要的同学可以直接使用:
ULog源码地址
相关文章推荐
- Android学习-JAVA基础 (三)
- Android SDK 体系介绍
- Android代码资源的国际化
- Android事件机制全然解析
- Android-注解处理器
- 优化android studio编译的apk大小
- android eclipse关联源码,以及源码(代码)以及jar查看软件
- Android-动态权限-解决方案
- [整理]Android屏幕适配(不同的屏幕分辨率和尺寸)
- android 布局优化标签<include/>、<merge />、<ViewStub />
- Android Studio 1.5错误
- Android起航系列第五章:片段Fragment
- Android学习-JAVA基础 (二)
- Android部分机型的动态权限的获取以及处理
- Android应用中OOM问题剖析和解决方案
- Android Studio实现代码混淆
- Android支付宝支付步骤简述
- 【Android错误总结】错误java.lang.NoSuchMethodError: android.ImageView.setBackground
- android task栈和activity的关系(Task/launchMode)
- Android SDK Manager下载问题