|
| 1 | +// Copyright 2025 Google LLC |
| 2 | +// |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +// you may not use this file except in compliance with the License. |
| 5 | +// You may obtain a copy of the License at |
| 6 | +// |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +// |
| 9 | +// Unless required by applicable law or agreed to in writing, software |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +// See the License for the specific language governing permissions and |
| 13 | +// limitations under the License. |
| 14 | + |
| 15 | +import Foundation |
| 16 | +#if os(Linux) |
| 17 | +import FoundationNetworking |
| 18 | +#endif |
| 19 | +import Logging |
| 20 | + |
| 21 | +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) |
| 22 | +enum AILog { |
| 23 | + /// Log message codes for the Firebase AI SDK |
| 24 | + /// |
| 25 | + /// These codes should ideally not be re-used in order to facilitate matching error codes in |
| 26 | + /// support requests to lines in the SDK. These codes should range between 0 and 999999 to avoid |
| 27 | + /// being truncated in log messages. |
| 28 | + enum MessageCode: Int { |
| 29 | + // Logging Configuration |
| 30 | + case verboseLoggingDisabled = 100 |
| 31 | + case verboseLoggingEnabled = 101 |
| 32 | + |
| 33 | + // API Enablement Errors |
| 34 | + case vertexAIInFirebaseAPIDisabled = 200 |
| 35 | + |
| 36 | + // Generative Model Configuration |
| 37 | + case generativeModelInitialized = 1000 |
| 38 | + case unsupportedGeminiModel = 1001 |
| 39 | + case invalidSchemaFormat = 1002 |
| 40 | + |
| 41 | + // Imagen Model Configuration |
| 42 | + case unsupportedImagenModel = 1200 |
| 43 | + case imagenInvalidJPEGCompressionQuality = 1201 |
| 44 | + |
| 45 | + // Network Errors |
| 46 | + case generativeAIServiceNonHTTPResponse = 2000 |
| 47 | + case loadRequestResponseError = 2001 |
| 48 | + case loadRequestResponseErrorPayload = 2002 |
| 49 | + case loadRequestStreamResponseError = 2003 |
| 50 | + case loadRequestStreamResponseErrorPayload = 2004 |
| 51 | + |
| 52 | + // Parsing Errors |
| 53 | + case loadRequestParseResponseFailedJSON = 3000 |
| 54 | + case loadRequestParseResponseFailedJSONError = 3001 |
| 55 | + case generateContentResponseUnrecognizedFinishReason = 3002 |
| 56 | + case generateContentResponseUnrecognizedBlockReason = 3003 |
| 57 | + case generateContentResponseUnrecognizedBlockThreshold = 3004 |
| 58 | + case generateContentResponseUnrecognizedHarmProbability = 3005 |
| 59 | + case generateContentResponseUnrecognizedHarmCategory = 3006 |
| 60 | + case generateContentResponseUnrecognizedHarmSeverity = 3007 |
| 61 | + case decodedInvalidProtoDateYear = 3008 |
| 62 | + case decodedInvalidProtoDateMonth = 3009 |
| 63 | + case decodedInvalidProtoDateDay = 3010 |
| 64 | + case decodedInvalidCitationPublicationDate = 3011 |
| 65 | + case generateContentResponseUnrecognizedContentModality = 3012 |
| 66 | + case decodedUnsupportedImagenPredictionType = 3013 |
| 67 | + case decodedUnsupportedPartData = 3014 |
| 68 | + case codeExecutionResultUnrecognizedOutcome = 3015 |
| 69 | + case executableCodeUnrecognizedLanguage = 3016 |
| 70 | + case fallbackValueUsed = 3017 |
| 71 | + |
| 72 | + // SDK State Errors |
| 73 | + case generateContentResponseNoCandidates = 4000 |
| 74 | + case generateContentResponseNoText = 4001 |
| 75 | + case appCheckTokenFetchFailed = 4002 |
| 76 | + case generateContentResponseEmptyCandidates = 4003 |
| 77 | + |
| 78 | + // SDK Debugging |
| 79 | + case loadRequestStreamResponseLine = 5000 |
| 80 | + } |
| 81 | + |
| 82 | + /// Subsystem that should be used for all Loggers. |
| 83 | + static let label = "com.google.firebase.GeneratedFirebaseAI" |
| 84 | + |
| 85 | + static let logger = Logger(label: label) |
| 86 | + |
| 87 | + /// The argument required to enable additional logging. |
| 88 | + static let enableArgumentKey = "-FIRDebugEnabled" |
| 89 | + |
| 90 | + static func log(level: Logger.Level, code: MessageCode, _ message: String) { |
| 91 | + let messageCode = String(format: "I-VTX%06d", code.rawValue) |
| 92 | + logger.log(level: level, "\(messageCode) \(message)") |
| 93 | + } |
| 94 | + |
| 95 | + static func error(code: MessageCode, _ message: String) { |
| 96 | + log(level: .error, code: code, message) |
| 97 | + } |
| 98 | + |
| 99 | + static func warning(code: MessageCode, _ message: String) { |
| 100 | + log(level: .warning, code: code, message) |
| 101 | + } |
| 102 | + |
| 103 | + static func notice(code: MessageCode, _ message: String) { |
| 104 | + log(level: .notice, code: code, message) |
| 105 | + } |
| 106 | + |
| 107 | + static func info(code: MessageCode, _ message: String) { |
| 108 | + log(level: .info, code: code, message) |
| 109 | + } |
| 110 | + |
| 111 | + static func debug(code: MessageCode, _ message: String) { |
| 112 | + log(level: .debug, code: code, message) |
| 113 | + } |
| 114 | + |
| 115 | + /// Returns `true` if additional logging has been enabled via a launch argument. |
| 116 | + static func additionalLoggingEnabled() -> Bool { |
| 117 | + return ProcessInfo.processInfo.arguments.contains(enableArgumentKey) |
| 118 | + } |
| 119 | + |
| 120 | + /// Returns the unwrapped optional value if non-nil or returns the fallback value and logs. |
| 121 | + /// |
| 122 | + /// This convenience method is intended for use in place of `optionalValue ?? fallbackValue` with |
| 123 | + /// the addition of logging on use of the fallback value. |
| 124 | + /// |
| 125 | + /// - Parameters: |
| 126 | + /// - optionalValue: The value to unwrap. |
| 127 | + /// - fallbackValue: The fallback (default) value to return when `optionalValue` is `nil`. |
| 128 | + /// - level: The logging level to use for fallback messages; defaults to |
| 129 | + /// `.default`. |
| 130 | + /// - code: The message code to use for fallback messages; defaults to |
| 131 | + /// `MessageCode.fallbackValueUsed`. |
| 132 | + /// - caller: The name of the unwrapped value; defaults to the name of the computed property or |
| 133 | + /// function name from which the unwrapping occurred. |
| 134 | + static func safeUnwrap<T>( |
| 135 | + _ optionalValue: T?, |
| 136 | + fallback fallbackValue: T, |
| 137 | + level: Logger.Level = .debug, |
| 138 | + code: MessageCode = .fallbackValueUsed, |
| 139 | + caller: String = #function |
| 140 | + ) -> T { |
| 141 | + guard let unwrappedValue = optionalValue else { |
| 142 | + AILog.log( |
| 143 | + level: level, code: code, |
| 144 | + """ |
| 145 | + No value specified for '\(caller)' (\(T.self)); using fallback value '\(fallbackValue)'. |
| 146 | + """) |
| 147 | + return fallbackValue |
| 148 | + } |
| 149 | + return unwrappedValue |
| 150 | + } |
| 151 | +} |
0 commit comments