본문 바로가기
WIKI 기술 지식 베이스

안드로이드 로그 수집

원문 보기 위키 갱신

Datadog의 dd-sdk-android-logs 클라이언트 측 로깅 라이브러리를 사용해 Android 애플리케이션에서 Datadog로 로그를 보내고 다음 기능을 활용할 수 있어요:

  • JSON 형식으로 기본 제공되는 형태로 Datadog에 로그 기록.
  • 보내는 모든 로그에 context와 추가 사용자 정의 속성 추가.
  • Java 또는 Kotlin에서 잡은 예외 전달.
  • 실제 클라이언트 IP 주소와 User-Agent 기록.
  • 자동 벌크 게시로 네트워크 사용량 최적화.

출처: 문서

본문

설정

  1. 모듈 레벨 build.gradle 파일에서 라이브러리를 의존성으로 선언해 Gradle 의존성을 추가하세요. 다음 예시의 x.x.x를 dd-sdk-android-logs의 최신 버전으로 바꾸세요.

    dependencies {
        implementation "com.datadoghq:dd-sdk-android-logs:x.x.x"
    }
    
  2. 애플리케이션 컨텍스트, 추적 동의(tracking consent), Datadog 클라이언트 토큰으로 Datadog SDK를 초기화하세요. 보안상의 이유로 클라이언트 토큰을 사용해야 해요. Datadog API 키는 Android 애플리케이션 APK 바이트 코드에서 클라이언트 측에 노출되므로 Datadog SDK 구성에 사용할 수 없어요.

APP_VARIANT_NAME은 데이터를 생성하는 애플리케이션의 변형(variant)을 지정해요. 이 값은 초기화 자격 증명에 필요하며, BuildConfig.FLAVOR 값을 사용하거나 변형이 없다면 빈 문자열을 사용하세요. 적절한 ProGuard mapping.txt 파일이 빌드 시 자동으로 업로드되어 난독화 해제된 오류 스택 트레이스를 볼 수 있어요. 자세한 내용은 Android Crash Reporting and Error Tracking을 참고하세요.

클라이언트 토큰 설정에 대한 자세한 내용은 클라이언트 토큰 문서를 참고하세요.

다음 Datadog 사이트 사용자를 위한 중요 참고 사항 — app.datadoghq.com:

{% tab title="Kotlin" %}

class SampleApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        val configuration = Configuration.Builder(
            clientToken = <CLIENT_TOKEN>,
            env = <ENV_NAME>,
            variant = <APP_VARIANT_NAME>
        ).build()
        Datadog.initialize(this, configuration, trackingConsent)
    }
}

{% /tab %}

{% tab title="Java" %}

public class SampleApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Configuration configuration =
                new Configuration.Builder(<CLIENT_TOKEN>, <ENV_NAME>, <APP_VARIANT_NAME>)
                        .build();
        Datadog.initialize(this, configuration, trackingConsent);
    }
}

{% /tab %}

다음 Datadog 사이트 사용자를 위한 중요 참고 사항 — app.datadoghq.eu:

{% tab title="Kotlin" %}

class SampleApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        val configuration = Configuration.Builder(
                clientToken = <CLIENT_TOKEN>,
                env = <ENV_NAME>,
                variant = <APP_VARIANT_NAME>
            )
            .useSite(DatadogSite.EU1)
            .build()
        Datadog.initialize(this, configuration, trackingConsent)
    }
}

{% /tab %}

{% tab title="Java" %}

public class SampleApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Configuration configuration =
                new Configuration.Builder(<CLIENT_TOKEN>, <ENV_NAME>, <APP_VARIANT_NAME>)
                        .useSite(DatadogSite.EU1)
                        .build();
        Datadog.initialize(this, configuration, trackingConsent);
    }
}

{% /tab %}

다음 Datadog 사이트 사용자를 위한 중요 참고 사항 — us3.datadoghq.com:

{% tab title="Kotlin" %}

class SampleApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        val configuration = Configuration.Builder(
                clientToken = <CLIENT_TOKEN>,
                env = <ENV_NAME>,
                variant = <APP_VARIANT_NAME>
            )
            .useSite(DatadogSite.US3)
            .build()
        Datadog.initialize(this, configuration, trackingConsent)
    }
}

