Skip to content

Commit

Permalink
Http Logger
Browse files Browse the repository at this point in the history
  • Loading branch information
Luis Ciber committed Sep 12, 2021
1 parent 2059541 commit 972fcb3
Show file tree
Hide file tree
Showing 3 changed files with 296 additions and 8 deletions.
6 changes: 5 additions & 1 deletion lib/app/data/core/platform/http_client/http_client.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import 'package:dio/dio.dart';
import 'package:injectable/injectable.dart';

// ignore: always_use_package_imports
// ignore_for_file: always_use_package_imports
import 'adapters/http_stub_adapter.dart'
if (dart.library.html) 'adapters/http_browser_adapter.dart'
if (dart.library.io) 'adapters/http_native_adapter.dart';

import 'http_logger.dart';

@injectable
class HttpClient with DioMixin implements Dio {
HttpClient() {
Expand All @@ -17,5 +19,7 @@ class HttpClient with DioMixin implements Dio {
);

httpClientAdapter = httpAdapter();

interceptors.add(HttpLogger());
}
}
284 changes: 284 additions & 0 deletions lib/app/data/core/platform/http_client/http_logger.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
// ignore_for_file: lines_longer_than_80_chars, comment_references

import 'dart:developer';
import 'dart:math' as math;

import 'package:dio/dio.dart';

void _log(Object obj) {
log(obj.toString());
}

class HttpLogger extends Interceptor {
HttpLogger({
this.request = true,
this.requestHeader = false,
this.requestBody = true,
this.responseHeader = false,
this.responseBody = true,
this.error = true,
this.maxWidth = 90,
this.compact = true,
this.logPrint = _log,
});

/// Print request [Options]
final bool request;

/// Print request header [Options.headers]
final bool requestHeader;

/// Print request data [Options.data]
final bool requestBody;

/// Print [Response.data]
final bool responseBody;

/// Print [Response.headers]
final bool responseHeader;

/// Print error message
final bool error;

/// InitialTab count to logPrint json response
static const int initialTab = 1;

/// 1 tab length
static const String tabStep = ' ';

/// Print compact json response
final bool compact;

/// Width size per logPrint
final int maxWidth;

/// Log printer; defaults logPrint log to console.
/// In flutter, you'd better use debugPrint.
/// you can also write log in a file.
void Function(Object object) logPrint;

@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
if (request) {
_printRequestHeader(options);
}
if (requestHeader) {
_printMapAsTable(options.queryParameters, header: 'Query Parameters');
final requestHeaders = <String, dynamic>{}..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 dynamic data = options.data;
if (data != null) {
if (data is Map) _printMapAsTable(options.data as Map?, header: 'Body');
if (data is FormData) {
final formDataMap = <String, dynamic>{}
..addEntries(data.fields)
..addEntries(data.files);
_printMapAsTable(formDataMap, header: 'Form data | ${data.boundary}');
} else {
_printBlock(data.toString());
}
}
}
super.onRequest(options, handler);
}

@override
void onError(DioError err, ErrorInterceptorHandler handler) {
if (error) {
if (err.type == DioErrorType.response) {
final uri = err.response?.requestOptions.uri;
_printBoxed(
header:
'DioError ║ Status: ${err.response?.statusCode} ${err.response?.statusMessage}',
text: uri.toString(),
);
if (err.response != null && err.response?.data != null) {
logPrint('╔ ${err.type.toString()}');
_printResponse(err.response!);
}
_printLine('╚');
logPrint('');
} else {
_printBoxed(header: 'DioError ║ ${err.type}', text: err.message);
}
}
super.onError(err, handler);
}

@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
_printResponseHeader(response);
if (responseHeader) {
final responseHeaders = <String, String>{};
response.headers
.forEach((k, list) => responseHeaders[k] = list.toString());
_printMapAsTable(responseHeaders, header: 'Headers');
}

if (responseBody) {
logPrint('╔ Body');
logPrint('║');
_printResponse(response);
logPrint('║');
_printLine('╚');
}
super.onResponse(response, handler);
}

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 as Map);
} else if (response.data is List) {
logPrint('║${_indent()}[');
_printList(response.data as List);
logPrint('║${_indent()}[');
} else {
_printBlock(response.data.toString());
}
}
}

void _printResponseHeader(Response response) {
final uri = response.requestOptions.uri;
final method = response.requestOptions.method;
_printBoxed(
header:
'Response ║ $method ║ Status: ${response.statusCode} ${response.statusMessage}',
text: uri.toString());
}

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

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

void _printBlock(String msg) {
final lines = (msg.length / maxWidth).ceil();
for (var i = 0; i < lines; ++i) {
logPrint((i >= 0 ? '║ ' : '') +
msg.substring(i * maxWidth,
math.min<int>(i * maxWidth + maxWidth, msg.length)));
}
}

String _indent([int tabCount = initialTab]) => tabStep * tabCount;

void _printPrettyMap(
Map data, {
int tabs = initialTab,
bool isListItem = false,
bool isLast = false,
}) {
var _tabs = tabs;
final isRoot = _tabs == initialTab;
final initialIndent = _indent(_tabs);
_tabs++;

if (isRoot || isListItem) logPrint('║$initialIndent{');

data.keys.toList().asMap().forEach((index, dynamic key) {
final isLast = index == data.length - 1;
dynamic value = data[key];
if (value is String) {
value = '"${value.toString().replaceAll(RegExp(r'(\r|\n)+'), " ")}"';
}
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 linWidth = maxWidth - indent.length;
if (msg.length + indent.length > linWidth) {
final lines = (msg.length / linWidth).ceil();
for (var i = 0; i < lines; ++i) {
logPrint(
'║${_indent(_tabs)} ${msg.substring(i * linWidth, math.min<int>(i * linWidth + linWidth, msg.length))}');
}
} else {
logPrint('║${_indent(_tabs)} $key: $msg${!isLast ? ',' : ''}');
}
}
});

logPrint('║$initialIndent}${isListItem && !isLast ? ',' : ''}');
}

void _printList(List list, {int tabs = initialTab}) {
list.asMap().forEach((i, dynamic 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((dynamic 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(
(dynamic key, dynamic value) => _printKV(key.toString(), value));
_printLine('╚');
}
}
14 changes: 7 additions & 7 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ packages:
name: analyzer
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.1"
version: "1.7.2"
archive:
dependency: transitive
description:
Expand All @@ -35,7 +35,7 @@ packages:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.6.1"
version: "2.8.1"
beamer:
dependency: "direct main"
description:
Expand Down Expand Up @@ -133,7 +133,7 @@ packages:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.3.1"
checked_yaml:
dependency: transitive
description:
Expand Down Expand Up @@ -449,7 +449,7 @@ packages:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
version: "1.7.0"
mime:
dependency: transitive
description:
Expand Down Expand Up @@ -720,21 +720,21 @@ packages:
name: test
url: "https://pub.dartlang.org"
source: hosted
version: "1.16.8"
version: "1.17.10"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.0"
version: "0.4.2"
test_core:
dependency: transitive
description:
name: test_core
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.19"
version: "0.4.0"
time:
dependency: transitive
description:
Expand Down

0 comments on commit 972fcb3

Please sign in to comment.