[카테고리:] 미분류

  • iOS 개발을 위한 효율적인 로깅 시스템 구현하기

    iOS 앱 개발 과정에서 로깅(logging)은 디버깅과 문제 해결을 위한 필수적인 도구입니다. 효과적인 로깅 시스템은 개발 과정을 원활하게 하고, 실제 사용자 환경에서 발생하는 문제를 추적하는 데 큰 도움이 됩니다. 이번 포스팅에서는 싱글톤 패턴과 OptionSet을 활용한 유연하고 확장 가능한 로깅 시스템 구현 방법을 소개합니다.

    로깅 시스템의 필요성

    앱 개발 시 다음과 같은 상황에서 로깅이 필요합니다:

    1. 개발 중 코드 흐름 추적
    2. 특정 기능의 동작 검증
    3. 사용자 환경에서 발생하는 오류 분석
    4. 성능 병목 현상 식별

    그러나 모든 정보를 항상 로깅하면 콘솔이 불필요한 정보로 넘쳐나고, 정작 중요한 정보를 놓칠 수 있습니다. 따라서 상황과 중요도에 따라 로깅 수준을 조절할 수 있는 시스템이 필요합니다.

    로깅 레벨 정의하기

    먼저 다양한 로깅 레벨을 정의하기 위해 Swift의 OptionSet을 활용합니다. OptionSet은 비트 마스크 방식으로 여러 옵션을 조합할 수 있게 해주는 프로토콜입니다.

    struct _logLevel: OptionSet {
        let rawValue: Int
        static let critical    = _logLevel(rawValue: 1 << 0)
        static let major       = _logLevel(rawValue: 1 << 1)
        static let minor       = _logLevel(rawValue: 1 << 2)
        static let just        = _logLevel(rawValue: 1 << 3)
        static let graph       = _logLevel(rawValue: 1 << 4)
        static let graph2      = _logLevel(rawValue: 1 << 5)
        static let graphPanel  = _logLevel(rawValue: 1 << 6)
        static let all: _logLevel = [.critical, .major, .minor, .just, .graph, .graph2, .graphPanel]
    }
    

    이 코드에서 각 로그 레벨은 비트 플래그로 정의되어 있습니다:

    • critical: 가장 중요한 오류와 문제(비트 위치: 0)
    • major: 주요 경고 및 오류(비트 위치: 1)
    • minor: 덜 중요한 문제(비트 위치: 2)
    • just: 일반적인 정보성 로그(비트 위치: 3)
    • graph, graph2, graphPanel: 그래픽 관련 로그(비트 위치: 4-6)
    • all: 모든 로그 레벨을 포함

    싱글톤 패턴으로 전역 설정 관리

    다음으로, 애플리케이션 전체에서 일관된 로깅 설정을 관리하기 위해 싱글톤 패턴을 사용합니다:

    class GlobalSettings {
        var logLevel: _logLevel
        
        private init() {
            // 기본 로그 레벨 설정
            // logLevel = .all
            // logLevel = .critical
            logLevel = .graphPanel
            // logLevel = .just
        }
        
        static let shared = GlobalSettings()
    }
    

    이 싱글톤 클래스는:

    1. 애플리케이션 전체에서 단일 인스턴스를 공유합니다.
    2. 로그 레벨을 중앙에서 관리합니다.
    3. private init()으로 외부에서 새 인스턴스 생성을 방지합니다.

    편리한 현재 위치 로깅 기능

    로깅할 때 현재 코드 위치를 쉽게 식별할 수 있도록 String 확장을 활용합니다:

    extension String {
        func pwd(_ x: Any) {
            print("pwd_", String(describing: x.self))
        }
    }
    

    이 간단한 확장을 사용하면 다음과 같이 현재 클래스나 객체의 위치를 쉽게 로깅할 수 있습니다:

    "".pwd(self)
    

    로깅 시스템 활용 방법

    이제 우리가 만든 로깅 시스템을 코드에서 어떻게 활용하는지 살펴보겠습니다:

    1. 특정 로그 레벨에 해당하는 로그만 출력하기

    if GlobalSettings.shared.logLevel.contains(.graph2) {
        print("그래프 관련 상세 정보: \(someGraphData)")
    }
    

    2. 현재 위치 로깅하기

    func someImportantMethod() {
        "".pwd(self)  // 출력: pwd_ YourClassName
        // 메서드 구현...
    }
    

    3. 다양한 로그 레벨 조합하기

    // 중요 로그와 그래프 관련 로그만 활성화
    GlobalSettings.shared.logLevel = [.critical, .major, .graph]
    

    로깅 시스템 확장하기

    이 기본 시스템을 더 확장하여 다음과 같은 기능을 추가할 수 있습니다:

    1. 로그 형식 개선

    extension String {
        func pwd(_ x: Any) {
            let dateFormatter = DateFormatter()
            dateFormatter.dateFormat = "HH:mm:ss.SSS"
            let timestamp = dateFormatter.string(from: Date())
            
            print("[\(timestamp)] pwd_", String(describing: x.self))
        }
    }
    

    2. 로그 파일 저장

    func saveLogToFile(_ message: String) {
        // 파일에 로그 저장 구현
    }
    
    extension String {
        func pwd(_ x: Any) {
            let message = "pwd_" + String(describing: x.self)
            print(message)
            
            if GlobalSettings.shared.saveLogsToFile {
                saveLogToFile(message)
            }
        }
    }
    

    3. 개발/프로덕션 환경에 따른 로그 레벨 자동 설정

    private init() {
        #if DEBUG
            logLevel = .all
        #else
            logLevel = .critical  // 프로덕션 환경에서는 중요 로그만
        #endif
    }
    

    실전 활용 사례

    그래픽 컴포넌트 디버깅

    func updateGraph(with newData: [DataPoint]) {
        if GlobalSettings.shared.logLevel.contains(.graph) {
            print("그래프 데이터 업데이트: \(newData)")
        }
        
        // 데이터 처리 로직...
        
        if GlobalSettings.shared.logLevel.contains(.graphPanel) {
            print("패널 상태: visible=\(isVisible), scale=\(currentScale)")
        }
    }
    

    네트워크 요청 추적

    func fetchData() {
        "".pwd(self)  // 현재 위치 로깅
        
        if GlobalSettings.shared.logLevel.contains(.major) {
            print("네트워크 요청 시작: \(apiEndpoint)")
        }
        
        // 네트워크 요청 구현...
        
        if GlobalSettings.shared.logLevel.contains(.minor) {
            print("응답 데이터: \(responseData)")
        }
    }
    

    결론

    효과적인 로깅 시스템은 개발 효율성을 높이고 문제 해결 시간을 단축시킵니다. 이 포스팅에서 소개한 싱글톤 패턴과 OptionSet을 활용한 로깅 시스템은:

    1. 다양한 로그 레벨을 지원합니다.
    2. 애플리케이션 전체에서 일관되게 사용할 수 있습니다.
    3. 필요에 따라 쉽게 확장할 수 있습니다.
    4. 코드 위치 추적 기능으로 디버깅을 돕습니다.

    이러한 로깅 시스템을 자신의 프로젝트에 맞게 조정하여 사용한다면, 디버깅과 유지보수 과정에서 큰 도움이 될 것입니다. 로깅은 단순한 print 문 이상의 강력한 도구가 될 수 있으며, 체계적인 접근 방식은 복잡한 앱 개발에서 그 가치를 더욱 발휘합니다.