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 문 이상의 강력한 도구가 될 수 있으며, 체계적인 접근 방식은 복잡한 앱 개발에서 그 가치를 더욱 발휘합니다.

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다