Skip to content

Commit f8ab8ec

Browse files
NickEntindfed
andcommitted
Add some documentation (#110)
* Add documentation for logging * Remove outdated iOS version specification from README * Clean up files and add missing headerdocs Co-authored-by: Dan Federman <[email protected]>
1 parent 7e30b10 commit f8ab8ec

File tree

6 files changed

+185
-53
lines changed

6 files changed

+185
-53
lines changed

Documentation/Logging.md

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Logging
2+
3+
One of the most helpful components of an actionable bug report is a complete set of logs. Aardvark makes it simple to add logs with the logging utilities in the CoreAardvark framework.
4+
5+
## Getting Started
6+
7+
The easiest way to get started logging with Aardvark is by replacing calls to `print` with `log` in Swift and calls to `NSLog` with `ARKLog` in Objective-C.
8+
9+
```swift
10+
log("Hello world")
11+
```
12+
13+
```objc
14+
ARKLog(@"Hello world");
15+
```
16+
17+
By default, this will log messages to the Aardvark log store _instead of_ the console. If you would like the messages to be logged to the console as well, toggle the default log store's `printsLogsToConsole` property:
18+
19+
```swift
20+
ARKLogDistributor.default().defaultLogStore.printsLogsToConsole = true
21+
```
22+
23+
## Adding Parameters
24+
25+
A recommended pattern for logging is to keep your log messages simple and attach details of the log in the `parameters` field. You can attach key/value pairs of strings to your log messages to provide further debugging data, while keeping your logs easy to scan through in a list of messages.
26+
27+
```swift
28+
log("Said hello to user", parameters: ["user_name": user.name])
29+
```
30+
31+
```objc
32+
ARKLogWithParameters(@{ @"user_name": [user name] }, @"Said hello to user");
33+
```
34+
35+
## Using Dependency Injection
36+
37+
If you prefer to use dependency injection rather than global functions, you can inject an `ARKLogDistributor` to your logging call sites.
38+
39+
```swift
40+
func sayHelloToPerson(named name: String, on logDistributor: ARKLogDistributor) {
41+
logDistributor.log("Hello \(name)")
42+
}
43+
```
44+
45+
```objc
46+
- (void)sayHelloToPersonNamed:(NSString *)name onLogDistributor:(ARKLogDistributor *)logDistributor;
47+
{
48+
[logDistributor logWithFormat:@"Hello %@", name];
49+
}
50+
```
51+
52+
## Using Multiple Log Stores
53+
54+
By default, all logs are sent to the same log file. If you would prefer to send some logs into separate log files, you can add multiple log stores to the distributor and set each store's `logFilterBlock` to filter incoming logs.
55+
56+
To facilitate filtering, log messages contain a `userInfo` dictionary. Unlike the `parameters` dictionary, the `userInfo` is not persisted with the log message.
57+
58+
```swift
59+
log("Loaded data", userInfo: ["category": "Core Data"])
60+
```
61+
62+
For example, we can set up a second log store for all of the logs related to Core Data and filter these logs out of the main log store. This could be helpful if our Core Data implementation consistently runs tasks in the background and creates noise in the main log store.
63+
64+
```swift
65+
// Create the new log store.
66+
let coreDataLogStore = ARKLogStore(persistedLogFileName: "CoreDataLogs.data")
67+
68+
// Set the new log store to only include logs related to Core Data.
69+
coreDataLogStore.logFilterBlock = { message in
70+
return message.userInfo?["category"] == "Core Data"
71+
}
72+
73+
// Add the new log store to the default distributor.
74+
ARKLogDistributor.default().add(coreDataLogStore)
75+
76+
// Set the default log store to exclude log related to Core Data.
77+
ARKLogDistributor.default().defaultLogStore.logFilterBlock = { message in
78+
return !coreDataLogStore.logFilterBlock(message)
79+
}
80+
```
81+
82+
See [SampleViewController](../AardvarkSample/AardvarkSample/SampleViewController.swift)’s `tapGestureLogStore` for an example of how this works.
83+
84+
## Sending Logs to Other Services
85+
86+
One log can be easily distributed to multiple services by adding objects conforming to [ARKLogObserver](../Sources/CoreAardvark/Logging/ARKLogObserver.h) to the default [ARKLogDistributor](../Sources/CoreAardvark/Logging/ARKLogDistributor.h) via `addLogObserver:`. [SampleCrashlyticsLogObserver](../AardvarkSample/AardvarkSample/SampleCrashlyticsLogObserver.h) is an example of an [ARKLogObserver](../Sources/CoreAardvark/Logging/ARKLogObserver.h) that sends event logs to Crashlytics.
87+
88+
## Formatting Logs
89+
90+
When logs are shown in the in-app log viewer or attached to a bug report email, they are formatted into plain text. You can change how these messages look by creating a formatter that conforms to [ARKLogFormatter](../Sources/CoreAardvark/Logging/ARKLogFormatter.h) and setting it as the formatter where desired (e.g. as the `logFormatter` on an [ARKEmailBugReporter](../Sources/AardvarkMailUI/ARKEmailBugReporter.h)).

README.md

+1-12
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ The easiest way to get Aardvark up and running is to add a dependency on the Aar
3030
#### Using [CocoaPods](https://cocoapods.org)
3131

3232
```
33-
platform :ios, '8.0'
3433
pod 'AardvarkMailUI'
3534
```
3635

@@ -64,12 +63,6 @@ In Objective-C:
6463

6564
In Swift, replace calls to `print` with `log`. In Objective-C, replace calls to `NSLog` with `ARKLog`.
6665

67-
By default, this will log messages to the Aardvark log store _instead of_ the console. If you would like the messages to be logged to the console as well, toggle the default log store's `printsLogsToConsole` property:
68-
69-
```swift
70-
ARKLogDistributor.default().defaultLogStore.printsLogsToConsole = true
71-
```
72-
7366
## Reporting Bugs
7467

7568
After doing the above, your users can report a bug by making a two-finger long-press gesture. This gesture triggers a UIAlert asking the user what went wrong. When the user enters this information, an email bug report is generated complete with an attached app screenshot and a text file containing the last 2000 logs. Screenshots are created and stored within Aardvark and do not require camera roll access.
@@ -92,11 +85,7 @@ Once the exception is logged, it will be propogated to any existing uncaught exc
9285

9386
Want to customize how bug reports are filed? Pass your own object conforming to the [ARKBugReporter](Sources/Aardvark/Bug%20Reporting/ARKBugReporter.h) protocol and the desired subclass of `UIGestureRecognizer` to `[Aardvark addBugReporter:triggeringGestureRecognizerClass:]`. You can further customize how bug reports will be triggered by modifying the returned gesture recognizer.
9487

95-
Want to change how logs are formatted? Set your own `logFormatter` on the [ARKEmailBugReporter](Sources/AardvarkMailUI/ARKEmailBugReporter.h) returned from `[Aardvark addDefaultBugReportingGestureWithEmailBugReporterWithRecipient:]`.
96-
97-
Want different log files for different features? Create an [ARKLogStore](Sources/CoreAardvark/Logging/ARKLogStore.h) for each feature you want to have its own log file and add them to the default log distributor with `[[ARKLogDistributor defaultDistributor] addLogObserver:featureLogStore]`. Set the `logFilterBlock` on your [ARKLogStore](Sources/CoreAardvark/Logging/ARKLogStore.h) to make sure only the logs you want are observed by the [ARKLogStore](Sources/CoreAardvark/Logging/ARKLogStore.h). Use `ARKLogWithType`’s `userInfo` dictionary to specify to which feature a log pertains. See [SampleViewController](AardvarkSample/AardvarkSample/SampleViewController.swift)’s `tapGestureLogStore` for an example.
98-
99-
Want to send your logs to third party services? One log can be easily distributed to multiple services by adding objects conforming to [ARKLogObserver](Sources/CoreAardvark/Logging/ARKLogObserver.h) to the default [ARKLogDistributor](Sources/CoreAardvark/Logging/ARKLogDistributor.h) via `addLogObserver:`. [SampleCrashlyticsLogObserver](AardvarkSample/AardvarkSample/SampleCrashlyticsLogObserver.h) is an example of an [ARKLogObserver](Sources/CoreAardvark/Logging/ARKLogObserver.h) that sends event logs to Crashlytics.
88+
Want to change how logs are formatted? Or use different log files for different features? Or send your logs to third party services? Check out the [logging documentation](Documentation/Logging.md).
10089

10190
Want to log with Aardvark but don’t want to use Aardvark’s bug reporting tool? Change the dependency to be on `Aardvark` and skip step #2 in the Getting Started guide.
10291

Sources/Aardvark/Aardvark.swift

+21-10
Original file line numberDiff line numberDiff line change
@@ -17,27 +17,38 @@
1717
import CoreAardvark
1818
import Foundation
1919

20-
2120
public typealias EmailAddress = String
2221

2322
@objc
2423
public class Aardvark : NSObject {
25-
26-
/// Creates and returns a gesture recognizer that when triggered will call [bugReporter composeBugReport].
24+
25+
/// Creates and returns a gesture recognizer that when triggered will call `bugReporter.composeBugReport()`.
2726
@nonobjc
28-
public static func add<GestureRecognizer: UIGestureRecognizer>(bugReporter: ARKBugReporter, triggeringGestureRecognizerClass: GestureRecognizer.Type) -> GestureRecognizer? {
29-
return UIApplication.shared.add(bugReporter: bugReporter, triggeringGestureRecognizerClass: triggeringGestureRecognizerClass)
27+
public static func add<GestureRecognizer: UIGestureRecognizer>(
28+
bugReporter: ARKBugReporter,
29+
triggeringGestureRecognizerClass: GestureRecognizer.Type
30+
) -> GestureRecognizer? {
31+
return UIApplication.shared.add(
32+
bugReporter: bugReporter,
33+
triggeringGestureRecognizerClass: triggeringGestureRecognizerClass
34+
)
3035
}
31-
32-
/// Creates and returns a gesture recognizer that when triggered will call [bugReporter composeBugReport].
36+
37+
/// Creates and returns a gesture recognizer that when triggered will call `bugReporter.composeBugReport()`.
3338
@objc(addBugReporter:gestureRecognizerClass:)
34-
public static func objc_add(bugReporter: ARKBugReporter, triggeringGestureRecognizerClass gestureRecognizerClass: AnyClass) -> AnyObject? {
39+
public static func objc_add(
40+
bugReporter: ARKBugReporter,
41+
triggeringGestureRecognizerClass gestureRecognizerClass: AnyClass
42+
) -> AnyObject? {
3543
guard let triggeringGestureRecognizerClass = gestureRecognizerClass as? UIGestureRecognizer.Type else {
3644
noteImproperAPIUse("\(gestureRecognizerClass) is not a gesture recognizer class!")
3745
return nil
3846
}
39-
40-
return UIApplication.shared.add(bugReporter: bugReporter, triggeringGestureRecognizerClass: triggeringGestureRecognizerClass)
47+
48+
return UIApplication.shared.add(
49+
bugReporter: bugReporter,
50+
triggeringGestureRecognizerClass: triggeringGestureRecognizerClass
51+
)
4152
}
4253
}
4354

Sources/Aardvark/UIApplication+ARKAdditions.swift

+49-29
Original file line numberDiff line numberDiff line change
@@ -16,104 +16,124 @@
1616

1717
import Foundation
1818

19-
2019
extension UIApplication {
20+
2121
@nonobjc
2222
private static var observingKeyWindowNotifications = false
2323

2424
@nonobjc
2525
private static let bugReporterToGestureRecognizerMap: NSMapTable<ARKBugReporter, UIGestureRecognizer> = NSMapTable.strongToStrongObjects()
26-
27-
/// Creates and returns a gesture recognizer that when triggered will call [bugReporter composeBugReport]. Must be called from the main thread.
26+
27+
/// Creates and returns a gesture recognizer that when triggered will call `bugReporter.composeBugReport()`. Must be
28+
/// called from the main thread.
2829
@nonobjc
29-
func add<GestureRecognizer: UIGestureRecognizer>(bugReporter: ARKBugReporter, triggeringGestureRecognizerClass: GestureRecognizer.Type) -> GestureRecognizer? {
30+
func add<GestureRecognizer: UIGestureRecognizer>(
31+
bugReporter: ARKBugReporter,
32+
triggeringGestureRecognizerClass: GestureRecognizer.Type
33+
) -> GestureRecognizer? {
3034
guard Thread.isMainThread else {
3135
noteImproperAPIUse("Must call \(#function) from the main thread!")
3236
return nil
3337
}
34-
38+
3539
guard bugReporter.logStores().count > 0 else {
3640
noteImproperAPIUse("Attempting to add a bug reporter without a log store!")
3741
return nil
3842
}
39-
40-
let bugReportingGestureRecognizer = triggeringGestureRecognizerClass.init(target: self, action: #selector(UIApplication.didFire(bugReportGestureRecognizer:)))
43+
44+
let bugReportingGestureRecognizer = triggeringGestureRecognizerClass.init(
45+
target: self,
46+
action: #selector(UIApplication.didFire(bugReportGestureRecognizer:))
47+
)
4148
keyWindow?.addGestureRecognizer(bugReportingGestureRecognizer)
42-
49+
4350
UIApplication.bugReporterToGestureRecognizerMap.setObject(bugReportingGestureRecognizer, forKey: bugReporter)
44-
51+
4552
if !UIApplication.observingKeyWindowNotifications {
46-
NotificationCenter.default.addObserver(self, selector: #selector(windowDidBecomeKey(notification:)), name: UIWindow.didBecomeKeyNotification, object: nil)
47-
NotificationCenter.default.addObserver(self, selector: #selector(windowDidResignKey(notification:)), name: UIWindow.didResignKeyNotification, object: nil)
48-
53+
NotificationCenter.default.addObserver(
54+
self,
55+
selector: #selector(windowDidBecomeKey(notification:)),
56+
name: UIWindow.didBecomeKeyNotification,
57+
object: nil
58+
)
59+
NotificationCenter.default.addObserver(
60+
self,
61+
selector: #selector(windowDidResignKey(notification:)),
62+
name: UIWindow.didResignKeyNotification,
63+
object: nil
64+
)
65+
4966
UIApplication.observingKeyWindowNotifications = true
5067
}
51-
68+
5269
return bugReportingGestureRecognizer
5370
}
5471

5572
@nonobjc
5673
func remove(bugReporter: ARKBugReporter) {
57-
if let gestureRecognizerToRemove: UIGestureRecognizer = UIApplication.bugReporterToGestureRecognizerMap.object(forKey: bugReporter) {
74+
if let gestureRecognizerToRemove = UIApplication.bugReporterToGestureRecognizerMap.object(forKey: bugReporter) {
5875
gestureRecognizerToRemove.view?.removeGestureRecognizer(gestureRecognizerToRemove)
59-
76+
6077
UIApplication.bugReporterToGestureRecognizerMap.removeObject(forKey: bugReporter)
6178
}
6279
}
63-
80+
6481
@objc(ARK_didFireBugReportGestureRecognizer:)
6582
private func didFire(bugReportGestureRecognizer: UIGestureRecognizer) {
6683
guard bugReportGestureRecognizer.state == .began else {
6784
return
6885
}
69-
86+
7087
var bugReporters = [ARKBugReporter]()
7188
for bugReporter in UIApplication.bugReporterToGestureRecognizerMap.keyEnumerator() {
72-
guard let bugReporter = bugReporter as? ARKBugReporter, !bugReporters.contains(where: { $0 === bugReporter }) else {
89+
guard
90+
let bugReporter = bugReporter as? ARKBugReporter,
91+
!bugReporters.contains(where: { $0 === bugReporter })
92+
else {
7393
continue
7494
}
75-
95+
7696
let recognizerForBugReport = UIApplication.bugReporterToGestureRecognizerMap.object(forKey: bugReporter)
7797
if recognizerForBugReport === bugReportGestureRecognizer {
7898
bugReporters.append(bugReporter)
7999
}
80100
}
81-
101+
82102
guard bugReporters.count > 0 else {
83103
return
84104
}
85-
105+
86106
for bugReporter in bugReporters {
87107
bugReporter.composeBugReport()
88108
}
89109
}
90-
110+
91111
@objc(ARK_windowDidBecomeKeyNotification:)
92112
private func windowDidBecomeKey(notification: Notification) {
93113
guard let window = notification.object as? UIWindow else {
94114
return
95115
}
96-
guard let gestureRecognizersEnumerator = UIApplication.bugReporterToGestureRecognizerMap.objectEnumerator() else {
116+
guard let enumerator = UIApplication.bugReporterToGestureRecognizerMap.objectEnumerator() else {
97117
return
98118
}
99-
100-
for gestureRecognizer in gestureRecognizersEnumerator {
119+
120+
for gestureRecognizer in enumerator {
101121
if let gestureRecognizer = gestureRecognizer as? UIGestureRecognizer {
102122
window.addGestureRecognizer(gestureRecognizer)
103123
}
104124
}
105125
}
106-
126+
107127
@objc(ARK_windowDidResignKeyNotification:)
108128
private func windowDidResignKey(notification: Notification) {
109129
guard let window = notification.object as? UIWindow else {
110130
return
111131
}
112-
guard let gestureRecognizersEnumerator = UIApplication.bugReporterToGestureRecognizerMap.objectEnumerator() else {
132+
guard let enumerator = UIApplication.bugReporterToGestureRecognizerMap.objectEnumerator() else {
113133
return
114134
}
115-
116-
for gestureRecognizer in gestureRecognizersEnumerator {
135+
136+
for gestureRecognizer in enumerator {
117137
if let gestureRecognizer = gestureRecognizer as? UIGestureRecognizer {
118138
window.removeGestureRecognizer(gestureRecognizer)
119139
}

Sources/CoreAardvark/Logging/ARKLogMessage.h

+8
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,19 @@
3939
- (nonnull instancetype)init NS_UNAVAILABLE;
4040
+ (nonnull instancetype)new NS_UNAVAILABLE;
4141

42+
/// The date at which the message was logged.
4243
@property (nonnull, nonatomic, copy, readonly) NSDate *date;
44+
45+
/// The text of the log message.
4346
@property (nonnull, nonatomic, copy, readonly) NSString *text;
47+
48+
/// An optional image associated with the log message. Typically used for logs of type `ARKLogTypeScreenshot`.
4449
@property (nullable, nonatomic, readonly) UIImage *image;
50+
51+
/// The type of the log message.
4552
@property (nonatomic, readonly) ARKLogType type;
4653

54+
/// Details about the log message. This data is persisted.
4755
@property (nonnull, nonatomic, copy, readonly) NSDictionary<NSString *, NSString *> *parameters;
4856

4957
/// Arbitrary information that can be used by ARKLogObserver objects. This data is not persisted.

Sources/CoreAardvark/Logging/Logging.swift

+16-2
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,27 @@
1616

1717
import Foundation
1818

19-
19+
/// Logs a new messages to the default Aardvark log distributor.
20+
///
21+
/// - parameter text: The text of the log message.
22+
/// - parameter type: The type of log message.
23+
/// - parameter image: An optional image to be attached to the log message. Typically used for messages of type
24+
/// `.screenshot`.
25+
/// - parameter parameters: A set of key/value pairs that is persisted with the log message.
26+
/// - parameter userInfo: A set of key/value pairs that is _not_ persisted with the log message, but rather can be
27+
/// used to control how the message is processed by various log observers.
2028
public func log(
2129
_ text: String,
2230
type: ARKLogType = .`default`,
2331
image: UIImage? = nil,
2432
parameters: [String : String] = [:],
2533
userInfo: [NSObject : AnyObject]? = nil
2634
) {
27-
ARKLogDistributor.default().log(withText: text, image: image, type: type, parameters: parameters, userInfo: userInfo);
35+
ARKLogDistributor.default().log(
36+
withText: text,
37+
image: image,
38+
type: type,
39+
parameters: parameters,
40+
userInfo: userInfo
41+
)
2842
}

0 commit comments

Comments
 (0)