Skip to content

Commit 45251d8

Browse files
committed
Minor tweaks
1 parent ede6a6d commit 45251d8

File tree

1 file changed

+308
-28
lines changed

1 file changed

+308
-28
lines changed

README.md

Lines changed: 308 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,48 +10,58 @@
1010
> Simple console logger
1111
1212
### 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
1616

1717
### 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.
2121

2222
### Logging format:
2323
```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
3032
```
3133

3234
### Configure:
3335
```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)
4247
```
4348

4449
### Add custom log end-point like GA or Firebase crashalytics
4550
```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+
}
5057
}
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
5565
```
5666

5767
> [!NOTE]
@@ -72,6 +82,9 @@ Logger.info("Something happened") // Prints to Console.app (filter by category o
7282
```
7383

7484
### Tracing
85+
86+
The `Trace` class can be combined with `Logger` to include function names, class names, and line numbers in your logs.
87+
7588
```swift
7689
class Test {
7790
func myFunction() {
@@ -139,4 +152,271 @@ class ConsoleLogger: LoggerProtocol {
139152
class FileLogger: LoggerProtocol {
140153
// File logging implementation...
141154
}
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+
}
142422
```

0 commit comments

Comments
 (0)