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获取方式

 

原文地址:https://www.cnblogs.com/duzhaoquan/p/10843963.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


当我们远离最新的 iOS 16 更新版本时,我们听到了困扰 Apple 最新软件的错误和性能问题。
欧版/美版 特别说一下,美版选错了 可能会永久丧失4G,不过只有5%的概率会遇到选择运营商界面且部分必须连接到iTunes才可以激活
一般在接外包的时候, 通常第三方需要安装你的app进行测试(这时候你的app肯定是还没传到app store之前)。
前言为了让更多的人永远记住12月13日,各大厂都在这一天将应用变灰了。那么接下来我们看一下Flutter是如何实现的。Flutter中实现整个App变为灰色在Flutter中实现整个App变为灰色是非常简单的,只需要在最外层的控件上包裹ColorFiltered,用法如下:ColorFiltered(颜色过滤器)看名字就知道是增加颜色滤镜效果的,ColorFiltered( colorFilter:ColorFilter.mode(Colors.grey, BlendMode.
flutter升级/版本切换
(1)在C++11标准时,open函数的文件路径可以传char指针也可以传string指针,而在C++98标准,open函数的文件路径只能传char指针;(2)open函数的第二个参数是打开文件的模式,从函数定义可以看出,如果调用open函数时省略mode模式参数,则默认按照可读可写(ios_base:in | ios_base::out)的方式打开;(3)打开文件时的mode的模式是从内存的角度来定义的,比如:in表示可读,就是从文件读数据往内存读写;out表示可写,就是把内存数据写到文件中;
文章目录方法一:分别将图片和文字置灰UIImage转成灰度图UIColor转成灰度颜色方法二:给App整体添加灰色滤镜参考App页面置灰,本质是将彩色图像转换为灰度图像,本文提供两种方法实现,一种是App整体置灰,一种是单个页面置灰,可结合具体的业务场景使用。方法一:分别将图片和文字置灰一般情况下,App页面的颜色深度是24bit,也就是RGB各8bit;如果算上Alpha通道的话就是32bit,RGBA(或者ARGB)各8bit。灰度图像的颜色深度是8bit,这8bit表示的颜色不是彩色,而是256
领导让调研下黑(灰)白化实现方案,自己调研了两天,根据网上资料,做下记录只是学习过程中的记录,还是写作者牛逼
让学前端不再害怕英语单词(二),通过本文,可以对css,js和es6的单词进行了在逻辑上和联想上的记忆,让初学者更快的上手前端代码
用Python送你一颗跳动的爱心
在uni-app项目中实现人脸识别,既使用uni-app中的live-pusher开启摄像头,创建直播推流。通过快照截取和压缩图片,以base64格式发往后端。
商户APP调用微信提供的SDK调用微信支付模块,商户APP会跳转到微信中完成支付,支付完后跳回到商户APP内,最后展示支付结果。CSDN前端领域优质创作者,资深前端开发工程师,专注前端开发,在CSDN总结工作中遇到的问题或者问题解决方法以及对新技术的分享,欢迎咨询交流,共同学习。),验证通过打开选择支付方式弹窗页面,选择微信支付或者支付宝支付;4.可取消支付,放弃支付会返回会员页面,页面提示支付取消;2.判断支付方式,如果是1,则是微信支付方式。1.判断是否在微信内支付,需要在微信外支付。
Mac命令行修改ipa并重新签名打包
首先在 iOS 设备中打开开发者模式。位于:设置 - 隐私&安全 - 开发者模式(需重启)
一 现象导入MBProgressHUD显示信息时,出现如下异常现象Undefined symbols for architecture x86_64: "_OBJC_CLASS_$_MBProgressHUD", referenced from: objc-class-ref in ViewController.old: symbol(s) not found for architecture x86_64clang: error: linker command failed wit
Profiles >> 加号添加 >> Distribution >> "App Store" >> 选择 2.1 创建的App ID >> 选择绑定 2.3 的发布证书(.cer)>> 输入描述文件名称 >> Generate 生成描述文件 >> Download。Certificates >> 加号添加 >> "App Store and Ad Hoc" >> “Choose File...” >> 选择上一步生成的证书请求文件 >> Continue >> Download。
今天有需求,要实现的功能大致如下:在安卓和ios端实现分享功能可以分享链接,图片,文字,视频,文件,等欢迎大佬多多来给萌新指正,欢迎大家来共同探讨。如果各位看官觉得文章有点点帮助,跪求各位给点个“一键三连”,谢啦~声明:本博文章若非特殊注明皆为原创原文链接。