DKSocketLog 实现终端输出设备日志

前言

程序员调试程序无非就两种,打印日志和断点调试。大部分情况下,这是足够的,但有些特殊情况,比如 iPhone 接着外接设备比如 OTG,这个时候就没办法用数据线连电脑了,自然也就无法断点调试,只能靠日志。而日志也没办法直接显示在 Xcode 上,只能用保存文件的方式将日志文件保存到手机,再去找到日志去看。非常麻烦,于是想了一下用 webSocket 与服务器通讯,就能以一种更直接方便的方式来看日志。

准备工作

Node.JS

Node.js 官网 安装 nodejs 环境,安装完成后敲node -v,如果 log 输出了版本号,说明 node 环境已经配好了。

1
2
$ node -v
v4.4.1

socket.io

先贴上 Socket.io 官网Socket.io GitHub

安装 socket.io

1
$ sudo npm install 'socket.io'@0.9

注意,要指定版本号为0.9,用1.0以上的1.x版本有问题,因为 Client(iOS)端的 socket.io 是0.9版本,不支持1.x,如果使用0.9版本的客户端给1.x版本的服务端发消息,会出现400报错:

1
2
3
4
{
"code":0,
"message":"Transport unknown"
}

解决办法就是 fall back,卸载重装

1
2
$ sudo npm uninstall 'socket.io'
$ sudo npm install 'socket.io'@0.9

app.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var io = require('socket.io').listen(8888);
io.sockets.on('connection', function (socket) {
socket.on('message', function (data) {
console.info(data);
});
});
io.configure('development', function(){
io.enable('browser client etag');
io.set('log level', 1);
});
io.configure('release', function(){
io.set('transports', ['websocket']);
});

其中 8888 为 node 监听的端口,可根据需求更改。
log level 设为 1 是为了不输出 debug 的 log。

保存这个 js 文件并用 node 命令执行,(建议保存到项目目录下,后期可能会做一些自动化脚本来执行。)

1
2
$ node app.js
info - socket.io started

如果看到info - socket.io started 就说明,服务端已经启动完毕。当然,可能没有那么一帆风顺,可能有几个报错:

Error: Cannot find module 'express' : 缺少插件,将 expressejs 都安装即可。

1
2
$ sudo npm install 'express'
$ sudo npm install 'ejs'

warn - error raised: Error: listen EADDRINUSE :::8888 : 端口占用,要么监听其它端口,要么将端口占用的程序杀掉。

查看 PID

1
2
3
$ sudo lsof -i tcp:8888
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
node 1371 bingo 11u IPv6 0xdaf792eabddb3d4d 0t0 TCP *:ddi-tcp-1 (LISTEN)

杀死进程

1
$ sudo kill -9 1371

再试着执行 nodejs 即可

1
2
$ node app.js
info - socket.io started

如果还有其它问题,自行网上查找资料解决,后面我会考虑将端口占用的问题也用脚本自动化解决。

以上都是准备工作,其实也不多,只需要准备一次环境,以后就不需要再搞服务端了。

使用方法

将源代码中的 DKSocketLog 文件夹整个添加到 iOS 项目中,并在 PCH 文件中引入头文件

1
#import "DKSocketLog.h"

项目需要在 Build Phases 添加 libicucore.tbd,否则会报如下错误。

1
2
3
4
5
Undefined symbols for architecture arm64:
"_utf8_nextCharSafeBody", referenced from:
_validate_dispatch_data_partial_string in SRWebSocket.o
ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

找一个地方写 DKSocketLog 的配置,可以写在 AppDelegate 或者其它地方,根据项目架构自行安排。Demo 中我新建了一个类 DKLogConfig,并在 load 方法中进行配置。

1
2
3
4
5
6
+ (void)load
{
// [[DKSocketLog sharedInstance] setDebugMode:YES];
// [[DKSocketLog sharedInstance] setSecureEnable:YES];
[[DKSocketLog sharedInstance] setupHostname:@"192.168.1.89" port:8888];
}
方法 说明
setDebugMode 是否开启DEBUG模式,默认关闭
setSecureEnable 是否使用安全传输协议(HTTPS)
setupHostname:port 设置主机名和端口,模拟器可以不设置,默认是 localhost;真机要确保与电脑在同一个网段下,局域网下或使用 WIFI

使用 DKLog(…) 代替 NSLog(…)

1
DKLog(@"打印100内的随机数:%d", arc4random_uniform(100));

如果没问题,就可以看到 Xcode 的控制台与终端同步输出 log

1
2017-03-02 15:36:40.705 -[ViewController logBtnClick] 第25行: 打印100内的随机数:14

补充

或许会有疑问,真实环境下会不会浪费带宽,每次都尝试链接会不会消耗性能?

可以放心,这种 log 只在 DEBUG 包下有效,发布 Release 包什么操作也没有,详情可以看 DKLog 宏的定义。

1
2
3
4
5
6
7
8
9
10
11
#ifdef DEBUG
#define DKLog(...) \
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; \
[dateFormatter setDateFormat:@"yyyy-MM-dd hh:mm:ss.SSS"]; \
NSString *dateStr = [dateFormatter stringFromDate:[NSDate date]]; \
NSString *logStr = [NSString stringWithFormat:@"%@ %s 第%d行: %@",dateStr, __func__, __LINE__, [NSString stringWithFormat:__VA_ARGS__]]; \
printf("%s\n", [logStr UTF8String]);\
[[DKSocketLog sharedInstance].socketIO sendMessage:logStr];
#else
#define DKLog(...)
#endif

后话

一个简单的终端监听日志并输出的功能就这样集成实现了,当然还是显得有些简陋,可扩展的功能还有很多,比如很多东西都可以封成一个 Shell 脚本,除了上面说到的执行 node 命令,杀死占用端口的进程外,还可以将日志写到电脑上,在项目根目录下建一个 log 文件夹并将日志都保存到 log 文件夹下,并按日期分文件,好像挺好玩的样子,还有同事说既然都用到了 webSocket,为什么不用网页来展示日志?可以更美观,好像也有道理,这样就不用开着终端了,node 在后台跑着进程就行,再写一个守护进程的保证它不死掉等等,后面有时间慢慢扩展。

  • 本文作者: Bingo
  • 本文链接: https://blog.bingo.ren/26.html
  • 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!