diff --git a/Sources/Internal/CodableTimestamp.swift b/Sources/Internal/CodableTimestamp.swift index 714b578..5864bd8 100644 --- a/Sources/Internal/CodableTimestamp.swift +++ b/Sources/Internal/CodableTimestamp.swift @@ -52,10 +52,9 @@ extension CodableTimestamp { .firstMatch(in: timestampString, range: NSRange(location: 0, length: timestampString.count)) != nil else { - FirebaseLogger.dataConnect - .error( - "Timestamp string format doesn't support." - ) + DataConnectLogger.error( + "Timestamp string format \(timestampString) is not supported." + ) throw DataConnectError.invalidTimestampFormat } diff --git a/Sources/Internal/FirebaseLogger/FirebaseLogger.swift b/Sources/Internal/FirebaseLogger/FirebaseLogger.swift deleted file mode 100644 index 838f441..0000000 --- a/Sources/Internal/FirebaseLogger/FirebaseLogger.swift +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import Foundation -import OSLog - -@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) -public class FirebaseLogger { - let subsystem: String = "com.google.firebase" - - let category: String - - private let logger: Logger - - public init(category: String) { - self.category = category - logger = Logger(subsystem: subsystem, category: category) - } - - public func notice(_ message: String) { - logger.notice("\(message)") - } - - public func info(_ message: String) { - logger.info("\(message)") - } - - public func debug(_ message: String) { - logger.debug("\(message)") - } - - public func warning(_ message: String) { - logger.warning("\(message)") - } - - public func error(_ message: String) { - logger.error("\(message)") - } - - public func fault(_ message: String) { - logger.fault("\(message)") - } -} diff --git a/Sources/Internal/GrpcClient.swift b/Sources/Internal/GrpcClient.swift index 29071fc..5f41f24 100644 --- a/Sources/Internal/GrpcClient.swift +++ b/Sources/Internal/GrpcClient.swift @@ -18,10 +18,10 @@ import Foundation @preconcurrency import FirebaseAuth import FirebaseCore import GRPC +import Logging import NIOCore import NIOHPACK import NIOPosix -import OSLog import SwiftProtobuf @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) @@ -50,9 +50,6 @@ actor GrpcClient: CustomStringConvertible { private let callerSDKType: CallerSDKType - // Remove debug flag when logging privacy is properly handled. - private let debugEnable = false - enum RequestHeaders { static let googRequestParamsHeader = "x-goog-request-params" static let authorizationHeader = "x-firebase-auth-token" @@ -65,11 +62,8 @@ actor GrpcClient: CustomStringConvertible { private lazy var client: FirebaseDataConnectAsyncClient? = { do { - if debugEnable == true { - FirebaseLogger.dataConnect.debug("\(self.description) initialization starts.") - } else { - FirebaseLogger.dataConnect.debug("GrpcClient initialization starts.") - } + DataConnectLogger + .debug("GrpcClient: \(self.description, privacy: .private) initialization starts.") let group = PlatformSupport.makeEventLoopGroup(loopCount: threadPoolSize) let channel = try GRPCChannelPool.with( target: .host(serverSettings.host, port: serverSettings.port), @@ -78,18 +72,12 @@ actor GrpcClient: CustomStringConvertible { .plaintext, eventLoopGroup: group ) - if debugEnable == true { - FirebaseLogger.dataConnect.debug("\(self.description) has been created.") - } else { - FirebaseLogger.dataConnect.debug("GrpcClient has been created.") - } + DataConnectLogger + .debug("GrpcClient: \(self.description, privacy: .private) has been created.") return FirebaseDataConnectAsyncClient(channel: channel) } catch { - if debugEnable == true { - FirebaseLogger.dataConnect.error("Error:\(error) when creating \(self.description).") - } else { - FirebaseLogger.dataConnect.debug("Error:\(error) when creating GrpcClient.") - } + DataConnectLogger + .debug("Error:\(error) when creating GrpcClient: \(self.description, privacy: .private).") return nil } }() @@ -128,7 +116,6 @@ actor GrpcClient: CustomStringConvertible { googRequestHeaderValue = "location=\(self.connectorConfig.location)&frontend=data" description = """ - GrpcClient: \ projectId=\(projectId) \ connector=\(connectorConfig.connector) \ host=\(serverSettings.host) \ @@ -143,8 +130,7 @@ actor GrpcClient: CustomStringConvertible { .Type) async throws -> OperationResult { guard let client else { - FirebaseLogger.dataConnect - .error("When calling executeQuery(), grpc client has not been configured.") + DataConnectLogger.error("When calling executeQuery(), grpc client has not been configured.") throw DataConnectError.grpcNotConfigured } @@ -153,49 +139,28 @@ actor GrpcClient: CustomStringConvertible { connectorName: connectorName, request: request ) + let requestString = try grpcRequest.jsonString() do { - if debugEnable == true { - try FirebaseLogger.dataConnect - .debug("executeQuery() sends grpc request: \(grpcRequest.jsonString()).") - } else { - try FirebaseLogger.dataConnect - .debug("executeQuery() sends grpc request.") - } + DataConnectLogger + .debug("executeQuery() sends grpc request: \(requestString, privacy: .private).") let results = try await client.executeQuery(grpcRequest, callOptions: createCallOptions()) - if debugEnable == true { - try FirebaseLogger.dataConnect - .debug("executeQuery() receives response: \(results.jsonString()).") - } else { - try FirebaseLogger.dataConnect - .debug("executeQuery() receives response.") - } + let resultsString = try results.jsonString() + DataConnectLogger + .debug("executeQuery() receives response: \(resultsString, privacy: .private).") // Not doing error decoding here if let decodedResults = try codec.decode(result: results.data, asType: resultType) { return OperationResult(data: decodedResults) } else { // In future, set this as error in OperationResult - if debugEnable == true { - try FirebaseLogger.dataConnect - .error("executeQuery() response: \(results.jsonString()) decode failed.") - } else { - try FirebaseLogger.dataConnect - .error("executeQuery() response decode failed.") - } + DataConnectLogger + .debug("executeQuery() response: \(resultsString, privacy: .private) decode failed.") throw DataConnectError.decodeFailed } } catch { - if debugEnable == true { - try FirebaseLogger.dataConnect - .error( - "executeQuery() with request: \(grpcRequest.jsonString()) grpc call FAILED with \(error)." - ) - } else { - try FirebaseLogger.dataConnect - .error( - "executeQuery() grpc call FAILED with \(error)." - ) - } + DataConnectLogger.error( + "executeQuery(): \(requestString, privacy: .private) grpc call FAILED with \(error)." + ) throw error } } @@ -206,7 +171,7 @@ actor GrpcClient: CustomStringConvertible { .Type) async throws -> OperationResult { guard let client else { - FirebaseLogger.dataConnect + DataConnectLogger .error("When calling executeMutation(), grpc client has not been configured.") throw DataConnectError.grpcNotConfigured } @@ -217,46 +182,26 @@ actor GrpcClient: CustomStringConvertible { request: request ) + let requestString = try grpcRequest.jsonString() + do { - if debugEnable == true { - try FirebaseLogger.dataConnect - .debug("executeMutation() sends grpc request: \(grpcRequest.jsonString()).") - } else { - try FirebaseLogger.dataConnect - .debug("executeMutation() sends grpc request.") - } + DataConnectLogger + .debug("executeMutation() sends grpc request: \(requestString, privacy: .private).") let results = try await client.executeMutation(grpcRequest, callOptions: createCallOptions()) - if debugEnable == true { - try FirebaseLogger.dataConnect - .debug("executeMutation() receives response: \(results.jsonString()).") - } else { - try FirebaseLogger.dataConnect - .debug("executeMutation() receives response.") - } + let resultsString = try results.jsonString() + DataConnectLogger + .debug("executeMutation() receives response: \(resultsString, privacy: .private).") if let decodedResults = try codec.decode(result: results.data, asType: resultType) { return OperationResult(data: decodedResults) } else { - if debugEnable == true { - try FirebaseLogger.dataConnect - .error("executeMutation() response: \(results.jsonString()) decode failed.") - } else { - try FirebaseLogger.dataConnect - .error("executeMutation() response decode failed.") - } + DataConnectLogger + .debug("executeMutation() response: \(resultsString, privacy: .private) decode failed.") throw DataConnectError.decodeFailed } } catch { - if debugEnable == true { - try FirebaseLogger.dataConnect - .error( - "executeMutation() with request: \(grpcRequest.jsonString()) grpc call FAILED with \(error)." - ) - } else { - try FirebaseLogger.dataConnect - .error( - "executeMutation() grpc call FAILED with \(error)." - ) - } + DataConnectLogger.error( + "executeMutation(): \(requestString, privacy: .private) grpc call FAILED with \(error)." + ) throw error } } @@ -274,18 +219,12 @@ actor GrpcClient: CustomStringConvertible { do { if let token = try await auth.currentUser?.getIDToken() { headers.add(name: RequestHeaders.authorizationHeader, value: "\(token)") - if debugEnable == true { - FirebaseLogger.dataConnect - .debug("Auth token added: \(token)") - } else { - FirebaseLogger.dataConnect - .debug("Auth token added.") - } + DataConnectLogger.debug("Auth token added.") } else { - FirebaseLogger.dataConnect.debug("No auth token available. Not adding auth header.") + DataConnectLogger.debug("No auth token available. Not adding auth header.") } } catch { - FirebaseLogger.dataConnect + DataConnectLogger .debug("Cannot get auth token successfully due to: \(error). Not adding auth header.") } @@ -293,25 +232,26 @@ actor GrpcClient: CustomStringConvertible { do { if let token = try await appCheck?.token(forcingRefresh: false) { headers.add(name: RequestHeaders.appCheckHeader, value: token.token) - if debugEnable == true { - FirebaseLogger.dataConnect - .debug("App Check token added: \(token.token)") - } else { - FirebaseLogger.dataConnect - .debug("App Check token added.") - } + DataConnectLogger.debug("App Check token added.") } else { - FirebaseLogger.dataConnect - .debug("App Check token unavailable. Not adding App Check header.") + DataConnectLogger.debug("App Check token unavailable. Not adding App Check header.") } } catch { - FirebaseLogger.dataConnect - .debug( - "Cannot get App Check token successfully due to: \(error). Not adding App Check header." - ) + DataConnectLogger.debug( + "Cannot get App Check token successfully due to: \(error). Not adding App Check header." + ) + } + + var options = CallOptions(customMetadata: headers) + + // Enable GRPC tracing + if DataConnectLogger.logLevel.rawValue >= FirebaseLoggerLevel.debug.rawValue, + DataConnectLogger.privateLoggingEnabled == false { + var logger = Logger(label: "com.google.firebase.dataconnect.grpc") + logger.logLevel = .trace + options.logger = logger } - let options = CallOptions(customMetadata: headers) return options } } diff --git a/Sources/Internal/Logger/DataConnectLogger.swift b/Sources/Internal/Logger/DataConnectLogger.swift new file mode 100644 index 0000000..f4dc679 --- /dev/null +++ b/Sources/Internal/Logger/DataConnectLogger.swift @@ -0,0 +1,94 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import FirebaseCore +import os + +let privateLogDisabledArgument = "-FIRPrivateLogDisabled" + +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) +class DataConnectLogger { + static let logger = Logger( + subsystem: "com.google.firebase", + category: "[FirebaseDataConnect]" + ) + + static let privateLoggingEnabled: Bool = { + let arguments = ProcessInfo.processInfo.arguments + if arguments.contains(privateLogDisabledArgument) { + DataConnectLogger.debug("DataConnect private logging disabled.") + return false + } else { + DataConnectLogger.debug("DataConnect private logging enabled.") + return true + } + }() + + private static let logPrefix = "\(Version.sdkVersion) - [FirebaseDataConnect]" + + static var logLevel: FirebaseLoggerLevel { + return FirebaseConfiguration.shared.loggerLevel() + } + + static func error(_ message: String, code: MessageCode = .placeHolder) { + if logLevel.rawValue >= FirebaseLoggerLevel.error.rawValue { + let messageCode = String(format: "I-FDC%06d", code.rawValue) + logger.error("\(logPrefix)[\(messageCode)] \(message)") + } + } + + static func warning(_ message: String, code: MessageCode = .placeHolder) { + if logLevel.rawValue >= FirebaseLoggerLevel.warning.rawValue { + let messageCode = String(format: "I-FDC%06d", code.rawValue) + logger.warning("\(logPrefix)[\(messageCode)] \(message)") + } + } + + static func notice(_ message: String, code: MessageCode = .placeHolder) { + if logLevel.rawValue >= FirebaseLoggerLevel.notice.rawValue { + let messageCode = String(format: "I-FDC%06d", code.rawValue) + logger.notice("\(logPrefix)[\(messageCode)] \(message)") + } + } + + static func info(_ message: String, code: MessageCode = .placeHolder) { + if logLevel.rawValue >= FirebaseLoggerLevel.info.rawValue { + let messageCode = String(format: "I-FDC%06d", code.rawValue) + logger.info("\(logPrefix)[\(messageCode)] \(message)") + } + } + + static func debug(_ message: String, code: MessageCode = .placeHolder) { + if logLevel.rawValue >= FirebaseLoggerLevel.debug.rawValue { + let messageCode = String(format: "I-FDC%06d", code.rawValue) + logger.debug("\(logPrefix)[\(messageCode)] \(message)") + } + } +} + +enum LogPrivacy { + case `public` + case `private` +} + +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) +extension DefaultStringInterpolation { + mutating func appendInterpolation(_ value: String, privacy: LogPrivacy = .public) { + if privacy == .private, DataConnectLogger.privateLoggingEnabled { + appendLiteral("") + } else { + appendLiteral(value) + } + } +} diff --git a/Sources/Internal/FirebaseLogger/DataConnectLogger.swift b/Sources/Internal/Logger/DataConnectMessageCode.swift similarity index 79% rename from Sources/Internal/FirebaseLogger/DataConnectLogger.swift rename to Sources/Internal/Logger/DataConnectMessageCode.swift index 199c9a8..2136e76 100644 --- a/Sources/Internal/FirebaseLogger/DataConnectLogger.swift +++ b/Sources/Internal/Logger/DataConnectMessageCode.swift @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) -extension FirebaseLogger { - static let dataConnect = FirebaseLogger(category: "data_connect") +enum MessageCode: Int { + // DataConnect Logging message code should be align with backend and is TBD + case placeHolder = 0 }