10
10
> Simple console logger
11
11
12
12
### Features
13
- - 4 levels of severity (🔴 error , 🟠 warning , 🔵️ debug , 🟣 info)
14
- - 9 tag types (📡 network , 🗄 database , 🖥 UI, 💾 file , 🔑 security , 🛍 payment , ⚙️ system , 🧰 util , 📝 other)
15
- - Output to ** consol ** , ** file** , or a ** custom** end-point like Google analytics or Firebase crashalytics etc (Use Telemetry for GA)
13
+ - Four levels of severity: 🔴 Error , 🟠 Warning , 🔵 Debug , 🟣 Info
14
+ - Nine tag types: 📡 Network , 🗄 Database , 🖥 UI, 💾 File , 🔑 Security , 🛍 Payment , ⚙️ System , 🧰 Utility , 📝 Other
15
+ - Output to ** console ** , ** file** , or a ** custom endpoint ** like Google Analytics or Firebase Crashlytics
16
16
17
17
### Why Logger?
18
- - Debugging complex apps efficiently requires filtering to prevent console clutter.
19
- - Fixing network bugs becomes easier when UI and DB logging can be turned off .
20
- - Sending errors to endpoints like Google Analytics or Firebase Crashlytics is beneficial .
18
+ - Efficiently debug complex apps by filtering logs to avoid console clutter.
19
+ - Easily toggle logging for specific components like UI or database interactions .
20
+ - Send errors and warnings to external services like Google Analytics or Firebase Crashlytics for better monitoring .
21
21
22
22
### Logging format:
23
23
``` swift
24
- Logger.debug (text : " Network.connect - connection established successfully" , type : .net )
25
- // Output: [🔵️ Debug] [23-12-24 22:00:45] ➞ 📡 Network.connect: connection established successfully
26
- Logger.warning (text : " Network.connect \( error.localDescription ) " , type : .net )
27
- // Output: [️🟠 Warning] [23-12-24 22:00:45] ➞ 📡 Network.connect: Wifi not turned on
28
- Logger.error (text : " Network.process-data \( error.localDescription ) " , type : .net )
29
- // Output: [🔴 Error] [23-12-24 22:00:45] ➞ 📡 Network.process-data: Decoding was unsuccessful. Nothing was saved
24
+ Logger.debug (" Network.connect - connection established successfully" , tag : .net )
25
+ // Output: [🔵 Debug] [2023-12-24 22:00:45] ➞ 📡 Network.connect: connection established successfully
26
+
27
+ Logger.warning (" Network.connect \( error.localizedDescription ) " , tag : .net )
28
+ // Output: [🟠 Warning] [2023-12-24 22:00:45] ➞ 📡 Network.connect: Wi-Fi is not turned on
29
+
30
+ Logger.error (" Network.processData \( error.localizedDescription ) " , tag : .net )
31
+ // Output: [🔴 Error] [2023-12-24 22:00:45] ➞ 📡 Network.processData: Decoding was unsuccessful. Nothing was saved
30
32
```
31
33
32
34
### Configure:
33
35
``` swift
34
- // Print text format
35
- Logger.config = .plain // .full
36
- // Output transport
37
- Logger.type = .console // .file(filePath), .custom({ level, tag, msg in })
38
- // Levels and tags
39
- Logger.mode = .everything // .nothing, .essential
40
- // Or use this convenient one-liner:
41
- Logger.setup (config : .plain , mode : .everything , type : .console )
36
+ // Configure the logger output format
37
+ Logger.config = .plain // Options: .plain (no date), .full (includes date and verbose level)
38
+
39
+ // Set the output transport method
40
+ Logger.type = .console // Options: .console, .file(filePath: String), .custom(onLog: LogType.OnLog)
41
+
42
+ // Define the logging mode for levels and tags
43
+ Logger.mode = .everything // Options: .everything (all logs), .nothing (disable logging), .essential (warnings and errors only)
44
+
45
+ // Convenient one-liner setup
46
+ Logger.setup (config : .full , mode : .essential , type : .console )
42
47
```
43
48
44
49
### Add custom log end-point like GA or Firebase crashalytics
45
50
``` swift
46
- let onLog: LogType.OnLog = { msg, level, _ in
47
- if [LogLevel.error , .warning ].contains (where : { $0 == level }) {
48
- Swift.print (msg) // Only prints warning and error, replace w/ call to GA etc
49
- }
51
+ // Define a custom logging function
52
+ let onLog: LogType.OnLog = { msg, level, tag in
53
+ // Only send warnings and errors to the custom endpoint
54
+ if [.error , .warning ].contains (level) {
55
+ sendToAnalytics (msg, level : level, tag : tag)
56
+ }
50
57
}
51
- Logger.type = .custom (onLog) // Add the custom output closure to the logger
52
- Logger.warn (" Uh-Oh something went wrong" ) // Prints
53
- Logger.error (" Unsupported format, bail" ) // Prints
54
- Logger.debug (" Entered backround" ) // Does not print
58
+
59
+ // Set the logger to use the custom output
60
+ Logger.type = .custom (onLog)
61
+
62
+ Logger.warn (" User session expired" , tag : .security ) // This will be sent to the custom endpoint
63
+ Logger.error (" Failed to save data" , tag : .db ) // This will be sent to the custom endpoint
64
+ Logger.info (" User opened settings" , tag : .ui ) // This will not be sent
55
65
```
56
66
57
67
> [ !NOTE]
@@ -72,6 +82,9 @@ Logger.info("Something happened") // Prints to Console.app (filter by category o
72
82
```
73
83
74
84
### Tracing
85
+
86
+ The ` Trace ` class can be combined with ` Logger ` to include function names, class names, and line numbers in your logs.
87
+
75
88
``` swift
76
89
class Test {
77
90
func myFunction () {
@@ -139,4 +152,271 @@ class ConsoleLogger: LoggerProtocol {
139
152
class FileLogger: LoggerProtocol {
140
153
// File logging implementation...
141
154
}
155
+ ```
156
+ - Add console color codes:
157
+
158
+ ```swift
159
+ extension String {
160
+ enum ConsoleColor : String {
161
+ case red = " \u{001B} [0;31m"
162
+ case orange = " \u{001B} [0;33m"
163
+ case blue = " \u{001B} [0;34m"
164
+ case purple = " \u{001B} [0;35m"
165
+ case reset = " \u{001B} [0;0m"
166
+ }
167
+
168
+ func colored (_ color : ConsoleColor) -> String {
169
+ return " \( color.rawValue ) \( self ) \( ConsoleColor.reset .rawValue ) "
170
+ }
171
+ }
172
+
173
+ // Apply Colors in Logging:
174
+
175
+ extension Logger {
176
+ fileprivate static func formatMessage (_ msg : String , level : LogLevel, tag : LogTag) -> String {
177
+ // Existing formatting code...
178
+ var text = " [\( levelText ) ]"
179
+ if config.showDate {
180
+ let date = config.dateFormatter .string (from : Date ())
181
+ text += " [\( date ) ]"
182
+ }
183
+ text += " ➞ \( tag.rawValue ) \( msg ) "
184
+
185
+ // Apply color based on log level
186
+ switch level {
187
+ case .error :
188
+ text = text.colored (.red )
189
+ case .warning :
190
+ text = text.colored (.orange )
191
+ case .debug :
192
+ text = text.colored (.blue )
193
+ case .info :
194
+ text = text.colored (.purple )
195
+ }
196
+
197
+ return text
198
+ }
199
+ }
200
+ ```
201
+
202
+ - Add Native OS Logging Support (os.log )
203
+
204
+ ```swift
205
+ import os
206
+
207
+ public enum LogType {
208
+ // Existing cases...
209
+ case osLog (OSLog = .default )
210
+ }
211
+
212
+ extension LogType {
213
+ internal func log (msg : String , level : LogLevel, tag : LogTag) {
214
+ switch self {
215
+ // Existing cases...
216
+ case let .osLog (logger):
217
+ if #available (iOS 14.0 , macOS 11.0 , * ) {
218
+ logger.log (" \( msg, privacy: .public ) " )
219
+ } else {
220
+ os_log (" %{public}@" , log : logger, type : .default , msg)
221
+ }
222
+ }
223
+ }
224
+ }
225
+
226
+ // Configure logger to use osLog
227
+ Logger.type = .osLog ()
228
+
229
+ // Log messages
230
+ Logger.info (" Application started" , tag : .system )
231
+
232
+ ```
233
+
234
+ - Implement Exception Handling for Fatal Crashes
235
+
236
+
237
+ ```swift
238
+ // Set Up Exception Handler:
239
+ func setUpExceptionHandler () {
240
+ NSSetUncaughtExceptionHandler { exception in
241
+ Logger.error (" Uncaught exception: \( exception ) " , tag : .system )
242
+ }
243
+ }
244
+ // Set Up Signal Handler:
245
+ import Darwin
246
+
247
+ func setUpSignalHandler () {
248
+ signal (SIGABRT) { _ in
249
+ Logger.error (" Received SIGABRT signal" , tag : .system )
250
+ }
251
+ signal (SIGILL) { _ in
252
+ Logger.error (" Received SIGILL signal" , tag : .system )
253
+ }
254
+ // Add handlers for other signals as needed
255
+ }
256
+ // Call Handlers at App Launch:
257
+ // In AppDelegate or main entry point
258
+ func applicationDidFinishLaunching (_ application: UIApplication) {
259
+ setUpExceptionHandler ()
260
+ setUpSignalHandler ()
261
+ // Other initialization code...
262
+ }
263
+ // Extend LogType:
264
+ public enum LogType {
265
+ // Existing cases...
266
+ case crashlytics
267
+ }
268
+ // Implement Crashlytics Logging:
269
+ extension LogType {
270
+ internal func log (msg : String , level : LogLevel, tag : LogTag) {
271
+ switch self {
272
+ // Existing cases...
273
+ case .crashlytics :
274
+ Crashlytics.crashlytics ().log (msg)
275
+ if level == .error {
276
+ let error = NSError (domain : Bundle.main .bundleIdentifier ?? " Logger" , code : 0 , userInfo : [NSLocalizedDescriptionKey: msg])
277
+ Crashlytics.crashlytics ().record (error : error)
278
+ }
279
+ }
280
+ }
281
+ }
282
+ // Usage Example:
283
+ Logger.type = .crashlytics
284
+ Logger.error (" Critical failure" , tag : .system )
285
+
286
+ ```
287
+
288
+ ** Introduce a New Log Level " Important" **
289
+
290
+ ** Description** : Add a new log level for messages that are more significant than `info` but not quite `warning`.
291
+
292
+ ** Implementation** :
293
+
294
+ - ** Add New Case in `LogLevel`** :
295
+
296
+ ```swift
297
+ public enum LogLevel: String , CaseIterable {
298
+ // Existing cases...
299
+ case important = " 🟢"
300
+ }
301
+ ```
302
+
303
+ - ** Add Title for the New Level** :
304
+
305
+ ```swift
306
+ extension LogLevel {
307
+ var title: String {
308
+ switch self {
309
+ // Existing cases...
310
+ case .important :
311
+ return " Important"
312
+ }
313
+ }
314
+ }
315
+ ```
316
+ - ** Add Method in `Logger+ Command`** :
317
+
318
+ ```swift
319
+ extension Logger {
320
+ public static func important (_ msg : String , tag : LogTag = .other ) {
321
+ log (msg, level : .important , tag : tag)
322
+ }
323
+ }
324
+ ```
325
+
326
+ - ** Usage Example** :
327
+
328
+ ```swift
329
+ Logger.important (" User achieved a significant milestone" , tag : .achievement )
330
+ ```
331
+
332
+
333
+ ** Adopt Protocol- Oriented Design**
334
+
335
+ ** Description** : Refactor the logger to use protocols, allowing for greater flexibility, easier testing, and adherence to SOLID principles.
336
+
337
+ ** Implementation** :
338
+
339
+ - ** Define `LoggerProtocol`** :
340
+
341
+ ```swift
342
+ public protocol LoggerProtocol {
343
+ func log (_ msg : String , level : LogLevel, tag : LogTag)
344
+ }
345
+ ```
346
+
347
+ - ** Create Concrete Implementations** :
348
+
349
+ ```swift
350
+ public class ConsoleLogger: LoggerProtocol {
351
+ public func log (_ msg : String , level : LogLevel, tag : LogTag) {
352
+ print (msg)
353
+ }
354
+ }
355
+
356
+ public class FileLogger: LoggerProtocol {
357
+ private let filePath: String
358
+
359
+ public init (filePath : String ) {
360
+ self .filePath = filePath
361
+ }
362
+
363
+ public func log (_ msg : String , level : LogLevel, tag : LogTag) {
364
+ // Implement file writing logic here
365
+ }
366
+ }
367
+ ```
368
+
369
+ - ** Modify `Logger` to Use `LoggerProtocol`** :
370
+
371
+ ```swift
372
+ public final class Logger {
373
+ public static var logger: LoggerProtocol = ConsoleLogger ()
374
+ // Modify log methods to use `logger.log(...)`
375
+ }
376
+ ```
377
+
378
+ - ** Usage Example** :
379
+ ```
380
+ Logger.logger = FileLogger (filePath : " /path/to/log.txt" )
381
+ Logger.info (" This will be logged to a file" )
382
+ ```
383
+
384
+ ** Add Support for Swift Concurrency (`async`/ `await `)**
385
+
386
+ ** Description** : Modernize the logger to be compatible with Swift's async/ await concurrency model.
387
+
388
+ ** Implementation** :
389
+
390
+ ```swift
391
+ extension Logger {
392
+ public static func logAsync (_ msg : String , level : LogLevel, tag : LogTag) async {
393
+ await withCheckedContinuation { continuation in
394
+ DispatchQueue.global (qos : .utility ).async {
395
+ log (msg, level : level, tag : tag)
396
+ continuation.resume ()
397
+ }
398
+ }
399
+ }
400
+ }
401
+ Task {
402
+ await Logger.logAsync (" Asynchronous log message" , level : .info , tag : .system )
403
+ }
404
+ ```
405
+
406
+ ** Description** : Ensure that logging is safe in multi- threaded environments, particularly when writing to shared resources like files.
407
+
408
+ ** Implementation** :
409
+
410
+ - ** Use Serial Dispatch Queues** :
411
+
412
+ ```swift
413
+ extension LogType {
414
+ private static let fileWriteQueue = DispatchQueue (label : " com.logger.fileWriteQueue" )
415
+
416
+ static func writeToFile (string : String , filePath : String ) {
417
+ fileWriteQueue.async {
418
+ // File writing code...
419
+ }
420
+ }
421
+ }
142
422
```
0 commit comments