필자는 모든 프로젝트마다 Timber를 도입했다. 단지 이유는 따로 Tag를 설정하지 않아도 된다는 이점 때문이였다. 블로그 스터디를 하던 도중 Timber 사용 이점에 대해서 질문을 받았고, Tag이외의 이점에 대해 설명할 수 없었다. 그래서 이번에 Timber가 Log에 비해 가지는 장점과 추가적으로 Logger에 대해서 알아보고, 소개하려고 한다.
사전 지식 : Log, LogCat
Log는 시스템이 동작하면서 발생하는 이벤트 및 메시지의 기록을 의미한다. 오류, HTTP 호출과 같은 상황에서 발생하는 상황에 대한 단계별로 상세한 정보를 나타낼 수 있다.
LogCat은 Android Studio에서 제공하는 기능으로 실시간으로 발생한 Log를 확인할 수 있는 창이다. LogCat 상세 기능
사전 지식 : 로그 수준
LogCat에서 아래의 로그 수준을 통해 필터링하여 원하는 로그만 확인할 수 있다.
- Verbose: 모든 로그 메시지를 표시합니다(기본 설정).
- Debug: 개발 단계에서만 유용한 디버그 로그 메시지뿐 아니라 이 목록에서 그보다 레벨이 낮은 메시지도 표시합니다.
- Info: 일반적인 사용을 위해 예상할 수 있는 로그 메시지뿐 아니라 이 목록에서 그보다 레벨이 낮은 메시지도 표시합니다.
- Warn: 아직 오류는 아니지만 발생할 수 있는 문제뿐 아니라 이 목록에서 그보다 레벨이 낮은 메시지도 표시합니다.
- Error: 오류를 일으킨 문제뿐 아니라 이 목록에서 그보다 레벨이 낮은 메시지도 표시합니다.
- Assert: 개발자가 발생해서는 안 된다고 생각하는 문제를 표시합니다.
출처 : https://developer.android.com/studio/debug/am-logcat?hl=ko
android.util.Log
기본적으로 Log 클래스는 안드로이드 유틸 클래스에서 제공하는 기능이다. Log의 형식은 Log.(Level)(Tag, Message)의 형태로 구성되어 있다. 각 로그 메서드에서 첫 번째 매개변수는 고유한 태그여야 하며, 두 번째 매개변수는 메시지이다. 태그는 로그 메시지가 시작되는 시스템 구성요소를 나타내는 짧은 문자열이다. 태그는 식별가능한 어떤 문자열이든 가능하며, 일반적으로 클래스 이름을 설정할 수 있다.
작성할 수 있는 로그 메세지의 종류는 아래와 같다.
- 오류: Log.e(String, String) -> 에러 메세지를 출력하기 위해 사용이 가능하다. ERROR에서 확인 가능하다.
- 경고: Log.w(String, String) -> 오류가 발생할 수 있는 환경에서 경고성 메세지를 출력하기 위해 사용이 가능하다. WARNING에서 확인 가능하다.
- 정보: Log.i(String, String) -> 정보를 제공하는 곳에서 사용이 가능하다. 서버와의 통신이 성공했을 경우 전달하는 용도로 사용이 가능하다. INFO에서 확인 가능하다.
- 디버그: Log.d(String, String) -> 디버깅 목적으로 현재 상황과 흐름을 파악하기 위해 사용한다. DEBUG에서 확인 가능하다.
- 상세: Log.v(String, String) -> 상세한 설명 및 작은 부분을 확인하는 곳에 사용이 가능하다. VERBOSE에서 확인 가능하다.
- What a Terrible Failure: Log.wtf(String, String) -> 절대 발생해서는 안되는 곳에 사용이 가능하다. ASSERT에서 확인 가능하다.
Log의 예시는 아래와 같다.
private fun printLog(){
Log.d("LOG_DEBUG", "hello_Log")
Log.v("LOG_VERBOSE", "hello_Log")
Log.wtf("LOG_WTF", "hello_Log")
Log.e("LOG_ERROR", "hello_Log", IOException()) // 발생하는 예외를 추가적인 인자로 전달할 수 있다.
}
Timber
Timber는 JakeWharton이 만든 로깅 라이브러리이다. 실제 구현을 통해서 Timber의 장점 및 Log와의 차이를 소개한다.
종속성 추가하기
dependencies {
// Timber
implementation 'com.jakewharton.timber:timber:5.0.1'
}
가장 먼저 build.gradle 파일에 종속성을 추가한다. Timber Git을 통해 현재 라이브러리 버전을 확인할 수 있다.
Timber 초기화
종속성을 추가한 후 Timber을 초기화해야 한다. Timber를 초기화하기 위해 프로그램의 수명동안 활성화되는 Application 클래스에서 초기화 할 수 있다. 따로 Application 클래스를 생성 후 Manifest에서 설정해야 한다.
class LogApplication : Application() {
override fun onCreate() {
super.onCreate()
initialTimber()
}
private fun initialTimber(){
if(BuildConfig.DEBUG){
plantDebugTimberTree()
}
else{
plantReleaseTimberTree()
}
}
private fun plantDebugTimberTree(){
Timber.plant(object : Timber.DebugTree() {
override fun createStackElementTag(element: StackTraceElement): String? {
return String.format(
"Class:%s: Line: %s, Method: %s",
super.createStackElementTag(element),
element.lineNumber,
element.methodName
)
}
})
}
private fun plantReleaseTimberTree(){
Timber.plant(ReleaseTree())
}
// A tree which logs important information for crash reporting
class ReleaseTree : Timber.Tree() {
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
if(priority == Log.VERBOSE || priority == Log.DEBUG){
return
}
// FakeCrashLibrary.log(priority, tag, message)
if(t != null){
if(priority == Log.ERROR){
// FakeCrashLibrary.logError(priority, tag, message)
}
else if(priority == Log.WARN){
// FakeCrashLibrary.logWarning(priority, tag, message)
}
}
}
}
}
위의 코드를 통해 Timber의 장점을 확인할 수 있다.
- 기존 Log의 경우 릴리즈 앱에서도 로그를 확인할 수 있다. 릴리즈 앱에서 유저 토큰 정보나, ApiKey가 그대로 로그를 통해 노출될 수 있다. 따라서 기존에는 보안상의 문제로 로그를 수동적으로 제거해야 했다. 반면 Timber의 경우는 로그 활성화 시점을 지정할 수 있어, 따로 릴리즈 앱을 위해 로그를 제거할 필요가 없어진다.
- 로그에 사용자 지정 데이터를 추가할 수 있다. 라인넘버와 클래스를 추가할 수 있어 디버깅시 더 유용하게 처리할 수 있다.
- 따로 릴리즈 앱의 경우 로그가 아닌, 사용자 통계 및 충돌 보고서(Firebase Crashlytics)와 같은 형태로 확인해야 한다. Timber를 활용하면, 따로 Timber.tree를 상속받은 릴리즈 앱 전용 클래스를 생성해 구현할 수 있다.
Timber 사용
private fun printTimber(){
Timber.v("Hello, Timber")
Timber.tag("TIMBER_TAG").d("Hello, Timber")
Timber.wtf("Hello, Timber")
Timber.e("Hello, Timber", IOException())
}
위의 코드에서 Timber의 특징과 Log와의 차이를 확인할 수 있다.
- Timber에서 Tag는 필수가 아닌 선택사항이다. 자동적으로 클래스 이름을 배정하고, 이전에 Tree에서 지정한 형식이 있다면 해당 형식으로 Tag를 설정한다.
- Log 클래스의 모든 로그 종류를 지원한다. Log와 같이 d, v, i, w, e, wtf 형식을 지원한다.
Logger
logger는 orhanobut이 만든 로깅 라이브러리이다. 로그 디자인에서 특징을 가지고 있다. 구현을 통해 특징과 디자인에 대해서 소개한다.
종속성 추가하기
dependencies {
// Logger
implementation 'com.orhanobut:logger:2.2.0'
}
가장 먼저 build.gradle 파일에 종속성을 추가한다. Logger Git을 통해 현재 라이브러리 버전을 확인할 수 있다.
Logger 초기화
Timber와 마찬가지로 Application 클래스에서 초기화한다. 아래의 코드에서 isLoggable의 함수를 오버라이딩하여, return 값 변경을 통해 릴리즈 앱에서의 로그 활성화를 막을 수 있다(true면 활성화, false면 비활성화를 의미).
class LogApplication : Application() {
override fun onCreate() {
super.onCreate()
initialLogger()
}
private fun initialLogger(){
Logger.addLogAdapter(object : AndroidLogAdapter() {
override fun isLoggable(priority: Int, tag: String?): Boolean {
return BuildConfig.DEBUG
}
})
}
}
따로 Timber와 같이, FormatStrategy를 활용해 모든 로그에 적용되는 규칙을 설정할 수 있다(세부 옵션은 Logger 공식 Git 참조).
Logger 사용
private fun printLogger(){
Logger.v("Hello, Logger")
Logger.t("LOGGER_TAG").d("Hello, Logger")
Logger.wtf("Hello, Logger")
Logger.e("Hello, Logger", IOException())
}
Logger의 사용방법 역시 Tag는 필수가 아니며, d, v, i, w, e, wtf모든 형식을 지원한다.
실행결과를 통해 특징을 확인할 수 있다.
- 구분선을 통해 Log를 가독성이 좋게 만들어 준다.
- 또한 기본적인 메세지외에도 현재 스레드, 호출 클래스 및 라인넘버를 알려준다.
- Application에서 Logger를 초기화하지 않으면 TAG가 자동적으로 PRETTY_LOGGER가 설정되며, 이후 태그를 추가해도, PRETTY_LOGGER + "사용자 지정 태그"로 설정된다.
Logger의 추가적인 기능
Logger는 JSON과 XML형식을 Log로 출력하는 기능을 가지고 있다. 아래의 예시를 통해 소개한다.
private fun printXmlWithLogger(){
Logger.xml("<사용자><이메일>user@example.com</이메일><이름>홍길동</이름><나이>30</나이></사용자>")
Log.d("LOG_XML", "<사용자><이메일>user@example.com</이메일><이름>홍길동</이름><나이>30</나이></사용자>")
}
private fun printJsonWithLogger(){
Logger.json("{" +
" \"이메일\": \"user@example.com\"," +
" \"이름\": \"홍길동\"," +
" \"나이\": 30" +
"}")
Log.d("LOG_JSON","{" +
" \"이메일\": \"user@example.com\"," +
" \"이름\": \"홍길동\"," +
" \"나이\": 30" +
"}")
}
입력하는 문자열 모두 형식은 갖추었지만, 개행처리 및 들여쓰기가 되지 않은 문자열의 형태이다. Logger의 경우 개행 및 들여쓰기 처리가 되어있는 것을 확인할 수 있고, Log는 그대로 출력된 것을 확인할 수 있다.
마지막으로
개인적으로 TAG를 따로 작성하지 않는 것만으로도 로깅 라이브러리를 사용할 수 있는 큰 이점이라고 생각하여 도입하였는데, 추가적으로 로깅 라이브러리가 제공하는 기능에 대해서 알 수 있게 되었다. 효율적인 디버깅을 위해 로깅은 필수적이라고 생각하며, 로깅을 더 직관적이고, 편리하게 하기 위한 커스텀 로깅에 대한 필요성을 느낄 수 있었다.
사용된 모든 코드는 https://github.com/jeongjaino/EveryAndroid3/tree/main/LogExample 확인할 수 있습니다.
참고 문헌
https://www.freecodecamp.org/news/how-to-log-more-efficiently-with-timber-a3f41b193940/