Firebase Crashlytics 비정상 종료 보고서 맞춤설정

이 가이드에서는 Firebase Crashlytics SDK를 사용하여 비정상 종료 보고서를 맞춤설정하는 방법을 설명합니다. 기본적으로 Crashlytics는 모든 앱 사용자의 비정상 종료 보고서를 자동으로 수집하지만, 사용자를 대신하여 자동 비정상 종료 보고를 해제하고 보고 선택 옵션을 사용 설정할 수 있습니다. Firebase Crashlytics는 커스텀 키, 커스텀 로그, 사용자 식별자, 포착된 예외라는 4가지 로깅 메커니즘을 제공합니다.

커스텀 키 추가

커스텀 키를 사용하면 비정상 종료로 이어지는 앱의 구체적인 상태를 확인할 수 있습니다. 임의의 키-값 쌍을 비정상 종료 보고서에 연결하고 커스텀 키를 사용하여 Firebase Console에서 비정상 종료 보고서를 검색하고 필터링할 수 있습니다.

  • Crashlytics 대시보드에서 커스텀 키와 일치하는 문제를 검색할 수 있습니다.
  • 콘솔에서 특정 문제를 검토할 때 각 이벤트와 연결된 커스텀 키를 검토하고( 하위 탭) 이벤트를 커스텀 키로 필터링할 수도 있습니다(페이지 상단의 필터 메뉴).

setCustomValue 메서드를 사용하여 키-값 쌍을 설정합니다. 예를 들면 다음과 같습니다.

Swift

// Set int_key to 100.
Crashlytics.crashlytics().setCustomValue(100, forKey: "int_key")

// Set str_key to "hello".
Crashlytics.crashlytics().setCustomValue("hello", forKey: "str_key")

Objective-C

정수, 불리언 또는 부동 소수점 수를 설정할 때 값을 @(value)로 입력합니다.

// Set int_key to 100.
[[FIRCrashlytics crashlytics] setCustomValue:@(100) forKey:@"int_key"];

// Set str_key to "hello".
[[FIRCrashlytics crashlytics] setCustomValue:@"hello" forKey:@"str_key"];

키를 호출하고 다른 값으로 설정하여 기존 키의 값을 수정할 수도 있습니다. 예를 들면 다음과 같습니다.

Swift

Crashlytics.crashlytics().setCustomValue(100, forKey: "int_key")

// Set int_key to 50 from 100.
Crashlytics.crashlytics().setCustomValue(50, forKey: "int_key")

Objective-C

[[FIRCrashlytics crashlytics] setCustomValue:@(100) forKey:@"int_key"];

// Set int_key to 50 from 100.
[[FIRCrashlytics crashlytics] setCustomValue:@(50) forKey:@"int_key"];

setCustomKeysAndValues 메서드에 NSDictionary를 유일한 매개변수로 사용하여 키-값 쌍을 일괄 추가합니다.

Swift

let keysAndValues = [
                 "string key" : "string value",
                 "string key 2" : "string value 2",
                 "boolean key" : true,
                 "boolean key 2" : false,
                 "float key" : 1.01,
                 "float key 2" : 2.02
                ] as [String : Any]

Crashlytics.crashlytics().setCustomKeysAndValues(keysAndValues)

Objective-C

NSDictionary *keysAndValues =
    @{@"string key" : @"string value",
      @"string key 2" : @"string value 2",
      @"boolean key" : @(YES),
      @"boolean key 2" : @(NO),
      @"float key" : @(1.01),
      @"float key 2" : @(2.02)};

[[FIRCrashlytics crashlytics] setCustomKeysAndValues: keysAndValues];

커스텀 로그 메시지 추가

비정상 종료로 이어지는 이벤트에 대한 배경 정보를 자세히 파악하기 위해 앱에 커스텀 Crashlytics 로그를 추가할 수 있습니다. Crashlytics가 로그를 비정상 종료 데이터에 연결하고 Firebase Console의 Crashlytics 페이지에서 로그 탭 아래에 표시합니다.

Swift

