{"id":1362,"url":"https://github.com/AvdLee/Diagnostics","last_synced_at":"2025-08-02T04:31:02.791Z","repository":{"id":36467880,"uuid":"225350163","full_name":"WeTransfer/Diagnostics","owner":"WeTransfer","description":"Allow users to easily share Diagnostics with your support team to improve the flow of fixing bugs.","archived":false,"fork":false,"pushed_at":"2024-09-10T17:37:23.000Z","size":4037,"stargazers_count":959,"open_issues_count":3,"forks_count":57,"subscribers_count":14,"default_branch":"master","last_synced_at":"2024-11-11T10:25:06.907Z","etag":null,"topics":["swift","wt-branch-protection-exempt","wt-branch-protection-two-approvals"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/WeTransfer.png","metadata":{"files":{"readme":"README.md","changelog":"Changelog.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-12-02T10:43:07.000Z","updated_at":"2024-11-08T19:09:24.000Z","dependencies_parsed_at":"2024-01-07T22:23:27.803Z","dependency_job_id":"7ff2459a-6898-4413-9989-2d1bc9b089b7","html_url":"https://github.com/WeTransfer/Diagnostics","commit_stats":{"total_commits":179,"total_committers":22,"mean_commits":8.136363636363637,"dds":0.5195530726256983,"last_synced_commit":"75d26254276bdf12d7e71477eb0fbf1dfe939b8f"},"previous_names":[],"tags_count":31,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WeTransfer%2FDiagnostics","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WeTransfer%2FDiagnostics/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WeTransfer%2FDiagnostics/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WeTransfer%2FDiagnostics/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/WeTransfer","download_url":"https://codeload.github.com/WeTransfer/Diagnostics/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228438883,"owners_count":17920014,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["swift","wt-branch-protection-exempt","wt-branch-protection-two-approvals"],"created_at":"2024-01-05T20:15:44.716Z","updated_at":"2025-09-15T09:47:24.556Z","avatar_url":"https://github.com/WeTransfer.png","language":"Swift","funding_links":[],"categories":["Logging","Swift"],"sub_categories":["Other Hardware"],"readme":"\u003cp align=\"center\"\u003e\n    \u003cimg width=\"900px\" src=\"Assets/banner.jpg\"\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n\u003cimg src=\"https://app.bitrise.io/app/93c4448b37e42816.svg?token=iHe2fyIwNXWEIh3BshG8oQ\"/\u003e\n\u003cimg src=\"https://img.shields.io/cocoapods/v/Diagnostics.svg?style=flat\"/\u003e\n\u003cimg src=\"https://img.shields.io/cocoapods/l/Diagnostics.svg?style=flat\"/\u003e\n\u003cimg src=\"https://img.shields.io/cocoapods/p/Diagnostics.svg?style=flat\"/\u003e\n\u003cimg src=\"https://img.shields.io/badge/language-swift5.1-f48041.svg?style=flat\"/\u003e\n\u003cimg src=\"https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat\"/\u003e\n\u003cimg src=\"https://img.shields.io/badge/License-MIT-yellow.svg?style=flat\"/\u003e\n\u003c/p\u003e\n\n| Example mail composer | Example Report |\n| --- | --- |\n| ![](Assets/example_composed_email.png) | ![](Assets/example_report.png) |\n\nDiagnostics is a library written in Swift which makes it really easy to share Diagnostics Reports to your support team.\n\n- [Features](#features)\n- [Requirements](#requirements)\n- [Usage](#usage)\n\t- [Using a custom UserDefaults type](#using-a-custom-userdefaults-type) \n\t- [Filtering out sensitive data](#filtering-out-sensitive-data) \n\t- [Adding your own custom logs](#adding-your-own-custom-logs)\n\t- [Adding your own custom report](#adding-your-own-custom-report)\n    - [Smart insights](#smart-insights)   \n- [Communication](#communication)\n- [Installation](#installation)\n- [Release Notes](#release-notes)\n- [License](#license)\n\n## Features\n\nThe library allows to easily attach the Diagnostics Report as an attachment to the `MFMailComposeViewController`.\n\n- [x] Integrated with the `MFMailComposeViewController`\n- [x] Default reporters include:\n    - App metadata\n    - System metadata\n    - System logs divided per session\n- [x] Possibility to filter out sensitive data using a `DiagnosticsReportFilter`\n- [x] A custom `DiagnosticsLogger` to add your own logs\n- [x] Smart insights like _\"⚠️ User is low on storage\"_ and *\"✅ User is using the latest app version\"*\n- [x] Flexible setup to add your own smart insights\n- [x] Flexible setup to add your own custom diagnostics\n- [x] Native cross-platform support, e.g. iOS, iPadOS and macOS\n\n## Usage\n\nThe default report already contains a lot of valuable information and could be enough to get you going. \n\nMake sure to set up the `DiagnosticsLogger` as early as possible to catch all the system logs, for example in the `didLaunchWithOptions`:\n\n```swift\nfunc application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -\u003e Bool {\n    do {\n        try DiagnosticsLogger.setup()\n    } catch {\n        print(\"Failed to setup the Diagnostics Logger\")\n    }\n    return true\n}\n```\n\nThen, simply show the `MFMailComposeViewController` using the following code:\n\n```swift\nimport UIKit\nimport MessageUI\nimport Diagnostics\n\nclass ViewController: UIViewController {\n\n    @IBAction func sendDiagnostics(_ sender: UIButton) {\n        /// Create the report.\n        let report = DiagnosticsReporter.create()\n\n        guard MFMailComposeViewController.canSendMail() else {\n            /// For debugging purposes you can save the report to desktop when testing on the simulator.\n            /// This allows you to iterate fast on your report.\n            report.saveToDesktop()\n            return\n        }\n\n        let mail = MFMailComposeViewController()\n        mail.mailComposeDelegate = self\n        mail.setToRecipients([\"support@yourcompany.com\"])\n        mail.setSubject(\"Diagnostics Report\")\n        mail.setMessageBody(\"An issue in the app is making me crazy, help!\", isHTML: false)\n\n        /// Add the Diagnostics Report as an attachment.\n        mail.addDiagnosticReport(report)\n\n        present(mail, animated: true)\n    }\n\n}\n\nextension ViewController: MFMailComposeViewControllerDelegate {\n    func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {\n        controller.dismiss(animated: true)\n    }\n}\n```\n\nOn macOS you could send the report by using the `NSSharingService`:\n\n```swift\nimport AppKit\nimport Diagnostics\n\nfunc send(report: DiagnosticsReport) {\n    let service = NSSharingService(named: NSSharingService.Name.composeEmail)!\n    service.recipients = [\"support@yourcompany.com\"]\n    service.subject = \"Diagnostics Report\"\n            \n    let url = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(\"Diagnostics-Report.html\")\n    \n    // remove previous report\n    try? FileManager.default.removeItem(at: url)\n\n    do {\n        try report.data.write(to: url)\n    } catch {\n        print(\"Failed with error: \\(error)\")\n    }\n\n    service.perform(withItems: [url])\n}\n```\n\n### Using a UserDefaultsReporter\nIn order to use `UserDefaultsReporter`, you need to specify the desired `UserDefaults` instance together with all the keys you would like to read, and use it in `DiagnosticsReporter.create(filename:using:filters:smartInsightsProvider)` to create a `DiagnosticsReport`.\n\n```swift\nlet userDefaultsReporter = UserDefaultsReporter(\n    userDefaults: UserDefaults(suiteName: \"a.userdefaults.instance\"),\n    keys: [\"key_1\"]\n)\n\nlet diagnosticsReport = DiagnosticsReporter.create(using: [userDefaultsReporter])\n```\n\n### Filtering out sensitive data\nIt could be that your report is containing sensitive data. You can filter this out by creating a `DiagnosticsReportFilter`.\n\nThe example project contains an example of this:\n\n```swift\nstruct DiagnosticsDictionaryFilter: DiagnosticsReportFilter {\n\n    // This demonstrates how a filter can be used to filter out sensible data.\n    static func filter(_ diagnostics: Diagnostics) -\u003e Diagnostics {\n        guard let dictionary = diagnostics as? [String: Any] else { return diagnostics }\n        return dictionary.filter { keyValue -\u003e Bool in\n            if keyValue.key == \"App Display Name\" {\n                // Filter out the key with the value \"App Display Name\"\n                return false\n            } else if keyValue.key == \"AppleLanguages\" {\n                // Filter out a user defaults key.\n                return false\n            }\n            return true\n        }\n    }\n}\n```\n\nWhich can be used by passing in the filter into the `create(..)` method:\n\n```swift\nlet report = DiagnosticsReporter.create(using: reporters, filters: [DiagnosticsDictionaryFilter.self])\n```\n\n### Adding your own custom logs\nTo make your own logs appear in the logs diagnostics you need to make use of the `DiagnosticsLogger`. \n\n```swift\n/// Support logging simple `String` messages.\nDiagnosticsLogger.log(message: \"Application started\")\n\n/// Support logging `Error` types.\nDiagnosticsLogger.log(error: ExampleError.missingData)\n```\n\nThe error logger will make use of the localized description if available which you can add by making your error conform to `LocalizedError`.\n\n### Adding a directory tree report\nIt's possible to add a directory tree report for a given set of URL, resulting in the following output:\n\n```\n└── Documents\n    +-- contents\n    |   +-- B3F2F9AD-AB8D-4825-8369-181DEAAFF940.png\n    |   +-- 5B9C090E-6CE1-4A2F-956B-15897AB4B0A1.png\n    |   +-- 739416EF-8FF8-4502-9B36-CEB778385BBF.png\n    |   +-- 27A3C96B-1813-4553-A6B7-436E6F3DBB20.png\n    |   +-- 8F176CEE-B28F-49EB-8802-CC0438879FBE.png\n    |   +-- 340C2371-A81A-4188-8E04-BC19E94F9DAE.png\n    |   +-- E63AFEBC-B7E7-46D3-BC92-E34A53C0CE0A.png\n    |   +-- 6B363F44-AB69-4A60-957E-710494381739.png\n    |   +-- 9D31CA40-D152-45D9-BDCE-9BB09CCB825E.png\n    |   +-- 304E2E41-9697-4F9A-9EE0-8D487ED60C45.jpeg\n    |   └── 7 more file(s)\n    +-- diagnostics_log.txt\n    +-- Okapi.sqlite\n    +-- Library\n    |   +-- Preferences\n    |   |   └── group.com.wetransfer.app.plist\n    |   └── Caches\n    |       └── com.apple.nsurlsessiond\n    |           └── Downloads\n    |               └── com.wetransfer\n    +-- Coyote.sqlite-shm\n    +-- Coyote.sqlite\n    +-- Coyote.sqlite-wal\n    +-- Okapi.sqlite-shm\n    +-- Okapi.sqlite-wal\n    └── 1 more file(s)\n```\n\nYou can do this by adding the `DirectoryTreesReporter`:\n\n```swift\nvar reporters = DiagnosticsReporter.DefaultReporter.allReporters\nlet documentsURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)\nlet directoryTreesReporter = DirectoryTreesReporter(\n    directories: [\n        documentsURL\n    ]\n)\nreporters.insert(directoryTreesReporter, at: 1)\n```\n\n### Adding your own custom report\nTo add your own report you need to make use of the `DiagnosticsReporting` protocol.\n\n```swift\n/// An example Custom Reporter.\nstruct CustomReporter: DiagnosticsReporting {\n    static func report() -\u003e DiagnosticsChapter {\n        let diagnostics: [String: String] = [\n            \"Logged In\": Session.isLoggedIn.description\n        ]\n\n        return DiagnosticsChapter(title: \"My custom report\", diagnostics: diagnostics)\n    }\n}\n```\n\nYou can then add this report to the creation method:\n\n```swift\nvar reporters = DiagnosticsReporter.DefaultReporter.allReporters\nreporters.insert(CustomReporter.self, at: 1)\nlet report = DiagnosticsReporter.create(using: reporters)\n```\n\n## Smart Insights\n![](Assets/smart-insights.png)\nBy default, standard Smart Insights are provided:\n\n- `UpdateAvailableInsight` uses your bundle identifier to fetch the latest available app version. An insight will be shown whether an update is available to the user or not.\n- `DeviceStorageInsight` shows whether the user is out of storage or not\n\n### Adding your own custom insights\nIt's possible to provide your own custom insights based on the chapters in the report. A common example is to parse the errors and show a smart insight about an occurred error:\n\n```swift\nstruct SmartInsightsProvider: SmartInsightsProviding {\n    func smartInsights(for chapter: DiagnosticsChapter) -\u003e [SmartInsightProviding] {\n        guard let html = chapter.diagnostics as? HTML else { return [] }\n        if html.errorLogs.contains(where: { $0.contains(\"AppDelegate.ExampleLocalizedError\") }) {\n            return [\n                SmartInsight(\n                    name: \"Localized data\",\n                    result: .warn(message: \"An error was found regarding missing localisation.\")\n                )\n            ]\n        }\n        return []\n    }\n}\n```\n\nThe example project provides the above sample code for you to try out. You can make use of `html.errorLogs`, `.debugLogs`, and `.systemLogs` to quickly access specific logs from the report.\n\n#### Creating a custom HTML formatter for your report\nYou can make use of the `HTMLFormatting` protocol to customize the way the HTML is reported. \n\nSimply pass in the formatter into the `DiagnosticsChapter` initialiser:\n\n```swift\nDiagnosticsChapter(title: \"UserDefaults\", diagnostics: userDefaults, formatter: \u003c#HTMLFormatting.Type#\u003e)\n```\n\n## Communication\n\n- If you **found a bug**, open an issue.\n- If you **have a feature request**, open an issue.\n- If you **want to contribute**, submit a pull request.\n\n## Installation\n\n### Swift Package Manager\n\nThe [Swift Package Manager](https://swift.org/package-manager/) is a tool for managing the distribution of Swift code. It’s integrated with the Swift build system to automate the process of downloading, compiling, and linking dependencies.\n\n#### Manifest File\n\nAdd Diagnostics as a package to your `Package.swift` file and then specify it as a dependency of the Target in which you wish to use it.\n\n```swift\nimport PackageDescription\n\nlet package = Package(\n    name: \"MyProject\",\n    platforms: [\n       .macOS(.v10_15)\n    ],\n    dependencies: [\n        .package(url: \"https://github.com/WeTransfer/Diagnostics.git\", .upToNextMajor(from: \"1.8.0\"))\n    ],\n    targets: [\n        .target(\n            name: \"MyProject\",\n            dependencies: [\"Diagnostics\"]),\n        .testTarget(\n            name: \"MyProjectTests\",\n            dependencies: [\"MyProject\"]),\n    ]\n)\n```\n\n#### Xcode\n\nTo add Diagnostics as a [dependency](https://developer.apple.com/documentation/xcode/adding_package_dependencies_to_your_app) to your Xcode project, select *File \u003e Swift Packages \u003e Add Package Dependency* and enter the repository URL: `https://github.com/WeTransfer/Diagnostics.git`.\n\n### Carthage\n\n[Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks.\n\nYou can install Carthage with [Homebrew](http://brew.sh/) using the following command:\n\n```bash\n$ brew update\n$ brew install carthage\n```\n\nTo integrate Diagnostics into your Xcode project using Carthage, specify it in your `Cartfile`:\n\n```ogdl\ngithub \"WeTransfer/Diagnostics\" ~\u003e 1.00\n```\n\nRun `carthage update` to build the framework and drag the built `Diagnostics.framework` into your Xcode project.\n\n### Manually\n\nIf you prefer not to use any of the aforementioned dependency managers, you can integrate Diagnostics into your project manually.\n\n#### Embedded Framework\n\n- Open up Terminal, `cd` into your top-level project directory, and run the following command \"if\" your project is not initialized as a git repository:\n\n  ```bash\n  $ git init\n  ```\n\n- Add Diagnostics as a git [submodule](http://git-scm.com/docs/git-submodule) by running the following command:\n\n  ```bash\n  $ git submodule add https://github.com/WeTransfer/Diagnostics.git\n  ```\n\n- Open the new `Diagnostics` folder, and drag the `Diagnostics` folder into the Project Navigator of your application's Xcode project. This will add the SPM package as a local package.\n\n    \u003e It should appear nested underneath your application's blue project icon. Whether it is above or below all the other Xcode groups does not matter.\n\n- Next, select your application project in the Project Navigator (blue project icon) to navigate to the target configuration window and select the application target under the \"Targets\" heading in the sidebar.\n- In the tab bar at the top of that window, open the \"General\" panel.\n- Click on the `+` button under the \"Embedded Binaries\" section.\n- Select `Diagnostics.framework`.\n- And that's it!\n\n  \u003e The `Diagnostics.framework` is automagically added as a target dependency, linked framework and embedded framework in a copy files build phase which is all you need to build on the simulator and a device.\n\n---\n\n## Release Notes\n\nSee [CHANGELOG.md](https://github.com/WeTransfer/Diagnostics/blob/master/Changelog.md) for a list of changes.\n\n## Authors\nThis library is created as part of the [WeTransfer](https://www.wetransfer.com) Hackathon. Process has been reported on [Twitter](https://twitter.com/twannl/status/1201474263200550917?s=20). \n\nThanks to:\n\n- [\"Offie\"](https://twitter.com/offinga) for the HTML report\n- [Casper](https://twitter.com/_casperlourens) for the awesome logo\n- [Antoine](https://www.twitter.com/twannl) for the Swift Library\n\nAlso, a little shoutout to [1Password](https://www.1password.com) for [inspiring us](https://twitter.com/twannl/status/1200167786749874176?s=20) to create this library.\n\n## License\n\nDiagnostics is available under the MIT license. See the LICENSE file for more info.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FAvdLee%2FDiagnostics","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FAvdLee%2FDiagnostics","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FAvdLee%2FDiagnostics/lists"}