Sources/CrashReporter2/CrashReporter.swift (124 lines of code) (raw):
//
// Copyright 2023 aliyun-sls Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import Foundation
import AliyunLogOTelCommon
import OpenTelemetryApi
#if canImport(WPKMobiWrapper)
import WPKMobiWrapper
#else
import AliyunLogCrashReporter.WPKMobiWrapper
#endif
typealias DirectoryChangedBlock = (String) -> Void
open class CrashReporter: NSObject {
@objc
public static let shared: CrashReporter = CrashReporter()
private let crashFileHelper: CrashFileHelper
private var crashLogSource: DispatchSourceFileSystemObject?
private var debuggable: Bool = false
private override init() {
crashFileHelper = CrashFileHelper()
}
@objc
public func `init`(debuggable: Bool) {
CrashReporterOTel().initOtel()
observeDirectoryChanged()
initWPKMobi()
if let builder = CrashReporterOTel.spanBuilder("app.start") {
builder.setAttribute(key: "t", value: "pv")
AttributesHelper.setAttributes(builder)
builder.startSpan().end()
}
}
@objc
public func addLog(_ log: String) {
addLog(logs: ["content": log])
}
@objc
public func addLog(logs: [String: String]) {
if logs.count == 0 {
return
}
if let builder = CrashReporterOTel.spanBuilder("log") {
builder.setAttribute(key: "t", value: "log")
for (k, v) in logs {
builder.setAttribute(key: "log.\(k)", value: v)
}
AttributesHelper.setAttributes(builder)
builder.startSpan().end()
}
}
@objc
public func reportException(_ error: NSException) {
reportException(name: "exception", error: error, properties: nil)
}
@objc
public func reportException(_ error: NSException, properties: [String: String]?) {
reportException(name: "exception", error: error, properties: properties)
}
@objc
public func reportException(name: String, error: NSException, properties: [String: String]?) {
if let builder = CrashReporterOTel.spanBuilder("exception") {
builder.setAttribute(key: "t", value: "exception")
.setAttribute(key: "ex.name", value: name)
.setAttribute(key: "ex.type", value: "\(error.name.rawValue)")
.setAttribute(key: "ex.message", value: "\(error.reason ?? "")")
.setAttribute(key: "ex.stacktrace", value: "\(error.callStackSymbols.joined(separator: "\n"))")
if let properties = properties {
for (k, v) in properties {
builder.setAttribute(key: "ex.\(k)", value: v)
}
}
AttributesHelper.setAttributes(builder)
builder.startSpan().end()
}
}
private func observeDirectoryChanged() {
let libraryPath = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true).first ?? ""
let wpkLogpath = (libraryPath as NSString).appendingPathComponent(".WPKLog")
guard checkAndCreateDirectory(dir: wpkLogpath) else { return }
let crashLogPath = (wpkLogpath as NSString).appendingPathComponent("CrashLog")
guard checkAndCreateDirectory(dir: crashLogPath) else { return }
observeDirectory(&crashLogSource, path: crashLogPath) { path in
CrashReporter.shared.crashFileHelper.parseCrashFile(path)
}
crashFileHelper.scanAndReport(crashLogPath)
}
private func initWPKMobi() {
WPKMobiWrapper.`init`(true)
}
private func checkAndCreateDirectory(dir: String) -> Bool {
let fileManager = FileManager.default
if !fileManager.fileExists(atPath: dir) {
print("\(dir) path not exists.")
do {
try fileManager.createDirectory(atPath: dir, withIntermediateDirectories: true, attributes: nil)
return true
} catch {
print("create directory \(dir) error.")
return false
}
}
return true
}
private func observeDirectory(_ _source: inout DispatchSourceFileSystemObject?, path: String, handler: @escaping DirectoryChangedBlock) {
let dirURL = URL(fileURLWithPath: path)
let fd = open(dirURL.path, O_EVTONLY)
if fd < 0 {
print("unable to open the path: \(dirURL.path)")
return
}
let source = DispatchSource.makeFileSystemObjectSource(fileDescriptor: fd, eventMask: .write, queue: DispatchQueue.global())
source.setEventHandler {
let type = source.data
switch type {
case .write:
handler(path)
default:
break
}
}
source.setCancelHandler {
close(fd)
}
source.resume()
_source = source
}
}