ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Logger Caching & Performance
    iOS 2025. 6. 4. 13:15

    안녕하세요. 그린입니다 🍏

    이번 포스팅에서는 Logger 캐싱과 성능에 대해 한번 정리해볼까 합니다 🙋🏻


    Logger Caching & Performance

    iOS 개발에서 os.Logger를 포함한 로깅 시스템은 앱 진단 및 디버깅에 있어서 핵심적인 역할을 해줍니다.

     

    하지만, 혹시 Logger 인스턴스를 매번 새로 생성하고 있지는 않으신가요?

     

    이번 포스팅에서는 그것을 중심으로 Logger 인스턴스 캐싱이 왜 필요하고 실제로 어떤 성능 차이가 있는지에 대해 한번 분석해보면서 정리해봅니다 😃

     


    Logger를 매번 새로 만든다면?

    Apple은 iOS 14부터 os.Logger API를 도입하면서 기존의 os_log 기반 로깅을 더 정돈된 방식으로 관리할 수 있게 발전했습니다.

    Logger는 단순한 구조체지만, 내부적으로는 서브시스템 및 카테고리에 따라 로깅을 최적화하기 위한 내부 작업들이 수행됩니다.

     

    매번 새로운 Logger 인스턴스를 생성하는 예를 하나 들어볼까요?

     

    func logEvent(_ message: String) {
        let logger = Logger(subsystem: "com.myapp.analytics", category: "event")
        logger.info("\(message, privacy: .public)")
    }

     

    이렇게 된다면 매번 호출마다 Logger 구조체가 생성되고, 내부적으로는 서브시스템, 카테고리 매핑 및 시스템 리소스를 사용하는 과정이 반복되는것입니다.

     

    그럼 어떻게 해볼까요?


    Logger Caching

    Logger는 내부적으로 리소스를 사용하는 구조이기 때문에 매번 인스턴스를 새로 생성하는것보다 재사용하는것이 훨씬 효율적입니다.

     

    enum Log {
        static let event = Logger(subsystem: "com.myapp.analytics", category: "event")
        static let network = Logger(subsystem: "com.myapp.networking", category: "http")
        static let ui = Logger(subsystem: "com.myapp.ui", category: "rendering")
    }

     

    이렇게 된다면 Logger는 앱 생명주기 동안에 한번만 생성되며 이후 모든 로깅 작업에서 재사용될 수 있죠.

     

    그럼 Performance는 얼마나 차이나길래 이게 중요할까요?

     


    Logger Performance

    한번 이를 알아보기 위해서 Logger 생성에 대한 차이를 측정해봤습니다.

    측정 방식은 10만번의 로그 호출을 반복하고 측정 도구로는 DispatchTime을 이용했습니다.

     

    매번 생성

    let start = DispatchTime.now()
    for _ in 0..<100_000 {
        let logger = Logger(subsystem: "com.test.perf", category: "temp")
        logger.info("test log")
    }
    let end = DispatchTime.now()
    print("Time (new each time): \(end.uptimeNanoseconds - start.uptimeNanoseconds)")

     

    캐싱

    let cachedLogger = Logger(subsystem: "com.test.perf", category: "temp")
    
    let start = DispatchTime.now()
    for _ in 0..<100_000 {
        cachedLogger.info("test log")
    }
    let end = DispatchTime.now()
    print("Time (cached): \(end.uptimeNanoseconds - start.uptimeNanoseconds)")

     

    방식 평균 실행 시간 (ns)
    매번 생성 약 950,000,000
    캐싱 약 180,000,000

     

    약 수치적으로 5배 이상의 성능 차이를 보입니다.

    캐싱 시, 로그가 매우 빠르게 처리되며 시스템 리소스 사용량도 줄어들죠.

     

    그럼 이 캐싱 전략을 어떻게 구현해보면 좋을까요?

     


    Caching Policy

    여러 방식이 당연히 있어 이 방법이 정답은 아닙니다.

    하나의 예시라고 봐주시면 좋을것 같아요 🙏🏻

     

    1️⃣ 전역 LoggerStore 정의

    enum LoggerStore {
        static let app = Logger(subsystem: "com.myapp.core", category: "application")
        static let auth = Logger(subsystem: "com.myapp.auth", category: "authentication")
        static let db = Logger(subsystem: "com.myapp.db", category: "database")
    }

     

    2️⃣ 재사용

    LoggerStore.auth.debug("Login succeeded for user: \(username, privacy: .private)")
    LoggerStore.db.error("Failed to save entity: \(error.localizedDescription, privacy: .public)")

     

    3️⃣ 확장 - 카테고리별 관리

    enum LoggerCategory {
        case auth, network, ui, db
    
        var logger: Logger {
            switch self {
            case .auth:
                return Logger(subsystem: "com.myapp", category: "auth")
            case .network:
                return Logger(subsystem: "com.myapp", category: "network")
            case .ui:
                return Logger(subsystem: "com.myapp", category: "ui")
            case .db:
                return Logger(subsystem: "com.myapp", category: "db")
            }
        }
    }

     

    그런데 위 3번 방식은 switch 구문에서 매번 계속 Logger를 생성하기에 캐싱 효과가 사라질 수 있고 좋은 방법은 아닙니다.

     

    따라서 다음과 같은 정적인 캐시 맵 형태가 안전할 수 있어요.

     

    enum LoggerCache {
        static let shared: [LoggerCategory: Logger] = [
            .auth: Logger(subsystem: "com.myapp", category: "auth"),
            .network: Logger(subsystem: "com.myapp", category: "network"),
            .ui: Logger(subsystem: "com.myapp", category: "ui"),
            .db: Logger(subsystem: "com.myapp", category: "db"),
        ]
    }

     


    Conclusion

    정리해보면, 왜 Logger 캐싱을 해야 할까요?

     

    Logger는 값 타입이지만 내부적으로 시스템 리소스를 관리하기에 생성 비용이 높죠.

    그래서 재사용 가능한 인스턴스를 캐싱함으로 메모리 효율성을 증가시키고 로깅 속도를 향상시킵니다.

    또한, CPU 부하도 감소 시켜줄 수 있죠.

    로깅이 빈번하게 발생하는 앱이라면 이런 최적화가 전체적인 성능에 영향을 미칠 수 있어요.

    로깅은 단순한 디버깅 도구를 넘어 앱 품질에도 영향을 주는 중요한 요소입니다.

     

    Logger가 어떻게 사용되고 있는지 살펴보면서 전역 캐싱 방식으로 개선해보는건 어떨까요?

     


    References

     

    Logging | Apple Developer Documentation

    Capture telemetry from your app for debugging and performance analysis using the unified logging system.

    developer.apple.com

     

     

    Explore logging in Swift - WWDC20 - Videos - Apple Developer

    Meet the latest generation of Swift unified logging APIs. Learn how to log events and errors in your app while preserving privacy. Take...

    developer.apple.com

    'iOS' 카테고리의 다른 글

    Lottie vs WebP Animation  (2) 2025.05.28
    Server Image Format (feat. JPG, PNG, WebP)  (4) 2025.05.23
    AVCaptureVideoPreviewLayer  (0) 2025.04.18
    Crash 감지하고 다루기  (1) 2025.03.29
    iOS에서 서버 과부하 감지 및 API 호출 최적화  (0) 2025.03.15
Designed by Tistory.