INEEDACHACHA

Flutter Network Logging Interceptor 본문

Flutter/전환실록

Flutter Network Logging Interceptor

INEEDACHACHA 2025. 7. 22. 09:42

구현된 구조

25.07.23 수정 사항 

  • print -> developer.log로 최종 변경 (DevTools에 최적화)
    • VScode는 Terminal에서 안 보이나 DevTools에서는 상세히 보임
    • Android Studio에서는 developer.log가 Console에서 보임
    • developer로그로 변경한 이유
      • DevTools에 최적화
        • Tag, Level 등을 공식 기능으로 설정 가능

1. NetworkLoggable 인터페이스

abstract class NetworkLoggable {
  void info(NetworkLog log);
  void error(NetworkLog log);
}

class ConsoleLogger implements NetworkLoggable {
  @override
  void info(NetworkLog log) => developer.log(log.toString(), name: 'NetworkLog');

  @override
  void error(NetworkLog log) => developer.log(log.toString(), name: 'NetworkLog');
}

주요 특징:

  • 로깅 동작을 추상화
  • 다양한 로거 구현체를 만들 수 있도록 정의

2. NetworkLog 데이터 모델

enum NetworkLogType {
  willSend,
  didReceive,
}

class NetworkLog {
  final String id;
  final NetworkLogType type;
  final String method;
  final String baseUrl;
  final String path;
  final Map<String, dynamic>? headers;
  final String? body;
  final int? statusCode;
  final String? response;
  final DateTime loggedAt;

  NetworkLog({
    required this.type,
    required this.method,
    required this.baseUrl,
    required this.path,
    this.headers,
    this.body,
    this.statusCode,
    this.response,
  }) : id = const Uuid().v4().toString(),
       loggedAt = DateTime.now();
}
  @override
  String toString() {
    switch (type) {
      case NetworkLogType.willSend:
        return '''
⚡️ ━━━━━━━━━━━━━━━ 네트워크 요청 경로: $path ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[REQUEST DATE]: $loggedAt
[HTTP METHOD]: $method
[BASE URL]: $baseUrl
[BODY]: ${body ?? "EMPTY BODY"}
''';
      case NetworkLogType.didReceive:
        return '''
🌐 ━━━━━━━━━━━━━━━ 네트워크 응답 경로: $path ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[RESPONSE DATE]: $loggedAt
[HTTP METHOD]: $method
[STATUS CODE]: ${statusCode ?? "UNKNOWN"}
[BASE URL]: $baseUrl
[RESPONSE]: ${response ?? "EMPTY RESPONSE"}
''';
    }
  }
}

주요 특징:

  • UUID로 각 로그에 고유 ID 부여
  • 요청/응답 시점 자동 기록

3. NetworkLogInterceptor 구현

class NetworkLogInterceptor extends Interceptor {
  final NetworkLoggable logger;

  NetworkLogInterceptor({required this.logger});

  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    logger.info(
      NetworkLog(
        type: NetworkLogType.willSend,
        method: options.method,
        path: options.path,
        baseUrl: options.baseUrl,
        headers: options.headers,
        body: options.data,
      ),
    );
    handler.next(options);
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    logger.info(
      NetworkLog(
        type: NetworkLogType.didReceive,
        method: response.requestOptions.method,
        path: response.requestOptions.path,
        baseUrl: response.requestOptions.baseUrl,
        headers: response.requestOptions.headers,
        body: response.requestOptions.data,
        statusCode: response.statusCode,
        response: prettyJson(response.data),
      ),
    );
    handler.next(response);
  }

  @override
  void onError(DioException err, ErrorInterceptorHandler handler) {
    final descriptor = NetworkErrorDescriptor(err);
    logger.error(
      NetworkLog(
        type: NetworkLogType.didReceive,
        method: err.requestOptions.method,
        path: err.requestOptions.path,
        baseUrl: err.requestOptions.baseUrl,
        headers: err.requestOptions.headers,
        response: descriptor.errorLogMessage(),
      ),
    );
    handler.next(err);
  }
}

주요 특징:

  • Dio 인터셉터를 확장하여 네트워크 라이프사이클 센싱
  • 요청 전(onRequest), 응답 후(onResponse), 에러 발생시(onError) 로깅
  • 의존성 주입을 통한 유연한 로거 교체 가능

4. DioFactory 통합

@LazySingleton(as: DioFactory)
class DioFactoryImpl implements DioFactory {
  @override
  Dio create(String baseUrl) {
    final dio = Dio(BaseOptions(baseUrl: baseUrl));
    dio.interceptors.add(NetworkLogInterceptor(logger: ConsoleLogger()));
    return dio;
  }
}

주요 특징:

  • 모든 Dio 인스턴스에 로깅 인터셉터 자동 추가

로그 출력 예시

요청 로그

⚡️ ━━━━━━━━━━━━━━━ 네트워크 요청 경로: $path ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[REQUEST DATE]: $loggedAt
[HTTP METHOD]: $method
[BASE URL]: $baseUrl
[BODY]: ${body ?? "EMPTY BODY"}

응답 로그

🌐 ━━━━━━━━━━━━━━━ 네트워크 응답 경로: $path ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[RESPONSE DATE]: $loggedAt
[HTTP METHOD]: $method
[STATUS CODE]: ${statusCode ?? "UNKNOWN"}
[BASE URL]: $baseUrl
[RESPONSE]: ${response ?? "EMPTY RESPONSE"}

 

DevTools

  • 해당 작업물의 DevTools에서의 모습이다.
  • 요청 당 한 개의 로그를 쓰고있다.
  • 클릭 했을 시 디테일 출력이 깔끔하다.

확장성 및 개선 사항

개선 예정 사항

  • 플러그인 주입 개선: 현재 DioFactory에 하드코딩된 ConsoleLogger를 DI를 통해 주입 가능하도록 개선 예정
  • Refresh 관련 Interceptor 추가: 토큰 갱신 관련 인터셉터 추가 예정
  • 로그 레벨 설정: 개발/운영 환경에 따라 로그 레벨 조정 기능

Logger, Talker를 선택 안 한 이유

  • DevtTools의 활용도가 커질 것을 예상했기 때문
  • Native와 달리 DevTools가 상당히 잘 되어있고 디테일한 부분 까지 추적이 가능해보였다.

  • Logger, Talker의 DevTool의 모습이다.
  • 위와 같이 하나의 요청과 로그에 n개의 로그가 찍힌다.
  • Detail을 클릭해도 자세한 내용이 한 눈에 보이기 보다는 n개로 나눠져있어 가독성이 좋지 않다.