diff --git a/Package.swift b/Package.swift index cf1364965..7d4407db0 100644 --- a/Package.swift +++ b/Package.swift @@ -25,6 +25,8 @@ let package = Package( .package(url: "https://github.com/vapor/jwt-kit.git", from: "4.13.5"), // Only used for DocC generation .package(url: "https://github.com/apple/swift-docc-plugin.git", from: "1.3.0"), + // Local uniffi bindings + .package(path: "../rust-sdks/livekit-uniffi"), ], targets: [ .target( @@ -39,6 +41,7 @@ let package = Package( .product(name: "DequeModule", package: "swift-collections"), .product(name: "OrderedCollections", package: "swift-collections"), .product(name: "JWTKit", package: "jwt-kit"), + .product(name: "LiveKitFFI", package: "livekit-uniffi"), "LKObjCHelpers", ], exclude: [ diff --git a/Package@swift-6.0.swift b/Package@swift-6.0.swift index 6f101cb2e..7b085be46 100644 --- a/Package@swift-6.0.swift +++ b/Package@swift-6.0.swift @@ -26,6 +26,8 @@ let package = Package( .package(url: "https://github.com/vapor/jwt-kit.git", from: "4.13.5"), // Only used for DocC generation .package(url: "https://github.com/apple/swift-docc-plugin.git", from: "1.3.0"), + // Local uniffi bindings + .package(path: "../rust-sdks/livekit-uniffi"), ], targets: [ .target( @@ -40,6 +42,7 @@ let package = Package( .product(name: "DequeModule", package: "swift-collections"), .product(name: "OrderedCollections", package: "swift-collections"), .product(name: "JWTKit", package: "jwt-kit"), + .product(name: "LiveKitFFI", package: "livekit-uniffi"), "LKObjCHelpers", ], exclude: [ diff --git a/Sources/LiveKit/Support/Logger.swift b/Sources/LiveKit/Support/Logger.swift index 5403b4932..6962d6e79 100644 --- a/Sources/LiveKit/Support/Logger.swift +++ b/Sources/LiveKit/Support/Logger.swift @@ -16,6 +16,7 @@ import OSLog internal import LiveKitWebRTC +internal import LiveKitFFI // MARK: - Logger @@ -69,30 +70,36 @@ public struct DisabledLogger: Logger { /// A logger that logs to OSLog /// - Parameter minLevel: The minimum level to log /// - Parameter rtc: Whether to log WebRTC output +/// - Parameter ffi: Whether to log Rust FFI output open class OSLogger: Logger, @unchecked Sendable { private static let subsystem = "io.livekit.sdk" - private let queue = DispatchQueue(label: "io.livekit.oslogger", qos: .utility) + private static let queue = DispatchQueue(label: "io.livekit.oslogger", qos: .utility) + + private nonisolated(unsafe) static var ffiBootstrapped = false + private var logs: [String: OSLog] = [:] private lazy var rtcLogger = LKRTCCallbackLogger() + private var ffiLogForwardTask: Task? private let minLevel: LogLevel - public init(minLevel: LogLevel = .info, rtc: Bool = false) { + public init(minLevel: LogLevel = .info, rtc: Bool = false, ffi: Bool = true) { self.minLevel = minLevel - guard rtc else { return } + if rtc { + startRTCLogForwarding(minLevel: minLevel) + } - let rtcLog = OSLog(subsystem: Self.subsystem, category: "WebRTC") - rtcLogger.severity = minLevel.rtcSeverity - rtcLogger.start { message, severity in - os_log("%{public}@", log: rtcLog, type: severity.osLogType, message) + if ffi { + startFFILogForwarding(minLevel: minLevel) } } deinit { rtcLogger.stop() + ffiLogForwardTask?.cancel() } public func log( @@ -116,7 +123,7 @@ open class OSLogger: Logger, @unchecked Sendable { let metadata = buildScopedMetadataString() - queue.async { + Self.queue.async { func getOSLog(for type: Any.Type) -> OSLog { let typeName = String(describing: type) @@ -132,6 +139,36 @@ open class OSLogger: Logger, @unchecked Sendable { os_log("%{public}@", log: getOSLog(for: type), type: level.osLogType, "\(type).\(function) \(message)\(metadata)") } } + + private func startRTCLogForwarding(minLevel: LogLevel) { + let rtcLog = OSLog(subsystem: Self.subsystem, category: "WebRTC") + + rtcLogger.severity = minLevel.rtcSeverity + rtcLogger.start { message, severity in + os_log("%{public}@", log: rtcLog, type: severity.osLogType, message) + } + } + + private func startFFILogForwarding(minLevel: LogLevel) { + Self.queue.sync { + if !Self.ffiBootstrapped { + logForwardBootstrap(level: minLevel.logForwardFilter) + Self.ffiBootstrapped = true + } + } + + let ffiLog = OSLog(subsystem: Self.subsystem, category: "FFI") + + ffiLogForwardTask = Task(priority: .utility) { + for await entry in AsyncStream(unfolding: logForwardReceive) { + guard !Task.isCancelled else { break } + + let message = "\(entry.target) \(entry.message)" + + os_log("%{public}@", log: ffiLog, type: entry.level.osLogType, message) + } + } + } } // MARK: - Loggable @@ -196,6 +233,15 @@ public enum LogLevel: Int, Sendable, Comparable { } } + var logForwardFilter: LogForwardFilter { + switch self { + case .debug: .debug + case .info: .info + case .warning: .warn + case .error: .error + } + } + @inlinable public static func < (lhs: LogLevel, rhs: LogLevel) -> Bool { lhs.rawValue < rhs.rawValue @@ -214,3 +260,14 @@ extension LKRTCLoggingSeverity { } } } + +extension LogForwardLevel { + var osLogType: OSLogType { + switch self { + case .error: .error + case .warn: .default + case .info: .info + case .debug, .trace: .debug + } + } +} diff --git a/Tests/LiveKitTestSupport/TokenGenerator.swift b/Tests/LiveKitTestSupport/TokenGenerator.swift index bfeac2f80..f8f187b89 100644 --- a/Tests/LiveKitTestSupport/TokenGenerator.swift +++ b/Tests/LiveKitTestSupport/TokenGenerator.swift @@ -14,8 +14,8 @@ * limitations under the License. */ -import JWTKit @testable import LiveKit +import LiveKitFFI public class TokenGenerator { // 30 mins @@ -31,10 +31,6 @@ public class TokenGenerator { public var metadata: String? public var videoGrant: LiveKitJWTPayload.VideoGrant? - // MARK: - Private - - private let signers = JWTSigners() - public init(apiKey: String, apiSecret: String, identity: String, @@ -47,19 +43,40 @@ public class TokenGenerator { } public func sign() throws -> String { - // Add HMAC with SHA-256 signer. - signers.use(.hs256(key: apiSecret)) - - let n = Date().timeIntervalSince1970 + var ffiVideoGrants: VideoGrants? + if let grant = videoGrant { + ffiVideoGrants = VideoGrants( + roomCreate: grant.roomCreate ?? false, + roomList: grant.roomList ?? false, + roomRecord: grant.roomRecord ?? false, + roomAdmin: grant.roomAdmin ?? false, + roomJoin: grant.roomJoin ?? false, + room: grant.room ?? "", + destinationRoom: "", + canPublish: grant.canPublish ?? false, + canSubscribe: grant.canSubscribe ?? false, + canPublishData: grant.canPublishData ?? false, + canPublishSources: grant.canPublishSources ?? [], + canUpdateOwnMetadata: false, + ingressAdmin: false, + hidden: grant.hidden ?? false, + recorder: grant.recorder ?? false + ) + } - let p = LiveKitJWTPayload(exp: .init(value: Date(timeIntervalSince1970: floor(n + ttl))), - iss: .init(stringLiteral: apiKey), - nbf: .init(value: Date(timeIntervalSince1970: floor(n))), - sub: .init(stringLiteral: identity), - name: name, - metadata: metadata, - video: videoGrant) + let credentials = ApiCredentials(key: apiKey, secret: apiSecret) + let options = TokenOptions( + ttl: ttl, + videoGrants: ffiVideoGrants, + sipGrants: nil, + identity: identity, + name: name, + metadata: metadata, + attributes: nil, + sha256: nil, + roomName: videoGrant?.room + ) - return try signers.sign(p) + return try generateToken(options: options, credentials: credentials) } }