Files
purchases-ios/Sources/Logging/Logger.swift
T
NachoSoto ed80980d08 HTTPClient: verbose logs for request IDs (#3320)
See https://github.com/RevenueCat/khepri/pull/6798.
This can help debug issues as it helps us cross-reference requests on
our end.

### Changes:
- Logging `X-Request-ID` and `X-Amzn-Trace-ID` headers if present (only
in verbose mode)
- Logging these even for failed requests
- Also logging `request_handled_by_load_shedder` for failed requests
- To accomplish that. `HTTPURLResponse` now conforms to our
`HTTPResponseType`, this allows us to extract headers in an uniform way

### Example:
```
[network] DEBUG: ℹ️ API request completed: GET '/v1/subscribers/$RCAnonymousID%3Ad9dc4f3b33054cf28c0f2ec9da0f48d5' (304)
Request-ID: 'be534c50-5231-403b-be2f-7c077861751d'; Amzn-Trace-ID: 'Root=1-6532c6ca-7ef80f961ec9c4145484df13'
```
2023-10-25 20:24:23 -07:00

314 lines
11 KiB
Swift

//
// Copyright RevenueCat Inc. All Rights Reserved.
//
// Licensed under the MIT License (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://opensource.org/licenses/MIT
//
// Logger.swift
//
// Created by Andrés Boedo on 11/13/20.
//
import Foundation
/// A function that can handle a log message including file and method information.
public typealias VerboseLogHandler = (_ level: LogLevel,
_ message: String,
_ file: String?,
_ function: String?,
_ line: UInt) -> Void
/// A function that can handle a log message.
public typealias LogHandler = (_ level: LogLevel,
_ message: String) -> Void
internal typealias InternalLogHandler = (_ level: LogLevel,
_ message: String,
_ category: String,
_ file: String?,
_ function: String?,
_ line: UInt) -> Void
// MARK: - Logger
// This is a `struct` instead of `enum` so that
// we can use `Logger()` as a `LoggerType`.
// swiftlint:disable:next convenience_type
struct Logger {
static var logLevel: LogLevel = Self.defaultLogLevel
static var internalLogHandler: InternalLogHandler = Self.defaultLogHandler
static let defaultLogHandler: InternalLogHandler = { level, message, category, file, functionName, line in
RCDefaultLogHandler(
Self.frameworkDescription,
Logger.verbose,
level,
category,
message,
file,
functionName,
line
)
}
static var verbose: Bool = false
private static let defaultLogLevel: LogLevel = {
#if DEBUG
return .debug
#else
return .info
#endif
}()
internal static let frameworkDescription = "Purchases"
}
extension Logger {
static var verboseLogsEnabled: Bool { self.logLevel == .verbose }
}
// MARK: - LoggerType implementation
/// `Logger` can be used both with static or instance methods.
/// This allows us to use it directly (`Logger.info("...")`), or inject it:
/// ```swift
/// let logger: LoggerType
/// logger.info("...")
/// ```
extension Logger: LoggerType {
func verbose(_ message: @autoclosure () -> LogMessage,
fileName: String? = #fileID,
functionName: String? = #function,
line: UInt = #line) {
Self.verbose(message(), fileName: fileName, functionName: functionName, line: line)
}
func debug(_ message: @autoclosure () -> LogMessage,
fileName: String? = #fileID,
functionName: String? = #function,
line: UInt = #line) {
Self.debug(message(), fileName: fileName, functionName: functionName, line: line)
}
func info(_ message: @autoclosure () -> LogMessage,
fileName: String? = #fileID,
functionName: String? = #function,
line: UInt = #line) {
Self.info(message(), fileName: fileName, functionName: functionName, line: line)
}
func warn(_ message: @autoclosure () -> LogMessage,
fileName: String? = #fileID,
functionName: String? = #function,
line: UInt = #line) {
Self.warn(message(), fileName: fileName, functionName: functionName, line: line)
}
func error(_ message: @autoclosure () -> LogMessage,
fileName: String = #fileID,
functionName: String = #function,
line: UInt = #line) {
Self.error(message(), fileName: fileName, functionName: functionName, line: line)
}
}
// MARK: - Static implementation
extension Logger {
static func verbose(_ message: @autoclosure () -> LogMessage,
fileName: String? = #fileID,
functionName: String? = #function,
line: UInt = #line) {
Self.log(level: .verbose, intent: .verbose, message: message(),
fileName: fileName, functionName: functionName, line: line)
}
static func debug(_ message: @autoclosure () -> LogMessage,
fileName: String? = #fileID,
functionName: String? = #function,
line: UInt = #line) {
Self.log(level: .debug, intent: .info, message: message(),
fileName: fileName, functionName: functionName, line: line)
}
static func info(_ message: @autoclosure () -> LogMessage,
fileName: String? = #fileID,
functionName: String? = #function,
line: UInt = #line) {
Self.log(level: .info, intent: .info, message: message(),
fileName: fileName, functionName: functionName, line: line)
}
static func warn(_ message: @autoclosure () -> LogMessage,
fileName: String? = #fileID,
functionName: String? = #function,
line: UInt = #line) {
Self.log(level: .warn, intent: .warning, message: message(),
fileName: fileName, functionName: functionName, line: line)
}
static func error(_ message: @autoclosure () -> String,
fileName: String = #fileID,
functionName: String = #function,
line: UInt = #line) {
Self.error(
ErrorMessage(description: message()),
fileName: fileName,
functionName: functionName,
line: line
)
}
static func error(_ message: @autoclosure () -> LogMessage,
fileName: String = #fileID,
functionName: String = #function,
line: UInt = #line) {
Self.log(level: .error, intent: .rcError, message: message(),
fileName: fileName, functionName: functionName, line: line)
}
}
extension Logger {
static func appleError(_ message: @autoclosure () -> String,
fileName: String = #fileID,
functionName: String = #function,
line: UInt = #line) {
Self.appleError(
ErrorMessage(description: message()),
fileName: fileName,
functionName: functionName,
line: line
)
}
static func appleError(_ message: @autoclosure () -> LogMessage,
fileName: String = #fileID,
functionName: String = #function,
line: UInt = #line) {
Self.log(level: .error, intent: .appleError, message: message(),
fileName: fileName, functionName: functionName, line: line)
}
static func appleWarning(_ message: @autoclosure () -> LogMessage,
fileName: String = #fileID,
functionName: String = #function,
line: UInt = #line) {
Self.log(level: .warn, intent: .appleError, message: message(),
fileName: fileName, functionName: functionName, line: line)
}
static func purchase(_ message: @autoclosure () -> LogMessage,
fileName: String = #fileID,
functionName: String = #function,
line: UInt = #line) {
Self.log(level: .info, intent: .purchase, message: message(),
fileName: fileName, functionName: functionName, line: line)
}
static func rcPurchaseSuccess(_ message: @autoclosure () -> LogMessage,
fileName: String = #fileID,
functionName: String = #function,
line: UInt = #line) {
Self.log(level: .info, intent: .rcPurchaseSuccess, message: message(),
fileName: fileName, functionName: functionName, line: line)
}
static func rcPurchaseError(_ message: @autoclosure () -> LogMessage,
fileName: String = #fileID,
functionName: String = #function,
line: UInt = #line) {
Self.log(level: .error, intent: .purchase, message: message(),
fileName: fileName, functionName: functionName, line: line)
}
static func rcSuccess(_ message: @autoclosure () -> LogMessage,
fileName: String = #fileID,
functionName: String = #function,
line: UInt = #line) {
Self.log(level: .debug, intent: .rcSuccess, message: message(),
fileName: fileName, functionName: functionName, line: line)
}
static func user(_ message: @autoclosure () -> LogMessage,
fileName: String? = #fileID,
functionName: String? = #function,
line: UInt = #line) {
Self.log(level: .debug, intent: .user, message: message(),
fileName: fileName, functionName: functionName, line: line)
}
static func log(level: LogLevel,
intent: LogIntent,
message: @autoclosure () -> LogMessage,
fileName: String? = #fileID,
functionName: String? = #function,
line: UInt = #line) {
guard self.logLevel <= level else { return }
let message = message()
let content = [intent.prefix.notEmpty, message.description]
.compactMap { $0 }
.joined(separator: " ")
Self.internalLogHandler(
level,
content,
message.category,
fileName,
functionName,
line
)
}
}
// MARK: - LogLevel comparable conformance
extension LogLevel: Comparable {
// swiftlint:disable:next missing_docs
public static func < (lhs: LogLevel, rhs: LogLevel) -> Bool {
// Tests ensure that this can't happen
guard let lhs = Self.order[lhs], let rhs = Self.order[rhs] else { return false }
return lhs < rhs
}
private static let orderedLevels: [LogLevel] = [
.verbose,
.debug,
.info,
.warn,
.error
]
static let order: [LogLevel: Int] = Dictionary(uniqueKeysWithValues:
Self.orderedLevels
.enumerated()
.lazy
.map { ($1, $0) }
)
}
// MARK: -
private struct ErrorMessage: LogMessage {
let description: String
let category: String = "error"
}