我们在app中对崩溃、卡顿、内存问题进行监控。一旦监控到问题,我们就需要记录下来,但是,很多问题的定位仅靠问题发生的那一刹那记录的信息是不够的,我们需要记录app的全量日志来获取更多的信息。

一,使用NSLog获取全量日志,通过CocoaLumberjack第三方库获取系统日志

对NSLog进行重定向采用Hook方式,因为NSLog时C的函数,使用fishHook实现重定向,具体实现如下:

static void (&orig_nslog)(NSString *format, ...);

void redirect_nslog(NSString *format, ...) {

// 可以在这里先进行自己的处理

// 继续执行原 NSLog

va_list va;

va_start(va, format);

NSLogv(format, va);

va_end(va);

}

int main(int argc, const char * argv[]) {

@autoreleasepool {

struct rebinding nslog_rebinding = {"NSLog",redirect_nslog,(void*)&orig_nslog};

NSLog(@"try redirect nslog %@,%d",@"is that ok?");

}

return

可以看到,我在上面这段代码中,利用了fishhook 对方法的符号地址进行了重新绑定,从而

只要是NSL og的调用就都会转向redirect_ nslog 方法调用。

在redirect_ nslog 方法中,你可以先进行自己的处理,比如将日志的输出重新输出到自己的持

久化存储系统里,接着调用NSLog也会调用的NSL _ogv方法进行原NSLog方法的调用。当

然了,你也可以使用fishhook提供的原方法调用方式orig_ _nslog, 进行原NSLog方法的调

用。上面代码里也已经声明了类orig_ nslog, 直接调用即可。

NSL og最后写文件时的句柄是STDERR,我先前跟你说了苹果对于NSL og的定义是记录错

误的信息,STDERR的全称是standard error,系统错误日志都会通过STDERR句柄来记

录,所以NSLog最终将错误日志进行写操作的时候也会使用STDERR句柄,而dup2函数是

专门进行文件重定向的,那么也就有了另一个不使用fishhook还可以捕获NSLog日志的方

法。你可以使用dup2重定向STDERR句柄,使得重定向的位置可以由你来控制,关键代码

如下:

int fd = open(path, (O_RDWR | O_CREAT), 0644);

dup2(fd, STDERR_FILENO);

path 就是你自定义的重定向输出的文件地址。

二,自己创建日志文件,定期上传,获取日志信息

第三方库 https://github.com/CocoaLumberjack/CocoaLumberjack 具体查看github,现在主要说说自己创建日志文件

1。创建log类

class Log {

//创建成单利,便于全局调用

static var shareInstance = Log()

var writeFileQueue: DispatchQueue

//log文件的存储路径

var logFile: Path {

get {

let now = Date()

let fileName = "ErrorLog_\(now.year)_\(now.month)_\(now.day).txt"

if !Path.cacheDir["Logs"].exists {

_ = Path.cacheDir["Logs"].mkdir()

}

if !Path.cacheDir["Logs"][fileName].exists {

_ = Path.cacheDir["Logs"][fileName].touch()

let write = DispatchWorkItem(qos: .background, flags: .barrier) {

let file = FileHandle(forUpdatingAtPath: Path.cacheDir["Logs"][fileName].asString)

file?.seekToEndOfFile()

file?.write(self.getDeviceInfo().data(using: String.Encoding.utf8)!)

}

writeFileQueue.async(execute: write)

//删除30天以前的Log文件

if let files = Path.cacheDir["Logs"].contents {

let sortedFiles = files.sorted { (p1, p2) -> Bool in

guard let attribute1 = p1.attributes else { return false }

guard let attribute2 = p2.attributes else { return false }

if let date1 = attribute1[FileAttributeKey.creationDate] as? Date, let date2 = attribute2[FileAttributeKey.creationDate] as? Date {

return date1 < date2

} else {

return false

}

}

if sortedFiles.count > 30 {

_ = sortedFiles.first!.remove()

}

}

}

return Path.cacheDir["Logs"][fileName]

}

}

fileprivate init() {

writeFileQueue = DispatchQueue(label: "写日志线程", qos: DispatchQoS.default, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil)

let _ = logFile

}

//添加日志的全局方法

func log(message: String, toCloudKit: Bool = false) {

let now = Date()

let m = convertToVisiable(str: message)

let string = "\(now.string()) : \(m)\n"

let write = DispatchWorkItem(qos: .background, flags: .barrier) {

let file = FileHandle(forUpdatingAtPath: self.logFile.asString)

file?.seekToEndOfFile()

file?.write(string.data(using: String.Encoding.utf8)!)

}

writeFileQueue.async(execute: write)

}

//获取当前日志的方法

func readLog() -> String? {

//展示日志信息,添加一些项目需要的信息

var debugStr = "BaseURL: \(BaseUrl)"

if let registerID = PalauDefaults.registerid.value {

debugStr += "\nRegisterID: \(registerID);"

}

//log文件中的内容

if let readStr = logFile.readString() {

debugStr += "\n \(readStr)"

}

return debugStr

}

}

2.在全局添加日志

Log.shareInstance.log(message: “login”)

3.查看当前日志(今天的)展示在textview上

if let logString = Log.shareInstance.readLog() {

let textView = UITextView(frame: CGRect(x: 0, y:0, width: 600, height: 400))

textView.center = view.center

view.addSubview(textView)

textView.text = logString

if textView.text.count > 0 {

let location = textView.text.count - 1

let bottom = NSMakeRange(location, 1)

textView.scrollRangeToVisible(bottom)

}

}

4通过通知方式定期上传日志文件

在AppDelegate中上传日志文件到服务器或发送日志文件到相应邮箱

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void

) {

_ = SyncTask.sendLogEmail(email: email)

}

