美文网首页
web实时输出iOS设备log

web实时输出iOS设备log

作者: chensifang | 来源:发表于2017-10-21 17:30 被阅读0次

web实时打印iOS设备log

项目进程中, 测试人员或者开发工程师在测试机没有连接Xcode的调试状况下如果出了问题需要debug, 需要插上线连接Xcode重新run, 查看相应的log, 耗时且问题不一定能稳定复现, 现在介绍一种在网页上能实时查看iOS设备log的方法

1. 截取logString

通常工程都会自定义log, 能够取到log的具体String.
这里创建一个Log类实现:

  • log.h代码如下:

#import <Foundation/Foundation.h>

#if DEBUG
#define PKLog(frmt,...) [Log logWithLine:__LINE__ method:NSStringFromSelector(_cmd) class:self.class time:[NSDate date] format:[NSString stringWithFormat:frmt, ## __VA_ARGS__]]
#else
#define PKLog(frmt,...)
#endif


@interface Log : NSObject
@property (nonatomic, strong) NSMutableArray *logs;
+ (instancetype)shared;

+ (void)logWithLine:(NSUInteger)line
             method:(NSString *)methodName
              class:(Class)className
               time:(NSDate *)timeStr
             format:(NSString *)format;

@end
  • Log.m代码如下:
#import "Log.h"
#import "PKHttpServerLogger.h"

@implementation Log
+ (instancetype)shared {
    static dispatch_once_t onceToken;
    static Log *shared;
    dispatch_once(&onceToken, ^{
        shared = [[Log alloc] init];
    });
    return shared;
}

- (NSMutableArray *)logs {
    if (!_logs) {
        _logs = [NSMutableArray array];
    }
    return _logs;
}

+ (void)logWithLine:(NSUInteger)line
             method:(NSString *)methodName
              class:(Class)className
               time:(NSDate *)timeStr
             format:(NSString *)format {
    
    NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
    NSInteger unitFlags = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitWeekday |
    NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond | NSCalendarUnitNanosecond;
    NSDateComponents *comps  = [calendar components:unitFlags fromDate:[NSDate date]];
    NSString *time = [NSString stringWithFormat:@"%ld/%ld,%ld:%ld:%ld:%@", (long)comps.month, (long)comps.day, (long)comps.hour, (long)comps.minute, (long)comps.second, [[NSString stringWithFormat:@"%ld", (long)comps.nanosecond] substringToIndex:2]];
    
    NSString *logStr = [NSString stringWithFormat:@"[%@][%@ %@] %tu行: ● %@.\n", time, className,methodName,line,format];
    [[Log shared].logs addObject:logStr];
    fprintf(stderr,"[%s][%s %s] %tu行: ● %s.\n", [time UTF8String], [NSStringFromClass(className) UTF8String],[methodName UTF8String],line,[format UTF8String]);
}

定义PKLog宏截取logStr, 可以自定义log格式, 且最终用fprintf输出比NSLog效率高, NSLog底层会将log写入系统文件, 影响效率.
截取到logStr以后将其添加到单例的logs数组中, 供后面用.

2. 创建一个Socket保证手机和网页能实时通信

这里我使用一个三方框架: GCDWebServer
集成方式:
在Podfile中添加pod 'GCDWebServer', 执行$pod install命令

3. 将logString通过'GCDWebServer'输出到网页

创建一个PKHttpServerLogger

  • PKHttpServerLogger.h代码如下
#import <Foundation/Foundation.h>
@interface PKHttpServerLogger : NSObject

+ (instancetype)shared;
- (void)startServer;
- (void)stopServer;

@end

一个单例方法, 开启服务和结束服务方法.

  • PKHttpServerLogger.m代码如下
#import "PKHttpServerLogger.h"
#import "GCDWebServer.h"
#import "GCDWebServerDataResponse.h"
#import "Log.h"
#define kMinRefreshDelay 500  // In milliseconds

@interface PKHttpServerLogger ()
@property (nonatomic,strong) GCDWebServer* webServer;
@end
@implementation PKHttpServerLogger

+ (instancetype)shared {
    static dispatch_once_t onceToken;
    static PKHttpServerLogger *shared;
    dispatch_once(&onceToken, ^{
        shared = [PKHttpServerLogger new];
    });
    return shared;
}


