Skip to content

Commit 44bf10f

Browse files
committed
Logs
1 parent 10daf94 commit 44bf10f

File tree

2 files changed

+263
-68
lines changed

2 files changed

+263
-68
lines changed

lib/src/core/log.dart

Lines changed: 262 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
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+
2529
class 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 = '\x1B[0m';
36+
static const String _red = '\x1B[31m';
37+
static const String _redDim = '\x1B[2m\x1B[31m'; // Dim red
38+
static const String _yellow = '\x1B[33m';
39+
static const String _yellowDim = '\x1B[2m\x1B[33m'; // Dim yellow
40+
static const String _green = '\x1B[32m';
41+
static const String _greenDim = '\x1B[2m\x1B[32m'; // Dim green
42+
static const String _cyan = '\x1B[36m';
43+
static const String _cyanDim = '\x1B[2m\x1B[36m'; // Dim cyan
44+
static const String _magenta = '\x1B[35m';
45+
static const String _magentaDim = '\x1B[2m\x1B[35m'; // Dim magenta
46+
static const String _gray = '\x1B[90m';
47+
static const String _bold = '\x1B[1m';
48+
static const String _dim = '\x1B[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
}

pubspec.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,9 @@ dependencies:
2222
path_provider: ^2.1.5
2323
# FFI for native Rust integration
2424
ffi: ^2.1.3
25-
# Logger for structured logging
26-
logger: ^2.4.0
2725
# Web interop for IndexedDB (Flutter 3.32.x)
2826
web: ^1.1.0
27+
logging: ^1.3.0
2928

3029
dev_dependencies:
3130
flutter_test:

0 commit comments

Comments
 (0)