Platforms Discord

Wells

一个轻量级的诊断报告提交系统。

集成

dependencies: [
    .package(url: "https://github.com/ChimeHQ/Wells")
]

入门

Wells 只是一个提交系统,并且尽量不对其传输的报告来源或内容做任何假设。 它包含两个主要组件:WellsReporterWellsUploader。 默认情况下,它们协同工作。 但是,如果需要更多控制权,可以单独使用 WellsUploader

由于其灵活性,Wells 需要您做更多的工作才能将其连接到诊断数据的来源。 这是一个简单的设置示例。 请记住,Wells 使用 NSURLSession 后台上传来上传数据。 这意味着上传的开始和结束可能不会在同一个应用程序启动期间发生。

如果您使用 WellsReporter 提交数据,它将自行管理跨启动的详细信息。 但是,如果您需要更多控制权,或者想自己管理磁盘上的文件,您需要向它提供一个 ReportLocationProvider,它可以将标识符映射回文件 URL。

import Foundation
import Wells

class MyDiagnosticReporter {
    private let reporter: WellsReporter

    init() {
        self.reporter = WellsReporter()
        
        reporter.existingLogHandler = { url, date in
            // might want to examine date to see how old
            // the date is (and handle errors more gracefully)
            try? submit(url: url)
        }
    }

    func start() throws {
        // submit files, including an identifier unique to each file
        let logURLs = getExistingLogs()

        for url in logURLs {
            try submit(url: url)
        }

        // or, just submit bytes
        let dataList = getExistingData()

        for data in dataList {
            let request = makeURLRequest()
            reporter.submit(data, uploadRequest: request)
        }

    }

    func submit(url: URL) throws {
        let logIdentifier = computeUniqueIdentifier(for: url)
        let request = makeURLRequest()

        try reporter.submit(fileURL: url, identifier: logIdentifier, uploadRequest: request)
    }

    func computeUniqueIdentifier(for url: URL) -> String {
        // this works, but a more robust solution would be based on the content of the data. Note that
        // the url itself *may not* be consistent from launch to launch.
        return UUID().uuidString
    }

    // Finding logs/data is up to you
    func getExistingLogs() -> [URL] {
        return []
    }

    func getExistingData() -> [Data] {
        return []
    }

    func makeURLRequest() -> URLRequest {
        // You have control over the URLRequest that Wells uses. However,
        // some additional metadata will be added to enablee cross-launch tracking.
        let endpoint = URL(string: "https://mydiagnosticservice.com")!

        var request = URLRequest(url: endpoint)

        request.httpMethod = "PUT"
        request.addValue("hiya", forHTTPHeaderField: "custom-header")

        return request
    }
}

重试

因为 Wells 管理 *跨* 应用程序启动的提交,所以重试逻辑可能很复杂。 Wells 将尽最大努力重试不成功的提交。 它尊重 Retry-After HTTP 标头并具有退避机制。 但是,可能会在退避延迟期间终止托管应用程序。 在这种情况下,WellsReporter 依靠其 existingLogHandler 属性来避免需要持久性存储。

默认情况下,如果在 baseURL 目录中发现早于 2 天的文件,Wells 将放弃并删除它们。

底线:Wells 提交是尽力而为。 强大的重试支持意味着您必须使用 existingLogHandler。 在某些不太可能的情况下,可能会阻止提交和重试系统以可预测的方式工作。

与 MetricKit 结合使用

Wells 非常适合提交从 MetricKit 收集的数据。 事实上,MeterReporter 使用它构建了一个完整的基于 MetricKit 的报告系统。

但是,您也可以自己完成。 这是一个简单的例子。

import Foundation
import MetricKit
import Wells

class MetricKitOnlyReporter: NSObject {
    private let reporter: WellsReporter
    private let endpoint = URL(string: "https://mydiagnosticservice.com")!

    override init() {
        self.reporter = WellsReporter()

        super.init()

        MXMetricManager.shared.add(self)
    }

    private func submitData(_ data: Data) {
        var request = URLRequest(url: endpoint)

        request.httpMethod = "PUT"

        // ok, yes, I have glossed over error handling
        try? reporter.submit(data, uploadRequest: request)
    }
}

extension MetricKitOnlyReporter: MXMetricManagerSubscriber {
    func didReceive(_ payloads: [MXMetricPayload]) {
    }

    func didReceive(_ payloads: [MXDiagnosticPayload]) {
        payloads.map({ $0.jsonRepresentation() }).forEach({ submitData($0) })
    }
}

命名

Wells 完全是关于报告的,所以用一位 著名的记者 的名字来命名它似乎很合乎逻辑。

建议或反馈

我很乐意收到您的来信! Issues 或 pull requests 效果很好。 Discord 服务器 也可用于实时帮助,但我非常倾向于以文档的形式回答。

我更喜欢协作,并且如果您有类似的项目,我很乐意找到合作的方式。

我更喜欢使用制表符进行缩进,以提高可访问性。 但是,我宁愿您使用您想要的系统并提出 PR,而不是因为空格而犹豫。

通过参与此项目,您同意遵守贡献者行为准则