Flutter에서 릴리즈 모드 앱은 어떻게 디버깅하나요?

Flutter 릴리즈 모드 앱은 최적화되어 있고 디버그 정보가 제거되어 있어 일반적인 디버깅 방법을 사용하기 어렵습니다. 그러나 다음과 같은 방법을 통해 릴리즈 모드 앱을 디버깅할 수 있습니다.

1. 로깅(Logging) 활용

릴리즈 모드에서는 print 문이 기본적으로 출력되지 않습니다. 대신 더 강력한 로깅 시스템을 사용해야 합니다.

Flutter 로깅 패키지 사용

import 'package:logging/logging.dart';

void main() {
  // 로거 설정
  Logger.root.level = Level.ALL;
  Logger.root.onRecord.listen((record) {
    // 로그를 파일에 저장하거나 원격 서버로 전송
    print('${record.level.name}: ${record.time}: ${record.message}');
  });

  runApp(MyApp());
}

// 앱 내에서 로깅
final logger = Logger('MyApp');

void someFunction() {
  try {
    // 코드 실행
    logger.info('함수가 정상적으로 실행되었습니다.');
  } catch (e, stackTrace) {
    logger.severe('오류 발생: $e', e, stackTrace);
  }
}

원격 로깅 서비스 사용

Firebase Crashlytics, Sentry 등의 서비스를 사용하여 원격으로 오류를 수집하고 분석할 수 있습니다.

import 'package:flutter/material.dart';
import 'package:sentry_flutter/sentry_flutter.dart';

Future<void> main() async {
  await SentryFlutter.init(
    (options) {
      options.dsn = 'https://example@sentry.io/example';
      // 릴리즈 모드 설정
      options.environment = 'production';
    },
    appRunner: () => runApp(MyApp()),
  );
}

// 앱 내에서 사용
void someFunction() async {
  try {
    // 코드 실행
  } catch (exception, stackTrace) {
    await Sentry.captureException(
      exception,
      stackTrace: stackTrace,
    );
  }
}

2. 앱 성능 모니터링

Firebase Performance Monitoring

릴리즈 모드에서 앱의 성능을 측정하고 병목 현상을 찾아내는 데 도움이 됩니다.

import 'package:firebase_performance/firebase_performance.dart';

void trackNetworkRequest() async {
  final metric = FirebasePerformance.instance
      .newHttpMetric('https://api.example.com/data', HttpMethod.Get);

  await metric.start();

  try {
    // 네트워크 요청 수행
    final response = await http.get(Uri.parse('https://api.example.com/data'));
    metric.httpResponseCode = response.statusCode;
    metric.responsePayloadSize = response.contentLength;
  } finally {
    await metric.stop();
  }
}

void trackCustomEvent() async {
  final trace = FirebasePerformance.instance.newTrace('load_data');
  await trace.start();

  // 속성 추가
  trace.putAttribute('user_id', '123');

  try {
    // 측정할 작업 수행
    trace.incrementMetric('items_loaded', 10);
  } finally {
    await trace.stop();
  }
}

커스텀 성능 측정

자체 성능 측정 코드를 작성하여 릴리즈 모드에서 특정 작업의 실행 시간을 측정할 수 있습니다.

class PerformanceMonitor {
  static final Map<String, Stopwatch> _watches = {};

  static void start(String operation) {
    final watch = Stopwatch()..start();
    _watches[operation] = watch;
  }

  static Duration stop(String operation) {
    final watch = _watches.remove(operation);
    if (watch == null) {
      return Duration.zero;
    }

    watch.stop();
    final duration = watch.elapsed;

    // 로그 또는 분석 서비스로 전송
    print('Operation $operation took ${duration.inMilliseconds}ms');

    return duration;
  }
}

// 사용 예
void loadData() {
  PerformanceMonitor.start('data_loading');

  // 데이터 로딩 로직

  final duration = PerformanceMonitor.stop('data_loading');
  if (duration.inMilliseconds > 1000) {
    // 성능 이슈가 있는 경우 로깅
    logger.warning('데이터 로딩에 ${duration.inSeconds}초 이상 소요됨');
  }
}

3. Android에서 릴리즈 모드 앱 디버깅

ProGuard/R8 매핑 파일 활용

Android에서는 코드 축소 및 난독화가 적용되어 있을 때 ProGuard/R8 매핑 파일을 사용하여 난독화된 스택 트레이스를 디코딩할 수 있습니다.

android/app/build.gradle 파일에서:

buildTypes {
  release {
    signingConfig signingConfigs.release
    minifyEnabled true
    shrinkResources true
    proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

    // 매핑 파일 저장
    testProguardFile 'test-proguard-rules.pro'
  }
}

난독화된 스택 트레이스를 원래 코드로 변환하려면 Android 스튜디오의 Analyze > Analyze Stack Trace 기능을 사용하고 매핑 파일을 참조하세요.

Android 로그캣으로 디버깅

릴리즈 모드 앱을 디바이스에 설치하고 Android Studio의 로그캣을 통해 앱에서 발생하는 로그를 확인할 수 있습니다. 이 경우 앱에 적절한 로깅 코드가 포함되어 있어야 합니다.

import 'package:flutter/foundation.dart';
import 'dart:developer' as developer;

void logError(String message, {Object? error, StackTrace? stackTrace}) {
  // kReleaseMode는 릴리즈 모드에서 true입니다
  if (kReleaseMode) {
    developer.log(
      message,
      name: 'AppError',
      error: error,
      stackTrace: stackTrace,
    );
  } else {
    print('Error: $message');
    if (error != null) print(error);
    if (stackTrace != null) print(stackTrace);
  }
}

