Domain 모듈(kotlin 모듈)에서 로그를 출력하는 방법

이지훈·2023년 12월 5일
0
post-thumbnail

사진: UnsplashPankaj Patel

서두

개발을 하다보면 문제를 찾기 위해 로그를 출력하는 코드를 작성할 때가 있는데, 평소에는 문제가 없다가, kotlin 모듈인 domain 모듈에서 로그를 출력 해야하는 상황에서 문제가 발생하여 글을 작성하게 되었다.

TL;DR

KMP 를 위한 Napier 라이브러리를 사용하여 kotlin 모듈에서도 로그를 찍을 수 있다.

문제 발생

로그를 작성하려고 평소처럼 자연스럽게 Timber 라이브러리를 모듈의 의존성으로 추가했는 때, 다음과 같은 에러가 발생하는 것을 확인할 수 있었다.

:domain:main: Could not resolve com.jakewharton.timber:timber:5.0.1.
Required by:
project :domain

그렇다. Timber 는 android 를 위한 라이브러리이기에 순수한 kotlin 모듈에서는 사용할 수 없다.
마찬가지로 Timber 를 사용하기 이전에 주로 사용했었던, androd.util.Log 도 사용할 수 없다. 그럼 어떻게? 울며 겨자먹기로 println 을 통해 로그를 출력하기엔 mode 설정이나(debug에서 출력할지, error 로 출력할지), tag 를 지정할 수 없기에 별로 좋은 방법은 아닌 것 같았다.

아래 글에서 println 에 대한 더 자세한 설명을 확인할 수 있습니다
https://stackoverflow.com/questions/18393888/why-shouldnt-i-use-system-out-println-in-android

해결

방법 1: Napier 라이브러리 사용하기

https://github.com/AAkira/Napier

Napier 는 KMP(Kotlin Multiplatform)을 위한 로깅을 위한 라이브러리이다.
인지도는 비교적 낮지만 KMP 의 Timber 라고 생각하면 쉽다.

레포의 readme 의 최하단을 보면 다음과 같은 내용을 확인 할 수있다.

This library is inspired by Timber.
I recommend using it if it supports kotlin multiplatform project.😜

KMP 를 위한 라이브러리이기 때문에 Android 모듈 뿐만 아니라, kotlin 모듈에서도 사용할 수 있다.

androidx-startup 과 version catalog 를 기반으로 한 사용방법은 다음과 같다.

1. Napier 의존성을 추가

libs.version.toml

[versions]

napier = "2.6.1"

[libraries]

napier = { module = "io.github.aakira:napier", version.ref = "napier"}

// or

napier = { group = "io.github.aakira", name = "napier", version.ref = "napier"}

위와 같이 추가한 후에 domain(or 다른 kotlin) 모듈에 의존성을 추가해준다.

dependencies {
  implementation(libs.napier)
}

2. app startup initializer 설정

application 클래스에 readme 에 언급된 코드를 추가해도 되지만, androidx-startup 라이브러리를 사용하여 앱의 빌드 속도에 이득을 취할 수 있다.

앱 시작 라이브러리를 사용하면 애플리케이션 시작 시 구성요소를 간단하고 효율적으로 초기화할 수 있습니다. 라이브러리 개발자는 물론 앱 개발자도 앱 시작을 사용하여 시작 시퀀스를 간소화하고 초기화 순서를 명시적으로 설정할 수 있습니다.
초기화해야 하는 각 구성요소에 관해 별도의 콘텐츠 제공자를 정의하는 대신 앱 시작을 사용하면 단일 콘텐츠 제공자를 공유하는 구성요소 이니셜라이저를 정의할 수 있습니다. 이렇게 하면 앱 시작 시간이 크게 개선됩니다.

적용을 위해 app 모듈에도 napier 의존성을 추가해준다.

dependencies {
  implementation(libs.napier)
}

다음으로 app 모듈 내에 NapierInitializaer 클래스를 추가한다.

class NapierInitializer : Initializer<Unit> {

  override fun create(context: Context) {
    if (BuildConfig.DEBUG) {
      Napier.base(DebugAntilog())
    }
  }

  override fun dependencies(): List<Class<out Initializer<*>>> {
    return emptyList()
  }
}

