From 1b9cbf5f99ceddc17997982a9464800ee65a3fe1 Mon Sep 17 00:00:00 2001 From: Michael Freno Date: Thu, 15 Jan 2026 14:02:18 -0500 Subject: [PATCH] feat: start better logging solution --- Gaze/AppDelegate.swift | 12 +++ Gaze/Services/LoggingManager.swift | 88 +++++++++++++++++++ .../ScreenCapturePermissionManager.swift | 2 + Gaze/Services/TimerEngine.swift | 21 +++-- run | 21 ++++- 5 files changed, 138 insertions(+), 6 deletions(-) create mode 100644 Gaze/Services/LoggingManager.swift diff --git a/Gaze/AppDelegate.swift b/Gaze/AppDelegate.swift index e9b9250..3224e03 100644 --- a/Gaze/AppDelegate.swift +++ b/Gaze/AppDelegate.swift @@ -8,6 +8,7 @@ import AppKit import Combine import SwiftUI +import os.log @MainActor class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { @@ -17,6 +18,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { private var updateManager: UpdateManager? private var cancellables = Set() private var hasStartedTimers = false + + // Logging manager + private let logger = LoggingManager.shared // Smart Mode services private var fullscreenService: FullscreenDetectionService? @@ -37,6 +41,10 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { func applicationDidFinishLaunching(_ notification: Notification) { // Set activation policy to hide dock icon NSApplication.shared.setActivationPolicy(.accessory) + + // Initialize logging + logger.configureLogging() + logger.appLogger.info("🚀 Application did finish launching") timerEngine = TimerEngine(settingsManager: settingsManager) @@ -103,6 +111,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { private func startTimers() { guard !hasStartedTimers else { return } hasStartedTimers = true + logger.appLogger.info("Starting timers") timerEngine?.start() observeReminderEvents() } @@ -123,6 +132,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { } func applicationWillTerminate(_ notification: Notification) { + logger.appLogger.info(" applicationWill terminate") settingsManager.saveImmediately() timerEngine?.stop() } @@ -144,11 +154,13 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { } @objc private func systemWillSleep() { + logger.systemLogger.info("System will sleep") timerEngine?.handleSystemSleep() settingsManager.saveImmediately() } @objc private func systemDidWake() { + logger.systemLogger.info("System did wake") timerEngine?.handleSystemWake() } diff --git a/Gaze/Services/LoggingManager.swift b/Gaze/Services/LoggingManager.swift new file mode 100644 index 0000000..26dcc61 --- /dev/null +++ b/Gaze/Services/LoggingManager.swift @@ -0,0 +1,88 @@ +// +// LoggingManager.swift +// Gaze +// +// Created by [Your Name] on [Date]. +// + +import Foundation +import os.log + +#if DEBUG +let isLoggingEnabled = true +#else +let isLoggingEnabled = false +#endif + +/// A centralized logging manager that provides structured, subsystem-aware logging +/// for the Gaze application to ensure logs are captured by the run script. +final class LoggingManager { + static let shared = LoggingManager() + + // MARK: - Private Properties + + private let subsystem = "com.mikefreno.Gaze" + + // MARK: - Public Loggers + + /// Logger for general application events + let appLogger = Logger(subsystem: "com.mikefreno.Gaze", category: "Application") + + /// Logger for timer-related events + let timerLogger = Logger(subsystem: "com.mikefreno.Gaze", category: "TimerEngine") + + /// Logger for settings and configuration changes + let settingsLogger = Logger(subsystem: "com.mikefreno.Gaze", category: "Settings") + + /// Logger for smart mode functionality + let smartModeLogger = Logger(subsystem: "com.mikefreno.Gaze", category: "SmartMode") + + /// Logger for UI and window management events + let uiLogger = Logger(subsystem: "com.mikefreno.Gaze", category: "UI") + + /// Logger for system events (sleep/wake) + let systemLogger = Logger(subsystem: "com.mikefreno.Gaze", category: "System") + + // MARK: - Initialization + + private init() { + // Private initializer to enforce singleton pattern + } + + // MARK: - Public Methods + + /// Configure the logging system for verbose output when needed + func configureLogging() { + // For now, we'll use standard OSLog behavior. + // This can be extended in the future to support runtime log level changes. + } + + /// Convenience method for debug logging + func debug(_ message: String, category: String = "General") { + guard isLoggingEnabled else { return } + let logger = Logger(subsystem: subsystem, category: category) + logger.debug("\(message)") + } + + /// Convenience method for info logging + func info(_ message: String, category: String = "General") { + guard isLoggingEnabled else { return } + let logger = Logger(subsystem: subsystem, category: category) + logger.info("\(message)") + } + + /// Convenience method for error logging + func error(_ message: String, category: String = "General") { + guard isLoggingEnabled else { return } + let logger = Logger(subsystem: subsystem, category: category) + logger.error("\(message)") + } + + /// Convenience method for warning logging + func warning(_ message: String, category: String = "General") { + guard isLoggingEnabled else { return } + let logger = Logger(subsystem: subsystem, category: category) + logger.warning("\(message)") + } +} + diff --git a/Gaze/Services/Permissions/ScreenCapturePermissionManager.swift b/Gaze/Services/Permissions/ScreenCapturePermissionManager.swift index c59ac8d..fbdc9b7 100644 --- a/Gaze/Services/Permissions/ScreenCapturePermissionManager.swift +++ b/Gaze/Services/Permissions/ScreenCapturePermissionManager.swift @@ -9,6 +9,7 @@ import AppKit import Combine import CoreGraphics import Foundation +import os.log public enum ScreenCaptureAuthorizationStatus: Equatable { case authorized @@ -71,6 +72,7 @@ final class ScreenCapturePermissionManager: ObservableObject, ScreenCapturePermi } func openSystemSettings() { + LoggingManager.shared.uiLogger.log("sup") // Try different variations let possibleUrls = [ "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenRecording", diff --git a/Gaze/Services/TimerEngine.swift b/Gaze/Services/TimerEngine.swift index 41f5696..1d6abda 100644 --- a/Gaze/Services/TimerEngine.swift +++ b/Gaze/Services/TimerEngine.swift @@ -7,6 +7,7 @@ import Combine import Foundation +import os.log @MainActor class TimerEngine: ObservableObject { @@ -27,6 +28,9 @@ class TimerEngine: ObservableObject { private var fullscreenService: FullscreenDetectionService? private var idleService: IdleMonitoringService? private var cancellables = Set() + + // Logging manager + private let logger = LoggingManager.shared.timerLogger init( settingsManager: any SettingsProviding, @@ -73,10 +77,10 @@ class TimerEngine: ObservableObject { if isFullscreen { pauseAllTimers(reason: .fullscreen) - print("⏸️ Timers paused: fullscreen detected") + logger.info("⏸️ Timers paused: fullscreen detected") } else { resumeAllTimers(reason: .fullscreen) - print("▶️ Timers resumed: fullscreen exited") + logger.info("▶️ Timers resumed: fullscreen exited") } } @@ -85,10 +89,10 @@ class TimerEngine: ObservableObject { if isIdle { pauseAllTimers(reason: .idle) - print("⏸️ Timers paused: user idle") + logger.info("⏸️ Timers paused: user idle") } else { resumeAllTimers(reason: .idle) - print("▶️ Timers resumed: user active") + logger.info("▶️ Timers resumed: user active") } } @@ -163,6 +167,7 @@ class TimerEngine: ObservableObject { } private func updateConfigurations() { + logger.debug("Updating timer configurations") var newStates: [TimerIdentifier: TimerState] = [:] // Update built-in timers @@ -175,6 +180,7 @@ class TimerEngine: ObservableObject { // Timer exists - check if interval changed if existingState.originalIntervalSeconds != config.intervalSeconds { // Interval changed - reset with new interval + logger.debug("Timer interval changed") newStates[identifier] = TimerState( identifier: identifier, intervalSeconds: config.intervalSeconds, @@ -187,6 +193,7 @@ class TimerEngine: ObservableObject { } } else { // Timer was just enabled - create new state + logger.debug("Timer enabled") newStates[identifier] = TimerState( identifier: identifier, intervalSeconds: config.intervalSeconds, @@ -208,6 +215,7 @@ class TimerEngine: ObservableObject { // Check if interval changed if existingState.originalIntervalSeconds != newIntervalSeconds { // Interval changed - reset with new interval + logger.debug("User timer interval changed") newStates[identifier] = TimerState( identifier: identifier, intervalSeconds: newIntervalSeconds, @@ -220,6 +228,7 @@ class TimerEngine: ObservableObject { } } else { // New timer - create state + logger.debug("User timer created") newStates[identifier] = TimerState( identifier: identifier, intervalSeconds: newIntervalSeconds, @@ -373,6 +382,7 @@ class TimerEngine: ObservableObject { /// - Saves current time for elapsed calculation /// - Pauses all active timers func handleSystemSleep() { + logger.debug("System going to sleep") sleepStartTime = timeProvider.now() for (id, var state) in timerStates { state.pauseReasons.insert(.system) @@ -387,6 +397,7 @@ class TimerEngine: ObservableObject { /// - Timers that expired during sleep will trigger immediately (1s delay) /// - Resumes all timers func handleSystemWake() { + logger.debug("System waking up") guard let sleepStart = sleepStartTime else { return } @@ -419,4 +430,4 @@ class TimerEngine: ObservableObject { timerStates[identifier] = updatedState } } -} +} \ No newline at end of file diff --git a/run b/run index 5b753cd..f2eb87e 100755 --- a/run +++ b/run @@ -129,7 +129,26 @@ elif [ "$ACTION" = "run" ]; then if [ -d "$APP_PATH" ]; then echo "🚀 Launching: $APP_PATH" - open "$APP_PATH" + + if [ "$VERBOSE" = true ]; then + echo "📝 Capturing application logs in terminal (Ctrl+C to stop)..." + # Launch the app and capture its logs + open "$APP_PATH" & + APP_PID=$! + + # Wait a moment for app to start, then capture logs + sleep 2 + + # Capture logs from the application using log stream + echo "Logs from Gaze.app will appear below (Ctrl+C to stop):" + echo "================================================================" + /usr/bin/log stream --predicate 'subsystem contains "com.mikefreno.Gaze"' --style compact 2>/dev/null | head -100 + echo "================================================================" + echo "Application runtime logging stopped." + else + # Standard launch without logging + open "$APP_PATH" + fi else echo "⚠️ App not found at expected location, trying fallback..." # Fallback to derived data location