iOS 앱 개발 과정에서 로깅(logging)은 디버깅과 문제 해결을 위한 필수적인 도구입니다. 효과적인 로깅 시스템은 개발 과정을 원활하게 하고, 실제 사용자 환경에서 발생하는 문제를 추적하는 데 큰 도움이 됩니다. 이번 포스팅에서는 싱글톤 패턴과 OptionSet을 활용한 유연하고 확장 가능한 로깅 시스템 구현 방법을 소개합니다.
로깅 시스템의 필요성
앱 개발 시 다음과 같은 상황에서 로깅이 필요합니다:
- 개발 중 코드 흐름 추적
- 특정 기능의 동작 검증
- 사용자 환경에서 발생하는 오류 분석
- 성능 병목 현상 식별
그러나 모든 정보를 항상 로깅하면 콘솔이 불필요한 정보로 넘쳐나고, 정작 중요한 정보를 놓칠 수 있습니다. 따라서 상황과 중요도에 따라 로깅 수준을 조절할 수 있는 시스템이 필요합니다.
로깅 레벨 정의하기
먼저 다양한 로깅 레벨을 정의하기 위해 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()
}
이 싱글톤 클래스는:
- 애플리케이션 전체에서 단일 인스턴스를 공유합니다.
- 로그 레벨을 중앙에서 관리합니다.
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을 활용한 로깅 시스템은:
- 다양한 로그 레벨을 지원합니다.
- 애플리케이션 전체에서 일관되게 사용할 수 있습니다.
- 필요에 따라 쉽게 확장할 수 있습니다.
- 코드 위치 추적 기능으로 디버깅을 돕습니다.
이러한 로깅 시스템을 자신의 프로젝트에 맞게 조정하여 사용한다면, 디버깅과 유지보수 과정에서 큰 도움이 될 것입니다. 로깅은 단순한 print
문 이상의 강력한 도구가 될 수 있으며, 체계적인 접근 방식은 복잡한 앱 개발에서 그 가치를 더욱 발휘합니다.
답글 남기기