log() 또는 log(format:, arguments:)를 사용하여 문제를 정확하게 파악할 수 있습니다. 유용한 로그 출력을 메시지와 함께 가져오려는 경우 log()에 전달하는 객체가 CustomStringConvertible 속성을 준수해야 합니다. log()는 객체에 정의된 설명 속성을 반환합니다. 예를 들면 다음과 같습니다.

Crashlytics.crashlytics().log("Higgs-Boson detected! Bailing out…, \(attributesDict)")

.log(format:, arguments:)getVaList()를 호출하여 반환되는 값의 형식을 지정합니다. 예를 들면 다음과 같습니다.

Crashlytics.crashlytics().log(format: "%@, %@", arguments: getVaList(["Higgs-Boson detected! Bailing out…", attributesDict]))

log() 또는 log(format:, arguments:)를 사용하는 방법에 대한 자세한 내용은 Crashlytics 함수 참조 문서를 확인하세요.

Objective-C

log 또는 logWithFormat를 사용하여 문제를 정확하게 파악할 수 있습니다. 유용한 로그 출력을 메시지와 함께 가져오려는 경우 두 메서드 중 하나에 전달하는 객체가 description 인스턴스 속성을 재정의해야 합니다. 예를 들면 다음과 같습니다.

[[FIRCrashlytics crashlytics] log:@"Simple string message"];

[[FIRCrashlytics crashlytics] logWithFormat:@"Higgs-Boson detected! Bailing out... %@", attributesDict];

[[FIRCrashlytics crashlytics] logWithFormat:@"Logging a variable argument list %@" arguments:va_list_arg];

loglogWithFormat을 사용하는 방법에 대한 자세한 내용은 Crashlytics 함수 참조 문서를 확인하세요.

사용자 식별자 설정

어떤 사용자에게 특정 비정상 종료가 발생했는지 파악하면 문제를 진단하는 데 도움이 될 수 있습니다. Crashlytics는 비정상 종료 보고서에서 사용자를 익명으로 식별할 수 있는 방법을 제공합니다.

보고서에 사용자 ID를 추가하려면 각 사용자에게 ID 번호, 토큰 또는 해시 값 형식으로 고유 식별자를 할당합니다.

Swift

Crashlytics.crashlytics().setUserID("123456789")

Objective-C

[[FIRCrashlytics crashlytics] setUserID:@"123456789"];

사용자 식별자를 설정한 후 삭제해야 하는 경우 값을 빈 문자열로 재설정합니다. 사용자 식별자를 지워도 기존 Crashlytics 레코드는 삭제되지 않습니다. 사용자 ID와 연결된 레코드를 삭제하려면 Firebase 지원팀에 문의하세요.

심각하지 않은 예외 보고

앱의 비정상 종료를 자동으로 보고하는 것 외에도 Crashlytics를 사용하면 심각하지 않은 예외를 기록하고 다음 번 앱 실행 시 이 로그를 받아볼 수 있습니다.

recordError 메서드로 NSError 객체를 기록하여 심각하지 않은 예외를 기록할 수 있습니다. recordError[NSThread callStackReturnAddresses]를 호출하여 스레드의 호출 스택을 캡처합니다.

Swift

Crashlytics.crashlytics().record(error: error)

Objective-C

[[FIRCrashlytics crashlytics] recordError:error];

recordError 메서드를 사용할 경우 NSError 구조 및 Crashlytics가 데이터를 사용하여 비정상 종료를 그룹화하는 방식을 이해해야 합니다. recordError 메서드를 잘못 사용하면 예측할 수 없는 동작이 발생할 수 있으며 Crashlytics에서 앱에 로깅된 오류의 보고를 제한해야 할 수도 있습니다.

NSError 객체에는 3가지 인수가 있습니다.

  • domain: String
  • code: Int
  • userInfo: [AnyHashable : Any]? = nil

스택 트레이스 분석을 통해 그룹화되는 심각한 비정상 종료와는 달리 로깅된 오류는 domaincode로 그룹화됩니다. 이 점이 심각한 비정상 종료와 로깅된 오류의 중요한 차이점입니다. 예를 들면 다음과 같습니다.

