Flutter 비정상 종료 모니터링 - 4부 | Rollbar 소스코드 심층 분석

1. Rollbar가 해결하는 문제점

Rollbar-flutter는 순수 Dart로 구현되어 있어 코드 재사용성이라는 큰 장점을 제공합니다. Flutter SDK가 네이티브 SDK에 의존하는 Bugsnag와 달리, Rollbar는 Dart 레이어에서 네트워크 통신, 데이터 저장, 예외 처리 로직을 모두 처리합니다. 이는 Android, iOS 등 각 플랫폼마다 중복된 네트워크 및 저장소 로직을 별도로 구현할 필요가 없음을 의미합니다. 결과적으로 개발 및 테스트 비용을 절감할 수 있습니다.

Rollbar는 예외 데이터를 가공하는 방식을 추상화하여 개발자에게 높은 자유도를 제공합니다. 예외를 마치 생선처럼 생각하면, Catcher는 담백하게 찌고 Bugsnag는 진하게 조리는 반면, Rollbar는 사용자가 직접 조리법을 선택할 수 있도록 기본 재료(원시 예외 데이터)를 제공합니다.

또한, 예외 처리 과정에서 발생하는 I/O 작업(데이터 직렬화, 저장, 암호화, 전송)은 메인 isolate의 UI 렌더링 성능에 영향을 줄 수 있습니다. Rollbar는 이러한 무거운 작업을 별도의 하위 isolate(crash isolate)에서 처리하도록 지원하여 애플리케이션의 반응성을 유지합니다.

마지막으로, Bugsnag와 유사하게 사용자 행동 경로(Breadcrumb)를 기록하여 문제 발생 지점까지의 컨텍스트를 추적할 수 있습니다. 다만, Bugsnag가 자동 및 수동 경로 생성을 모두 지원하는 반면, Rollbar는 수동 추가만 지원하지만 Breadcrumb 유형(error, navigation, widget, log)을 세분화하여 이벤트 성격에 맞게 구분할 수 있습니다.

2. 설치 및 기본 설정

pubspec.yaml에 의존성을 추가합니다:

dependencies:
  rollbar_flutter: ^0.3.0-beta

flutter pub get 명령을 실행한 후, 다음과 같이 초기화합니다:

import 'package:rollbar_flutter/rollbar.dart';

Future<void> main() async {
  const config = Config(
      accessToken: 'YOUR-ROLLBAR-ACCESSTOKEN', // Rollbar 계정에서 발급
      package: 'rollbar_flutter_example');

  await RollbarFlutter.run(config, () {
    runApp(const MyApp());
  });
}

요구 사항

  • Dart SDK >= 2.7.0
  • Flutter >= 1.20.0
  • Rollbar 계정

3. 아키텍처 분석

RollbarFlutter.run() 메서드를 통해 진입점을 분석합니다. Config 객체는 네 가지 핵심 구성 요소를 정의합니다:

  • Notifier: 예외 이벤트를 메인 스레드 또는 하위 스레드에서 처리할지 결정합니다.
  • Transformer: 원시 예외 데이터(Event)를 변환(Data)하는 역할을 합니다.
  • Wrangler: 변환된 데이터를 최종 전송 형식(Payload)으로 한 번 더 가공합니다.
  • Sender: Wrangler가 생성한 Payload를 실제로 Rollbar 서버로 전송합니다.

FlutterError.onErrorrunZonedGuarded를 통해 예외를 수집한 후, Rollbar.error() 메서드가 호출됩니다. 이 메서드는 예외를 Event 객체로 감싸 Notifier.notify()에 전달합니다. 기본 설정에서는 IsolatedNotifier가 사용되므로, 이 Event는 자동으로 하위 isolate로 전달되어 처리됩니다.

하위 isolate 내부에서는 다음과 같은 순서로 작업이 진행됩니다:

  1. 데이터베이스 정리: Telemetry 객체를 통해 캐시된 Breadcrumb와 오래된 Event를 정리합니다.
  2. 데이터 변환 및 래핑: TransformerWrangler를 통해 Event → Data → Payload 순서로 가공됩니다.
  3. 전송: Sender가 최종 Payload를 HTTP 요청으로 전송합니다.

4. 예외 데이터 가공 과정

예외 데이터 가공은 세 단계로 이루어집니다:

  1. Event → Data 변환: 원시 예외(Error + StackTrace)에 OS 정보, Dart 버전, 패키지명, 환경 정보 등 부가 데이터를 추가합니다.
  2. 개발자 정의 변환: Transformer.transform() 메서드를 오버라이드하여 데이터 암호화, 필드 추가/삭제 등 커스텀 로직을 수행할 수 있습니다.
  3. Payload 생성: Wrangler가 최종 전송 가능한 형식으로 데이터를 래핑합니다.