{% /tab %}

{% tab title="Java" %}

public class SampleApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Configuration configuration =
                new Configuration.Builder(<CLIENT_TOKEN>, <ENV_NAME>, <APP_VARIANT_NAME>)
                        .useSite(DatadogSite.US3)
                        .build();
        Datadog.initialize(this, configuration, trackingConsent);
    }
}

{% /tab %}

다음 Datadog 사이트 사용자를 위한 중요 참고 사항 — us5.datadoghq.com:

{% tab title="Kotlin" %}

class SampleApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        val configuration = Configuration.Builder(
                clientToken = <CLIENT_TOKEN>,
                env = <ENV_NAME>,
                variant = <APP_VARIANT_NAME>
            )
            .useSite(DatadogSite.US5)
            .build()
        Datadog.initialize(this, configuration, trackingConsent)
    }
}

{% /tab %}

{% tab title="Java" %}

public class SampleApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Configuration configuration =
                new Configuration.Builder(<CLIENT_TOKEN>, <ENV_NAME>, <APP_VARIANT_NAME>)
                        .useSite(DatadogSite.US5)
                        .build();
        Datadog.initialize(this, configuration, trackingConsent);
    }
}

{% /tab %}

다음 Datadog 사이트 사용자를 위한 중요 참고 사항 — app.ddog-gov.com:

{% tab title="Kotlin" %}

class SampleApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        val configuration = Configuration.Builder(
                clientToken = <CLIENT_TOKEN>,
                env = <ENV_NAME>,
                variant = <APP_VARIANT_NAME>
            )
            .useSite(DatadogSite.US1_FED)
            .build()
        Datadog.initialize(this, configuration, trackingConsent)
    }
}

{% /tab %}

{% tab title="Java" %}

public class SampleApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Configuration configuration =
                new Configuration.Builder(<CLIENT_TOKEN>, <ENV_NAME>, <APP_VARIANT_NAME>)
                        .useSite(DatadogSite.US1_FED)
                        .build();
        Datadog.initialize(this, configuration, trackingConsent);
    }
}

{% /tab %}

{% callout %}

다음 Datadog 사이트 사용자를 위한 중요 참고 사항: us2.ddog-gov.com

{% tab title="Kotlin" %}

class SampleApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        val configuration = Configuration.Builder(
                clientToken = <CLIENT_TOKEN>,
                env = <ENV_NAME>,
                variant = <APP_VARIANT_NAME>
            )
            .useSite(DatadogSite.US2_FED)
            .build()
        Datadog.initialize(this, configuration, trackingConsent)
    }
}

{% /tab %}

{% tab title="Java" %}

public class SampleApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Configuration configuration =
                new Configuration.Builder(<CLIENT_TOKEN>, <ENV_NAME>, <APP_VARIANT_NAME>)
                        .useSite(DatadogSite.US2_FED)
                        .build();
        Datadog.initialize(this, configuration, trackingConsent);
    }
}

{% /tab %}

{% /callout %}

{% callout %}

다음 Datadog 사이트 사용자를 위한 중요 참고 사항: ap1.datadoghq.com

{% tab title="Kotlin" %}

class SampleApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        val configuration = Configuration.Builder(
                clientToken = <CLIENT_TOKEN>,
                env = <ENV_NAME>,
                variant = <APP_VARIANT_NAME>
            )
            .useSite(DatadogSite.AP1)
            .build()
        Datadog.initialize(this, configuration, trackingConsent)
    }
}

{% /tab %}

{% tab title="Java" %}

public class SampleApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Configuration configuration =
                new Configuration.Builder(<CLIENT_TOKEN>, <ENV_NAME>, <APP_VARIANT_NAME>)
                        .useSite(DatadogSite.AP1)
                        .build();
        Datadog.initialize(this, configuration, trackingConsent);
    }
}

{% /tab %}

{% /callout %}

{% callout %}

다음 Datadog 사이트 사용자를 위한 중요 참고 사항: ap2.datadoghq.com

{% tab title="Kotlin" %}

class SampleApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        val configuration = Configuration.Builder(
                clientToken = <CLIENT_TOKEN>,
                env = <ENV_NAME>,
                variant = <APP_VARIANT_NAME>
            )
            .useSite(DatadogSite.AP2)
            .build()
        Datadog.initialize(this, configuration, trackingConsent)
    }
}

