267 lines
8.4 KiB
Dart
267 lines
8.4 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 SCDioLogger 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;
|
|
|
|
SCDioLogger({
|
|
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) _pReqHdr(options);
|
|
if (requestHeader) {
|
|
_pTable(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;
|
|
_pTable(requestHeaders, header: 'Headers');
|
|
_pTable(options.extra, header: 'Extras');
|
|
}
|
|
if (requestBody && options.method != 'GET') {
|
|
final data = options.data;
|
|
if (data != null) {
|
|
if (data is Map) {
|
|
_pTable(data, header: 'Body');
|
|
} else if (data is FormData) {
|
|
final formDataMap = Map()
|
|
..addEntries(data.fields)
|
|
..addEntries(data.files);
|
|
_pTable(formDataMap, header: 'Form data | ${data.boundary}');
|
|
} else {
|
|
_pBlock(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;
|
|
_pBox(
|
|
header: 'DioError ║ Status: ${err.response?.statusCode} ${err.response?.statusMessage}',
|
|
text: uri.toString());
|
|
if (err.response?.data != null) {
|
|
logPrint('╔ ${err.type.toString()}');
|
|
_pResp(err.response!);
|
|
}
|
|
_pLine('╚');
|
|
} else {
|
|
final uri = err.requestOptions.uri;
|
|
_pBox(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());
|
|
_pTable(responseHeaders, header: 'Headers');
|
|
}
|
|
|
|
if (responseBody) {
|
|
_pRespHdr(response);
|
|
logPrint('╔ Body');
|
|
logPrint('║');
|
|
_pResp(response);
|
|
logPrint('║');
|
|
_pLine('╚');
|
|
}
|
|
handler.next(response);
|
|
}
|
|
|
|
void _pBox({String? header, String? text}) {
|
|
logPrint('');
|
|
logPrint('╔╣ $header');
|
|
logPrint('║ $text');
|
|
_pLine('╚');
|
|
}
|
|
|
|
void _pResp(Response response) {
|
|
if (response.data != null) {
|
|
if (response.data is Map) {
|
|
_pMap(response.data);
|
|
} else if (response.data is List) {
|
|
logPrint('║${_ind()}[');
|
|
_pList(response.data);
|
|
logPrint('║${_ind()}]');
|
|
} else {
|
|
_pBlock(response.data.toString());
|
|
}
|
|
}
|
|
}
|
|
|
|
void _pRespHdr(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 _pReqHdr(RequestOptions options) {
|
|
final uri = options.uri;
|
|
final method = options.method;
|
|
_pBox(header: 'Request ║ $method ', text: uri.toString());
|
|
}
|
|
|
|
void _pLine([String pre = '', String suf = '╝']) => logPrint('$pre${'═' * maxWidth}');
|
|
|
|
void _pKV(String key, Object v) {
|
|
final pre = '╟ $key: ';
|
|
final msg = v.toString();
|
|
|
|
if (pre.length + msg.length > maxWidth) {
|
|
logPrint(pre);
|
|
_pBlock(msg);
|
|
} else {
|
|
logPrint('$pre$msg');
|
|
}
|
|
}
|
|
|
|
// 修复:使用 characters 处理截断,防止 Emoji 损坏
|
|
void _pBlock(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 _ind([int tabCount = initialTab]) => tabStep * tabCount;
|
|
|
|
void _pMap(Map data, {int tabs = initialTab, bool isListItem = false, bool isLast = false}) {
|
|
final bool isRoot = tabs == initialTab;
|
|
final initialIndent = _ind(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 && _flatMap(value)) {
|
|
logPrint('║${_ind(tabs)} $key: $value${!isLast ? ',' : ''}');
|
|
} else {
|
|
logPrint('║${_ind(tabs)} $key: {');
|
|
_pMap(value, tabs: tabs);
|
|
}
|
|
} else if (value is List) {
|
|
if (compact && _flatList(value)) {
|
|
logPrint('║${_ind(tabs)} $key: ${value.toString()}');
|
|
} else {
|
|
logPrint('║${_ind(tabs)} $key: [');
|
|
_pList(value, tabs: tabs);
|
|
logPrint('║${_ind(tabs)} ]${isLast ? '' : ','}');
|
|
}
|
|
} else {
|
|
final msg = value.toString().replaceAll('\n', '');
|
|
final indent = _ind(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('║${_ind(tabs)} ${charMsg.getRange(start, end).toString()}');
|
|
}
|
|
} else {
|
|
logPrint('║${_ind(tabs)} $key: $msg${!isLast ? ',' : ''}');
|
|
}
|
|
}
|
|
});
|
|
|
|
logPrint('║$initialIndent}${isListItem && !isLast ? ',' : ''}');
|
|
}
|
|
|
|
void _pList(List list, {int tabs = initialTab}) {
|
|
list.asMap().forEach((i, e) {
|
|
final isLast = i == list.length - 1;
|
|
if (e is Map) {
|
|
if (compact && _flatMap(e)) {
|
|
logPrint('║${_ind(tabs)} $e${!isLast ? ',' : ''}');
|
|
} else {
|
|
_pMap(e, tabs: tabs + 1, isListItem: true, isLast: isLast);
|
|
}
|
|
} else {
|
|
logPrint('║${_ind(tabs + 2)} $e${isLast ? '' : ','}');
|
|
}
|
|
});
|
|
}
|
|
|
|
bool _flatMap(Map map) {
|
|
return map.values.where((val) => val is Map || val is List).isEmpty &&
|
|
map.toString().length < maxWidth;
|
|
}
|
|
|
|
bool _flatList(List list) {
|
|
return (list.length < 10 && list.toString().length < maxWidth);
|
|
}
|
|
|
|
void _pTable(Map? map, {String? header}) {
|
|
if (map == null || map.isEmpty) return;
|
|
logPrint('╔ $header ');
|
|
map.forEach((key, value) {
|
|
_pKV(key.toString(), (value ?? 'null').toString());
|
|
});
|
|
_pLine('╚');
|
|
}
|
|
} |