class func sendLogEmail(email: String) -> Promise<Void> {

var sendEmail = "aldelo@126.com"

if email.trim().count > 0 {

sendEmail = email

}

let syncUrl = PalauDefaults.syncurl.value ?? ""

let url = syncUrl + "/express/email/endofday"

return firstly {

uploadDatabase()//上传日志文件到服务器,方法实现在下边

}.then { fileUrl in

return Promise<Void> { seal in

let storeName = PalauDefaults.storename.value ?? ""

let path = Path.temporaryDir["\(storeName)-LogFileAddress.txt"]

if path.exists {

_ = path.remove()//日志文件已经上传到服务端,删除本地的

}

let tmpPath = path.asString

try? fileUrl.data(using: .utf8, allowLossyConversion: true)?.write(to: URL(fileURLWithPath: tmpPath))

Alamofire.upload(multipartFormData: { multipartFormData in

multipartFormData.append(URL(fileURLWithPath: tmpPath), withName: "attachments")

multipartFormData.append(sendEmail.data(using: .utf8, allowLossyConversion: true)!, withName: "emailaddress")

}, usingThreshold: UInt64.init(), to: url, method: .post, headers: ECTicket(), encodingCompletion: { encodingResult in

switch encodingResult {

case .success(let upload, _, _):

upload.responseJSON { response in

let json = JSON(response.data as Any)

if let errCode = json["err_code"].int , errCode != 0 {

seal.reject(NSError(domain: json["err_msg"].stringValue, code: errCode, userInfo: nil))

return

}

seal.fulfill(())

}

case .failure(let encodingError):

print(encodingError)

seal.reject(encodingError)

}

})

}

}

}

//上传的方法