기본 TransformerNoopTransformer로, 아무런 변환 없이 입력 데이터를 그대로 반환합니다. 필요에 따라 transform 메서드를 재정의하여 사용자 정의 변환기를 구현할 수 있습니다.

5. 스레드 전환 메커니즘

스레드 전환은 Notifier 추상 클래스를 통해 제어됩니다:

abstract class Notifier {
  static const version = '0.4.0-beta';
  static const name = 'rollbar-dart';

  Sender get sender;
  Wrangler get wrangler;
  Telemetry get telemetry;

  FutureOr<void> notify(Event event);
  FutureOr<void> dispose();
}

기본값인 IsolatedNotifierIsolate.spawn()을 통해 새로운 하위 isolate를 생성하며, AsyncNotifier는 메인 isolate에서 동기적으로 처리합니다. 하위 isolate에서 처리할 경우 얻는 이점은 다음과 같습니다:

  • 데이터베이스 작업 분리: Telemetry의 읽기/쓰기/삭제 작업이 UI 스레드를 차단하지 않습니다.
  • 데이터 변환 부담 경감: 복잡한 Transformer 로직이나 Wrangler의 직렬화 과정이 UI에 영향을 주지 않습니다.
  • 전송 안정성 향상: 예외가 대량으로 발생할 때, 전송 과정의 네트워크 지연이 앱 성능에 미치는 영향을 최소화합니다.

6. 설계 패턴 분석

단일 책임 원칙 (Single Responsibility Principle)

  • Notifier: 실행 컨텍스트(스레드) 관리
  • Transformer: 데이터 변환
  • Wrangler: 최종 포맷 래핑
  • Sender: 데이터 전송 (HTTP, 파일 등)
  • Telemetry: 로컬 데이터베이스 관리

플러그 가능한 설계 (Pluggable Architecture)

각 모듈은 생성자 주입 방식으로 결합되며, Config 객체에서 기본 구현체를 제공하거나 사용자 정의 구현체로 교체할 수 있습니다:

const Config({
    this.notifier = IsolatedNotifier.spawn,
    this.wrangler = DataWrangler.new,
    this.transformer = NoopTransformer.new,
    this.sender = PersistentHttpSender.new,
  });

이는 개방-폐쇄 원칙(Open-Closed Principle)을 잘 따르며, 핵심 로직을 수정하지 않고도 확장이 가능합니다.

7. 추가 고려 사항

  • 데이터 직렬화 및 로컬 저장소 관리 로직은 Telemetry 클래스에서 자세히 다루고 있으므로, 필요시 참고하세요.
  • Android의 PlatformException과 같이 다중 StackTrace를 포함하는 예외 처리 방식이 구현되어 있습니다.
  • Dart 2.15의 생성자 분리(Constructors tear-off) 기능을 활용하여 코드를 간결하게 구성하고 있습니다.

8. 알려진 이슈

  • Rollbar-flutter는 아직 베타 버전이므로, Flutter 프로젝트 생성 시 Rollbar 아이콘이 선택 목록에 없을 수 있습니다. 이 경우 아이콘 선택 없이 진행하고, example 프로젝트에서 직접 AccessToken을 설정하여 테스트할 수 있습니다.
  • AccessToken이 잘못된 상태에서 전송 실패한 데이터가 로컬에 캐시되면, 이후 올바른 Token으로 수정해도 전송되지 않을 수 있습니다. 이때는 앱 데이터를 삭제하거나 재설치하여 캐시를 초기화해야 합니다.

9. 장점과 단점

장점

  • 하위 isolate를 활용한 비동기 예외 처리로 UI 성능 보장
  • Dart 레이어에서 데이터베이스 캐싱 및 큐 관리 지원
  • 다중 StackTrace 예외 처리 (Android PlatformException 등)
  • 명확한 모듈 분리와 Config 기반의 확장성
  • 사용자 행동 경로(Breadcrumb) 수동 기록 기능

단점

  • Breadcrumb 자동 수집(네비게이션, 네트워크 요청 등)은 지원되지 않아 수동 구현이 필요하며, 이는 Bugsnag 대비 불편함
  • 내부 저장소로 sqlite3를 사용하는데, 이는 네이티브 채널을 통한 구현이므로 완전한 순수 Dart 구현이 아님. Hive 같은 순수 Dart 솔루션으로 대체 가능성 검토 필요

10. 참고 자료

태그: Flutter Rollbar 예외처리 Isolate 비동기프로그래밍

6월 7일 21:16에 게시됨