1- import 'package:logger/logger.dart' ;
1+ import 'dart:io' ;
2+
3+ import 'package:logging/logging.dart' ;
24
35/// Standard logging implementation for Flutter Local DB
46///
@@ -22,77 +24,271 @@ import 'package:logger/logger.dart';
2224/// // Fatal errors
2325/// Log.f('Critical database corruption detected');
2426/// ```
27+
28+
2529class Log {
2630 static final Logger _log = _getLogger ();
2731
2832 Log ._();
2933
34+ // ANSI Color codes - Rust style with proper dim colors
35+ static const String _reset = '\x 1B[0m' ;
36+ static const String _red = '\x 1B[31m' ;
37+ static const String _redDim = '\x 1B[2m\x 1B[31m' ; // Dim red
38+ static const String _yellow = '\x 1B[33m' ;
39+ static const String _yellowDim = '\x 1B[2m\x 1B[33m' ; // Dim yellow
40+ static const String _green = '\x 1B[32m' ;
41+ static const String _greenDim = '\x 1B[2m\x 1B[32m' ; // Dim green
42+ static const String _cyan = '\x 1B[36m' ;
43+ static const String _cyanDim = '\x 1B[2m\x 1B[36m' ; // Dim cyan
44+ static const String _magenta = '\x 1B[35m' ;
45+ static const String _magentaDim = '\x 1B[2m\x 1B[35m' ; // Dim magenta
46+ static const String _gray = '\x 1B[90m' ;
47+ static const String _bold = '\x 1B[1m' ;
48+ static const String _dim = '\x 1B[2m' ;
49+
3050 static Logger _getLogger () {
31- final level = Level .debug;
32- final printer = PrettyPrinter (methodCount: 4 );
33- final output = ConsoleOutput ();
34- return Logger (level: level, printer: printer, output: output);
51+ final logger = Logger .root;
52+
53+ Logger .root.level = Level .ALL ;
54+
55+ Logger .root.onRecord.listen ((LogRecord record) {
56+ final now = DateTime .now ();
57+ final timestamp = '${now .hour .toString ().padLeft (2 , '0' )}:${now .minute .toString ().padLeft (2 , '0' )}:${now .second .toString ().padLeft (2 , '0' )}.${now .millisecond .toString ().padLeft (3 , '0' )}' ;
58+
59+ String levelName = '' ;
60+ String prefix = '' ;
61+ String message = '' ;
62+ String additionalInfo = '' ;
63+
64+ // Get caller information for Rust-like location display with clickable links
65+ final stackTrace = StackTrace .current.toString ();
66+ final lines = stackTrace.split ('\n ' );
67+ String location = 'unknown:0:0' ;
68+ String clickableLink = '' ;
69+
70+ // First pass: extract project root from any stackTrace line that has full path
71+ String ? projectRoot;
72+ for (final line in lines) {
73+ // Look for a line with full path to extract project root
74+ final fullPathMatch = RegExp (r'\(([^)]+)/lib/[^)]+\.dart:\d+:\d+\)' ).firstMatch (line);
75+ if (fullPathMatch != null ) {
76+ projectRoot = fullPathMatch.group (1 );
77+ break ;
78+ }
79+ }
80+
81+ // Extract precise caller location from stack trace
82+ for (final line in lines) {
83+ // Look for application code (skip logging infrastructure)
84+ if ((line.contains ('package:' ) || line.contains ('file:///' )) &&
85+ ! line.contains ('log.dart' ) &&
86+ ! line.contains ('logging.dart' ) &&
87+ ! line.contains ('zone.dart' ) &&
88+ ! line.contains ('Logger.' ) &&
89+ ! line.contains ('onRecord.listen' )) {
90+
91+ RegExpMatch ? match;
92+ String ? fileName, lineNum, colNum, fullPath;
93+ String ? packageName;
94+
95+ // Try generic package format: package:<pkg>/path/to/file.dart:48:7)
96+ match = RegExp (r'package:([^)]+)/([^)]+\.dart):(\d+):(\d+)' ).firstMatch (line);
97+ if (match != null ) {
98+ packageName = match.group (1 )! ;
99+ final filePath = match.group (2 )! ;
100+ fileName = filePath.split ('/' ).last;
101+ lineNum = match.group (3 )! ;
102+ colNum = match.group (4 )! ;
103+
104+ // Extract absolute path from stackTrace itself
105+ // Look for the full path in stackTrace lines
106+ String ? absolutePath;
107+ for (final stackLine in lines) {
108+ if (packageName != null && stackLine.contains ('package:$packageName /$filePath ' )) {
109+ // Try to find the real path in the stackTrace
110+ final fullPathMatch = RegExp ('\\ (([^)]+/$filePath ):\\ d+:\\ d+\\ )' ).firstMatch (stackLine);
111+ if (fullPathMatch != null ) {
112+ absolutePath = fullPathMatch.group (1 );
113+ break ;
114+ }
115+ }
116+ }
117+
118+ // If not found in stack, construct using extracted project root
119+ if (absolutePath != null ) {
120+ fullPath = absolutePath;
121+ } else if (projectRoot != null ) {
122+ // Use automatically detected project root
123+ fullPath = '$projectRoot /lib/$filePath ' ;
124+ } else {
125+ // Last resort: try to guess from current script location
126+ final scriptUri = Platform .script;
127+ if (scriptUri.path.contains ('/lib/' )) {
128+ final basePath = scriptUri.path.substring (0 , scriptUri.path.lastIndexOf ('/lib/' ));
129+ fullPath = '$basePath /lib/$filePath ' ;
130+ } else {
131+ // Ultimate fallback
132+ fullPath = './lib/$filePath ' ;
133+ }
134+ }
135+ } else {
136+ // Try file format: file:///absolute/path/file.dart:48:7)
137+ match = RegExp (r'file://([^:)]+\.dart):(\d+):(\d+)' ).firstMatch (line);
138+ if (match != null ) {
139+ fullPath = match.group (1 )! ;
140+ fileName = fullPath.split ('/' ).last;
141+ lineNum = match.group (2 )! ;
142+ colNum = match.group (3 )! ;
143+ }
144+ }
145+
146+ if (fileName != null && lineNum != null && colNum != null && fullPath != null ) {
147+ location = '$fileName :$lineNum :$colNum ' ;
148+ // Just show the clean path without file:// prefix
149+ clickableLink = '$fullPath :$lineNum :$colNum ' ;
150+ break ;
151+ }
152+ }
153+ }
154+
155+ // If no precise location found, try to get at least the method name
156+ if (location == 'unknown:0:0' ) {
157+ for (final line in lines) {
158+ if (line.contains ('_incrementCounter' ) || line.contains ('main.dart' )) {
159+ final match = RegExp (r'([^/\s]+\.dart):(\d+):(\d+)' ).firstMatch (line);
160+ if (match != null ) {
161+ final fallbackFile = match.group (1 )! ;
162+ final fallbackLine = match.group (2 )! ;
163+ final fallbackCol = match.group (3 )! ;
164+ location = '$fallbackFile :$fallbackLine :$fallbackCol ' ;
165+
166+ // Use detected project root or Platform.script fallback
167+ String fallbackFullPath;
168+ if (projectRoot != null ) {
169+ fallbackFullPath = '$projectRoot /lib/$fallbackFile ' ;
170+ } else {
171+ final scriptUri = Platform .script;
172+ if (scriptUri.path.contains ('/lib/' )) {
173+ final basePath = scriptUri.path.substring (0 , scriptUri.path.lastIndexOf ('/lib/' ));
174+ fallbackFullPath = '$basePath /lib/$fallbackFile ' ;
175+ } else {
176+ fallbackFullPath = './lib/$fallbackFile ' ;
177+ }
178+ }
179+ clickableLink = '$fallbackFullPath :$fallbackLine :$fallbackCol ' ;
180+ break ;
181+ }
182+ }
183+ }
184+ }
185+
186+ // Rust-like formatting with proper spacing and colors
187+ switch (record.level) {
188+ case Level .SEVERE : // Check if it's e() or f() call
189+ final stackLines = StackTrace .current.toString ().split ('\n ' );
190+ bool isFatalCall = false ;
191+
192+ // Check if called via f() method by looking at stack
193+ for (final line in stackLines) {
194+ if (line.contains ('Log.f' )) {
195+ isFatalCall = true ;
196+ break ;
197+ }
198+ }
199+
200+ if (isFatalCall) {
201+ // F = CRITICAL (morado)
202+ levelName = 'critical' ;
203+ prefix = '$_bold ${_magenta }CRITICAL$_reset : $_magentaDim ${record .message }$_reset ' ;
204+ additionalInfo = '\n $_bold $_magenta --> $_reset $_gray $location $_reset $_dim ($clickableLink )$_reset ' ;
205+ additionalInfo += '\n $_bold $_magenta |$_reset ' ;
206+ additionalInfo += '\n $_bold $_magenta = $_reset ${_bold }critical$_reset : ${_magentaDim }System requires immediate attention$_reset ' ;
207+ additionalInfo += '\n $_bold $_magenta = $_reset ${_bold }help$_reset : ${_magentaDim }Check system logs and restart if necessary$_reset ' ;
208+ } else {
209+ // E = ERROR (rojo)
210+ levelName = 'error' ;
211+ prefix = '$_bold ${_red }ERROR$_reset : $_redDim ${record .message }$_reset ' ;
212+ additionalInfo = '\n $_bold $_red --> $_reset $_gray $location $_reset $_dim ($clickableLink )$_reset ' ;
213+ additionalInfo += '\n $_bold $_red |$_reset ' ;
214+
215+ if (record.error != null ) {
216+ additionalInfo += '\n $_bold $_red = $_reset ${_bold }error$_reset : $_redDim ${record .error }$_reset ' ;
217+ }
218+ if (record.stackTrace != null ) {
219+ final errorStackLines = record.stackTrace.toString ().split ('\n ' ).take (4 ).toList ();
220+ for (int i = 0 ; i < errorStackLines.length; i++ ) {
221+ final lineNum = (i + 1 ).toString ().padLeft (2 );
222+ additionalInfo += '\n $_bold $_red $lineNum |$_reset $_gray ${errorStackLines [i ].trim ()}$_reset ' ;
223+ }
224+ additionalInfo += '\n $_bold $_red |$_reset ' ;
225+ }
226+ }
227+ additionalInfo += '\n ' ; // Empty line after error/critical
228+ message = '' ;
229+ break ;
230+
231+ case Level .WARNING : // Warning - Yellow like Rust warning
232+ levelName = 'warning' ;
233+ prefix = '$_bold ${_yellow }warning$_reset : $_yellowDim ${record .message }$_reset ' ;
234+ additionalInfo = '\n $_bold $_yellow --> $_reset $_gray $location $_reset $_dim ($clickableLink )$_reset ' ;
235+ additionalInfo += '\n $_bold $_yellow |$_reset ' ;
236+
237+ if (record.error != null ) {
238+ additionalInfo += '\n $_bold $_yellow = $_reset ${_bold }note$_reset : $_yellowDim ${record .error }$_reset ' ;
239+ }
240+ additionalInfo += '\n ' ; // Empty line after warning
241+ message = '' ;
242+ break ;
243+
244+ case Level .INFO : // Info - Green with spacing and clickable link
245+ levelName = 'info' ;
246+ prefix = '$_bold ${_green }INFO$_reset : $_greenDim ${record .message }$_reset $_dim ($clickableLink )$_reset ' ;
247+ message = '' ;
248+ additionalInfo = '\n ' ; // Empty line after info
249+ break ;
250+
251+ case Level .FINE : // Debug - Cyan with spacing and clickable link
252+ levelName = 'debug' ;
253+ prefix = '$_bold ${_cyan }DEBUG$_reset : $_cyanDim ${record .message }$_reset $_dim ($clickableLink )$_reset ' ;
254+ message = '' ;
255+ additionalInfo = '\n ' ; // Empty line after debug
256+ break ;
257+
258+ default : // Trace - Dim with spacing and clickable link
259+ levelName = 'trace' ;
260+ prefix = '$_dim ${_gray }TRACE$_reset : $_gray ${record .message }$_reset $_dim ($clickableLink )$_reset ' ;
261+ message = '' ;
262+ additionalInfo = '\n ' ; // Empty line after trace
263+ }
264+
265+ // Rust-like format with proper spacing
266+ final rustFormat = '$prefix $message $additionalInfo ' ;
267+
268+ // Print only with Rust-like format (no duplication)
269+
270+ if (Platform .isAndroid || Platform .isIOS || Platform .isMacOS || Platform .isLinux) {
271+ // ignore: avoid_print
272+ print (rustFormat);
273+ } else {
274+ // ignore: avoid_print
275+ print ('[$levelName ] $timestamp ${record .message }$additionalInfo ' );
276+ }
277+ });
278+
279+ return logger;
35280 }
36281
37- /// Debug logging - for development debugging information
38- ///
39- /// Use for detailed information that is useful during development
40- /// but not needed in production.
41- ///
42- /// Example:
43- /// ```dart
44- /// Log.d('FFI function called: ${functionName}');
45- /// Log.d('Database path: ${dbPath}');
46- /// ```
47- static void d (dynamic message) => _log.d (message);
48-
49- /// Info logging - for general application information
50- ///
51- /// Use for important application events and successful operations.
52- ///
53- /// Example:
54- /// ```dart
55- /// Log.i('LocalDB initialized successfully');
56- /// Log.i('User data saved: ${userId}');
57- /// ```
58- static void i (dynamic message) => _log.i (message);
59-
60- /// Warning logging - for potentially harmful situations
61- ///
62- /// Use when something unexpected happened but the application can continue.
63- ///
64- /// Example:
65- /// ```dart
66- /// Log.w('Hot restart detected, reestablishing connection');
67- /// Log.w('Data validation warning: ${field} may be invalid');
68- /// ```
69- static void w (dynamic message) => _log.w (message);
70-
71- /// Fatal logging - for very severe error events
72- ///
73- /// Use for critical errors that may cause the application to abort.
74- ///
75- /// Example:
76- /// ```dart
77- /// Log.f('Critical database failure: ${error}');
78- /// Log.f('Native library could not be loaded');
79- /// ```
80- static void f (dynamic message) => _log.f (message);
81-
82- /// Error logging - for error events with optional context
83- ///
84- /// Use for errors that occurred but the application can recover from.
85- /// Always include error details and stack trace when available.
86- ///
87- /// Example:
88- /// ```dart
89- /// Log.e('Failed to fetch user data', error: exception, stackTrace: stackTrace);
90- /// Log.e('FFI operation failed: ${operation}', error: error);
91- /// ```
92- static void e (
93- dynamic message, {
94- DateTime ? time,
95- Object ? error,
96- StackTrace ? stackTrace,
97- }) => _log.e (message, time: time, stackTrace: stackTrace, error: error);
282+
283+ static void d (dynamic message) => _log.fine (message);
284+
285+ static void i (dynamic message) => _log.info (message);
286+
287+ static void w (dynamic message) => _log.warning (message);
288+
289+ static void f (dynamic message) => _log.severe (message);
290+
291+ static void e (dynamic message, {DateTime ? time, Object ? error, StackTrace ? stackTrace}) =>
292+ _log.severe (message, error, stackTrace);
293+
98294}
0 commit comments