/
StoreLog.swift
149 lines (131 loc) · 7.55 KB
/
StoreLog.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
//
// StoreLog.swift
// StoreHelper
//
// Created by Russell Archer on 16/06/2021.
//
import Foundation
import os.log
/// We use Apple's unified logging system to log errors, notifications and general messages.
/// This system works on simulators and real devices for both debug and release builds.
/// You can view the logs in the Console app by selecting the test device in the left console pane.
/// If running on the simulator, select the machine the simulator is running on. Type your app's
/// bundle identifier into the search field and then narrow the results by selecting "SUBSYSTEM"
/// from the search field's filter. Logs also appear in Xcode's console in the same manner as
/// print statements.
///
/// When running the app on a real device that's not attached to the Xcode debugger,
/// dynamic strings (i.e. the error, event or message parameter you send to the event() function)
/// will not be publicly viewable. They're automatically redacted with the word "private" in the
/// console. This prevents the accidental logging of potentially sensistive user data. Because
/// we know in advance that StoreNotificaton enums do NOT contain sensitive information, we let the
/// unified logging system know it's OK to log these strings through the use of the "%{public}s"
/// keyword. However, we don't know what the event(message:) function will be used to display,
/// so its logs will be redacted.
@available(iOS 15.0, macOS 12.0, *)
public struct StoreLog {
/// Set to `true` if you want to log detailed events each time a product's isPurchased state is checked.
public static var logIsPurchasedEvents = false
private static let storeLog = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "STORE")
/// Logs a StoreNotification. Note that the text (shortDescription) of the log entry will be
/// publically available in the Console app.
/// - Parameter event: A StoreNotification.
public static func event(_ event: StoreNotification) { logEvent(event) }
/// Logs an StoreNotification. Note that the text (shortDescription) and the productId for the
/// log entry will be publically available in the Console app.
/// - Parameters:
/// - event: A StoreNotification.
/// - productId: A ProductId associated with the event.
public static func event(_ event: StoreNotification, productId: ProductId, transactionId: String? = nil) {
logEvent(event, productId: productId, transactionId: transactionId)
}
/// Logs an StoreNotification. Note that the text (shortDescription) and the productId for the
/// log entry will be publically available in the Console app.
/// - Parameters:
/// - event: A StoreNotification.
/// - productId: A ProductId associated with the event.
/// - webOrderLineItemId: A unique ID that identifies subscription purchase events across devices, including subscription renewals
public static func event(_ event: StoreNotification, productId: ProductId, webOrderLineItemId: String?, transactionId: String? = nil) {
logEvent(event, productId: productId, webOrderLineItemId: webOrderLineItemId, transactionId: transactionId)
}
public static var transactionLog: Set<TransactionLog> = []
/// Logs a StoreNotification as a transaction. Multiple transactions for the same event, product id and transaction id will only be logged once.
/// Note that the text (shortDescription) and the productId for the log entry will be publically available in the Console app.
/// - Parameters:
/// - event: A StoreNotification.
/// - productId: A ProductId associated with the event.
public static func transaction(_ event: StoreNotification, productId: ProductId, transactionId: String? = nil) {
let t = TransactionLog(notification: event, productId: productId)
if transactionLog.contains(t) { return }
transactionLog.insert(t)
#if DEBUG
print("\(event.shortDescription()) for product \(productId) \(transactionId == nil ? "" : "with transaction id \(transactionId!)")")
#else
os_log("%{public}s for product %{public}s", log: storeLog, type: .default, event.shortDescription(), productId)
#endif
}
/// Logs a StoreException. Note that the text (shortDescription) and the productId for the
/// log entry will be publically available in the Console app.
/// - Parameters:
/// - exception: A StoreException.
/// - productId: A ProductId associated with the event.
public static func exception(_ exception: StoreException, productId: ProductId, transactionId: String? = nil) {
#if DEBUG
print("\(exception.shortDescription()). For product \(productId) \(transactionId == nil ? "" : "with transaction id \(transactionId!)")")
#else
os_log("%{public}s for product %{public}s", log: storeLog, type: .default, exception.shortDescription(), productId)
#endif
}
/// Logs a message.
/// - Parameter message: The message to log.
public static func event(_ message: String) {
#if DEBUG
print(message)
#else
os_log("%s", log: storeLog, type: .info, message)
#endif
}
private static func logEvent(_ event: StoreNotification) {
#if DEBUG
var doLog = true
if event.isNotificationPurchaseState(), !logIsPurchasedEvents { doLog = false }
if doLog { print(event.shortDescription()) }
#else
var doLog = true
if event.isNotificationPurchaseState(), !logIsPurchasedEvents { doLog = false }
if doLog { os_log("%{public}s", log: storeLog, type: .default, event.shortDescription()) }
#endif
}
private static func logEvent(_ event: StoreNotification, productId: ProductId, transactionId: String? = nil) {
#if DEBUG
var doLog = true
if event.isNotificationPurchaseState(), !logIsPurchasedEvents { doLog = false }
if doLog { print("\(event.shortDescription()) for product \(productId) \(transactionId == nil ? "" : "with transaction id \(transactionId!)")") }
#else
var doLog = true
if event.isNotificationPurchaseState(), !logIsPurchasedEvents { doLog = false }
if doLog { os_log("%{public}s for product %{public}s", log: storeLog, type: .default, event.shortDescription(), productId) }
#endif
}
private static func logEvent(_ event: StoreNotification, productId: ProductId, webOrderLineItemId: String?, transactionId: String? = nil) {
#if DEBUG
var doLog = true
if event.isNotificationPurchaseState(), !logIsPurchasedEvents { doLog = false }
if doLog { print("\(event.shortDescription()) for product \(productId) with webOrderLineItemId \(webOrderLineItemId ?? "none") \(transactionId == nil ? "" : "and transaction id \(transactionId!)")") }
#else
var doLog = true
if event.isNotificationPurchaseState(), !logIsPurchasedEvents { doLog = false }
if doLog { os_log("%{public}s for product %{public}s with webOrderLineItemId %{public}s",
log: storeLog,
type: .default,
event.shortDescription(),
productId,
webOrderLineItemId ?? "none") }
#endif
}
}
public struct TransactionLog: Hashable {
let notification: StoreNotification
let productId: ProductId
public static func == (lhs: TransactionLog, rhs: TransactionLog) -> Bool { return (lhs.productId == rhs.productId) && (lhs.notification == rhs.notification) }
}