class func uploadDatabase() -> Promise<String> {

return Promise<String> { seal in

DispatchQueue.global().async {

let uploadDir = Path.cacheDir["UploadLog"]

if !uploadDir.exists {

_ = uploadDir.mkdir()

}

var files = [URL]()

let zipFilePath = URL(fileURLWithPath: uploadDir.toString() + “/database.zip”)//压缩文件的名字

if Path.cacheDir["Logs"].exists {

if let contents = Path.cacheDir["Logs"].contents {

for file in contents {

files.append(URL(fileURLWithPath: file.toString()))  //添加每个日志文件路径

}

}

}

do {//压缩所有的日志文件

try Zip.zipFiles(paths: files, zipFilePath: zipFilePath, password: nil, progress: { (progress) -> () in

print(progress)

})

} catch let error as NSError {

seal.reject(error)

return

}

let headers = CUTicket()

let uploadUrl = BaseUrl + "/express/device/upload/localdata/" + PalauDefaults.storeID.value!

let deviceGlobalID = PalauDefaults.terminalguid.value!

let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? ""

var deviceNumber = ""

if let dnumber = getDeviceNumber() {

deviceNumber = "\(dnumber)"

}

//以数据流的方式上传 ,默认的是上传数据的大小大于10M的时候采用数据流的方式上传

Alamofire.upload(multipartFormData: { multipartFormData in

multipartFormData.append(deviceGlobalID.data(using: String.Encoding.utf8, allowLossyConversion: false)!, withName :"DeviceGlobalID")

multipartFormData.append(deviceNumber.data(using: String.Encoding.utf8, allowLossyConversion: false)!, withName :"DeviceNumber")

multipartFormData.append(version.data(using: String.Encoding.utf8, allowLossyConversion: false)!, withName :"DeviceVersion")

multipartFormData.append(zipFilePath, withName :"file")

multipartFormData.append(PalauDefaults.storeID.value!.data(using: String.Encoding.utf8)!, withName: "storeid")

}, usingThreshold: UInt64.init(), to: uploadUrl, method: .post, headers: headers, encodingCompletion: { encodingResult in

switch encodingResult {

case .success(let upload, _, _):

upload.responseJSON { response in

guard let value = response.result.value else {

seal.reject(NSError(domain: "response value is Null", code: 0, userInfo: nil))

return

}

let json = JSON(value)

if let err_code = json["err_code"].int {

seal.reject(NSError(domain: json["err_msg"].stringValue, code: err_code, userInfo: nil))

} else {

if let url = json["url"].string {

seal.fulfill(url)

} else {

seal.reject(NSError(domain: "response value is Null", code: 0, userInfo: nil))

}

}

}

case .failure(let encodingError):

seal.reject(encodingError)

}

})

}

}

以上时我们的项目中日志的使用具体流程,可以借鉴一下,实现自己的log获取方式

iOS开发中全量日志的获取的更多相关文章

  1. iOS开发中获取视图在屏幕上显示的位置

    在iOS开发中,我们会经常遇到一个问题,例如,点击一个按钮,弹出一个遮罩层,上面显示一个弹框,弹框显示的位置在按钮附近.如果这个按钮的位置相对于屏幕边缘的距离是固定的,那就容易了,可以直接写死位置.可 ...

  2. iOS开发中获取WiFi相关信息

    iOS 开发中难免会遇到很多与网络方面的判断,这里做个汇总,大多可能是与WiFi相关的. 1.Ping域名.Ping某IP 有 时候可能会遇到ping 某个域名或者ip通不通,再做下一步操作.这里的p ...

  3. iOS开发中图片方向的获取与更改

    iOS开发中 再用到照片的时候  或多或少遇到过这样的问题  就是我想用的照片有横着拍的有竖着排的  所以导致我选取图片后的效果也横七竖八的   显示效果不好 比如: 图中红圈选中的图片选取的是横着拍 ...

  4. iOS:iOS开发非常全的三方库、插件等等

    iOS开发非常全的三方库.插件等等 github排名:https://github.com/trending, github搜索:https://github.com/search. 此文章转自git ...

  5. iOS开发 非常全的三方库、插件、大牛博客等等

    UI 下拉刷新 EGOTableViewPullRefresh- 最早的下拉刷新控件. SVPullToRefresh- 下拉刷新控件. MJRefresh- 仅需一行代码就可以为UITableVie ...

  6. iOS开发中你是否遇到这些经验问题

