TelemetryLogger.swift 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. // Copyright 2021 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. import FirebaseCore
  15. import Foundation
  16. import GoogleDataTransport
  17. /// Extension to set Firebase app info.
  18. extension SystemInfo {
  19. mutating func setAppInfo(apiKey: String?, projectID: String?) {
  20. appID = Bundle.main.bundleIdentifier ?? "unknownBundleID"
  21. let appVersionKey = "CFBundleShortVersionString"
  22. appVersion = Bundle.main.infoDictionary?[appVersionKey] as? String ?? "unknownAppVersion"
  23. mlSdkVersion = FirebaseVersion()
  24. self.apiKey = apiKey ?? "unknownAPIKey"
  25. firebaseProjectID = projectID ?? "unknownProjectID"
  26. }
  27. }
  28. /// Extension to set model info.
  29. extension ModelInfo {
  30. mutating func setModelInfo(modelName: String, modelHash: String) {
  31. name = modelName
  32. if !modelHash.isEmpty {
  33. hash = modelHash
  34. }
  35. modelType = .custom
  36. }
  37. }
  38. /// Extension to set model options.
  39. extension ModelOptions {
  40. mutating func setModelOptions(modelName: String, modelHash: String) {
  41. var modelInfo = ModelInfo()
  42. modelInfo.setModelInfo(modelName: modelName, modelHash: modelHash)
  43. self.modelInfo = modelInfo
  44. }
  45. }
  46. /// Extension to build model delete log event.
  47. extension DeleteModelLogEvent {
  48. mutating func setEvent(modelType: ModelInfo.ModelType = .custom, isSuccessful: Bool) {
  49. self.modelType = modelType
  50. self.isSuccessful = isSuccessful
  51. }
  52. }
  53. /// Extension to build model download log event.
  54. extension ModelDownloadLogEvent {
  55. mutating func setEvent(status: DownloadStatus, errorCode: ErrorCode,
  56. roughDownloadDuration: UInt64? = 0, exactDownloadDuration: UInt64? = 0,
  57. downloadFailureStatus: Int64? = 0, modelOptions: ModelOptions) {
  58. downloadStatus = status
  59. self.errorCode = errorCode
  60. if let roughDuration = roughDownloadDuration {
  61. roughDownloadDurationMs = roughDuration
  62. }
  63. if let exactDuration = exactDownloadDuration {
  64. exactDownloadDurationMs = exactDuration
  65. }
  66. if let failureStatus = downloadFailureStatus {
  67. self.downloadFailureStatus = failureStatus
  68. }
  69. options = modelOptions
  70. }
  71. }
  72. /// Extension to build log event.
  73. extension FirebaseMlLogEvent {
  74. mutating func setEvent(eventName: EventName, systemInfo: SystemInfo,
  75. modelDownloadLogEvent: ModelDownloadLogEvent) {
  76. self.eventName = eventName
  77. self.systemInfo = systemInfo
  78. self.modelDownloadLogEvent = modelDownloadLogEvent
  79. }
  80. mutating func setEvent(eventName: EventName, systemInfo: SystemInfo,
  81. deleteModelLogEvent: DeleteModelLogEvent) {
  82. self.eventName = eventName
  83. self.systemInfo = systemInfo
  84. self.deleteModelLogEvent = deleteModelLogEvent
  85. }
  86. }
  87. /// Data object for Firelog event.
  88. class FBMLDataObject: NSObject, GDTCOREventDataObject {
  89. private let event: FirebaseMlLogEvent
  90. init(event: FirebaseMlLogEvent) {
  91. self.event = event
  92. }
  93. /// Encode Firelog event for transport.
  94. func transportBytes() -> Data {
  95. do {
  96. let data = try event.serializedData()
  97. return data
  98. } catch {
  99. DeviceLogger.logEvent(level: .debug,
  100. message: TelemetryLogger.ErrorDescription.encodeEvent,
  101. messageCode: .analyticsEventEncodeError)
  102. return Data()
  103. }
  104. }
  105. }
  106. /// Firelog logger.
  107. class TelemetryLogger {
  108. /// Mapping ID for the log source.
  109. private let mappingID = "1326"
  110. /// Current Firebase app.
  111. private let app: FirebaseApp
  112. /// Transport for Firelog events.
  113. private let cctTransport: GDTCORTransport
  114. /// Init logger, could be nil if unable to get event transport.
  115. init?(app: FirebaseApp) {
  116. self.app = app
  117. guard let cctTransport = GDTCORTransport(
  118. mappingID: mappingID,
  119. transformers: nil,
  120. target: GDTCORTarget.CCT
  121. ) else {
  122. DeviceLogger.logEvent(level: .debug,
  123. message: TelemetryLogger.ErrorDescription.initTelemetryLogger,
  124. messageCode: .telemetryInitError)
  125. return nil
  126. }
  127. self.cctTransport = cctTransport
  128. }
  129. /// Log events to Firelog.
  130. private func logModelEvent(event: FirebaseMlLogEvent) {
  131. let eventForTransport: GDTCOREvent = cctTransport.eventForTransport()
  132. eventForTransport.dataObject = FBMLDataObject(event: event)
  133. cctTransport.sendTelemetryEvent(eventForTransport)
  134. }
  135. /// Log model deleted event to Firelog.
  136. func logModelDeletedEvent(eventName: EventName, isSuccessful: Bool) {
  137. guard app.isDataCollectionDefaultEnabled else { return }
  138. var systemInfo = SystemInfo()
  139. let apiKey = app.options.apiKey
  140. let projectID = app.options.projectID
  141. systemInfo.setAppInfo(apiKey: apiKey, projectID: projectID)
  142. var deleteModelLogEvent = DeleteModelLogEvent()
  143. deleteModelLogEvent.setEvent(isSuccessful: isSuccessful)
  144. var fbmlEvent = FirebaseMlLogEvent()
  145. fbmlEvent.setEvent(
  146. eventName: eventName,
  147. systemInfo: systemInfo,
  148. deleteModelLogEvent: deleteModelLogEvent
  149. )
  150. logModelEvent(event: fbmlEvent)
  151. }
  152. /// Log model info retrieval event to Firelog.
  153. func logModelInfoRetrievalEvent(eventName: EventName,
  154. status: ModelDownloadLogEvent.DownloadStatus,
  155. model: CustomModel,
  156. modelInfoErrorCode: ModelInfoErrorCode) {
  157. guard app.isDataCollectionDefaultEnabled else { return }
  158. var systemInfo = SystemInfo()
  159. let apiKey = app.options.apiKey
  160. let projectID = app.options.projectID
  161. systemInfo.setAppInfo(apiKey: apiKey, projectID: projectID)
  162. var errorCode = ErrorCode()
  163. var failureCode: Int64?
  164. switch modelInfoErrorCode {
  165. case .noError:
  166. errorCode = .noError
  167. case .noHash:
  168. errorCode = .modelInfoDownloadNoHash
  169. case .connectionFailed:
  170. errorCode = .modelInfoDownloadConnectionFailed
  171. case .hashMismatch:
  172. errorCode = .modelHashMismatch
  173. case let .httpError(code):
  174. errorCode = .modelInfoDownloadUnsuccessfulHTTPStatus
  175. failureCode = Int64(code)
  176. case .unknown:
  177. errorCode = .unknownError
  178. }
  179. var modelOptions = ModelOptions()
  180. modelOptions.setModelOptions(
  181. modelName: model.name,
  182. modelHash: model.hash
  183. )
  184. var modelDownloadLogEvent = ModelDownloadLogEvent()
  185. modelDownloadLogEvent.setEvent(
  186. status: status,
  187. errorCode: errorCode,
  188. downloadFailureStatus: failureCode,
  189. modelOptions: modelOptions
  190. )
  191. var fbmlEvent = FirebaseMlLogEvent()
  192. fbmlEvent.setEvent(
  193. eventName: eventName,
  194. systemInfo: systemInfo,
  195. modelDownloadLogEvent: modelDownloadLogEvent
  196. )
  197. logModelEvent(event: fbmlEvent)
  198. }
  199. /// Log model download event to Firelog.
  200. func logModelDownloadEvent(eventName: EventName,
  201. status: ModelDownloadLogEvent.DownloadStatus,
  202. model: CustomModel,
  203. downloadErrorCode: ModelDownloadErrorCode) {
  204. guard app.isDataCollectionDefaultEnabled else { return }
  205. var modelOptions = ModelOptions()
  206. modelOptions.setModelOptions(modelName: model.name, modelHash: model.hash)
  207. var systemInfo = SystemInfo()
  208. let apiKey = app.options.apiKey
  209. let projectID = app.options.projectID
  210. systemInfo.setAppInfo(apiKey: apiKey, projectID: projectID)
  211. var errorCode = ErrorCode()
  212. var failureCode: Int64?
  213. switch downloadErrorCode {
  214. case .noError:
  215. errorCode = .noError
  216. case .urlExpired:
  217. errorCode = .uriExpired
  218. case .noConnection:
  219. errorCode = .noNetworkConnection
  220. case .downloadFailed:
  221. errorCode = .downloadFailed
  222. case let .httpError(code):
  223. errorCode = .unknownError
  224. failureCode = Int64(code)
  225. }
  226. var modelDownloadLogEvent = ModelDownloadLogEvent()
  227. modelDownloadLogEvent.setEvent(
  228. status: status,
  229. errorCode: errorCode,
  230. downloadFailureStatus: failureCode,
  231. modelOptions: modelOptions
  232. )
  233. var fbmlEvent = FirebaseMlLogEvent()
  234. fbmlEvent.setEvent(
  235. eventName: eventName,
  236. systemInfo: systemInfo,
  237. modelDownloadLogEvent: modelDownloadLogEvent
  238. )
  239. logModelEvent(event: fbmlEvent)
  240. }
  241. }
  242. /// Possible error messages while logging telemetry.
  243. private extension TelemetryLogger {
  244. /// Error descriptions.
  245. enum ErrorDescription {
  246. static let encodeEvent = "Unable to encode event for Firelog."
  247. static let initTelemetryLogger = "Unable to create telemetry logger."
  248. }
  249. }