4. iOS에서 릴리즈 모드 앱 디버깅

심볼 파일(dSYM) 활용

iOS 릴리즈 빌드 시 생성되는 dSYM 파일을 사용하여 크래시 로그를 디코딩할 수 있습니다. 이 파일은 Xcode가 생성하며, Firebase Crashlytics나 Sentry와 같은 서비스에 업로드하여 크래시 보고서를 해석하는 데 사용됩니다.

콘솔 로그 확인

Xcode의 Console.app을 사용하여 iOS 디바이스에서 실행 중인 앱의 로그를 확인할 수 있습니다.

5. 네트워크 트래픽 분석

프록시 도구 활용

Charles, Fiddler와 같은 프록시 도구를 사용하여 앱의 네트워크 트래픽을 캡처하고 분석할 수 있습니다. 이를 통해 API 요청 및 응답을 검사하고 네트워크 관련 이슈를 디버깅할 수 있습니다.

디바이스에서:

  1. 프록시 설정 구성(Wi-Fi 설정에서)
  2. Charles/Fiddler를 컴퓨터에서 실행
  3. 릴리즈 모드 앱 실행
  4. 네트워크 트래픽 모니터링

앱 내 네트워크 로깅

앱 내에서 모든 네트워크 요청과 응답을 로깅하는 인터셉터를 구현할 수 있습니다.

import 'package:dio/dio.dart';

Dio createDioWithLogging() {
  final dio = Dio();

  dio.interceptors.add(
    InterceptorsWrapper(
      onRequest: (options, handler) {
        logger.info('API 요청: ${options.method} ${options.uri}');
        logger.info('요청 헤더: ${options.headers}');
        logger.info('요청 데이터: ${options.data}');
        return handler.next(options);
      },
      onResponse: (response, handler) {
        logger.info('API 응답: ${response.statusCode} ${response.requestOptions.uri}');
        logger.info('응답 데이터: ${response.data}');
        return handler.next(response);
      },
      onError: (DioError e, handler) {
        logger.severe('API 오류: ${e.message}', e, e.stackTrace);
        logger.severe('요청 정보: ${e.requestOptions.method} ${e.requestOptions.uri}');
        return handler.next(e);
      },
    ),
  );

  return dio;
}

6. 사용자 피드백 메커니즘

앱 내에서 사용자가 문제를 보고할 수 있는 기능을 추가하면 릴리즈 환경에서 발생하는 이슈를 파악하는 데 도움이 됩니다.

Future<void> reportIssue(String description, {List<String>? logs}) async {
  try {
    // 디바이스 정보 수집
    final deviceInfo = await DeviceInfoPlugin().androidInfo;
    final packageInfo = await PackageInfo.fromPlatform();

    // 보고서 작성
    final report = {
      'description': description,
      'app_version': packageInfo.version,
      'device_model': deviceInfo.model,
      'android_version': deviceInfo.version.release,
      'logs': logs,
      'timestamp': DateTime.now().toIso8601String(),
    };

    // 서버로 전송
    await http.post(
      Uri.parse('https://your-api.com/bug-reports'),
      body: jsonEncode(report),
      headers: {'Content-Type': 'application/json'},
    );
  } catch (e) {
    // 오류 처리
  }
}

7. 플레이버/빌드 설정으로 디버깅 지원

릴리즈 빌드이지만 추가 디버깅 정보를 포함하는 특별한 빌드 구성을 만들 수 있습니다.

enum BuildType { debug, profile, release, releaseWithLogs }

// 앱 설정
class AppConfig {
  static late final BuildType buildType;

  static bool get isDebug => buildType == BuildType.debug;
  static bool get isReleaseWithLogs => buildType == BuildType.releaseWithLogs;

  static void init() {
    // 빌드 환경에 따라 설정
    const buildTypeString = String.fromEnvironment('BUILD_TYPE');

    switch (buildTypeString) {
      case 'debug':
        buildType = BuildType.debug;
        break;
      case 'profile':
        buildType = BuildType.profile;
        break;
      case 'releaseWithLogs':
        buildType = BuildType.releaseWithLogs;
        break;
      default:
        buildType = BuildType.release;
    }

    // 로깅 설정
    if (isDebug || isReleaseWithLogs) {
      Logger.root.level = Level.ALL;
    } else {
      Logger.root.level = Level.SEVERE; // 릴리즈에서는 심각한 오류만 로깅
    }
  }
}

요약

Flutter 릴리즈 모드 앱을 디버깅하는 주요 방법:

  1. 강력한 로깅 시스템 구축: 기본 print 대신 구조화된 로깅 시스템 사용
  2. 원격 모니터링 서비스 통합: Firebase Crashlytics, Sentry 등을 활용
  3. 성능 측정 도구 사용: Firebase Performance Monitoring 또는 커스텀 성능 측정
  4. 플랫폼별 도구 활용:
    • Android: ProGuard/R8 매핑 파일과 로그캣
    • iOS: dSYM 파일과 Console.app
  5. 네트워크 트래픽 분석: 프록시 도구와 네트워크 인터셉터 활용
  6. 사용자 피드백 수집: 앱 내 문제 보고 메커니즘 구현
  7. 특수 릴리즈 빌드 구성: 더 많은 디버깅 정보를 포함하는 특별 릴리즈 빌드 만들기

이러한 방법들을 조합하여 릴리즈 모드에서도 앱의 문제를 효과적으로 진단하고 해결할 수 있습니다.

results matching ""

    No results matching ""