    前言 小伙伴们在开发中难免会遇到问题, 你是如何解决问题的?不妨也分享给大家!如果此文章其中的任何一条问题对大家有帮助,那么它的存在是有意义的! 反正不管怎样遇到问题就要去解决问题, 在解决问题的同时 ...

  7. ios开发中的小技巧

    在这里总结一些iOS开发中的小技巧,能大大方便我们的开发,持续更新. UITableView的Group样式下顶部空白处理 //分组列表头部空白处理 UIView *view = [[UIViewal ...

  8. iOS开发-捕获程序崩溃日志

    iOS开发中遇到程序崩溃是很正常的事情,如何在程序崩溃时捕获到异常信息并通知开发者,是大多数软件都选择的方法.下面就介绍如何在iOS中实现: 1. 在程序启动时加上一个异常捕获监听,用来处理程序崩溃时 ...

  9. iOS开发中的4种数据持久化方式【一、属性列表与归档解档】

    iOS中的永久存储,也就是在关机重新启动设备,或者关闭应用时,不会丢失数据.在实际开发应用时,往往需要持久存储数据的,这样用户才能在对应用进行操作后,再次启动能看到自己更改的结果与痕迹.ios开发中, ...

随机推荐

  1. luogu P2135 方块消除 |dp

    题目描述 Jimmy最近迷上了一款叫做方块消除的游戏.游戏规则如下:n个带颜色方格排成一列,相同颜色的方块连成一个区域(如果两个相邻方块颜色相同,则这两个方块属于同一区域).为简化题目,将连起来的同一 ...

  2. XML与JSON解析

    [XML简介] XML在线校验工具: http://tool.oschina.net/codeformat/xml 可扩展标记语言(EXtensible Markup Language) 一种标记语言 ...

  3. 解决pyinstaller在单一文件时无法正确添加权限清单问题,(UAC,uac_admin,manifest,asInvoker,python,requireAdministrator)

    做了3天的win10的兼容性测试,大部分时间都卡权限获取这了. 以下废话很多,想直接找解决方法,请跳至红字 首先,简单说下uac,自vista后windows再次加严了权限管理,uac (账户控制) ...

  4. 初步了解JVM第一篇

    大家都知道,Java中JVM的重要性,学习了JVM你对Java的运行机制.编译过程和如何对Java程序进行调优相信都会有一个很好的认知. 废话不多说,直接带大家来初步认识一下JVM. 什么是JVM? ...

  5. Python3 类的继承

    目录 继承的基本概念 什么是继承 继承有什么用 如何实现继承 初识继承 寻找继承关系 如何寻找继承关系 实例演示 继承背景下的对象属性查找顺序 派生 新式类和经典类 钻石继承 通过继承实现修改json ...

  6. git 中的 merge 和 rebase

    示例分支:master . dev 把 dev 分支上的新内容合并到 master 上 先切换分支到master git checkout master 合并操作 git merge dev 或者 g ...

  7. 二、Vue 页面渲染过程

    前言 上篇博文我们依葫芦画瓢已经将hello world 展现在界面上啦,但是是不是感觉新虚虚的,总觉得这么多文件,项目怎么就启动起来了呢?怎么访问到8080 端口就能进入到我们的首页呢.整个的流程是 ...

  8. 建议收藏:命令创建.net core3.0 web应用详解(超详细教程)

    你是不是曾经膜拜那些敲几行代码就可以创建项目的大神,学习了命令创建项目你也可以成为大神,其实命令创建项目很简单. (1)cmd命令行到你打算创建项目的位置   (2)在该目录下创建解决方案文件夹JIY ...

  9. 【JPA】字段访问、属性访问及混合访问

    [JPA]字段访问.属性访问及混合访问 转载:https://www.cnblogs.com/yangchongxing/p/10120318.html 1.字段访问 注解字段,通过反射来获得和设置字 ...

  10. 【MyBatis】配置文件提示

    [MyBatis]配置文件提示 官方帮助文档:http://www.mybatis.org/mybatis-3/zh/index.html config配置 http://mybatis.org/dt ...