{% /tab %}

{% tab title="Java" %}

public class SampleApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Configuration configuration =
                new Configuration.Builder(<CLIENT_TOKEN>, <ENV_NAME>, <APP_VARIANT_NAME>)
                        .useSite(DatadogSite.AP2)
                        .build();
        Datadog.initialize(this, configuration, trackingConsent);
    }
}

{% /tab %}

{% /callout %}

{% callout %}

다음 Datadog 사이트 사용자를 위한 중요 참고 사항: uk1.datadoghq.com

{% tab title="Kotlin" %}

class SampleApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        val configuration = Configuration.Builder(
                clientToken = <CLIENT_TOKEN>,
                env = <ENV_NAME>,
                variant = <APP_VARIANT_NAME>
            )
            .useSite(DatadogSite.UK1)
            .build()
        Datadog.initialize(this, configuration, trackingConsent)
    }
}

{% /tab %}

{% tab title="Java" %}

public class SampleApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Configuration configuration =
                new Configuration.Builder(<CLIENT_TOKEN>, <ENV_NAME>, <APP_VARIANT_NAME>)
                        .useSite(DatadogSite.UK1)
                        .build();
        Datadog.initialize(this, configuration, trackingConsent);
    }
}

{% /tab %}

{% /callout %}

GDPR 규정을 준수하기 위해 SDK는 초기화 시 추적 동의(tracking consent) 값이 필요해요. 추적 동의는 다음 값 중 하나가 될 수 있어요:

  • TrackingConsent.PENDING: SDK가 데이터를 수집·배칭하지만 데이터 수집 엔드포인트로는 보내지 않아요. SDK는 새 추적 동의 값을 기다렸다가 배칭된 데이터를 어떻게 처리할지 결정해요.
  • TrackingConsent.GRANTED: SDK가 데이터를 수집해 데이터 수집 엔드포인트로 보내요.
  • TrackingConsent.NOT_GRANTED: SDK가 어떤 데이터도 수집하지 않아요. 로그, 트레이스, RUM 이벤트를 수동으로 보낼 수 없어요.

SDK 초기화 후 추적 동의를 갱신하려면 Datadog.setTrackingConsent(<NEW CONSENT>)를 호출하세요. SDK는 새 동의에 따라 동작을 바꿔요. 예를 들어 현재 추적 동의가 TrackingConsent.PENDING이고 다음으로 갱신하면:

  • TrackingConsent.GRANTED: SDK가 현재 배칭된 모든 데이터와 향후 데이터를 데이터 수집 엔드포인트로 바로 보내요.
  • TrackingConsent.NOT_GRANTED: SDK가 배칭된 모든 데이터를 지우고 향후 데이터도 수집하지 않아요.

isInitialized 유틸리티 메서드를 사용해 SDK가 제대로 초기화됐는지 확인하세요:

if (Datadog.isInitialized()) {
    // your code here
}

애플리케이션을 작성할 때 setVerbosity 메서드를 호출해 개발 로그를 활성화할 수 있어요. 제공된 레벨 이상의 우선순위를 가진 라이브러리의 모든 내부 메시지가 Android의 Logcat에 기록돼요:

Datadog.setVerbosity(Log.INFO)

Logs 기능을 구성하고 활성화하세요:

{% tab title="Kotlin" %}

val logsConfig = LogsConfiguration.Builder().build()
Logs.enable(logsConfig)

{% /tab %}

{% tab title="Java" %}

LogsConfiguration logsConfig = new LogsConfiguration.Builder().build();
Logs.enable(logsConfig);

{% /tab %}

Android 로거를 구성하세요:

{% tab title="Kotlin" %}

val logger = Logger.Builder()
    .setNetworkInfoEnabled(true)
    .setLogcatLogsEnabled(true)
    .setRemoteSampleRate(100f)
    .setBundleWithTraceEnabled(true)
    .setName("<LOGGER_NAME>")
    .build()

{% /tab %}

{% tab title="Java" %}

