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' 카테고리의 다른 글

    Meet PaperKit (feat. WWDC 2025)  (0) 2025.06.25
    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
Designed by Tistory.