Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved logging/employed activity tracing. #8

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "Submodules/ReusableWorkflows"]
path = Submodules/ReusableWorkflows
url = https://github.com/grigorye/ReusableWorkflows
[submodule "Submodules/etcetera"]
path = Submodules/etcetera
url = https://github.com/jaredsinclair/etcetera
1 change: 1 addition & 0 deletions Submodules/etcetera
Submodule etcetera added at de9036
44 changes: 30 additions & 14 deletions URLHelperApp/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@ private let urlResolver: URLResolver = ScriptBasedURLResolver()
class AppDelegate : NSObject, NSApplicationDelegate {

func applicationDidFinishLaunching(_ notification: Notification) {
log.info("Did finish launching.")
let leave = Activity("Finish Launching").enter(); defer { leave() }
let bundleVersion = Bundle.main.object(forInfoDictionaryKey: kCFBundleVersionKey as String) as! String
log.info("Bundle version: \(bundleVersion)")
}

func application(_ application: NSApplication, open urls: [URL]) {
log.info("Opening \(urls).")
let leave = Activity("Open URLs").enter(); defer { leave() }
log.info("URLs: \(urls)")
let task = Task {
let urlsByAppBundleIdentifier = try await resolve(urls)
for (appBundleIdentifier, urls) in urlsByAppBundleIdentifier {
Expand All @@ -31,23 +34,30 @@ class AppDelegate : NSObject, NSApplicationDelegate {
Task {
do {
try await task.result.get()
log.info("Succeeded with opening \(urls).")
log.info("Open succeeded")
} catch {
log.error("Failed to open \(urls): \(error).")
log.error("Open failed: \(error)")
}
}
}
}

private func resolve(_ urls: [URL]) async throws -> [String: [URL]] {
try await withThrowingTaskGroup(of: (url: URL, appBundleIdentifier: String)?.self) { group in
let leave = Activity("Get Route For URLs").enter(); defer { leave() }
return try await withThrowingTaskGroup(of: (url: URL, appBundleIdentifier: String)?.self) { group in
for url in urls {
group.addTask {
guard let resolution = try await urlResolver.resolveURL(url) else {
log.error("Unable to resolve \(url).")
log.error("Unable to resolve \(url)")
return nil
}
return (resolution.finalURL, resolution.appBundleIdentifier)
let finalURL = resolution.finalURL
let appBundleIdentifier = resolution.appBundleIdentifier
if url != finalURL {
log.info("Rewrote \(url) into \(finalURL)")
}
log.info("Got \(appBundleIdentifier, privacy: .public) for opening \(finalURL)")
return (finalURL, appBundleIdentifier)
}
}
return try await group.compactMap { $0 }.reduce([:]) { acc, x in
Expand All @@ -66,25 +76,31 @@ private func open(_ urls: [URL], withAppWithBundleIdentifier appBundleIdentifier
}

private func open(urls: [URL], withAppAtURL appURL: URL) async throws {
log.info("Using \(appURL) to open \(urls).")
let leave = Activity("Open URLs With Single App").enter(); defer { leave() }
log.info("URLs: \(urls)")
log.info("App: \(appURL.standardizedFileURL.path)")
let configuration = NSWorkspace.OpenConfiguration()
configuration.promptsUserIfNeeded = true
do {
try await workspace.open(urls, withApplicationAt: appURL, configuration: configuration)
log.info("Succeeded with using \(appURL) to open \(urls).")
do {
let leave = Activity("Route Open Into Workspace").enter(); defer { leave() }
try await workspace.open(urls, withApplicationAt: appURL, configuration: configuration)
}
log.info("Open succeeded")
} catch {
log.error("Failed to use \(appURL) to open \(urls): \(error).")
log.error("Open failed: \(error)")
throw error
}
}

private func resolveAppURL(forBundleIdentifier bundleIdentifier: String) -> URL? {
log.info("Resolving URL for app bundle identifier \(bundleIdentifier).")
let leave = Activity("Resolve App By Bundle Identifier").enter(); defer { leave() }
log.info("App bundle identifier: \(bundleIdentifier)")
guard let appURL = workspace.urlForApplication(withBundleIdentifier: bundleIdentifier) else {
log.error("Could not get URL for app with bundle identifier \(bundleIdentifier).")
log.error("No app has bundle identifier \(bundleIdentifier)")
return nil
}
log.info("Resolved app bundle identifier \(bundleIdentifier) into \(appURL).")
log.info("Resolved app: \(appURL.standardizedFileURL.path)")
return appURL
}

Expand Down
42 changes: 42 additions & 0 deletions URLHelperApp/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,48 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>OSLogPreferences</key>
<dict>
<key>com.grigorye.URLHelperApp</key>
<dict>
<key>AppDelegate</key>
<dict>
<key>Enable-Private-Data</key>
<true/>
<key>Level</key>
<dict>
<key>Enable</key>
<string>Debug</string>
<key>Persist</key>
<string>Info</string>
</dict>
</dict>
<key>OutputFromLaunching</key>
<dict>
<key>Enable-Private-Data</key>
<true/>
<key>Level</key>
<dict>
<key>Enable</key>
<string>Debug</string>
<key>Persist</key>
<string>Info</string>
</dict>
</dict>
<key>ScriptBasedURLResolver</key>
<dict>
<key>Enable-Private-Data</key>
<true/>
<key>Level</key>
<dict>
<key>Enable</key>
<string>Debug</string>
<key>Persist</key>
<string>Info</string>
</dict>
</dict>
</dict>
</dict>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
Expand Down
1 change: 1 addition & 0 deletions URLHelperApp/OSActivity.swift
52 changes: 32 additions & 20 deletions URLHelperApp/OutputFromLaunching.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,48 +4,48 @@ import os.log
private let log = Logger(category: "OutputFromLaunching")

func outputFromLaunching(executableURL: URL, arguments: [String]) async throws -> Data {
log.info("Executing \(executableURL.standardizedFileURL.path) with \(arguments).")
let leave = Activity("Shell Output").enter(); defer { leave() }
let captureResultActivity = Activity("Capture Result")
log.info("Executable: \(executableURL.standardizedFileURL.path, privacy: .public)")
log.info("Arguments: \(arguments)")
return try await withCheckedThrowingContinuation { c in
let standardOutputPipe = Pipe()
let standardErrorPipe = Pipe()

let stdoutPipe = Pipe()
let stderrPipe = Pipe()
let terminationHandler = { (process: Process) in
let standardErrorData = standardErrorPipe.fileHandleForReading.readDataToEndOfFile()
log.error("Error data: \(standardErrorData).")
let standardOutputData = standardOutputPipe.fileHandleForReading.readDataToEndOfFile()
log.info("Output data: \(standardOutputData).")
let leave = captureResultActivity.enter(); defer { leave() }
let stdout = stdoutPipe.fileHandleForReading.readDataToEndOfFile()
let stderr = stderrPipe.fileHandleForReading.readDataToEndOfFile()
log.info("Stdout: \(formatted(output: stdout), privacy: .public)")
let terminationReason = process.terminationReason
guard case .exit = terminationReason else {
log.error("Exec failed. Not exited normally: \(String(describing: terminationReason)).")
log.error("Stderr: \(String(data: standardOutputData, encoding: .utf8) ?? "null").")
guard case .exit = process.terminationReason else {
log.error("Stderr: \(formatted(output: stderr), privacy: .private)")
log.error("Failed with reason: \(String(describing: terminationReason))")
throw OutputFromLaunchingError.badTerminationReason(terminationReason)
}
let terminationStatus = process.terminationStatus
guard 0 == terminationStatus else {
log.error("Exec failed. Termination status: \(terminationStatus).")
log.error("Stderr: \(formatted(output: stderr), privacy: .private)")
log.error("Failed with exit status: \(terminationStatus)")
throw OutputFromLaunchingError.badTerminationStatus(terminationStatus)
}
log.info("Exec succeeded.")
log.info("Stdout: \(String(data: standardOutputData, encoding: .utf8) ?? "<non-decodable-from-utf8>").")
return standardOutputData
return stdout
}

let process = {
$0.executableURL = executableURL
$0.arguments = arguments
$0.standardOutput = standardOutputPipe
$0.standardError = standardErrorPipe
$0.standardOutput = stdoutPipe
$0.standardError = stderrPipe
$0.terminationHandler = { process in
let r = Result { try terminationHandler(process) }
c.resume(with: r)
}
return $0
} (Process())

do {
try process.run()
log.info("Launch succeeded")
} catch {
log.error("Launch failed: \(error).")
log.error("Launch failed: \(error)")
c.resume(throwing: error)
}
}
Expand All @@ -55,3 +55,15 @@ enum OutputFromLaunchingError: Error {
case badTerminationReason(Process.TerminationReason)
case badTerminationStatus(Int32)
}

private func formatted(output data: Data) -> String {
guard let utf8 = String(data: data, encoding: .utf8) else {
return "<non-decodable-from-utf8>"
}
return """

```
\(utf8)
```
"""
}
9 changes: 9 additions & 0 deletions URLHelperApp/ScriptBasedURLResolver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
//

import Foundation
import os.log

private let log = Logger(category: "ScriptBasedURLResolver")

class ScriptBasedURLResolver : URLResolver {

Expand All @@ -24,19 +27,25 @@ class ScriptBasedURLResolver : URLResolver {
}

func makeSureResolverScriptExists(resolverURL: URL) async throws -> URL? {
let leave = Activity("Make Sure Resolver Script Exists").enter(); defer { leave() }
log.info("Attempting to copy \(self.bundledResolverURL.path, privacy: .public)")
log.info("Destination: \(resolverURL.standardizedFileURL.path, privacy: .public)")
do {
try fileManager.copyItem(at: bundledResolverURL, to: resolverURL)
return resolverURL
} catch {
switch error {
case CocoaError.fileWriteFileExists:
log.info("The script already exists: we're done")
return resolverURL
case CocoaError.fileWriteNoPermission:
log.info("User permission required")
guard let updatedResolverURL = try await facilitateWriteAccessForURLResolverScript(at: resolverURL) else {
return nil
}
return try await makeSureResolverScriptExists(resolverURL: updatedResolverURL)
default:
log.error("Copy failed: \(error)")
throw error
}
}
Expand Down
3 changes: 2 additions & 1 deletion URLHelperApp/WriteAccessFacilitator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ private var appName: String {
}

func facilitateWriteAccessForURLResolverScript(at url: URL) async throws -> URL? {
try await facilitateWriteAccessViaUserInteraction(to: url, message: String(localized: "Select the location for the resolver script for \(appName)"))
let leave = Activity("Facilitate Write Access To Resolver Script").enter(); defer { leave() }
return try await facilitateWriteAccessViaUserInteraction(to: url, message: String(localized: "Select the location for the resolver script for \(appName)"))
}

@MainActor
Expand Down
Loading