Logger logger = new Logger.Builder()
    .setNetworkInfoEnabled(true)
    .setLogcatLogsEnabled(true)
    .setRemoteSampleRate(100f)
    .setBundleWithTraceEnabled(true)
    .setName("<LOGGER_NAME>")
    .build();

{% /tab %}

다음 함수 중 하나로 사용자 정의 로그 항목을 Datadog로 직접 보내세요:

logger.d("A debug message.")
logger.i("Some relevant information ?")
logger.w("An important warning…")
logger.e("An error was met!")
logger.wtf("What a Terrible Failure!")

잡은 예외는 메시지와 함께 보낼 수 있어요:

{% tab title="Kotlin" %}

try {
    doSomething()
} catch (e: IOException) {
    logger.e("Error while doing something", e)
}

{% /tab %}

{% tab title="Java" %}

try {
    doSomething();
} catch (IOException e) {
    logger.e("Error while doing something", e);
}

{% /tab %}

참고: 모든 로깅 메서드에는 throwable을 붙일 수 있어요.

(선택 사항) 로그 메시지와 함께 맵을 제공해 생성되는 로그에 속성을 추가하세요. 맵의 각 항목이 속성으로 추가돼요.

{% tab title="Kotlin" %}

logger.i("onPageStarted", attributes = mapOf("http.url" to url))

{% /tab %}

{% tab title="Java" %}

Map<String, Object> attributes = new HashMap<>();
attributes.put("http.url", url);
logger.i("onPageStarted", null, attributes);

{% /tab %}

배칭 전에 Log 이벤트의 일부 속성을 수정해야 한다면 Logs 기능을 초기화할 때 EventMapper<LogEvent> 구현을 제공해 처리할 수 있어요:

{% tab title="Kotlin" %}

val logsConfig = LogsConfiguration.Builder()
            // ...
            .setEventMapper(logEventMapper)
            .build()

{% /tab %}

{% tab title="Java" %}

LogsConfiguration logsConfig = new LogsConfiguration.Builder()
            // ...
            .setEventMapper(logEventMapper)
            .build();

{% /tab %}

참고: EventMapper<LogEvent> 구현에서 null이나 다른 인스턴스를 반환하면 이벤트는 삭제돼요.

고급 로깅

로거 초기화

Datadog로 로그를 보내기 위해 로거를 초기화할 때 Logger.Builder의 다음 메서드를 사용할 수 있어요:

메서드 설명
setNetworkInfoEnabled(true) 모든 로그에 network.client.connectivity 속성을 추가해요. 기본으로 기록되는 데이터는 connectivity(Wifi, 3G, 4G…)와 carrier_name(AT&T - US)이에요. carrier_name은 Android API 레벨 28+에서만 사용할 수 있어요.
setService(<SERVICE_NAME>) Datadog로 보내는 모든 로그에 붙는 service 표준 속성 값으로 <SERVICE_NAME>을 설정해요.
setLogcatLogsEnabled(true) true로 설정하면 Logcat을 로거로 사용해요.
setBundleWithTraceEnabled(true) true(기본값)로 설정하면 로그를 애플리케이션의 활성 트레이스와 묶어요. 이 파라미터를 사용하면 Datadog 대시보드로 특정 트레이스 동안 보낸 모든 로그를 표시할 수 있어요.
setBundleWithRumEnabled(true) true(기본값)로 설정하면 로그를 애플리케이션의 현재 RUM 컨텍스트와 묶어요. 이 파라미터를 사용하면 Datadog RUM Explorer로 특정 View가 활성 상태인 동안 보낸 모든 로그를 표시할 수 있어요.
setName(<LOGGER_NAME>) Datadog로 보내는 모든 로그에 붙는 logger.name 속성 값으로 <LOGGER_NAME>을 설정해요.
setRemoteSampleRate(<SAMPLE_RATE>) 이 로거의 샘플링 비율을 설정해요. 로거 인스턴스가 생성하는 모든 로그는 제공된 샘플링 비율(기본값 100f = 모든 로그)에 따라 무작위로 샘플링돼요. 참고: Logcat 로그는 샘플링되지 않아요.
build() 모든 옵션을 설정한 새 로거 인스턴스를 빌드해요.

글로벌 구성

지정된 로거가 보내는 모든 로그에 태그와 속성을 추가/제거하는 함수는 다음과 같아요.

