• 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) "

            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 += " RegisterID: (registerID);"

            }

           //log文件中的内容

             if let readStr = logFile.readString() {

                debugStr += " (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, 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获取方式

  • 相关阅读:
    宝宝的成长脚印9/2
    宝宝的成长脚印9/5
    手动作花灯10/6
    EasyUI中EasyLoader加载数组模块
    easyui常用属性
    VS2010如何在一个web项目中使用APP_CODE下的自定义类
    MSSQL系统常用视图命令及其作用
    db_autopwn渗透流程
    渗透测试工具Nmap从初级到高级
    EasyUI中在表单提交之前进行验证
  • 原文地址:https://www.cnblogs.com/duzhaoquan/p/10843963.html
Copyright © 2020-2023  润新知