[iOS] 뒤죽박죽 print는 더이상 Naver...

유인호·2024년 10월 16일
1

iOS

목록 보기
69/73
post-thumbnail

0. 서론

내가 별로 안좋아하던게 몇몇개 있었다. 그 중 하나가 Test 코드이고, 또 다른걸로는 Logger가 있다.

최근들어 만들고 버리는 프로젝트가 아닌, 앞으로 계속 책임져야할 프로젝트를 맡게 되니 생각이 좀 달라졌다. 생각이 좀 짧았다는 생각도 많이 들었다.

오늘은 그 중 하나였던 Logger에 대해 알아보도록 한다.

1. Logger가 뭔데요

print는 콘솔에 데이터를 출력한다. Logger도 사실 이와 다르지 않다. 성능상의 이점이니 뭐니 이런건 매우 의미가 없다고 생각하고, 그냥 Print를 좀더 있어 뵈게 찍는다. 라고 생각하면 좋다. 사실 그래서 별로 안좋아했는데, 체계화된 Logging이 얼마나 중요한지 많이 깨닫게 되어 이번 회사 프로젝트에 첫 도입하게 되었음.

2. Logger를 왜 도입했는데요

print를 찍으면 나는 보통 그냥 이렇게 찍었다.

print(error)

이렇게 찍으면 당연히 error가 뭔지 나오겠지만, 이게 한두개가 아니다보니까 어디서난 에런지, 이게 에런지 그냥 print인지 전혀 알수가 없었다. 게다가

print("=======================")

본인은 이런 print도 즐겨 써서, 저번에 회사앱 돌리면서 콘솔창을 보니 매우 맘에 안들고 디버깅 속도에 지장이 생긴다는걸 깨달아 버렸음.

회사 앱은 BLE, Network와 연동하기 때문에, 여러 Service Layer들과 ViewModel이 소통을 해야한다. 심지어 같은 BLE, Network 더라도 내가 목적마다 여러개 만들어놨다. 그래서 복잡성이 높아졌는데, 이를 Logger로 로그를 체계화를 시켜 디버깅을 빠르게 해야겠다 라는 생각이 들었다.

3. 체계화가 뭔데요

적어도 내 기준에선,

  1. 이 Log가 무슨 용도인지 알 것
  2. 이 Log가 어디서 온놈인지 알 것
  3. Log를 찍으려는 데이터가 디버깅하는데에 도움이 될 것
  4. 실 성능에 아무런 효과를 주지 않을 것.

라고 생각했음.

인터넷에 뒤져보면 OsLog로 열심히 구조화 해놓은게 많던데 나는 내 기준을 가지고 이 기준에 맞춰 구조화를 진행해보았다.

4. 코드

import OSLog

/*
 MARK: - Logger System
 Network: 서버(Firebase) 관련 데이터
 Bluetooth: 블루투스 관련 데이터
 Debug: Debugging을 위한 데이터
 Data: 서버나 블루투스에서 받아온 데이터
 error: 서버나 블루투스, UI등에서 발생하는 Error 데이터
 */

extension OSLog {
    static let subsystem = Bundle.main.bundleIdentifier!
    static let network = OSLog(subsystem: subsystem, category: "Network")
    static let bluetooth = OSLog(subsystem: subsystem, category: "Bluetooth")
    static let debug = OSLog(subsystem: subsystem, category: "Debug")
    static let data = OSLog(subsystem: subsystem, category: "Data")
    static let error = OSLog(subsystem: subsystem, category: "Error")
}

extension Utility {
    enum Log {
        private enum Level {
            case debug
            case data
            case network
            case bluetooth
            case error(error: Case)
            case custom(categoryName: String)
            case defaultPrint
            
            fileprivate var category: String {
                switch self {
                case .debug:
                    return "Debug"
                case .data:
                    return "Info"
                case .network:
                    return "Network"
                case .bluetooth:
                    return "Bluetooth"
                case .error:
                    return "Error"
                case .custom(let categoryName):
                    return categoryName
                case .defaultPrint:
                    return "Default"
                }
            }
            
