Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: WeTransfer/Diagnostics
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: master
Choose a base ref
...
head repository: unorderly/Diagnostics
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref
Can’t automatically merge. Don’t worry, you can still create the pull request.
  • 14 commits
  • 18 files changed
  • 3 contributors

Commits on Feb 20, 2023

  1. Make async

    leoMehlig committed Feb 20, 2023
    Copy the full SHA
    3b2c1d1 View commit details
  2. Fixes for watchOS

    leoMehlig committed Feb 20, 2023
    Copy the full SHA
    25cdcc6 View commit details
  3. Adds JSON formatting

    leoMehlig committed Feb 20, 2023
    Copy the full SHA
    a2ee94b View commit details

Commits on Mar 21, 2023

  1. Copy the full SHA
    c0266f4 View commit details
  2. Copy the full SHA
    8d32c51 View commit details
  3. Bump version number

    leoMehlig committed Mar 21, 2023
    Copy the full SHA
    965ba26 View commit details

Commits on Mar 24, 2023

  1. disable system log

    leoMehlig committed Mar 24, 2023
    Copy the full SHA
    914b189 View commit details

Commits on Apr 21, 2023

  1. Copy the full SHA
    5e9ae4a View commit details

Commits on May 9, 2023

  1. WIP Enable OS Logging

    Kanishka3 committed May 9, 2023
    Copy the full SHA
    4368a49 View commit details

Commits on May 14, 2023

  1. Enable Back Logging

    Kanishka3 committed May 14, 2023
    Copy the full SHA
    284dca9 View commit details

Commits on May 15, 2023

  1. Copy the full SHA
    5168665 View commit details

Commits on Jul 30, 2023

  1. Copy the full SHA
    5c67417 View commit details

Commits on Aug 3, 2023

  1. Copy the full SHA
    82f6ead View commit details

Commits on Aug 24, 2023

  1. Copy the full SHA
    9a06e86 View commit details
28 changes: 14 additions & 14 deletions DiagnosticsTests/DiagnosticsReporterTests.swift
Original file line number Diff line number Diff line change
@@ -22,63 +22,63 @@ final class DiagnosticsReporterTests: XCTestCase {
}

/// It should correctly generate HTML from the reporters.
func testHTMLGeneration() {
func testHTMLGeneration() async {
let diagnosticsChapter = DiagnosticsChapter(title: UUID().uuidString, diagnostics: UUID().uuidString)
var reporter = MockedReporter()
reporter.diagnosticsChapter = diagnosticsChapter
let reporters = [reporter]
let report = DiagnosticsReporter.create(using: reporters)
let report = await DiagnosticsReporter.create(using: reporters)
let html = String(data: report.data, encoding: .utf8)!

XCTAssertTrue(html.contains("<h3>\(diagnosticsChapter.title)</h3>"))
XCTAssertTrue(html.contains(diagnosticsChapter.diagnostics as! String))
}

/// It should create a chapter for each reporter.
func testReportingChapters() {
let report = DiagnosticsReporter.create()
func testReportingChapters() async {
let report = await DiagnosticsReporter.create()
let html = String(data: report.data, encoding: .utf8)!
let expectedChaptersCount = DiagnosticsReporter.DefaultReporter.allCases.count
let chaptersCount = html.components(separatedBy: "<div class=\"chapter\"").count - 1
XCTAssertEqual(expectedChaptersCount, chaptersCount)
}

/// It should filter using passed filters.
func testFilters() {
func testFilters() async {
let keyToFilter = UUID().uuidString
let mockedReport = MockedReport(diagnostics: [keyToFilter: UUID().uuidString])
let report = DiagnosticsReporter.create(using: [mockedReport], filters: [MockedFilter.self])
let report = await DiagnosticsReporter.create(using: [mockedReport], filters: [MockedFilter.self])
let html = String(data: report.data, encoding: .utf8)!
XCTAssertFalse(html.contains(keyToFilter))
XCTAssertTrue(html.contains("FILTERED"))
}

func testWithoutProvidingSmartInsightsProvider() {
func testWithoutProvidingSmartInsightsProvider() async {
let mockedReport = MockedReport(diagnostics: ["key": UUID().uuidString])
let report = DiagnosticsReporter.create(using: [mockedReport, SmartInsightsReporter()], filters: [MockedFilter.self], smartInsightsProvider: nil)
let report = await DiagnosticsReporter.create(using: [mockedReport, SmartInsightsReporter()], filters: [MockedFilter.self], smartInsightsProviders: [])
let html = String(data: report.data, encoding: .utf8)!
XCTAssertTrue(html.contains("Smart Insights"), "Default insights should still be added")
}

func testWithSmartInsightsProviderReturningNoExtraInsights() {
func testWithSmartInsightsProviderReturningNoExtraInsights() async {
let mockedReport = MockedReport(diagnostics: ["key": UUID().uuidString])
let report = DiagnosticsReporter.create(using: [mockedReport, SmartInsightsReporter()], filters: [MockedFilter.self], smartInsightsProvider: MockedInsightsProvider(insightToReturn: nil))
let report = await DiagnosticsReporter.create(using: [mockedReport, SmartInsightsReporter()], filters: [MockedFilter.self], smartInsightsProviders: [MockedInsightsProvider(insightToReturn: nil)])
let html = String(data: report.data, encoding: .utf8)!
XCTAssertTrue(html.contains("Smart Insights"), "Default insights should still be added")
}

func testWithSmartInsightsProviderReturningExtraInsights() {
func testWithSmartInsightsProviderReturningExtraInsights() async {
let mockedReport = MockedReport(diagnostics: ["key": UUID().uuidString])
let insightToReturn = SmartInsight(name: UUID().uuidString, result: .success(message: UUID().uuidString))
let report = DiagnosticsReporter.create(using: [mockedReport, SmartInsightsReporter()], filters: [MockedFilter.self], smartInsightsProvider: MockedInsightsProvider(insightToReturn: insightToReturn))
let report = await DiagnosticsReporter.create(using: [mockedReport, SmartInsightsReporter()], filters: [MockedFilter.self], smartInsightsProviders: [MockedInsightsProvider(insightToReturn: insightToReturn)])
let html = String(data: report.data, encoding: .utf8)!
XCTAssertTrue(html.contains(insightToReturn.name))
XCTAssertTrue(html.contains(insightToReturn.result.message))
}

/// It should correctly generate the header.
func testHeaderGeneration() {
let report = DiagnosticsReporter.create(using: [])
func testHeaderGeneration() async {
let report = await DiagnosticsReporter.create(using: [])
let html = String(data: report.data, encoding: .utf8)!

XCTAssertTrue(html.contains("<head>"))
8 changes: 4 additions & 4 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -5,10 +5,10 @@ import PackageDescription

let package = Package(name: "Diagnostics",
platforms: [
.macOS(.v10_15),
.iOS(.v11),
.tvOS(.v12),
.watchOS(.v6)],
.macOS(.v12),
.iOS(.v13),
.tvOS(.v13),
.watchOS(.v8)],
products: [
.library(name: "Diagnostics", targets: ["Diagnostics"])
],
62 changes: 55 additions & 7 deletions Sources/Device.swift
Original file line number Diff line number Diff line change
@@ -9,29 +9,38 @@
import Foundation
#if os(macOS)
import AppKit
#elseif os(watchOS)
import WatchKit
#else
import UIKit
#endif

enum Device {
static var systemName: String {
#if os(macOS)
return ProcessInfo().hostName
#if os(macOS) || targetEnvironment(macCatalyst)
return ProcessInfo.systemName
#elseif os(watchOS)
return WKInterfaceDevice.current().systemName
#else
return UIDevice.current.systemName
#endif
}

static var systemVersion: String {
#if os(macOS)
#if os(macOS) || targetEnvironment(macCatalyst)
return ProcessInfo().operatingSystemVersionString
#elseif os(watchOS)
return WKInterfaceDevice.current().systemVersion
#else
return UIDevice.current.systemVersion
#endif
}

static var freeDiskSpace: ByteCountFormatter.Units.GigaBytes {
ByteCountFormatter.string(fromByteCount: freeDiskSpaceInBytes, countStyle: ByteCountFormatter.CountStyle.decimal)
static var freeDiskSpace: ByteCountFormatter.Units.GigaBytes? {
if let freeDiskSpaceInBytes {
return ByteCountFormatter.string(fromByteCount: freeDiskSpaceInBytes, countStyle: ByteCountFormatter.CountStyle.decimal)
}
return nil
}

static var totalDiskSpace: ByteCountFormatter.Units.GigaBytes {
@@ -47,12 +56,51 @@ enum Device {
return Int64(space)
}

static var freeDiskSpaceInBytes: ByteCountFormatter.Units.Bytes {
static var freeDiskSpaceInBytes: ByteCountFormatter.Units.Bytes? {
#if !os(watchOS)
guard let space = try? URL(fileURLWithPath: NSHomeDirectory() as String)
.resourceValues(forKeys: [URLResourceKey.volumeAvailableCapacityForOpportunisticUsageKey])
.volumeAvailableCapacityForOpportunisticUsage else {
return 0
return nil
}
return space
#else
return nil
#endif
}
}

#if os(macOS) || targetEnvironment(macCatalyst)
extension ProcessInfo {
static var model: String {
if #available(macCatalyst 15.0, *) {
let service = IOServiceGetMatchingService(kIOMainPortDefault,
IOServiceMatching("IOPlatformExpertDevice"))

var modelIdentifier: String?
if let modelData = IORegistryEntryCreateCFProperty(service, "model" as CFString, kCFAllocatorDefault, 0).takeRetainedValue() as? Data {
modelIdentifier = String(data: modelData, encoding: .utf8)?.trimmingCharacters(in: .controlCharacters)
}

IOObjectRelease(service)
return modelIdentifier ?? "macOS"
} else {
return "macOS"
}
}

static var systemName: String {
var sysinfo = utsname()
let result = uname(&sysinfo)
guard result == EXIT_SUCCESS else { return "macOS (unknown)" }
let data = Data(bytes: &sysinfo.machine, count: Int(_SYS_NAMELEN))
guard let identifier = String(bytes: data, encoding: .ascii) else { return "macOS (unknown)" }
let arch = identifier.trimmingCharacters(in: .controlCharacters)
switch arch {
case "arm64": return "macOS (Apple Silicon)"
case "x86_64": return "macOS (Intel)"
default: return "macOS (\(arch))"
}
}
}
#endif
1 change: 1 addition & 0 deletions Sources/Diagnostics.swift
Original file line number Diff line number Diff line change
@@ -14,3 +14,4 @@ extension Dictionary: Diagnostics where Key == String { }
extension KeyValuePairs: Diagnostics where Key == String, Value == String { }
extension String: Diagnostics { }
extension DirectoryTreeNode: Diagnostics { }
extension Array: Diagnostics where Element == String { }
2 changes: 1 addition & 1 deletion Sources/DiagnosticsReport.swift
Original file line number Diff line number Diff line change
@@ -40,7 +40,7 @@ public extension DiagnosticsReport {
try? FileManager.default.createDirectory(atPath: folderPath, withIntermediateDirectories: true, attributes: nil)
let filePath = folderPath + filename
save(to: filePath)
#else
#elseif os(macOS)
let folderPath = "/\(userPath)/Desktop/"
saveUsingPanel(initialDirectoryPath: folderPath, filename: filename)
#endif
26 changes: 13 additions & 13 deletions Sources/DiagnosticsReporter.swift
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ import Foundation

public protocol DiagnosticsReporting {
/// Creates the report chapter.
func report() -> DiagnosticsChapter
func report() async -> DiagnosticsChapter
}

public enum DiagnosticsReporter {
@@ -55,26 +55,26 @@ public enum DiagnosticsReporter {
public static func create(
filename: String = "Diagnostics-Report.html",
using reporters: [DiagnosticsReporting] = DefaultReporter.allReporters,
filters: [DiagnosticsReportFilter.Type]? = nil,
smartInsightsProvider: SmartInsightsProviding? = nil
) -> DiagnosticsReport {
filters: [DiagnosticsReportFilter.Type] = [],
smartInsightsProviders: [SmartInsightsProviding] = []
) async -> DiagnosticsReport {
/// We should be able to parse Smart insights out of other chapters.
/// For example: read out errors from the log chapter and create insights out of it.
///
/// Therefore, we are generating insights on the go and add them to the Smart Insights later.
var smartInsights: [SmartInsightProviding] = []
var smartInsights: [SmartInsightProviding] = await smartInsightsProviders.flatMap({
await $0.smartInsights()
})

var reportChapters = reporters
var reportChapters = await reporters
.filter { ($0 is SmartInsightsReporter) == false }
.map { reporter -> DiagnosticsChapter in
var chapter = reporter.report()
if let filters = filters, !filters.isEmpty {
.map { reporter async -> DiagnosticsChapter in
var chapter = await reporter.report()
if filters.isEmpty {
chapter.applyingFilters(filters)
}
if let smartInsightsProvider = smartInsightsProvider {
let insights = smartInsightsProvider.smartInsights(for: chapter)
smartInsights.append(contentsOf: insights)
}
let insights = await smartInsightsProviders.flatMap({ await $0.smartInsights(for: chapter) })
smartInsights.append(contentsOf: insights)

return chapter
}
25 changes: 25 additions & 0 deletions Sources/Extensions/AsyncExtensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
extension Sequence {
func map<T>(
_ transform: (Element) async throws -> T
) async rethrows -> [T] {
var values = [T]()

for element in self {
try await values.append(transform(element))
}

return values
}

func flatMap<SegmentOfResult>(
_ transform: (Self.Element) async throws -> SegmentOfResult
) async rethrows -> [SegmentOfResult.Element] where SegmentOfResult : Sequence {
var values = [SegmentOfResult.Element]()
for element in self {
try await values.append(contentsOf: transform(element))
}

return values
}

}
18 changes: 18 additions & 0 deletions Sources/Extensions/OSLogEntryLog.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// OSLogEntryLog.swift
//
//
// Created by Kanishka on 10/05/23.
//

import Foundation
import OSLog

@available(iOS 15.0, *)
extension OSLogEntryLog {
var formattedMessage: String {
"""
<p class="debug"><span class="log-date">\(DateFormatter.current.string(from: self.date))</span><span class="log-separator"> | </span><span class="log-prefix">\(self.subsystem)-\(self.category)</span><span class="log-separator"> | </span><span class="log-message">\(self.composedMessage)</span></p>
"""
}
}
36 changes: 36 additions & 0 deletions Sources/HTMLGenerating.swift
Original file line number Diff line number Diff line change
@@ -46,6 +46,20 @@ extension KeyValuePairs: HTMLGenerating where Key == String, Value == String {
}
}

extension Array: HTMLGenerating where Element == String {
public func html() -> HTML {
var html = "<table>"

for element in self {
html += "<tr>\(element)</tr>"
}

html += "</table>"

return html
}
}

extension String: HTMLGenerating {
public func html() -> HTML {
return self
@@ -79,3 +93,25 @@ extension DiagnosticsChapter: HTMLGenerating {
return html
}
}

public struct JSONFormatting: HTMLFormatting {
public static func format(_ diagnostics: Diagnostics) -> HTML {
guard let text = diagnostics as? String else { return diagnostics.html() }
let id = UUID().uuidString.replacingOccurrences(of: "-", with: "")
return """
<a id="download-\(id)">Download</a>
<div id="content-\(id)">\(text)</div>
<script src="https://cdn.jsdelivr.net/gh/pgrabovets/json-view@master/dist/jsonview.js"></script>
<script>
var a = document.getElementById("download-\(id)");
var content = document.getElementById("content-\(id)").textContent;
a.download = "Export.json";
a.href = "data:application/json," + encodeURIComponent(content);
const tree\(id) = jsonview.create(content);
document.getElementById("content-\(id)").textContent = "";
jsonview.render(tree\(id), document.getElementById("content-\(id)"));
jsonview.expand(tree\(id));
</script>
"""
}
}
Loading