글로벌 태그
태그 추가

addTag("<TAG_KEY>", "<TAG_VALUE>") 함수를 사용해 특정 로거가 보내는 모든 로그에 태그를 추가하세요:

// This adds a tag "build_type:debug" or "build_type:release" accordingly
logger.addTag("build_type", BuildConfig.BUILD_TYPE)

// This adds a tag "device:android"
logger.addTag("device", "android")

<TAG_VALUE>는 String이어야 해요.

태그 제거

removeTagsWithKey("<TAG_KEY>") 함수를 사용해 특정 로거가 보내는 모든 로그에서 태그를 제거하세요:

// This removes any tag starting with "build_type"
logger.removeTagsWithKey("build_type")

자세한 내용은 태그 시작하기를 참고하세요.

글로벌 속성
속성 추가

기본적으로 로거가 보내는 모든 로그에 다음 속성이 추가돼요:

  • http.useragent와 여기서 추출한 device, OS 속성
  • network.client.ip와 여기서 추출한 지리적 속성(country, city)

addAttribute("<ATTRIBUTE_KEY>", "<ATTRIBUTE_VALUE>") 함수를 사용해 특정 로거가 보내는 모든 로그에 사용자 정의 속성을 추가하세요:

// This adds an attribute "version_code" with an Integer value for this logger instance
logger.addAttribute("version_code", BuildConfig.VERSION_CODE)

// This adds an attribute "version_name" with a String value for this logger instance
logger.addAttribute("version_name", BuildConfig.VERSION_NAME)

다음을 사용해 모든 Logs 인스턴스에 걸쳐 속성을 전역으로 추가할 수 있어요(예: 서비스 이름, 환경):

// This adds an attribute "version_code" with an integer value in all Logs instances.
Logs.addAttribute("version_code", BuildConfig.VERSION_CODE)

// This adds an attribute "version_name" with a String value in all Logs instances.
Logs.addAttribute("version_name", BuildConfig.VERSION_NAME)

<ATTRIBUTE_VALUE>는 어떤 프리미티브, String, Date가 될 수 있어요.

속성 제거

removeAttribute("<ATTRIBUTE_KEY>", "<ATTRIBUTE_VALUE>") 함수를 사용해 특정 로거가 보내는 모든 로그에서 사용자 정의 속성을 제거하세요:

// This removes the attribute "version_code" from all further log send.
logger.removeAttribute("version_code")

// This removes the attribute "version_name" from all further log send.
logger.removeAttribute("version_name")

모든 Logs 인스턴스에서 글로벌 속성을 제거하려면:

// This removes the attribute "version_code" from all Logs instances.
Logs.removeAttribute("version_code")

// This removes the attribute "version_name" from all Logs instances.
Logs.removeAttribute("version_name")

배치 수집

모든 로그는 먼저 로컬 디바이스에 배치로 저장돼요. 각 배치는 수집 규격을 따르며, 네트워크가 사용 가능하고 배터리가 충분해 Datadog SDK가 최종 사용자 경험에 영향을 주지 않을 때 보내져요. 애플리케이션이 포그라운드에 있을 때 네트워크를 사용할 수 없거나 데이터 업로드가 실패하면, 배치는 성공적으로 보낼 수 있을 때까지 유지돼요.

즉, 사용자가 오프라인 상태에서 애플리케이션을 열어도 데이터는 손실되지 않아요.

SDK가 디스크 공간을 과도하게 사용하지 않도록, 디스크의 데이터는 너무 오래되면 자동으로 폐기돼요.

데이터가 Datadog로 업로드되기 전에는 애플리케이션의 캐시 디렉터리에 일반 텍스트로 저장돼요. 이 캐시 폴더는 Android의 Application Sandbox로 보호되므로 대부분의 디바이스에서 다른 애플리케이션이 이 데이터를 읽을 수 없어요. 다만 모바일 디바이스가 루팅됐거나 누군가 Linux 커널을 조작하면 저장된 데이터를 읽을 수 있게 될 수 있어요.

확장

Timber

기존 코드베이스에서 Timber를 사용 중이라면 전용 라이브러리를 사용해 그 모든 로그를 Datadog로 자동 전달할 수 있어요.

더 알아보기 (Learn more)