- (GCDWebServer *)webServer {
    if (!_webServer) {
        _webServer = [[GCDWebServer alloc] init];
        __weak __typeof__(self) weakSelf = self;
        // Add a handler to respond to GET requests on any URL
        [_webServer addDefaultHandlerForMethod:@"GET"
                                  requestClass:[GCDWebServerRequest class]
                                  processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
                                      return [weakSelf createResponseBody:request];
                                      
                                      
                                  }];
        
        
        NSLog(@"Visit %@ in your web browser", _webServer.serverURL);
        
    }
    return _webServer;
}
- (void)startServer{
    // Use convenience method that runs server on port 8079
    // until SIGINT (Ctrl-C in Terminal) or SIGTERM is received
    [self.webServer startWithPort:8079 bonjourName:nil];
    
}

- (void)stopServer {
    [_webServer stop];
    _webServer = nil;
}


- (GCDWebServerDataResponse *)createResponseBody :(GCDWebServerRequest* )request{
    GCDWebServerDataResponse *response = nil;
    
    NSString* path = request.path;
    NSDictionary* query = request.query;
    //NSLog(@"path = %@,query = %@",path,query);
    NSMutableString* string;
    if ([path isEqualToString:@"/"]) {
        string = [[NSMutableString alloc] init];
        [string appendString:@"<!DOCTYPE html><html lang=\"en\">"];
        [string appendString:@"<head><meta charset=\"utf-8\"></head>"];
        [string appendFormat:@"<title>%s[%i]</title>", getprogname(), getpid()];
        [string appendString:@"<style>\
         body {\n\
         margin: 0px;\n\
         font-family: Courier, monospace;\n\
         font-size: 0.8em;\n\
         }\n\
         table {\n\
         width: 100%;\n\
         border-collapse: collapse;\n\
         }\n\
         tr {\n\
         vertical-align: top;\n\
         }\n\
         tr:nth-child(odd) {\n\
         background-color: #eeeeee;\n\
         }\n\
         td {\n\
         padding: 2px 10px;\n\
         }\n\
         #footer {\n\
         text-align: center;\n\
         margin: 20px 0px;\n\
         color: darkgray;\n\
         }\n\
         .error {\n\
         color: red;\n\
         font-weight: bold;\n\
         }\n\
         </style>"];
        [string appendFormat:@"<script type=\"text/javascript\">\n\
         var refreshDelay = %i;\n\
         var footerElement = null;\n\
         function updateTimestamp() {\n\
         var now = new Date();\n\
         footerElement.innerHTML = \"Last updated on \" + now.toLocaleDateString() + \" \" + now.toLocaleTimeString();\n\
         }\n\
         function refresh() {\n\
         var timeElement = document.getElementById(\"maxTime\");\n\
         var maxTime = timeElement.getAttribute(\"data-value\");\n\
         timeElement.parentNode.removeChild(timeElement);\n\
         \n\
         var xmlhttp = new XMLHttpRequest();\n\
         xmlhttp.onreadystatechange = function() {\n\
         if (xmlhttp.readyState == 4) {\n\
         if (xmlhttp.status == 200) {\n\
         var contentElement = document.getElementById(\"content\");\n\
         contentElement.innerHTML = contentElement.innerHTML + xmlhttp.responseText;\n\
         updateTimestamp();\n\
         setTimeout(refresh, refreshDelay);\n\
         } else {\n\
         footerElement.innerHTML = \"<span class=\\\"error\\\">Connection failed! Reload page to try again.</span>\";\n\
         }\n\
         }\n\
         }\n\
         xmlhttp.open(\"GET\", \"/log?after=\" + maxTime, true);\n\
         xmlhttp.send();\n\
         }\n\
         window.onload = function() {\n\
         footerElement = document.getElementById(\"footer\");\n\
         updateTimestamp();\n\
         setTimeout(refresh, refreshDelay);\n\
         }\n\
         </script>", kMinRefreshDelay];
        [string appendString:@"</head>"];
        [string appendString:@"<body>"];
        [string appendString:@"<table><tbody id=\"content\">"];
        [self _appendLogRecordsToString:string afterAbsoluteTime:0.0];
        
        [string appendString:@"</tbody></table>"];
        [string appendString:@"<div id=\"footer\"></div>"];
        [string appendString:@"</body>"];
        [string appendString:@"</html>"];
        
        
    }
    else if ([path isEqualToString:@"/log"] && query[@"after"]) {
        string = [[NSMutableString alloc] init];
        double time = [query[@"after"] doubleValue];
        [self _appendLogRecordsToString:string afterAbsoluteTime:time];
        
    }
    else {
        string = [@" <html><body><p>无数据</p></body></html>" mutableCopy];
    }
    if (string == nil) {
        string = [@"" mutableCopy];
    }
    response = [GCDWebServerDataResponse responseWithHTML:string];
    return response;
}

