[WWDC20] Explore logging in Swift

sokyte
daily-monster
Published in
8 min readApr 7, 2024

안녕하세요. 소깡입니다.

오늘은 프로젝트를 개발하면서 Logger 모듈을 만들게 되었는데, 여기서 이 Log가 무엇이고 어떤 역할을 하는지 소개하고자 합니다.

WWDC20의 세션에서 Log API에 대해 설명하고 있어, 해당 세션을 바탕으로 글을 적어보았습니다. 잘못된 부분이 있거나 질문이 있으시다면 댓글로 남겨주세요!

Log API

앱에서 Debug 하기 쉽도록 애플에서 제공하는 logging API입니다.

앱을 개발하면 다양한 버그를 마주하게 되는데 그중 몇몇 버그는 특수한 경우에 드물게 나타나므로 이슈 트래킹이 어렵습니다.

Log는 이렇게 재현하기 어려운 버그에 대해서, 재현할 필요 없이 버그가 발생하기 전 상황들을 기록하여 어떤 식으로 버그가 발생했는지 추적할 수 있도록 도와주는 API 입니다.

WWDC20 영상에서는 Fruta라는 앱을 예시로 버그 상황을 보여줍니다.

Fruta에는 gift card라는 탭이 존재합니다. 탭에서는 카드 리스트가 페이징 스크롤로 구현되어 있습니다. 스크롤 뷰의 마지막에 도달하게 되면 서버 통신을 하여 그 외의 카드를 로드합니다.

만약 이 때, (로딩 중에) 카드를 선택하게 되면 (선택한 카드에 대한 로직을 처리하기 위해) 앱은 로딩과 진행 중인 다른 통신을 중지하게 됩니다. 이 상황에서 종종 로딩이 실패하는 버그가 발생하게 됩니다.

그런데 이런 버그는 매번 발생하는 버그가 아닙니다. 이런 식으로 재현이 어려운 앱에 logging을 추가하면 다시 처음부터 앱에 진입하여 ~ 로딩까지의 과정 없이 오류가 어떤 상황에서 어떻게 발생하게 된 것인지 추적할 수 있습니다.

Xcode12 부터 통합된 로깅을 위해 새로운 SwiftAPI가 도입되었습니다.

이 API를 사용함으로써 앱에서 발생하는 중요한 이벤트를 기록할 수 있고, 이러한 로그는 OS에 보관이 되므로 장치에서 쉽게 찾을 수 있습니다.

앱에 로그를 남기기 위해서는 세 가지 단계가 필요합니다

import os // 1

let logger = Logger(subsystem: "com.example.Fruta", category: "giftcards") // 2
func beginTask(url: URL, handler: (Data) -> Void) {
launchTask(with: url) {
handler($0)
}
logger.log("Started a task \\(taskId)") // 3
}
  • 로그 API가 존재하는 ‘os’모듈을 import 하고
  • logger라는 인스턴스를 생성합니다. logger 인스턴스를 생성할 때는 생성자에 ‘subsystem’과 ‘category’ 값을 전달해야 합니다. 이 값은 해당 logger가 기록하는 메세지에 함께 기록됩니다. ‘subsystem’은 기록된 로그에서 앱을 식별하는데 도움이 되는 식별자이고, ‘category’를 통해 앱 내부의 어떤 부분에서 기록된 메세지인지 구분할 수 있도록 해줍니다.

(→ 제가 이해한 바로는, subsystem이 앱을 / category가 앱의 어떤 부분, 즉, 탭, 화면 등을 의미한다고 이해했습니다.)

  • 그리고 로그를 남깁니다.

로그를 사용하여 메세지 내부에 런타임 데이터를 추가할 수 있습니다

이러한 방식은 우리가 일반적으로 런타임 시 데이터를 확인하기 위해 사용하는 print문과 비슷합니다.

로그 메세지 안에는 다양한 형태의 데이터가 들어갈 수 있고, CustomStringCovertible 프로토콜을 준수하는 모든 타입 뿐만 아니라 Int와 Double과 같은 숫자도 기록할 수 있습니다.

만약, 사용자가 설정한 타입을 추가하기 위해서는 해당 타입이 CustomStringConvertible 프로토콜을 준수해야 합니다.

다만, 런타임 데이터를 로그메세지에 추가할 경우 사용자 개인 정보가 기록될 수 있으므로 이를 방지하기 위해서 문자열 같이 숫자가 아닌 타입의 경우 로그에서 다시 처리합니다.

예를 들어서 로그에서 계좌 번호와 함께 메세지를 로깅했다면 출력 로그에서는 가려진 채로 보이는 것을 확인할 수 있습니다.

만약, 민감한 정보가 아니라 단순히 갯수와 같은 정보라면 값을 표시할 수 있도록 설정할 수 있습니다.

앱에서 생성한 로그 메시지를 → 운영 체제에 압축한 형태로 저장하고 mac에서는 터미널에서 log collect 명령어를 사용해서 해당 로그를 검색할 수 있습니다.

검색 방법은 먼저, 장치를 mac에 연결하고 터미널에서 검색어를 입력하면 됩니다.

위와 같이 정확한 시점을 지정하여 실행할 수 있고, output 옵션을 통해 파일명(추출된 로그가 담겨 있는)을 지정할 수 있습니다.

영상에서는 앱 내에 로그를 기록하여 버그를 확인할 수 있도록 코드를 작성했습니다.

그리고 기록된 로그 중에서 logger 인스턴스를 생성할 때 지정한 subsystem을 통해 검색을 하여 실패한 task만 확인할 수 있습니다.

위와 같이 실패한 ID를 검색하여 어떤 과정에서 오류가 났는지도 확인할 수 있습니다.

영상 속 과정에서는 앱이 더 많은 기프트 카드를 가져오기 위한 작업을 시작하였으나, 네트워크 에러로 인해 작업이 완료되었고, 시간 초과 후 다시 시도하려고 대기하고 있음을 알 수 있습니다.

하지만, 사용자는 기프트 카드에서 어떠한 항목을 선택했고, 이로 인해 활성된 작업을 중지되도록 하였습니다. 하지만 이 시점에는 활성된 작업이 존재하지 않으므로 실패하게 됩니다. 이렇게 버그가 발생한 상황을 살펴보고 개발자는 알맞는 조치를 취할 수 있게 될 것 입니다.

이렇게 log collect를 통해 로그를 수집할 수 있습니다. 또한 앱이 실행되는 동안에도 맥이 장치에 연결되어 있다면 Console.app 을 통해 로그를 streaming 할 수도 있습니다. 그리고 Xcode와 연결되어 있다면, Xcode Console에서도 확인할 수 있습니다.

이러한 디버깅은 print 문을 통한 디버깅보다 쉽게 필터링 할 수 있고, 구조화된 출력을 가질 수 있습니다.

log level

콘솔앱에서는 로그를 탐색할 때 ‘failure’ 메세지가 강조되었다는 것을 볼 수 있는데, 이는 로그 메세지의 level을 fault level로 기록했기 때문입니다.

  • Debug : 디버깅에 유용한 정보를 표시 하는 단계
  • Info : 오류 해결에 필수적이진 않지만 유용한 정보를 표시 하는 단계
  • Notice(Default) : 문제 해결에 절대적으로 필요한 정보를 표시하는 단계
  • Error : 실행 중에 발생하는 오류를 표시하는 단계
  • Fault : 결함 수준이 심각한 단계

이러한 단계에서 Error와 Fault 는 각각 노란색, 빨간색으로 강조 표시 됩니다.

실제로 Swift 의 로깅 API는 각각 level에 대한 메서드가 존재합니다.

이러한 level 을 선택하는데 고려해야할 중요한 점은 지속성 입니다.

즉, 로그 메세지를 아카이브하여 나중에 검색할 수 있는 지 여부를 체크해주어야 합니다. 지속되지 않는 로그메세지는 스트리밍 동안에만 확인할 수 있으며, 이러한 지속 여부는 로그 level에 따라 다릅니다. 물론 이러한 지속성은 level에 따라 증가합니다.

예를 들어 ‘Debug’ level은 앱 실행이 완료 된 후에는 검색을 할 수가 없습니다. 그리고 ‘Info’ level은 ‘log collect’ 명령어를 호출하기 몇 분 전에 생성되는 경우를 제외하고는 대부분 지속되지 않습니다.

그리고 보관되는 메세지 수에는 제한이 있기 때문에 제한을 초과하게 되면 이전 제한을 삭제하고 사용할 수 없게 됩니다. 하지만 ‘Error’ 와 ‘Fault’ 메세지는 보다 더 오래 유지됩니다. 이는 며칠 동안 유지됩니다.

지금까지 알아봤던 Logger API 는 iOS 14에서 사용할 수 있습니다. 만약 이전 버전을 대상으로 하는 경우에는 os_log 함수를 통해 사용이 가능합니다.

iOS의 로그 API를 올바르게 사용하면 앱의 버그 추적과 디버깅에 큰 도움이 되며, 앱 개발 및 유지 보수 과정에서 중요한 도구로 활용됩니다.

따라서 개발자는 적절한 로깅 레벨과 개인 정보 보호를 고려하여 로깅을 구현해야 합니다.

이번 글도 읽어주셔서 감사드리며, 다음주 글도 많은 관심 부탁드립니다. :)

--

--