iOS开发中全量日志的获取
我们在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开发中全量日志的获取的更多相关文章
- iOS开发中获取视图在屏幕上显示的位置
在iOS开发中,我们会经常遇到一个问题,例如,点击一个按钮,弹出一个遮罩层,上面显示一个弹框,弹框显示的位置在按钮附近.如果这个按钮的位置相对于屏幕边缘的距离是固定的,那就容易了,可以直接写死位置.可 ...
- iOS开发中获取WiFi相关信息
iOS 开发中难免会遇到很多与网络方面的判断,这里做个汇总,大多可能是与WiFi相关的. 1.Ping域名.Ping某IP 有 时候可能会遇到ping 某个域名或者ip通不通,再做下一步操作.这里的p ...
- iOS开发中图片方向的获取与更改
iOS开发中 再用到照片的时候 或多或少遇到过这样的问题 就是我想用的照片有横着拍的有竖着排的 所以导致我选取图片后的效果也横七竖八的 显示效果不好 比如: 图中红圈选中的图片选取的是横着拍 ...
- iOS:iOS开发非常全的三方库、插件等等
iOS开发非常全的三方库.插件等等 github排名:https://github.com/trending, github搜索:https://github.com/search. 此文章转自git ...
- iOS开发 非常全的三方库、插件、大牛博客等等
UI 下拉刷新 EGOTableViewPullRefresh- 最早的下拉刷新控件. SVPullToRefresh- 下拉刷新控件. MJRefresh- 仅需一行代码就可以为UITableVie ...
- iOS开发中你是否遇到这些经验问题
前言 小伙伴们在开发中难免会遇到问题, 你是如何解决问题的?不妨也分享给大家!如果此文章其中的任何一条问题对大家有帮助,那么它的存在是有意义的! 反正不管怎样遇到问题就要去解决问题, 在解决问题的同时 ...
- ios开发中的小技巧
在这里总结一些iOS开发中的小技巧,能大大方便我们的开发,持续更新. UITableView的Group样式下顶部空白处理 //分组列表头部空白处理 UIView *view = [[UIViewal ...
- iOS开发-捕获程序崩溃日志
iOS开发中遇到程序崩溃是很正常的事情,如何在程序崩溃时捕获到异常信息并通知开发者,是大多数软件都选择的方法.下面就介绍如何在iOS中实现: 1. 在程序启动时加上一个异常捕获监听,用来处理程序崩溃时 ...
- iOS开发中的4种数据持久化方式【一、属性列表与归档解档】
iOS中的永久存储,也就是在关机重新启动设备,或者关闭应用时,不会丢失数据.在实际开发应用时,往往需要持久存储数据的,这样用户才能在对应用进行操作后,再次启动能看到自己更改的结果与痕迹.ios开发中, ...
随机推荐
- react-native Android release打包失败
npm run build报错(android) react-native 0.5x在安卓环境 gradle 3.x版本下编译release版本的时候提示编译失败,但是debug模式下是没有问题的. ...
- Java修炼——基于TCP协议的Socket编程_双向通信_实现模拟用户登录
首先我们需要客户端和服务器端. 服务器端需要:1.创建ServerSocket对象.2.监听客户端的请求数据.3.获取输入流(对象流)即用户在客户端所发过来的信息. ...
- HDU1529-Casher Emploryment(最最...最经典的差分约束 差分约束-最长路+将环变线)
A supermarket in Tehran is open 24 hours a day every day and needs a number of cashiers to fit its n ...
- Spring Cloud第八篇 | Hystrix集群监控Turbine
本文是Spring Cloud专栏的第八篇文章,了解前七篇文章内容有助于更好的理解本文: Spring Cloud第一篇 | Spring Cloud前言及其常用组件介绍概览 Spring Clo ...
- 统计学习方法与Python实现(二)——k近邻法
统计学习方法与Python实现(二)——k近邻法 iwehdio的博客园:https://www.cnblogs.com/iwehdio/ 1.定义 k近邻法假设给定一个训练数据集,其中的实例类别已定 ...
- Proxmox VE虚拟化管理平台-相关概念
请阅读此文用户务必阅读以下链接,其中包含了汉化作者.张自然copy过来的原因等信息. www.zhangziran.com/proxmox-docs-zh-cn.htm a Proxmox VE 集群 ...
- zabbix4.0.1 安装部署
zabbix安装部署 目录 一.环境准备... 3 1.1.版本:... 3 1.2.部署环境... 3 二.安装部署... 3 2.1.zabbix安装... 3 2.1.1.下载zabbix的rp ...
- WebAPI接口的自动化测试1
自动化测试要满足四个条件: 1 - 自动化用例能够完成所有测试步骤 -------- postman不支持 没有完整的用例管理系统 2 - 每个用例的输入数据,必须要自动填入 -------- ...
- usb工业相机之硬件设计-双缓冲-双端口sdram-fpga
usb工业相机之硬件设计-双缓冲-双端口sdram-fpga 在前期的产品设计中,采用cb提供的结构,68013直接操作摄像头,iic配置摄像头寄存器,板载晶振提供时钟,摄像头的pclk直接接ifcl ...
- Eclipse添加spring-tool-suite插件
Eclipse添加spring-tool-suite插件 步骤 1.help --> Eclipse Marketplace,在search框中搜索spring-tool-suite,点击右下角 ...