- (void)_appendLogRecordsToString:(NSMutableString*)string afterAbsoluteTime:(double)time {
    __block double maxTime = time;
    [[Log shared].logs enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        const char* style = "color: dimgray;";
        NSString* formattedMessage = [self displayedTextForLogMessage:obj];
        [string appendFormat:@"<tr style=\"%s\">%@</tr>", style, formattedMessage];
        [[Log shared].logs removeObject:obj];
    }];
    
    [string appendFormat:@"<tr id=\"maxTime\" data-value=\"%f\"></tr>", maxTime];
    
}


- (NSString *)displayedTextForLogMessage:(NSString *)msg{
    NSMutableString *string = [[NSMutableString alloc] init];
    [string appendFormat:@"%@",msg];
    return string;
}

[self.webServer startWithPort:8079 bonjourName:nil];
这句代码开启服务, 走8079端口, 注意: 这个端口可以自定义, 如果失败, 多是端口占用, 只需要再换一个就可以

下面的代码多是一些网页和H5的内容, 定义web输出的格式, iOS工程师可以直接copy使用.

这段代码会轮询[Log shared].logs中的log信息, 一旦输出完毕会立即清空, 保证既不重复也不丢失.

4. 调用开启方法

在控制器Viewdidload方法中开启服务并开启定时器输出:

[[PKHttpServerLogger shared] startServer];
    __block int num = 0;
    [NSTimer scheduledTimerWithTimeInterval:2 repeats:YES block:^(NSTimer * _Nonnull timer) {
        PKLog(@"log %d", num++);
    }];

5. 网页操作

打开浏览器, 输入ip地址, 带上上面的端口号即可.

服务启动后允许一个网络权限即可, 如下图:

图片.png

浏览器效果: 每隔2s输出一次(实时)

  • 图片.png

相关文章

  • web实时输出iOS设备log

    web实时打印iOS设备log 项目进程中, 测试人员或者开发工程师在测试机没有连接Xcode的调试状况下如果出了...

  • 2019-01-15

    一、SpringBoot Web 开发:常用的输出是json输出、filters、property、log等1、j...

  • docker的日志参数详解管理

    docker提供了logs命令来对日志进行处理 -f 对你的项目进行log监听,所有log日志实时输出 -t 提供...

  • HTML学习小记六

    1.关于console.log()向web控制台输出调试内容:window.console.log();windo...

  • iOS宏关闭NSLog输出

    Release版本关闭Log输出 输出更详细的调试信息 参考文章:iOS开发-使用宏自定义输出(NSLog)

  • iOS 关于log输出

    一个常用的宏 它的作用精简系统的打印输出,省去了一大串没用的信息,比如年月日,项目名等等,让log更加清晰明了。 ...

  • iOS Crash日志获取和上传

    漫谈iOS Crash收集框架-程序媛念茜 iOS摇一摇在屏幕上实时显示log和crash日志 使用NSSetUn...

  • 沙盒文件浏览

    站在巨人的肩膀上实现实时浏览log文件方案 iOS本地写log方案,如下,就可以把通过NSLog打印的东东,写入文...

  • vConsole-移动端调试神器

    前言 在web应用开发过程中,可以console.log去输出信息,但是在移动端,console.log的信息我们...

  • APP开发实战128-APP Log功能设计

    31 Log功能设计 31.1Log输出控制 1 debug版本输出log,release版本不输出log A 通...

网友评论

      本文标题:web实时输出iOS设备log

      本文链接:https://www.haomeiwen.com/subject/qpbiuxtx.html