post_enrollment_app/lib/logger.dart
2026-05-19 12:30:28 +05:30

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