Swift

let userInfo = [
  NSLocalizedDescriptionKey: NSLocalizedString("The request failed.", comment: ""),
  NSLocalizedFailureReasonErrorKey: NSLocalizedString("The response returned a 404.", comment: ""),
  NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString("Does this page exist?", comment: ""),
  "ProductID": "123456",
  "View": "MainView"
]

let error = NSError.init(domain: NSCocoaErrorDomain,
                         code: -1001,
                         userInfo: userInfo)

Objective-C

NSDictionary *userInfo = @{
  NSLocalizedDescriptionKey: NSLocalizedString(@"The request failed.", nil),
  NSLocalizedFailureReasonErrorKey: NSLocalizedString(@"The response returned a 404.", nil),
  NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Does this page exist?", nil),
  @"ProductID": @"123456",
  @"View": @"MainView",
};

NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain
                                     code:-1001
                                 userInfo:userInfo];

위 오류를 로깅하면 NSSomeErrorDomain-1001로 그룹화되는 새로운 문제가 생성됩니다. 동일한 도메인과 코드 값을 사용하는 오류가 또 로깅되면 동일한 문제로 그룹화됩니다. userInfo 객체 내에 포함된 데이터는 키-값 쌍으로 전환되며 개별 문제 내의 키/로그 섹션에 표시됩니다.

로그 및 커스텀 키

비정상 종료 보고서와 마찬가지로 로그 및 커스텀 키를 삽입하여 NSError에 배경 정보를 추가할 수 있습니다. 하지만 비정상 종료에 연결되는 로그 및 로깅된 오류에는 차이점이 있습니다. 비정상 종료가 발생하고 앱이 다시 실행될 때 Crashlytics가 디스크에서 가져오는 로그가 바로 비정상 종료 당시에 기록된 로그입니다. NSError를 로깅하면 앱이 즉시 종료되지 않습니다. Crashlytics는 다음에 앱을 실행할 때만 로깅된 오류 보고서를 전송하며 디스크에서 로그에 할당되는 공간을 제한해야 합니다. 이 때문에 Crashlytics가 기기에서 보고서를 전송한 시점에 모든 관련 로그가 순환되도록 NSError가 기록되고 충분히 시간이 지난 후에 로깅할 수도 있습니다. 앱에서 NSErrors를 로깅하고 로그 및 커스텀 키를 사용할 때 이 점에 유의하세요.

성능에 대한 고려사항

NSError 로깅은 리소스가 꽤 많이 들 수 있습니다. 호출을 하는 시점에서 Crashlytics가 스택 풀기라는 프로세스를 사용하여 현재 스레드의 호출 스택을 캡처합니다. 이 프로세스는 특히 DWARF 풀기(arm64 및 x86)를 지원하는 아키텍처에서 CPU와 I/O를 많이 사용할 수 있습니다. 풀기가 완료된 후 정보가 동기식으로 디스크에 작성됩니다. 이렇게 하면 다음 줄이 비정상 종료되어도 데이터가 손실되지 않습니다.

백그라운드 스레드에서 이 API를 호출해도 괜찮지만 이 호출을 다른 큐에 전달하면 현재 스택 트레이스의 배경 정보가 사라집니다.

NSException은 어떤가요?

Crashlytics는 NSException 인스턴스를 직접 로깅 및 기록하는 기능을 제공하지 않습니다. 일반적으로 Cocoa 및 Cocoa Touch API는 예외 안전성이 적용되지 않습니다. 즉, @catch를 사용할 때 극도로 주의해도 프로세스에 의도하지 않은 매우 심각한 부작용이 발생할 수 있습니다. 그러므로 코드에 @catch 문을 사용해서는 안 됩니다. 이 주제에 대한 Apple 문서를 참조하세요.

스택 트레이스 맞춤설정

