Skip to content

Instantly share code, notes, and snippets.

@IuriiIaremenko
Last active January 25, 2021 10:00
Show Gist options
  • Save IuriiIaremenko/ea459665cc4c7a90c0ba8031d0b41ba0 to your computer and use it in GitHub Desktop.
Save IuriiIaremenko/ea459665cc4c7a90c0ba8031d0b41ba0 to your computer and use it in GitHub Desktop.
//
// Logger.swift
//
// Created by Iurii Iaremenko on 7/1/19.
//
import Foundation
import os
import FirebaseCrashlytics
public struct LoggerOptions: OptionSet {
public init(rawValue: Int) {
self.rawValue = rawValue
}
public let rawValue: Int
static let verbose = LoggerOptions(rawValue: 1 << 0)
static let debug = LoggerOptions(rawValue: 1 << 1)
static let info = LoggerOptions(rawValue: 1 << 2)
static let warning = LoggerOptions(rawValue: 1 << 3)
static let error = LoggerOptions(rawValue: 1 << 4)
static let all: LoggerOptions = [.verbose, .debug, .info, .warning, .error]
}
public struct LoggerDestinations: OptionSet {
public init(rawValue: Int) {
self.rawValue = rawValue
}
public let rawValue: Int
static let console = LoggerDestinations(rawValue: 1 << 0)
static let crashlytics = LoggerDestinations(rawValue: 1 << 1)
static let crashlyticsNonFatals = LoggerDestinations(rawValue: 1 << 2)
static let all: LoggerDestinations = [.console, .crashlytics]
}
public struct Logger {
private enum Level: Int {
case verbose = 0
case debug
case info
case warning
case error
var prefix: StaticString {
switch self {
case .verbose: return "⚪️ [VERBOSE] %s"
case .debug: return "⚫️ [DEBUG] %s"
case .info: return "🔵 [INFO] %s"
case .warning: return "⭕️ [WARNING] %s"
case .error: return "🔴 [ERROR] %s"
}
}
}
private init() {}
static public var logLevel: LoggerOptions = .all
static public var destinations: LoggerDestinations = .console
static public func verbose(_ message: @autoclosure () -> String, file: String = #file, function: String = #function, line: Int = #line) {
guard logLevel.contains(.verbose) else { return }
log(level: .verbose, message: message(), file: file, function: function, line: line)
}
static public func debug(_ message: @autoclosure () -> String, file: String = #file, function: String = #function, line: Int = #line) {
guard logLevel.contains(.debug) else { return }
log(level: .debug, message: message(), file: file, function: function, line: line)
}
static public func info(_ message: @autoclosure () -> String, file: String = #file, function: String = #function, line: Int = #line) {
guard logLevel.contains(.info) else { return }
log(level: .info, message: message(), file: file, function: function, line: line)
}
static public func warning(_ message: @autoclosure () -> String, file: String = #file, function: String = #function, line: Int = #line) {
guard logLevel.contains(.warning) else { return }
log(level: .warning, message: message(), file: file, function: function, line: line)
}
static public func error(_ message: @autoclosure () -> String, file: String = #file, function: String = #function, line: Int = #line) {
guard logLevel.contains(.error) else { return }
log(level: .error, message: message(), file: file, function: function, line: line)
}
static private func log(level: Logger.Level, message: @autoclosure () -> String, file: String, function: String, line: Int) {
guard !destinations.isEmpty else { return }
let formattedString = getFormattedString(file: file, function: function, line: line, message: message())
if destinations.contains(.console) {
os_log(level.prefix, formattedString)
}
if destinations.contains(.crashlytics) {
Crashlytics.crashlytics().log(formattedString.nonPrivacySensativeString)
}
if destinations.contains(.crashlyticsNonFatals) && level == .error {
Crashlytics.crashlytics().record(error: NSError(domain: function, code: line))
}
}
static private func getFormattedString(file: String, function: String, line: Int, message: String) -> String {
return "\(file.fileName)::\(function.functonName):\(line) \(message)"
}
}
private extension String {
var nonPrivacySensativeString: String {
let string = self.lowercased()
for forbiddenString in GlobalConstants.forbiddenStrings where string.contains(forbiddenString) {
return "\(forbiddenString): ***"
}
return self
}
var fileName: String {
return self.components(separatedBy: "/").last?.components(separatedBy: ".").first ?? ""
}
var functonName: String {
return String(prefix(while: { return $0 != "(" }))
}
}
struct GlobalConstants {
static let forbiddenStrings: [String] = ["password", "certificate", "token"]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment