Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- 리펙토링
- 클린 코드
- RxCocoa
- Human interface guide
- rxswift
- Protocol
- MVVM
- 리팩토링
- combine
- Xcode
- Observable
- uitableview
- Refactoring
- ios
- ribs
- SWIFT
- clean architecture
- 애니메이션
- tableView
- swiftUI
- 스위프트
- Clean Code
- 리펙터링
- swift documentation
- map
- HIG
- UITextView
- collectionview
- UICollectionView
- uiscrollview
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - swift] os_log, Logger, 통합 로깅 시스템 (unified logging system) 본문
iOS 응용 (swift)
[iOS - swift] os_log, Logger, 통합 로깅 시스템 (unified logging system)
jake-kim 2021. 3. 21. 00:09통합 로깅 시스템 (unified logging system)
- 통합의 의미: 로그 데이터 저장을 level에 따라 메모리와 디스크의 데이터 저장소에 모으는 것 (iOS 10+ 에서 가능)
- 장점: 모든 level의 시스템에서 로깅표출 가능
- xcode를 실행하지 않아도 console앱을 실행시키면 로깅 가능
- os_log를 사용하면 mac의 콘솔 앱에서 로그를 쉽게 확인(필터 기능) & 저장기능 등을 활용 가능
level
- 주로 Fault를 제외한 4가지 사용
level 종류 | disk에 저장 | 내용 |
Default (notice) | o | 문제 해결을 위한 level |
Info | o | Error케이스와 유사하지만, 에러 설명이 긴 경우 |
Debug | x | 개발 환경에서의 간단한 로깅 (mac의 '콘솔'앱에는 찍히지 않고 xcode console에만 표출) |
Error | o | Info케이스와 유사하지만, 간단한 에러인 경우 |
Fault | o | Error와 유사하지만 시스템 레벨 or 다중 프로세스 오류 캡쳐를 위한 것 |
os_log 사용방법
0. OSLog프레임워크 link
1. OSLog 속성 정의
- subsystem 문자열 정의 (bundleIdentifier를 이용)
- category정의
extension OSLog {
static let subsystem = Bundle.main.bundleIdentifier!
static let network = OSLog(subsystem: subsystem, category: "Network")
static let debug = OSLog(subsystem: subsystem, category: "Debug")
static let info = OSLog(subsystem: subsystem, category: "Info")
static let error = OSLog(subsystem: subsystem, category: "Error")
}
2. os_log를 편리하게 사용할 os_log메소드를 실행하는 Wrapper 형태 구현
- 종속성
import os.log
- os.log를 사용하는 Wrapper로 구현 `Log`
import os.log
extension OSLog {
...
}
struct Log {
// TODO
}
- Log의 level 정의
extension OSLog {
...
}
struct Log {
enum Level {
case debug
case info
case network
case error
case custom(categoryName: String)
}
}
- category 이름을 반환하는 `categoryName`변수 추가
extension OSLog {
...
}
struct Log {
enum Level {
...
fileprivate var categoryName: String {
switch self {
case .debug:
return "Debug"
case .info:
return "Info"
case .network:
return "Network"
case .error:
return "Error"
case .custom(let categoryName):
return categoryName
}
}
}
}
- level타입에 따라 OSLog의 level 객체를 반환하는 osLog 변수 추가(os_log메소드의 파라미터에 사용하기 위함)
extension OSLog {
...
}
struct Log {
enum Level {
...
fileprivate var category: String {
...
}
fileprivate var osLog: OSLog {
switch self {
case .debug:
return OSLog.debug
case .info:
return OSLog.info
case .network:
return OSLog.network
case .error:
return OSLog.error
case .custom:
return OSLog.debug
}
}
}
}
- level 타입에 따라 OSLog의 level type을 반환하는 osLogType 변수 추가(os_log메소드의 파라미터에 사용하기 위함)
extension OSLog {
...
}
struct Log {
enum Level {
...
fileprivate var category: String {
...
}
fileprivate var osLog: OSLog {
...
}
fileprivate var osLogType: OSLogType {
switch self {
case .debug:
return .debug
case .info:
return .info
case .network:
return .default
case .error:
return .error
case .custom:
return .debug
}
}
}
}
- os_log (iOS 14.0 미만), Logger(iOS 14.0 이상) wrapper 함수 정의
extension OSLog {
...
}
struct Log {
enum Level {
...
}
static private func log(_ message: Any, _ arguments: [Any], level: Level) {
#if LOG
// 이곳에 os_log, Logger를 통해 로깅하는 부분 구현
#endif
}
}
- log(_:_:level) 메소드 구현
extension OSLog {
...
}
struct Log {
enum Level {
...
}
static private func log(_ message: Any, _ arguments: [Any], level: Level) {
#if LOG
if #available(iOS 14.0, *) {
let extraMessage: String = arguments.map({ String(describing: $0) }).joined(separator: " ")
let logger = Logger(subsystem: OSLog.subsystem, category: level.category)
let logMessage = "\(message) \(extraMessage)"
switch level {
case .debug,
.custom:
logger.debug("\(logMessage, privacy: .public)")
case .info:
logger.info("\(logMessage, privacy: .public)")
case .network:
logger.log("\(logMessage, privacy: .public)")
case .error:
logger.error("\(logMessage, privacy: .public)")
}
} else {
let extraMessage: String = arguments.map({ String(describing: $0) }).joined(separator: " ")
os_log("%{public}@", log: level.osLog, type: level.osLogType, "\(message) \(extraMessage)")
}
#endif
}
}
3. 외부에서 `Log.`으로 접근하는 메소드 정의
extension OSLog {
...
}
struct Log {
enum Level {
...
}
static private func log(_:_:level:) {
...
}
}
// MARK: - utils
extension Log {
static func debug(_ message: Any, _ arguments: Any...) {
log(message, arguments, level: .debug)
}
static func info(_ message: Any, _ arguments: Any...) {
log(message, arguments, level: .info)
}
static func network(_ message: Any, _ arguments: Any...) {
log(message, arguments, level: .network)
}
static func error(_ message: Any, _ arguments: Any...) {
log(message, arguments, level: .network)
}
static func custom(category: String, _ message: Any, _ arguments: Any...) {
log(message, arguments, level: .custom(categoryName: category))
}
}
- 전체 코드
import Foundation
import os.log
extension OSLog {
static let subsystem = Bundle.main.bundleIdentifier!
static let network = OSLog(subsystem: subsystem, category: "Network")
static let debug = OSLog(subsystem: subsystem, category: "Debug")
static let info = OSLog(subsystem: subsystem, category: "Info")
static let error = OSLog(subsystem: subsystem, category: "Error")
}
struct Log {
enum Level {
case debug
case info
case network
case error
case custom(categoryName: String)
fileprivate var category: String {
switch self {
case .debug:
return "Debug"
case .info:
return "Info"
case .network:
return "Network"
case .error:
return "Error"
case .custom(let categoryName):
return categoryName
}
}
fileprivate var osLog: OSLog {
switch self {
case .debug:
return OSLog.debug
case .info:
return OSLog.info
case .network:
return OSLog.network
case .error:
return OSLog.error
case .custom:
return OSLog.debug
}
}
fileprivate var osLogType: OSLogType {
switch self {
case .debug:
return .debug
case .info:
return .info
case .network:
return .default
case .error:
return .error
case .custom:
return .debug
}
}
}
static private func log(_ message: Any, _ arguments: [Any], level: Level) {
#if LOG
if #available(iOS 14.0, *) {
let extraMessage: String = arguments.map({ String(describing: $0) }).joined(separator: " ")
let logger = Logger(subsystem: OSLog.subsystem, category: level.category)
let logMessage = "\(message) \(extraMessage)"
switch level {
case .debug,
.custom:
logger.debug("\(logMessage, privacy: .public)")
case .info:
logger.info("\(logMessage, privacy: .public)")
case .network:
logger.log("\(logMessage, privacy: .public)")
case .error:
logger.error("\(logMessage, privacy: .public)")
}
} else {
let extraMessage: String = arguments.map({ String(describing: $0) }).joined(separator: " ")
os_log("%{public}@", log: level.osLog, type: level.osLogType, "\(message) \(extraMessage)")
}
#endif
}
}
// MARK: - utils
extension Log {
static func debug(_ message: Any, _ arguments: Any...) {
log(message, arguments, level: .debug)
}
static func info(_ message: Any, _ arguments: Any...) {
log(message, arguments, level: .info)
}
static func network(_ message: Any, _ arguments: Any...) {
log(message, arguments, level: .network)
}
static func error(_ message: Any, _ arguments: Any...) {
log(message, arguments, level: .network)
}
static func custom(category: String, _ message: Any, _ arguments: Any...) {
log(message, arguments, level: .custom(categoryName: category))
}
}
4. 디버그 모드 설정 방법
- 전처리기 개념: c에서의 개념과 동일하게, #if TEST1, #endif와 같이 소스코드에서 표현되고, 전처리기에 따라 분기를 결정할 수 있는 요소
#if TEST1
// debug
#endif
- 전처리기 관련 문자열 ("TEST1"와 같은 경우)은 Xcode에서 추가: -D TEST1
- Target -> Build Setting -> OTHER_SWIFT_FLAGS에 전처리기 관련 flag 추가
- Log에서 정의한 전처리기 문자열은 "LOG"이므로, Release를 제외한 곳에 모두 "LOG"추가
- Other_swift_flags에 $(inherited) 추가 - defalut로 추가되어 있지 않은 경우만 추가
- 해주는 이유: cocoapods 프레임워크에서 "COCOAPODS"라는 플래그를 사용하는데, $(inherited)안에 포함된 -D COCOAPODS을 따로 추가해야, 종속적인 cocoapods 프레임워크를 사용할 때 빌드 warning이 나지 않기 때문
- cf) OTHER_SWIFT_FLAG 대신에 Active Compilation Conditions를 사용해도 무방 (-D 없이 바로 기입하여 사용)
5. 사용 예시
Log.debug("123")
Log.network("[Send] Splash화면에서 api 호출")
Log.info("Splash화면에서 어떤 정보 로깅")
Log.error("Splash화면에서 에러")
6. mac의 '콘솔' 앱을 사용하여 로깅
- 콘솔 앱 실행
- 로깅 확인
1. xcode에서 실행한 디바이스 or 시뮬레이터 선택
2. 하위 시스템으로 필터링 (OSLog의 subsystem을 bundle id로 했으므로 bundle id기입)
3. 로깅 확인
- 검색했던 '필터' 저장 기능: 저장 버튼을 누르면 좌측에 지정한 이름으로 저장
- 로깅 저장 방법 - terminal에서 저장할 곳으로 이동
sudo log collect --device --start <날짜> --output <저장될 이름>.logarchive
* 조금 더 추상적으로 os_log 사용 방법: https://ios-development.tistory.com/534
* 참고
- mokacoding.com/blog/cocoapods-the-inherited-flag/
- stackoverflow.com/questions/15343122/what-is-inherited-in-xcodes-search-path-settings
'iOS 응용 (swift)' 카테고리의 다른 글
[iOS - swift] long touch 시 개발자모드 불러오기 (0) | 2021.03.22 |
---|---|
[iOS - swift] 로컬 푸시 (local notification) (5) | 2021.03.21 |
[iOS - swift] Web crawling(웹 크롤링), web scraping, (swiftsoup, Alamofire, 한글 깨짐) (0) | 2021.03.17 |
[iOS - swift] app store로 이동 (버전 업데이트, 다시 돌아온 경우 처리, 현재 버전, 최신 버전 가져오는 방법) (6) | 2021.03.15 |
[iOS - swift] tableView refresh (상단, 하단 새로고침) (0) | 2021.03.13 |
Comments