mirror of
https://github.com/RevenueCat/purchases-ios
synced 2026-11-11 22:55:55 +05:45
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' ```
314 lines
11 KiB
Swift
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"
|
|
|
|
}
|