From 08f0bc8f6ced7bc80cffdd7cf371b63c105433eb Mon Sep 17 00:00:00 2001 From: Nick Payne Date: Wed, 25 Feb 2026 20:26:39 +0000 Subject: [PATCH] library improvements --- Sources/AudioTeeCLI/AudioTee.swift | 20 ++++----- Sources/AudioTeeCLI/BinaryOutputHandler.swift | 32 ++++++++++++++ Sources/AudioTeeCore/Core/AudioBuffer.swift | 2 +- .../Core/AudioFormatConverter.swift | 17 ++++--- .../Core/AudioFormatManager.swift | 28 +++++------- Sources/AudioTeeCore/Core/AudioRecorder.swift | 40 ++++++++++------- .../AudioTeeCore/Core/AudioTapManager.swift | 22 +++++----- .../AudioTeeCore/Core/AudioTeeErrors.swift | 4 ++ .../Output/BinaryOutputHandler.swift | 28 ------------ .../AudioTeeCore/Utils/AudioTeeLogger.swift | 44 +++++++++++++++++++ Sources/AudioTeeCore/Utils/Logger.swift | 32 +++++++++----- Sources/AudioTeeCore/Utils/Utils.swift | 14 ++---- 12 files changed, 171 insertions(+), 112 deletions(-) create mode 100644 Sources/AudioTeeCLI/BinaryOutputHandler.swift delete mode 100644 Sources/AudioTeeCore/Output/BinaryOutputHandler.swift create mode 100644 Sources/AudioTeeCore/Utils/AudioTeeLogger.swift diff --git a/Sources/AudioTeeCLI/AudioTee.swift b/Sources/AudioTeeCLI/AudioTee.swift index 13ff419..4835d5a 100644 --- a/Sources/AudioTeeCLI/AudioTee.swift +++ b/Sources/AudioTeeCLI/AudioTee.swift @@ -99,11 +99,11 @@ struct AudioTee { func run() throws { setupSignalHandlers() - Logger.info("Starting AudioTee...") + AudioTeeLogging.logger.info("Starting AudioTee...") // Validate chunk duration guard chunkDuration > 0 && chunkDuration <= 5.0 else { - Logger.error( + AudioTeeLogging.logger.error( "Invalid chunk duration", context: ["chunk_duration": String(chunkDuration), "valid_range": "0.0 < duration <= 5.0"]) throw ExitCode.failure @@ -123,7 +123,7 @@ struct AudioTee { do { try audioTapManager.setupAudioTap(with: tapConfig) } catch AudioTeeError.pidTranslationFailed(let failedPIDs) { - Logger.error( + AudioTeeLogging.logger.error( "Failed to translate process IDs to audio objects", context: [ "failed_pids": failedPIDs.map(String.init).joined(separator: ", "), @@ -131,21 +131,21 @@ struct AudioTee { ]) throw ExitCode.failure } catch { - Logger.error( + AudioTeeLogging.logger.error( "Failed to setup audio tap", context: ["error": String(describing: error)]) throw ExitCode.failure } guard let deviceID = audioTapManager.getDeviceID() else { - Logger.error("Failed to get device ID from audio tap manager") + AudioTeeLogging.logger.error("Failed to get device ID from audio tap manager") throw ExitCode.failure } let outputHandler = BinaryAudioOutputHandler(flushAfterWrite: flush) - let recorder = AudioRecorder( + let recorder = try AudioRecorder( deviceID: deviceID, outputHandler: outputHandler, convertToSampleRate: sampleRate, chunkDuration: chunkDuration) - recorder.startRecording() + try recorder.startRecording() // Run until the run loop is stopped (by signal handler) while true { @@ -155,17 +155,17 @@ struct AudioTee { } } - Logger.info("Shutting down...") + AudioTeeLogging.logger.info("Shutting down...") recorder.stopRecording() } private func setupSignalHandlers() { signal(SIGINT) { _ in - Logger.info("Received SIGINT, initiating graceful shutdown...") + AudioTeeLogging.logger.info("Received SIGINT, initiating graceful shutdown...") CFRunLoopStop(CFRunLoopGetMain()) } signal(SIGTERM) { _ in - Logger.info("Received SIGTERM, initiating graceful shutdown...") + AudioTeeLogging.logger.info("Received SIGTERM, initiating graceful shutdown...") CFRunLoopStop(CFRunLoopGetMain()) } } diff --git a/Sources/AudioTeeCLI/BinaryOutputHandler.swift b/Sources/AudioTeeCLI/BinaryOutputHandler.swift new file mode 100644 index 0000000..9f8a5b6 --- /dev/null +++ b/Sources/AudioTeeCLI/BinaryOutputHandler.swift @@ -0,0 +1,32 @@ +import AudioTeeCore +import Foundation + +/// CLI-specific output handler that writes raw PCM audio to stdout +/// and lifecycle messages to stderr via the logger. +class BinaryAudioOutputHandler: AudioOutputHandler { + private let flushAfterWrite: Bool + + init(flushAfterWrite: Bool = false) { + self.flushAfterWrite = flushAfterWrite + } + + func handleAudioPacket(_ packet: AudioPacket) { + // Write raw binary audio data directly to stdout + FileHandle.standardOutput.write(packet.data) + if flushAfterWrite { + fflush(stdout) + } + } + + func handleMetadata(_ metadata: AudioStreamMetadata) { + AudioTeeLogging.logger.writeMessage(.metadata, data: metadata) + } + + func handleStreamStart() { + AudioTeeLogging.logger.writeMessage(.streamStart, data: Optional.none) + } + + func handleStreamStop() { + AudioTeeLogging.logger.writeMessage(.streamStop, data: Optional.none) + } +} diff --git a/Sources/AudioTeeCore/Core/AudioBuffer.swift b/Sources/AudioTeeCore/Core/AudioBuffer.swift index 3eee0a0..10ef775 100644 --- a/Sources/AudioTeeCore/Core/AudioBuffer.swift +++ b/Sources/AudioTeeCore/Core/AudioBuffer.swift @@ -29,7 +29,7 @@ public class AudioBuffer { public func append(_ data: Data) { guard availableBytes + data.count <= maxBufferSize else { - Logger.error( + AudioTeeLogging.logger.error( "Audio buffer overflow", context: [ "requested": String(data.count), diff --git a/Sources/AudioTeeCore/Core/AudioFormatConverter.swift b/Sources/AudioTeeCore/Core/AudioFormatConverter.swift index d689629..f8f7bae 100644 --- a/Sources/AudioTeeCore/Core/AudioFormatConverter.swift +++ b/Sources/AudioTeeCore/Core/AudioFormatConverter.swift @@ -28,7 +28,7 @@ public class AudioFormatConverter { self.targetFormat = targetAVFormat self.avConverter = converter - Logger.debug( + AudioTeeLogging.logger.debug( "Audio converter created", context: [ "source_sample_rate": String(sourceAVFormat.sampleRate), @@ -39,7 +39,7 @@ public class AudioFormatConverter { // Warn about upsampling once during initialization if targetAVFormat.sampleRate > sourceAVFormat.sampleRate { - Logger.info( + AudioTeeLogging.logger.info( "Upsampling audio - this doesn't add frequency content above the original Nyquist limit", context: [ "source_rate": String(sourceAVFormat.sampleRate), @@ -48,7 +48,12 @@ public class AudioFormatConverter { } } - /// Get the target format as AudioStreamBasicDescription + /// The source format this converter reads from. + public var sourceFormatDescription: AudioStreamBasicDescription { + return sourceFormat.streamDescription.pointee + } + + /// The target format this converter produces. public var targetFormatDescription: AudioStreamBasicDescription { return targetFormat.streamDescription.pointee } @@ -67,7 +72,7 @@ public class AudioFormatConverter { let inputBuffer = AVAudioPCMBuffer( pcmFormat: sourceFormat, frameCapacity: AVAudioFrameCount(inputFrameCount)) else { - Logger.error("Failed to create input buffer") + AudioTeeLogging.logger.error("Failed to create input buffer") return packet } @@ -83,7 +88,7 @@ public class AudioFormatConverter { let outputBuffer = AVAudioPCMBuffer( pcmFormat: targetFormat, frameCapacity: AVAudioFrameCount(outputFrameCount)) else { - Logger.error("Failed to create output buffer") + AudioTeeLogging.logger.error("Failed to create output buffer") return packet } @@ -99,7 +104,7 @@ public class AudioFormatConverter { // Check if conversion produced output (regardless of status code) guard outputBuffer.frameLength > 0 else { - Logger.error( + AudioTeeLogging.logger.error( "Audio conversion produced no output", context: [ "status": String(describing: status), diff --git a/Sources/AudioTeeCore/Core/AudioFormatManager.swift b/Sources/AudioTeeCore/Core/AudioFormatManager.swift index fc87d69..c59a19f 100644 --- a/Sources/AudioTeeCore/Core/AudioFormatManager.swift +++ b/Sources/AudioTeeCore/Core/AudioFormatManager.swift @@ -3,25 +3,25 @@ import CoreAudio import Foundation public class AudioFormatManager { - public static func getDeviceFormat(deviceID: AudioObjectID) -> AudioStreamBasicDescription { + public static func getDeviceFormat(deviceID: AudioObjectID) throws -> AudioStreamBasicDescription { // First, wait for the device to become alive/ready let deviceReadyTimeout = 2.0 // 2 seconds max wait let pollInterval = 0.1 // 100ms poll interval let maxPolls = Int(deviceReadyTimeout / pollInterval) - Logger.debug( + AudioTeeLogging.logger.debug( "Waiting for audio device to become ready", context: ["device_id": String(deviceID)]) // Poll device readiness for poll in 1...maxPolls { if isAudioDeviceValid(deviceID) { - Logger.debug( + AudioTeeLogging.logger.debug( "Audio device is ready", context: ["device_id": String(deviceID), "polls": String(poll)]) break } if poll == maxPolls { - Logger.info( + AudioTeeLogging.logger.info( "Device did not become ready within timeout, proceeding anyway", context: [ "device_id": String(deviceID), @@ -30,7 +30,7 @@ public class AudioFormatManager { break } - Logger.info("------- not ready; retrying...") + AudioTeeLogging.logger.info("------- not ready; retrying...") Thread.sleep(forTimeInterval: pollInterval) } @@ -49,11 +49,11 @@ public class AudioFormatManager { deviceID, &propertyAddress, 0, nil, &propertySize, &streamFormat) if status == noErr { - Logger.debug("Successfully retrieved device format", context: ["attempt": String(attempt)]) + AudioTeeLogging.logger.debug("Successfully retrieved device format", context: ["attempt": String(attempt)]) return streamFormat } - Logger.info( + AudioTeeLogging.logger.info( "------- Failed to get stream format after device ready check, retrying...", context: [ "attempt": String(attempt), @@ -69,16 +69,14 @@ public class AudioFormatManager { } // If all attempts failed after device readiness confirmation, this is a genuine error - Logger.error( + AudioTeeLogging.logger.error( "Failed to get device format after device readiness check and retries", context: [ "device_id": String(deviceID), "device_was_ready": "true", ]) - fatalError( - "Failed to get stream format from ready device: \(deviceID). This indicates a Core Audio subsystem error." - ) + throw AudioTeeError.deviceFormatUnavailable(deviceID) } static func createMetadata(for format: AudioStreamBasicDescription) -> AudioStreamMetadata { @@ -94,14 +92,8 @@ public class AudioFormatManager { ) } - public static func writeMetadata(for format: AudioStreamBasicDescription) { - let metadata = createMetadata(for: format) - Logger.writeMessage(.metadata, data: metadata) - Logger.writeMessage(.streamStart, data: Optional.none) - } - public static func logFormatInfo(_ format: AudioStreamBasicDescription) { - Logger.debug( + AudioTeeLogging.logger.debug( "Using device's native format", context: [ "channels": String(format.mChannelsPerFrame), diff --git a/Sources/AudioTeeCore/Core/AudioRecorder.swift b/Sources/AudioTeeCore/Core/AudioRecorder.swift index 9a7aecd..ade0e6e 100644 --- a/Sources/AudioTeeCore/Core/AudioRecorder.swift +++ b/Sources/AudioTeeCore/Core/AudioRecorder.swift @@ -10,15 +10,25 @@ public class AudioRecorder { private var outputHandler: AudioOutputHandler private var converter: AudioFormatConverter? + /// The audio format this recorder produces (after any conversion). + public var outputFormat: AudioStreamBasicDescription { + return finalFormat + } + + /// Whether this recorder is performing sample rate conversion. + public var isConverting: Bool { + return converter != nil + } + public init( deviceID: AudioObjectID, outputHandler: AudioOutputHandler, convertToSampleRate: Double? = nil, chunkDuration: Double = 0.2 - ) { + ) throws { self.deviceID = deviceID self.outputHandler = outputHandler // Get source format and set up conversion if requested - let sourceFormat = AudioFormatManager.getDeviceFormat(deviceID: deviceID) + let sourceFormat = try AudioFormatManager.getDeviceFormat(deviceID: deviceID) // Set up the audio buffer using source format and configurable chunk duration self.audioBuffer = AudioBuffer(format: sourceFormat, chunkDuration: chunkDuration) @@ -26,7 +36,7 @@ public class AudioRecorder { if let targetSampleRate = convertToSampleRate { // Validate sample rate guard AudioFormatConverter.isValidSampleRate(targetSampleRate) else { - Logger.error("Invalid sample rate", context: ["sample_rate": String(targetSampleRate)]) + AudioTeeLogging.logger.error("Invalid sample rate", context: ["sample_rate": String(targetSampleRate)]) self.converter = nil self.finalFormat = sourceFormat return @@ -36,10 +46,10 @@ public class AudioRecorder { let converter = try AudioFormatConverter.toSampleRate(targetSampleRate, from: sourceFormat) self.converter = converter self.finalFormat = converter.targetFormatDescription - Logger.info( + AudioTeeLogging.logger.info( "Audio conversion enabled", context: ["target_sample_rate": String(targetSampleRate)]) } catch { - Logger.error( + AudioTeeLogging.logger.error( "Failed to create audio converter, using original format", context: ["error": String(describing: error)]) self.converter = nil @@ -51,8 +61,8 @@ public class AudioRecorder { } } - public func startRecording() { - Logger.debug("Starting audio recording") + public func startRecording() throws { + AudioTeeLogging.logger.debug("Starting audio recording") // Log format info and send metadata for final format AudioFormatManager.logFormatInfo(finalFormat) @@ -60,15 +70,15 @@ public class AudioRecorder { outputHandler.handleMetadata(metadata) outputHandler.handleStreamStart() - setupAndStartIOProc() + try setupAndStartIOProc() - Logger.info("Audio device started successfully") + AudioTeeLogging.logger.info("Audio device started successfully") } // Note to self, what about installTap? Would require audio engine and a node? // No; AudioEngine.installTap() can only fire as often as 100ms. too slow for us - private func setupAndStartIOProc() { - Logger.debug("Creating IO proc") + private func setupAndStartIOProc() throws { + AudioTeeLogging.logger.debug("Creating IO proc") var status = AudioDeviceCreateIOProcID( deviceID, { @@ -82,15 +92,15 @@ public class AudioRecorder { ) guard status == noErr else { - fatalError("Failed to create IO proc: \(status)") + throw AudioTeeError.ioProcCreationFailed(status) } - Logger.debug("Starting audio device") + AudioTeeLogging.logger.debug("Starting audio device") status = AudioDeviceStart(deviceID, ioProcID) if status != noErr { cleanupIOProc() - fatalError("Failed to start audio device: \(status). Device ID: \(deviceID)") + throw AudioTeeError.deviceStartFailed(status) } } @@ -99,7 +109,7 @@ public class AudioRecorder { let firstBuffer = bufferList.mBuffers guard firstBuffer.mData != nil && firstBuffer.mDataByteSize > 0 else { - "Warning: Received empty audio buffer".print(to: .standardError) + AudioTeeLogging.logger.error("Received empty audio buffer") return noErr } diff --git a/Sources/AudioTeeCore/Core/AudioTapManager.swift b/Sources/AudioTeeCore/Core/AudioTapManager.swift index 9594531..ef4c9fc 100644 --- a/Sources/AudioTeeCore/Core/AudioTapManager.swift +++ b/Sources/AudioTeeCore/Core/AudioTapManager.swift @@ -10,7 +10,7 @@ public class AudioTapManager { public init() {} deinit { - Logger.debug("Cleaning up audio tap manager") + AudioTeeLogging.logger.debug("Cleaning up audio tap manager") if let tapID = tapID { AudioHardwareDestroyProcessTap(tapID) @@ -25,7 +25,7 @@ public class AudioTapManager { /// Sets up the audio tap and aggregate device public func setupAudioTap(with config: TapConfiguration) throws { - Logger.debug("Setting up audio tap manager") + AudioTeeLogging.logger.debug("Setting up audio tap manager") tapID = try createSystemAudioTap(with: config) deviceID = try createAggregateDevice() @@ -36,7 +36,7 @@ public class AudioTapManager { try addTapToAggregateDevice(tapID: tapID, deviceID: deviceID) - Logger.debug("Audio tap manager setup complete") + AudioTeeLogging.logger.debug("Audio tap manager setup complete") } /// Returns the aggregate device ID for recording @@ -45,7 +45,7 @@ public class AudioTapManager { } private func createSystemAudioTap(with config: TapConfiguration) throws -> AudioObjectID { - Logger.debug("Creating tap description") + AudioTeeLogging.logger.debug("Creating tap description") let description = CATapDescription() description.name = "audiotee-tap" @@ -58,7 +58,7 @@ public class AudioTapManager { description.deviceUID = nil // system default description.stream = 0 // first stream of output device - Logger.debug( + AudioTeeLogging.logger.debug( "Tap description configured", context: [ "name": description.name, @@ -69,14 +69,14 @@ public class AudioTapManager { ]) // Create the tap - Logger.debug("Creating tap") + AudioTeeLogging.logger.debug("Creating tap") var tapID = AudioObjectID(kAudioObjectUnknown) let status = AudioHardwareCreateProcessTap(description, &tapID) - Logger.debug( + AudioTeeLogging.logger.debug( "AudioHardwareCreateProcessTap completed", context: ["status": String(status)]) guard status == kAudioHardwareNoError else { - Logger.error("Failed to create audio tap", context: ["status": String(status)]) + AudioTeeLogging.logger.error("Failed to create audio tap", context: ["status": String(status)]) throw AudioTeeError.tapCreationFailed(status) } @@ -88,7 +88,7 @@ public class AudioTapManager { tapID, &propertyAddress, 0, nil, &propertySize, &streamDescription) if formatStatus == noErr { - Logger.debug( + AudioTeeLogging.logger.debug( "Tap format retrieved", context: [ "channels": String(streamDescription.mChannelsPerFrame), @@ -115,7 +115,7 @@ public class AudioTapManager { let status = AudioHardwareCreateAggregateDevice(description as CFDictionary, &deviceID) guard status == kAudioHardwareNoError else { - Logger.error("Failed to create aggregate device", context: ["status": String(status)]) + AudioTeeLogging.logger.error("Failed to create aggregate device", context: ["status": String(status)]) throw AudioTeeError.aggregateDeviceCreationFailed(status) } @@ -142,7 +142,7 @@ public class AudioTapManager { } guard status == kAudioHardwareNoError else { - Logger.error( + AudioTeeLogging.logger.error( "Failed to add tap to aggregate device", context: ["status": String(status)]) throw AudioTeeError.tapAssignmentFailed(status) } diff --git a/Sources/AudioTeeCore/Core/AudioTeeErrors.swift b/Sources/AudioTeeCore/Core/AudioTeeErrors.swift index 0accec5..acfbbfd 100644 --- a/Sources/AudioTeeCore/Core/AudioTeeErrors.swift +++ b/Sources/AudioTeeCore/Core/AudioTeeErrors.swift @@ -1,3 +1,4 @@ +import CoreAudio import Foundation // MARK: - Core AudioTee Errors @@ -8,6 +9,9 @@ public enum AudioTeeError: Error { case aggregateDeviceCreationFailed(OSStatus) case tapAssignmentFailed(OSStatus) case pidTranslationFailed([Int32]) + case deviceFormatUnavailable(AudioObjectID) + case ioProcCreationFailed(OSStatus) + case deviceStartFailed(OSStatus) } // MARK: - Audio Format Conversion Errors diff --git a/Sources/AudioTeeCore/Output/BinaryOutputHandler.swift b/Sources/AudioTeeCore/Output/BinaryOutputHandler.swift deleted file mode 100644 index 703026c..0000000 --- a/Sources/AudioTeeCore/Output/BinaryOutputHandler.swift +++ /dev/null @@ -1,28 +0,0 @@ -import Foundation - -public class BinaryAudioOutputHandler: AudioOutputHandler { - private let flushAfterWrite: Bool - - public init(flushAfterWrite: Bool = false) { - self.flushAfterWrite = flushAfterWrite - } - public func handleAudioPacket(_ packet: AudioPacket) { - // Write raw binary audio data directly to stdout - FileHandle.standardOutput.write(packet.data) - if flushAfterWrite { - fflush(stdout) - } - } - - public func handleMetadata(_ metadata: AudioStreamMetadata) { - Logger.writeMessage(.metadata, data: metadata) - } - - public func handleStreamStart() { - Logger.writeMessage(.streamStart, data: Optional.none) - } - - public func handleStreamStop() { - Logger.writeMessage(.streamStop, data: Optional.none) - } -} diff --git a/Sources/AudioTeeCore/Utils/AudioTeeLogger.swift b/Sources/AudioTeeCore/Utils/AudioTeeLogger.swift new file mode 100644 index 0000000..98abf50 --- /dev/null +++ b/Sources/AudioTeeCore/Utils/AudioTeeLogger.swift @@ -0,0 +1,44 @@ +import Foundation + +// MARK: - Logging protocol + +/// Protocol that library consumers implement to receive log output. +/// The library never writes to stderr directly — it calls through this. +public protocol AudioTeeLogger { + func debug(_ message: String, context: [String: String]?) + func info(_ message: String, context: [String: String]?) + func error(_ message: String, context: [String: String]?) + + /// Called for structured lifecycle messages (metadata, stream_start, stream_stop). + /// Default implementation is a no-op — pure library consumers get metadata + /// via AudioOutputHandler instead. + func writeMessage(_ type: MessageType, data: T?) +} + +// MARK: - Defaults + +extension AudioTeeLogger { + /// Library consumers typically don't need structured message output; + /// they receive metadata via the AudioOutputHandler protocol instead. + public func writeMessage(_ type: MessageType, data: T?) {} + + /// Convenience overloads so callers can omit context when it's nil. + public func debug(_ message: String) { debug(message, context: nil) } + public func info(_ message: String) { info(message, context: nil) } + public func error(_ message: String) { error(message, context: nil) } +} + +// MARK: - Global logging configuration + +/// Global logger instance. Defaults to StderrJSONLogger (CLI behavior). +/// Library consumers can replace this before calling any AudioTeeCore API. +/// +/// // Silence all logging: +/// AudioTeeLogging.logger = NullLogger() +/// +/// // Custom logging: +/// AudioTeeLogging.logger = MyOSLogLogger() +/// +public enum AudioTeeLogging { + nonisolated(unsafe) public static var logger: AudioTeeLogger = StderrJSONLogger() +} diff --git a/Sources/AudioTeeCore/Utils/Logger.swift b/Sources/AudioTeeCore/Utils/Logger.swift index b19139e..9e2b8e2 100644 --- a/Sources/AudioTeeCore/Utils/Logger.swift +++ b/Sources/AudioTeeCore/Utils/Logger.swift @@ -1,7 +1,10 @@ import Foundation -public class Logger { - nonisolated(unsafe) private static let dateFormatter: ISO8601DateFormatter = { +/// Default logger implementation that writes JSON messages to stderr. +/// This is the CLI-appropriate logger; library consumers can replace it +/// via AudioTeeLogging.logger. +public class StderrJSONLogger: AudioTeeLogger { + private let dateFormatter: ISO8601DateFormatter = { let formatter = ISO8601DateFormatter() formatter.formatOptions = [ .withInternetDateTime, @@ -10,17 +13,22 @@ public class Logger { return formatter }() - private static let jsonEncoder: JSONEncoder = { + private let jsonEncoder: JSONEncoder = { let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .custom { date, encoder in - var container = encoder.singleValueContainer() - try container.encode(dateFormatter.string(from: date)) - } return encoder }() - // Write any message with the unified envelope - public static func writeMessage(_ type: MessageType, data: T? = nil) { + public init() { + // Configured in init because stored property initializers can't + // reference other instance properties (self.dateFormatter). + jsonEncoder.dateEncodingStrategy = .custom { [dateFormatter] date, encoder in + var container = encoder.singleValueContainer() + try container.encode(dateFormatter.string(from: date)) + } + } + + // Write any message with the unified envelope to stderr + public func writeMessage(_ type: MessageType, data: T?) { let message = Message(type: type, data: data) do { let jsonData = try jsonEncoder.encode(message) @@ -32,17 +40,17 @@ public class Logger { } // Convenience methods for different message types - public static func info(_ message: String, context: [String: String]? = nil) { + public func info(_ message: String, context: [String: String]? = nil) { let logData = LogData(message: message, context: context) writeMessage(.info, data: logData) } - public static func error(_ message: String, context: [String: String]? = nil) { + public func error(_ message: String, context: [String: String]? = nil) { let logData = LogData(message: message, context: context) writeMessage(.error, data: logData) } - public static func debug(_ message: String, context: [String: String]? = nil) { + public func debug(_ message: String, context: [String: String]? = nil) { let logData = LogData(message: message, context: context) writeMessage(.debug, data: logData) } diff --git a/Sources/AudioTeeCore/Utils/Utils.swift b/Sources/AudioTeeCore/Utils/Utils.swift index f4ca4d3..b1ceeaa 100644 --- a/Sources/AudioTeeCore/Utils/Utils.swift +++ b/Sources/AudioTeeCore/Utils/Utils.swift @@ -15,7 +15,7 @@ func isAudioDeviceValid(_ deviceID: AudioObjectID) -> Bool { let valid = status == kAudioHardwareNoError && isAlive == 1 - Logger.debug( + AudioTeeLogging.logger.debug( "Checked device validity", context: [ "device_id": String(deviceID), @@ -63,7 +63,7 @@ func translatePIDsToProcessObjects(_ pids: [Int32]) throws -> [AudioObjectID] { if status == kAudioHardwareNoError && processObject != kAudioObjectUnknown { processObjects.append(processObject) - Logger.debug( + AudioTeeLogging.logger.debug( "Translated PID to process object", context: [ "pid": String(pid), @@ -71,7 +71,7 @@ func translatePIDsToProcessObjects(_ pids: [Int32]) throws -> [AudioObjectID] { ]) } else { failedPIDs.append(pid) - Logger.debug( + AudioTeeLogging.logger.debug( "Failed to translate PID to process object", context: [ "pid": String(pid), @@ -87,11 +87,3 @@ func translatePIDsToProcessObjects(_ pids: [Int32]) throws -> [AudioObjectID] { return processObjects } - -extension String { - func print(to fileHandle: FileHandle) { - if let data = (self + "\n").data(using: .utf8) { - fileHandle.write(data) - } - } -}