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