2026-04-09 21:32:23 +08:00

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('');
}
}