            fileprivate var osLog: OSLog {
                switch self {
                case .debug, .defaultPrint:
                    return OSLog.debug
                case .data:
                    return OSLog.data
                case .network:
                    return OSLog.network
                case .bluetooth:
                    return OSLog.bluetooth
                case .error:
                    return OSLog.error
                case .custom:
                    return OSLog.debug
                }
            }
            
            fileprivate var osLogType: OSLogType {
                switch self {
                case .debug, .defaultPrint:
                    return .default
                case .data:
                    return .info
                case .network:
                    return .default
                case .bluetooth:
                    return .default
                case .error:
                    return .error
                case .custom:
                    return .debug
                }
            }
        }
        
        static private func log(_ message: Any, _ arguments: [Any], level: Level, functionName: String, fileName: String) {
#if DEBUG
            let extraMessage: String = arguments.map({ String(describing: $0) }).joined(separator: " ")
            let logger = Logger(subsystem: OSLog.subsystem, category: level.category)
            let logMessage = "\(message) \(extraMessage)"
            switch level {
            case .debug, .custom:
                logger.debug("[Debug] [\(fileName) -> \(functionName)]: \(logMessage, privacy: .public)")
            case .data:
                logger.info("[Data] [\(fileName) -> \(functionName)]: \(logMessage, privacy: .public)")
            case .network:
                logger.log("[Network] [\(fileName) -> \(functionName)]: \(logMessage, privacy: .public)")
            case .error(let error):
                logger.error("[\(error.rawValue) Error] [\(fileName) -> \(functionName)]: \(logMessage, privacy: .public)")
            case .bluetooth:
                logger.debug("[Bluetooth] [\(fileName) -> \(functionName)]: \(logMessage, privacy: .public)")
            case .defaultPrint:
                logger.debug("\(logMessage, privacy: .public)")
            }
#endif
        }
        
        static func debug(_ message: Any, _ arguments: [Any] = [], functionName: String = #function, fileName: String = #file) {
            log(message, arguments, level: .debug, functionName: functionName, fileName: fileName.fileName)
        }
        
        static func data(_ message: Any, _ arguments: [Any] = [], functionName: String = #function, fileName: String = #file) {
            log(message, arguments, level: .data, functionName: functionName, fileName: fileName.fileName)
        }
        
        static func network(_ message: Any, _ arguments: [Any] = [], functionName: String = #function, fileName: String = #file) {
            log(message, arguments, level: .network, functionName: functionName, fileName: fileName.fileName)
        }
        
        static func error(_ message: Any, error: Case, _ arguments: [Any] = [], functionName: String = #function, fileName: String = #file) {
            log(message, arguments, level: .error(error: error), functionName: functionName, fileName: fileName.fileName)
        }
        
        static func bluetooth(_ message: Any, _ arguments: [Any] = [], functionName: String = #function, fileName: String = #file) {
            log(message, arguments, level: .bluetooth, functionName: functionName, fileName: fileName.fileName)
        }
        
        static func defaultPrint(_ message: Any) {
            log(message, [], level: .defaultPrint, functionName: "", fileName: "")
        }
    }
}

인터넷에 있던 다른 코드들은 목적만을 가지고 log를 찍는 모습이 많이 보였다.
그러나 앱의 규모가 점점 커지다보니 이 log가 목적도 목적이지만 대체 어디서 나타난 log인지 알 수가 없었음.

그래서 #function과 #file을 이용하여 이 함수가 무슨 목적이고, 어디서 온놈인지, 어느 함수인지 바로 알 수 있게 되었다.

[Debug][ContentView.swift -> testFunction()]: Test입니다

이런식으로 깔끔하게 Log를 찍을 수 있게 되었다.

profile
🍎Apple Developer Academy @ POSTECH 2nd, 🌱SeSAC iOS 4th
post-custom-banner

0개의 댓글