267 lines
8.5 KiB
Dart
267 lines
8.5 KiB
Dart
/*
|
|
* @message: 日志拦截器
|
|
* @Author: Jack
|
|
* @Email: Jack@163.com
|
|
* @Date: 2020-06-18 19:47:32
|
|
*/
|
|
import 'package:dio/dio.dart';
|
|
import 'dart:math' as math;
|
|
import 'package:flutter/foundation.dart';
|
|
// 必须引入此库处理 Emoji 截断问题
|
|
import 'package:characters/characters.dart';
|
|
|
|
/// description: Dio 日志拦截器
|
|
class ATDioLogger extends Interceptor {
|
|
final bool request;
|
|
final bool requestHeader;
|
|
final bool requestBody;
|
|
final bool responseBody;
|
|
final bool responseHeader;
|
|
final bool error;
|
|
static const int initialTab = 1;
|
|
static const String tabStep = ' ';
|
|
final bool compact;
|
|
final int maxWidth;
|
|
|
|
/// Log printer; defaults debugPrint to console.
|
|
void Function(Object object) logPrint;
|
|
final bool enableLog;
|
|
|
|
ATDioLogger({
|
|
this.enableLog = kDebugMode,
|
|
this.request = true,
|
|
this.requestHeader = true,
|
|
this.requestBody = true,
|
|
this.responseHeader = false,
|
|
this.responseBody = true,
|
|
this.error = true,
|
|
this.maxWidth = 90,
|
|
this.compact = true,
|
|
this.logPrint = print, // 修复:在 iOS 上 debugPrint 比 print 更稳健
|
|
});
|
|
|
|
@override
|
|
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
|
if (!enableLog) return handler.next(options);
|
|
|
|
if (request) _printRequestHeader(options);
|
|
if (requestHeader) {
|
|
_printMapAsTable(options.queryParameters, header: 'Query Parameters');
|
|
final requestHeaders = <String, dynamic>{};
|
|
requestHeaders.addAll(options.headers);
|
|
requestHeaders['contentType'] = options.contentType?.toString();
|
|
requestHeaders['responseType'] = options.responseType?.toString();
|
|
requestHeaders['followRedirects'] = options.followRedirects;
|
|
requestHeaders['connectTimeout'] = options.connectTimeout;
|
|
requestHeaders['receiveTimeout'] = options.receiveTimeout;
|
|
_printMapAsTable(requestHeaders, header: 'Headers');
|
|
_printMapAsTable(options.extra, header: 'Extras');
|
|
}
|
|
if (requestBody && options.method != 'GET') {
|
|
final data = options.data;
|
|
if (data != null) {
|
|
if (data is Map) {
|
|
_printMapAsTable(data, header: 'Body');
|
|
} else if (data is FormData) {
|
|
final formDataMap = Map()
|
|
..addEntries(data.fields)
|
|
..addEntries(data.files);
|
|
_printMapAsTable(formDataMap, header: 'Form data | ${data.boundary}');
|
|
} else {
|
|
_printBlock(data.toString());
|
|
}
|
|
}
|
|
}
|
|
handler.next(options);
|
|
}
|
|
|
|
@override
|
|
void onError(DioException err, ErrorInterceptorHandler handler) {
|
|
if (!enableLog) return handler.next(err);
|
|
|
|
if (error) {
|
|
if (err.type == DioExceptionType.badResponse) {
|
|
final uri = err.response?.requestOptions.uri;
|
|
_printBoxed(
|
|
header: 'DioError ║ Status: ${err.response?.statusCode} ${err.response?.statusMessage}',
|
|
text: uri.toString());
|
|
if (err.response?.data != null) {
|
|
logPrint('╔ ${err.type.toString()}');
|
|
_printResponse(err.response!);
|
|
}
|
|
_printLine('╚');
|
|
} else {
|
|
final uri = err.requestOptions.uri;
|
|
_printBoxed(header: 'DioError ║ ${err.type} ║ ${uri.toString()}', text: err.message);
|
|
}
|
|
}
|
|
handler.next(err);
|
|
}
|
|
|
|
@override
|
|
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
|
if (!enableLog) return handler.next(response);
|
|
|
|
if (responseHeader) {
|
|
final responseHeaders = <String, String>{};
|
|
response.headers.forEach((k, list) => responseHeaders[k] = list.toString());
|
|
_printMapAsTable(responseHeaders, header: 'Headers');
|
|
}
|
|
|
|
if (responseBody) {
|
|
_printResponseHeader(response);
|
|
logPrint('╔ Body');
|
|
logPrint('║');
|
|
_printResponse(response);
|
|
logPrint('║');
|
|
_printLine('╚');
|
|
}
|
|
handler.next(response);
|
|
}
|
|
|
|
void _printBoxed({String? header, String? text}) {
|
|
logPrint('');
|
|
logPrint('╔╣ $header');
|
|
logPrint('║ $text');
|
|
_printLine('╚');
|
|
}
|
|
|
|
void _printResponse(Response response) {
|
|
if (response.data != null) {
|
|
if (response.data is Map) {
|
|
_printPrettyMap(response.data);
|
|
} else if (response.data is List) {
|
|
logPrint('║${_indent()}[');
|
|
_printList(response.data);
|
|
logPrint('║${_indent()}]');
|
|
} else {
|
|
_printBlock(response.data.toString());
|
|
}
|
|
}
|
|
}
|
|
|
|
void _printResponseHeader(Response response) {
|
|
final uri = response.requestOptions.uri;
|
|
final method = response.requestOptions.method;
|
|
String header = 'Response ║ $method ║ Status: ${response.statusCode} ${response.statusMessage}';
|
|
logPrint('╔╣ $header ${'═' * 20}');
|
|
logPrint('║ ${uri.toString()}');
|
|
logPrint('║ ');
|
|
}
|
|
|
|
void _printRequestHeader(RequestOptions options) {
|
|
final uri = options.uri;
|
|
final method = options.method;
|
|
_printBoxed(header: 'Request ║ $method ', text: uri.toString());
|
|
}
|
|
|
|
void _printLine([String pre = '', String suf = '╝']) => logPrint('$pre${'═' * maxWidth}');
|
|
|
|
void _printKV(String key, Object v) {
|
|
final pre = '╟ $key: ';
|
|
final msg = v.toString();
|
|
|
|
if (pre.length + msg.length > maxWidth) {
|
|
logPrint(pre);
|
|
_printBlock(msg);
|
|
} else {
|
|
logPrint('$pre$msg');
|
|
}
|
|
}
|
|
|
|
// 修复:使用 characters 处理截断,防止 Emoji 损坏
|
|
void _printBlock(String msg) {
|
|
final charData = msg.characters;
|
|
int lines = (charData.length / maxWidth).ceil();
|
|
for (int i = 0; i < lines; ++i) {
|
|
final start = i * maxWidth;
|
|
final end = math.min<int>(start + maxWidth, charData.length);
|
|
logPrint('║ ' + charData.getRange(start, end).toString());
|
|
}
|
|
}
|
|
|
|
String _indent([int tabCount = initialTab]) => tabStep * tabCount;
|
|
|
|
void _printPrettyMap(Map data, {int tabs = initialTab, bool isListItem = false, bool isLast = false}) {
|
|
final bool isRoot = tabs == initialTab;
|
|
final initialIndent = _indent(tabs);
|
|
tabs++;
|
|
|
|
if (isRoot || isListItem) logPrint('║$initialIndent{');
|
|
|
|
data.keys.toList().asMap().forEach((index, key) {
|
|
final isLast = index == data.length - 1;
|
|
var value = data[key];
|
|
if (value is String) value = '\"$value\"';
|
|
|
|
if (value is Map) {
|
|
if (compact && _canFlattenMap(value)) {
|
|
logPrint('║${_indent(tabs)} $key: $value${!isLast ? ',' : ''}');
|
|
} else {
|
|
logPrint('║${_indent(tabs)} $key: {');
|
|
_printPrettyMap(value, tabs: tabs);
|
|
}
|
|
} else if (value is List) {
|
|
if (compact && _canFlattenList(value)) {
|
|
logPrint('║${_indent(tabs)} $key: ${value.toString()}');
|
|
} else {
|
|
logPrint('║${_indent(tabs)} $key: [');
|
|
_printList(value, tabs: tabs);
|
|
logPrint('║${_indent(tabs)} ]${isLast ? '' : ','}');
|
|
}
|
|
} else {
|
|
final msg = value.toString().replaceAll('\n', '');
|
|
final indent = _indent(tabs);
|
|
final charMsg = msg.characters;
|
|
final linWidth = maxWidth - indent.length;
|
|
|
|
// 修复:字符串分段时处理 Emoji
|
|
if (charMsg.length + indent.length > maxWidth) {
|
|
int lines = (charMsg.length / linWidth).ceil();
|
|
for (int i = 0; i < lines; ++i) {
|
|
final start = i * linWidth;
|
|
final end = math.min<int>(start + linWidth, charMsg.length);
|
|
logPrint('║${_indent(tabs)} ${charMsg.getRange(start, end).toString()}');
|
|
}
|
|
} else {
|
|
logPrint('║${_indent(tabs)} $key: $msg${!isLast ? ',' : ''}');
|
|
}
|
|
}
|
|
});
|
|
|
|
logPrint('║$initialIndent}${isListItem && !isLast ? ',' : ''}');
|
|
}
|
|
|
|
void _printList(List list, {int tabs = initialTab}) {
|
|
list.asMap().forEach((i, e) {
|
|
final isLast = i == list.length - 1;
|
|
if (e is Map) {
|
|
if (compact && _canFlattenMap(e)) {
|
|
logPrint('║${_indent(tabs)} $e${!isLast ? ',' : ''}');
|
|
} else {
|
|
_printPrettyMap(e, tabs: tabs + 1, isListItem: true, isLast: isLast);
|
|
}
|
|
} else {
|
|
logPrint('║${_indent(tabs + 2)} $e${isLast ? '' : ','}');
|
|
}
|
|
});
|
|
}
|
|
|
|
bool _canFlattenMap(Map map) {
|
|
return map.values.where((val) => val is Map || val is List).isEmpty &&
|
|
map.toString().length < maxWidth;
|
|
}
|
|
|
|
bool _canFlattenList(List list) {
|
|
return (list.length < 10 && list.toString().length < maxWidth);
|
|
}
|
|
|
|
void _printMapAsTable(Map? map, {String? header}) {
|
|
if (map == null || map.isEmpty) return;
|
|
logPrint('╔ $header ');
|
|
map.forEach((key, value) {
|
|
_printKV(key.toString(), (value ?? 'null').toString());
|
|
});
|
|
_printLine('╚');
|
|
}
|
|
} |