104 lines
3.4 KiB
Dart
104 lines
3.4 KiB
Dart
import 'dart:developer' as developer;
|
|
|
|
import 'package:flutter/foundation.dart' show debugPrint, kDebugMode;
|
|
import 'package:nhance_app_pwa/config/environment.dart';
|
|
|
|
String _timestamp() => DateTime.now().toIso8601String();
|
|
|
|
enum LogLevel { debug, info, warning, error }
|
|
|
|
class LoggerConfig {
|
|
static bool enabled = true;
|
|
|
|
/// Logs below this level are dropped ([logDebug] when this is [LogLevel.info]).
|
|
static LogLevel minLevel = LogLevel.info;
|
|
|
|
// Stack parsing is expensive; keep it disabled unless specifically needed.
|
|
static bool includeCallerFromStack = false;
|
|
}
|
|
|
|
String? _inferCallerTag(StackTrace stackTrace) {
|
|
final lines = stackTrace.toString().split('\n');
|
|
|
|
// Find the first stack frame that is not inside this logger file.
|
|
for (final line in lines) {
|
|
if (line.contains('logger.dart')) continue;
|
|
|
|
// Example formats vary by platform; we try to extract a readable symbol name.
|
|
// Common patterns:
|
|
// - "#0 foo.bar (package:.../file.dart:12:3)"
|
|
// - "foo.bar (file.dart:12:3)"
|
|
final match = RegExp(r'(?:(?:#\d+)\s+)?([A-Za-z0-9_$.<>]+)\s*\(').firstMatch(line);
|
|
final symbol = match?.group(1);
|
|
if (symbol != null && symbol.trim().isNotEmpty) return symbol.trim();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
void _log(
|
|
LogLevel level,
|
|
Object? message, {
|
|
String? tag,
|
|
Object? error,
|
|
StackTrace? stackTrace,
|
|
}) {
|
|
if (!LoggerConfig.enabled) return;
|
|
|
|
// Verbose minimum: UAT (all modes), or local Dev when using a debug Flutter build.
|
|
final LogLevel effectiveMinLevel = switch (Environment.flavor) {
|
|
Flavor.uat => LogLevel.debug,
|
|
Flavor.dev when kDebugMode => LogLevel.debug,
|
|
_ => LoggerConfig.minLevel,
|
|
};
|
|
if (level.index < effectiveMinLevel.index) return;
|
|
|
|
// Debug lines: debug Flutter build (typical `flutter run`), or any UAT build.
|
|
if (level == LogLevel.debug && !kDebugMode && Environment.flavor != Flavor.uat) {
|
|
return;
|
|
}
|
|
|
|
final inferredTag = tag ??
|
|
(LoggerConfig.includeCallerFromStack
|
|
? _inferCallerTag(stackTrace ?? StackTrace.current)
|
|
: null);
|
|
final levelName = level.name.toUpperCase();
|
|
final fullMessage = '[${_timestamp()}] [$levelName]${inferredTag != null ? ' [$inferredTag]' : ''} $message';
|
|
|
|
developer.log(
|
|
fullMessage,
|
|
name: inferredTag ?? 'app',
|
|
error: error,
|
|
stackTrace: stackTrace,
|
|
);
|
|
|
|
// `developer.log` is easy to miss in release/profile (IDE filters, logcat tags).
|
|
// UAT: always mirror to stdout-style logging so `adb logcat`, Xcode, and web consoles show lines.
|
|
// Debug builds: mirror too for parity with `print` during local runs.
|
|
if (Environment.flavor == Flavor.uat || kDebugMode) {
|
|
debugPrint(fullMessage);
|
|
if (Environment.flavor == Flavor.uat && (error != null || stackTrace != null)) {
|
|
debugPrint('$error\n$stackTrace');
|
|
}
|
|
}
|
|
}
|
|
|
|
/// **Dev + localhost:** shown when you use `flutter run` (debug mode); not in `flutter run --release`.
|
|
/// **UAT:** also emitted in profile/release (verbose).
|
|
/// **Prod / Prod1:** debug Flutter build only; in release/profile use [logInfo] or `print`.
|
|
void logDebug(Object? message, {String? tag}) {
|
|
_log(LogLevel.debug, message, tag: tag);
|
|
}
|
|
|
|
void logInfo(Object? message, {String? tag}) {
|
|
_log(LogLevel.info, message, tag: tag);
|
|
}
|
|
|
|
void logWarning(Object? message, {String? tag}) {
|
|
_log(LogLevel.warning, message, tag: tag);
|
|
}
|
|
|
|
void logError(Object? message, {String? tag, Object? error, StackTrace? stackTrace}) {
|
|
_log(LogLevel.error, message, tag: tag, error: error, stackTrace: stackTrace);
|
|
}
|
|
|