앱을 네이티브가 아닌 환경(예: C++ 또는 Unity)에서 실행하는 경우 Exception Model API를 사용하여 앱의 네이티브 예외 형식으로 비정상 종료 메타데이터를 보고할 수 있습니다. 보고된 예외는 심각하지 않은 예외로 표시됩니다.

Swift

var  ex = ExceptionModel(name:"FooException", reason:"There was a foo.")
ex.stackTrace = [
  StackFrame(symbol:"makeError", file:"handler.js", line:495),
  StackFrame(symbol:"then", file:"routes.js", line:102),
  StackFrame(symbol:"main", file:"app.js", line:12),
]

crashlytics.record(exceptionModel:ex)

Objective-C

FIRExceptionModel *model =
    [FIRExceptionModel exceptionModelWithName:@"FooException" reason:@"There was a foo."];
model.stackTrace = @[
  [FIRStackFrame stackFrameWithSymbol:@"makeError" file:@"handler.js" line:495],
  [FIRStackFrame stackFrameWithSymbol:@"then" file:@"routes.js" line:102],
  [FIRStackFrame stackFrameWithSymbol:@"main" file:@"app.js" line:12],
];

[[FIRCrashlytics crashlytics] recordExceptionModel:model];

주소만을 사용하여 커스텀 스택 프레임을 초기화할 수도 있습니다.

Swift

var  ex = ExceptionModel.init(name:"FooException", reason:"There was a foo.")
ex.stackTrace = [
  StackFrame(address:0xfa12123),
  StackFrame(address:12412412),
  StackFrame(address:194129124),
]

crashlytics.record(exceptionModel:ex)

Objective-C

FIRExceptionModel *model =
    [FIRExceptionModel exceptionModelWithName:@"FooException" reason:@"There was a foo."];
model.stackTrace = @[
  [FIRStackFrame stackFrameWithAddress:0xfa12123],
  [FIRStackFrame stackFrameWithAddress:12412412],
  [FIRStackFrame stackFrameWithAddress:194129124],
];


[[FIRCrashlytics crashlytics] recordExceptionModel:model];

보고 선택 옵션 사용 설정

기본적으로 Crashlytics는 모든 앱 사용자의 비정상 종료 보고서를 자동으로 수집하지만 사용자가 자신이 전송하는 데이터를 더 잘 관리할 수 있도록 다음과 같이 코드에서 자동 보고를 사용 중지하고, 선택하는 경우에만 Crashlytics에 데이터를 전송하여, 보고 선택 옵션을 사용 설정할 수 있습니다.

  1. Info.plist 파일에 새 키를 추가하여 자동 수집을 사용 중지합니다.

    • 키: FirebaseCrashlyticsCollectionEnabled
    • 값: false
  2. 런타임에 Crashlytics 데이터 수집 재정의를 호출하여 특정 사용자에만 수집을 사용 설정합니다. 재정의 값은 앱이 실행되는 동안 유지되므로 Crashlytics는 보고서를 자동으로 수집할 수 있습니다.

    자동 비정상 종료 보고를 선택 해제하려면 false를 재정의 값으로 전달합니다. false로 설정하면 앱이 다음 번 실행될 때까지 새 값이 적용되지 않습니다.

    Swift

    Crashlytics.crashlytics().setCrashlyticsCollectionEnabled(true)

    Objective-C

    [[FIRCrashlytics crashlytics] setCrashlyticsCollectionEnabled:YES];

비정상 종료 통계 데이터 관리

비정상 종료 통계는 익명화된 스택 트레이스를 다른 Firebase 앱의 트레이스와 비교하여 문제가 더 큰 트렌드에 속하는지 여부를 파악함으로써 문제를 해결하는 데 도움을 줍니다. 비정상 종료 통계가 디버깅에 도움이 되는 경우도 많습니다.

비정상 종료 통계에서는 비정상 종료 집계 데이터를 사용하여 공통적인 안정성 트렌드를 파악합니다. 앱의 데이터를 공유하지 않으려는 경우 Firebase Console에 있는 Crashlytics 문제 목록 상단의 비정상 종료 통계 메뉴에서 비정상 종료 통계를 선택 해제할 수 있습니다.