library improvements
This commit is contained in:
@@ -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<T: Codable>(_ 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<T: Codable>(_ 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()
|
||||
}
|
||||
@@ -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<T: Codable>(_ 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<T: Codable>(_ 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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user