You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
134 lines
4.7 KiB
Dart
134 lines
4.7 KiB
Dart
|
6 days ago
|
// filepath: lib/core/service/client/api_logger_service.dart
|
||
|
|
import 'dart:convert';
|
||
|
|
|
||
|
|
import 'package:flutter/foundation.dart';
|
||
|
|
|
||
|
|
/// Holds the details of a single API call.
|
||
|
|
class ApiLogEntry {
|
||
|
|
final String id;
|
||
|
|
final DateTime timestamp;
|
||
|
|
final String method;
|
||
|
|
final String url;
|
||
|
|
final String endpoint;
|
||
|
|
final String environment;
|
||
|
|
final Map<String, String> headers;
|
||
|
|
final Map<String, dynamic>? requestBody;
|
||
|
|
final dynamic responseBody;
|
||
|
|
final int statusCode;
|
||
|
|
final bool isSuccess;
|
||
|
|
final Duration? duration;
|
||
|
|
|
||
|
|
ApiLogEntry({
|
||
|
|
required this.id,
|
||
|
|
required this.timestamp,
|
||
|
|
required this.method,
|
||
|
|
required this.url,
|
||
|
|
required this.endpoint,
|
||
|
|
this.environment = '',
|
||
|
|
required this.headers,
|
||
|
|
this.requestBody,
|
||
|
|
this.responseBody,
|
||
|
|
required this.statusCode,
|
||
|
|
required this.isSuccess,
|
||
|
|
this.duration,
|
||
|
|
});
|
||
|
|
|
||
|
|
/// Derives a short environment label from a full URL string.
|
||
|
|
static String envFromUrl(String url) {
|
||
|
|
if (url.contains('vidamergeuat')) return 'VidaMerge';
|
||
|
|
if (url.contains('vidauat')) return 'VidaPlus';
|
||
|
|
if (url.contains('orash')) return 'Orash';
|
||
|
|
if (url.contains('uat.hmgwebservices')) return 'UAT';
|
||
|
|
if (url.contains('rc.hmg.com') && url.contains('/test')) return 'RC-Test';
|
||
|
|
if (url.contains('ms.hmg.com/rc')) return 'MS-RC';
|
||
|
|
if (url.contains('rc.hmg.com')) return 'RC';
|
||
|
|
if (url.contains('mdlaboratories.com/exacartapitest')) return 'Pharmacy-Test';
|
||
|
|
if (url.contains('mdlaboratories.com')) return 'Pharmacy';
|
||
|
|
if (url.contains('hmgwebservices.com')) return 'Production';
|
||
|
|
// External / other
|
||
|
|
final uri = Uri.tryParse(url);
|
||
|
|
return uri?.host.isNotEmpty == true ? uri!.host.split('.').first : 'External';
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Returns a pretty-printed JSON string of the request body.
|
||
|
|
String get prettyRequestBody {
|
||
|
|
if (requestBody == null) return '(none)';
|
||
|
|
try {
|
||
|
|
return const JsonEncoder.withIndent(' ').convert(requestBody);
|
||
|
|
} catch (_) {
|
||
|
|
return requestBody.toString();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Returns a pretty-printed JSON string of the response body.
|
||
|
|
String get prettyResponseBody {
|
||
|
|
if (responseBody == null) return '(none)';
|
||
|
|
try {
|
||
|
|
if (responseBody is String) {
|
||
|
|
final decoded = json.decode(responseBody as String);
|
||
|
|
return const JsonEncoder.withIndent(' ').convert(decoded);
|
||
|
|
}
|
||
|
|
return const JsonEncoder.withIndent(' ').convert(responseBody);
|
||
|
|
} catch (_) {
|
||
|
|
return responseBody.toString();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Returns formatted text report (headers + request + response sectioned).
|
||
|
|
String toFormattedText() {
|
||
|
|
final buf = StringBuffer();
|
||
|
|
buf.writeln('═══════════════════════════════════');
|
||
|
|
buf.writeln('[$method] $url');
|
||
|
|
buf.writeln('Status : $statusCode | ${isSuccess ? '✅ Success' : '❌ Failed'}');
|
||
|
|
if (environment.isNotEmpty) buf.writeln('Env : $environment');
|
||
|
|
buf.writeln('Time : ${timestamp.toIso8601String()}');
|
||
|
|
if (duration != null) buf.writeln('Duration: ${duration!.inMilliseconds}ms');
|
||
|
|
buf.writeln('───────────────── HEADERS ─────────────────');
|
||
|
|
headers.forEach((k, v) => buf.writeln('$k: $v'));
|
||
|
|
buf.writeln('───────────────── REQUEST ─────────────────');
|
||
|
|
buf.writeln(prettyRequestBody);
|
||
|
|
buf.writeln('──────────────── RESPONSE ─────────────────');
|
||
|
|
buf.writeln(prettyResponseBody);
|
||
|
|
buf.writeln('═══════════════════════════════════');
|
||
|
|
return buf.toString();
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Returns a cURL command equivalent.
|
||
|
|
String toCurl() {
|
||
|
|
final buf = StringBuffer();
|
||
|
|
buf.write("curl -X $method \\\n");
|
||
|
|
buf.write(" '${url.trim()}' \\\n");
|
||
|
|
headers.forEach((k, v) {
|
||
|
|
buf.write(" -H '$k: $v' \\\n");
|
||
|
|
});
|
||
|
|
if (requestBody != null && method != 'GET') {
|
||
|
|
final bodyStr = json.encode(requestBody).replaceAll("'", "\\'");
|
||
|
|
buf.write(" -d '$bodyStr'");
|
||
|
|
}
|
||
|
|
return buf.toString();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Singleton service that collects API call logs in-memory.
|
||
|
|
class ApiLoggerService extends ChangeNotifier {
|
||
|
|
ApiLoggerService._internal();
|
||
|
|
static final ApiLoggerService instance = ApiLoggerService._internal();
|
||
|
|
|
||
|
|
final List<ApiLogEntry> _logs = [];
|
||
|
|
|
||
|
|
List<ApiLogEntry> get logs => List.unmodifiable(_logs.reversed.toList());
|
||
|
|
|
||
|
|
void addLog(ApiLogEntry entry) {
|
||
|
|
_logs.add(entry);
|
||
|
|
// Keep last 500 entries to avoid huge memory usage
|
||
|
|
if (_logs.length > 500) _logs.removeAt(0);
|
||
|
|
notifyListeners();
|
||
|
|
}
|
||
|
|
|
||
|
|
void clear() {
|
||
|
|
_logs.clear();
|
||
|
|
notifyListeners();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|