마지막으로 app 모듈 내에 AndroidManifest.xml 에 다음과 같은 provider 를 추가해준다.

  <application
    android:name=".App"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/Theme.Eggeum"
    android:roundIcon="@mipmap/ic_launcher_round"
    tools:ignore="MissingApplicationIcon">

    <provider
      android:name="androidx.startup.InitializationProvider"
      android:authorities="${applicationId}.androidx-startup"
      android:exported="false"
      tools:node="merge">

      <meta-data
        android:name="us.wedemy.eggeum.android.initialize.NapierInitializer"
        android:value="androidx.startup" />

    </provider>

실행 결과

이제 Napier 를 사용하기 위한 준비는 완료되었고, sync now 를 눌러 변경사항을 반영한 뒤에 로깅이 필요한 클래스에 로그를 위한 코드를 추가해준다.

@Singleton
public class GetPlaceListUseCase @Inject constructor(
  private val repository: PlaceRepository,
) {
  public operator fun invoke(): Flow<PagingData<PlaceEntity>> {
    Napier.d("GetPlaceListUseCase 호출")
    return repository.getPlaceList()
  }
}

성공적으로 로그가 출력되는 것을 확인할 수 있었고, Timber 와 같이 별다른 tag 를 설정해주지 않아도, default 로 현재 로그가 작성된 class의 name 과 함수가 자동으로 tag에 추가되어 로그를 추적하기에 용이한 것을 확인할 수 있었다.

마찬가지로 KMP Logging 라이브러리인 touchlab 의 kermit 을 사용하는 방법도 존재한다. 두 라이브러리를 비교해보고 선호도에 따라 선택하면 될 것 같다.

KMP 를 위한 라이브러리가 점점 활성화됨에 따라, Android 개발에도 좋은 부수효과가 발생하는 것 같아 좋다.

비슷한 사례로, 그동안 domain의 UseCase 단에서 androidx-paging3 라이브러리를 사용할 때 Android 플랫폼 종속성 라이브러리에 대한 domain(kotlin) 모듈에서의 의존성 추가 문제가 존재 했었고, 이를 해결하기 위한 방법들이 존재했었다.

  1. Android dependency가 없는 Test 를 위한 paging-common 라이브러리를 의존성으로 추가해준다.

  1. domain 모듈에선 의존성을 추가하지 않고, 라이브러리에서 의해 빌드 타임에 생성되는 클래스를 직접 작성해준다.

    2번째로 언급한 방법은 아래 블로그 링크에서 상세한 내용을 확인할 수 있습니다.
    https://nanamare.tistory.com/207

  2. paging 라이브러리 없이 직접 페이징을 구현한다.
    https://proandroiddev.com/pagination-in-jetpack-compose-with-and-without-paging-3-e45473a352f4

번외) paging 의존성만 domain 을 거치지 않고 data 에서 presentation 으로 바로 꼽는다

그런데 최근 업데이트에 의해 androidx-paging3 라이브러리가 KMP 를 지원하는 라이브러리가 되었기 때문에 위와 같은 문제를 신경쓰지 않아도 될 것 같다.

점차 더 많은 Jetpack 라이브러리들이 KMP 를 지원하는 쪽으로 업데이트가 진행되는 추세인 것 같다.
위에서 언급한 paging 과 마찬가지로 datastore 도 KMP 를 지원하고 있다.

Android 개발자 입장에서 KMP 개발을 할 때, 기존에 사용하던 라이브러리들을 그대로 사용할 수 있게 되어, 그 허들이 낮아지게 되어 정말 편해질 것 같다는 생각이 든다.

방법 2: java.util.logging.Logger 사용하기

https://proandroiddev.com/logging-in-a-multi-module-android-project-7294382e59fa

두번째 방법으론 위의 미디엄 블로그에서 처럼 별도의 외부 라이브러리를 사용하지 않고, Java의 Socket IO Client 라이브러리를 위한 java.util.logging.Logger 를 사용하는 방법이 있는데,
이는 직접 사용해보진 않았기에 언급만 하고 넘어가도록 하겠다.

reference)

https://medium.com/preat/%ED%85%8C%EC%8A%A4%ED%8A%B8-eafc76e723fa

https://developer.android.com/jetpack/androidx/releases/paging

profile
실력은 고통의 총합이다. Android Developer

2개의 댓글

comment-user-thumbnail
2024년 9월 9일
  1. Napier 의존성을 추가 에서 name, group 순서가 바뀐거 같아요
1개의 답글