Skip to content

Commit 85a5ee8

Browse files
feat(logging): introduce LogHandler for structured logging and configurable log levels
1 parent 6287ec5 commit 85a5ee8

File tree

2 files changed

+1086
-0
lines changed

2 files changed

+1086
-0
lines changed

lib/utils/log_handler.dart

Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
import 'dart:developer' as developer;
2+
import 'dart:io';
3+
import 'package:flutter/foundation.dart';
4+
5+
/// Configurable log levels
6+
enum LogLevel {
7+
debug(0),
8+
info(1),
9+
warning(2),
10+
error(3);
11+
12+
const LogLevel(this.value);
13+
final int value;
14+
}
15+
16+
/// Structured log data for better debugging
17+
class LogData {
18+
final String message;
19+
final LogLevel level;
20+
final String? tag;
21+
final String? screen;
22+
final String? operation;
23+
final Duration? duration;
24+
final Object? error;
25+
final StackTrace? stackTrace;
26+
final Map<String, dynamic>? context;
27+
final DateTime timestamp;
28+
29+
LogData({
30+
required this.message,
31+
required this.level,
32+
this.tag,
33+
this.screen,
34+
this.operation,
35+
this.duration,
36+
this.error,
37+
this.stackTrace,
38+
this.context,
39+
DateTime? timestamp,
40+
}) : timestamp = timestamp ?? DateTime.now();
41+
42+
/// Convert to structured log format
43+
Map<String, dynamic> toStructuredLog() {
44+
return {
45+
'message': message,
46+
'level': level.name,
47+
'timestamp': timestamp.toIso8601String(),
48+
if (tag != null) 'tag': tag,
49+
if (screen != null) 'screen': screen,
50+
if (operation != null) 'operation': operation,
51+
if (duration != null) 'duration_ms': duration!.inMilliseconds,
52+
if (error != null) 'error': error.toString(),
53+
if (context != null && context!.isNotEmpty) 'context': context,
54+
};
55+
}
56+
}
57+
58+
class LogHandler {
59+
static const String _defaultTag = '[PolicyEngine]';
60+
static bool _isDebugMode = kDebugMode;
61+
static String _tag = _defaultTag;
62+
static String _currentScreen = '';
63+
static bool _includeTimestamp = true;
64+
static bool _includeStackTrace = true;
65+
static bool _includeSystemInfo = false;
66+
static LogLevel _minLogLevel = LogLevel.debug;
67+
static bool _useStructuredLogging = true;
68+
static bool _useDeveloperLog = true;
69+
70+
/// Set the current screen for logging context
71+
static void setScreen(String screenName) {
72+
_currentScreen = screenName;
73+
_updateTag();
74+
}
75+
76+
/// Set a custom tag for the current screen
77+
static void setScreenTag(String screenName, String customTag) {
78+
_currentScreen = screenName;
79+
_tag = customTag;
80+
}
81+
82+
/// Get the current screen name
83+
static String get currentScreen => _currentScreen;
84+
85+
/// Get the current tag
86+
static String get currentTag => _tag;
87+
88+
/// Update tag based on current screen
89+
static void _updateTag() {
90+
if (_currentScreen.isNotEmpty) {
91+
_tag = '[$_currentScreen]';
92+
} else {
93+
_tag = _defaultTag;
94+
}
95+
}
96+
97+
/// Configure the LogHandler settings
98+
static void configure({
99+
String? tag,
100+
String? screen,
101+
bool? isDebugMode,
102+
bool? includeTimestamp,
103+
bool? includeStackTrace,
104+
bool? includeSystemInfo,
105+
LogLevel? minLogLevel,
106+
bool? useStructuredLogging,
107+
bool? useDeveloperLog,
108+
}) {
109+
_tag = tag ?? _defaultTag;
110+
_currentScreen = screen ?? '';
111+
_isDebugMode = isDebugMode ?? kDebugMode;
112+
_includeTimestamp = includeTimestamp ?? true;
113+
_includeStackTrace = includeStackTrace ?? true;
114+
_includeSystemInfo = includeSystemInfo ?? false;
115+
_minLogLevel = minLogLevel ?? LogLevel.debug;
116+
_useStructuredLogging = useStructuredLogging ?? true;
117+
_useDeveloperLog = useDeveloperLog ?? true;
118+
119+
// Only update tag if no custom tag was provided
120+
if (screen != null && tag == null) {
121+
_updateTag();
122+
}
123+
}
124+
125+
/// Internal method to output logs using dart:developer
126+
static void _logWithDeveloper(LogData logData) {
127+
if (!_useDeveloperLog) return;
128+
129+
final structuredData = logData.toStructuredLog();
130+
131+
// Use developer.log with structured data
132+
developer.log(
133+
logData.message,
134+
name: logData.tag ?? _tag,
135+
error: logData.error,
136+
stackTrace: logData.stackTrace,
137+
time: logData.timestamp,
138+
);
139+
140+
// Log structured data as separate entries for better debugging
141+
if (_useStructuredLogging && structuredData.length > 2) {
142+
developer.log(
143+
'Structured Data: ${structuredData.toString()}',
144+
name: '${logData.tag ?? _tag}_structured',
145+
time: logData.timestamp,
146+
);
147+
}
148+
}
149+
150+
/// Generic log method with configurable parameters
151+
static void output(
152+
String message, {
153+
LogLevel level = LogLevel.debug,
154+
Object? error,
155+
StackTrace? stackTrace,
156+
Map<String, dynamic>? context,
157+
String? operation,
158+
Duration? duration,
159+
String? screenOverride,
160+
}) {
161+
if (!_isDebugMode || level.value < _minLogLevel.value) return;
162+
163+
final logData = LogData(
164+
message: message,
165+
level: level,
166+
tag: screenOverride != null ? '[$screenOverride]' : _tag,
167+
screen: screenOverride ?? _currentScreen,
168+
operation: operation,
169+
duration: duration,
170+
error: error,
171+
stackTrace: stackTrace,
172+
context: context,
173+
);
174+
175+
// Use developer.log for better debugging experience
176+
_logWithDeveloper(logData);
177+
}
178+
179+
/// Convenience methods for different log levels
180+
static void debug(
181+
String message, {
182+
Object? error,
183+
StackTrace? stackTrace,
184+
Map<String, dynamic>? context,
185+
String? operation,
186+
Duration? duration,
187+
String? screenOverride,
188+
}) {
189+
output(
190+
message,
191+
level: LogLevel.debug,
192+
error: error,
193+
stackTrace: stackTrace,
194+
context: context,
195+
operation: operation,
196+
duration: duration,
197+
screenOverride: screenOverride,
198+
);
199+
}
200+
201+
static void info(
202+
String message, {
203+
Object? error,
204+
StackTrace? stackTrace,
205+
Map<String, dynamic>? context,
206+
String? operation,
207+
Duration? duration,
208+
String? screenOverride,
209+
}) {
210+
output(
211+
message,
212+
level: LogLevel.info,
213+
error: error,
214+
stackTrace: stackTrace,
215+
context: context,
216+
operation: operation,
217+
duration: duration,
218+
screenOverride: screenOverride,
219+
);
220+
}
221+
222+
static void warning(
223+
String message, {
224+
Object? error,
225+
StackTrace? stackTrace,
226+
Map<String, dynamic>? context,
227+
String? operation,
228+
Duration? duration,
229+
String? screenOverride,
230+
}) {
231+
output(
232+
message,
233+
level: LogLevel.warning,
234+
error: error,
235+
stackTrace: stackTrace,
236+
context: context,
237+
operation: operation,
238+
duration: duration,
239+
screenOverride: screenOverride,
240+
);
241+
}
242+
243+
static void error(
244+
String message, {
245+
Object? error,
246+
StackTrace? stackTrace,
247+
Map<String, dynamic>? context,
248+
String? operation,
249+
Duration? duration,
250+
String? screenOverride,
251+
}) {
252+
output(
253+
message,
254+
level: LogLevel.error,
255+
error: error,
256+
stackTrace: stackTrace,
257+
context: context,
258+
operation: operation,
259+
duration: duration,
260+
screenOverride: screenOverride,
261+
);
262+
}
263+
264+
/// Performance logging with timing
265+
static void time(String operation, void Function() callback) {
266+
final stopwatch = Stopwatch()..start();
267+
try {
268+
callback();
269+
} finally {
270+
stopwatch.stop();
271+
info(
272+
'Operation completed',
273+
operation: operation,
274+
duration: stopwatch.elapsed,
275+
);
276+
}
277+
}
278+
279+
/// Async performance logging
280+
static Future<T> timeAsync<T>(
281+
String operation,
282+
Future<T> Function() callback,
283+
) async {
284+
final stopwatch = Stopwatch()..start();
285+
try {
286+
final result = await callback();
287+
return result;
288+
} finally {
289+
stopwatch.stop();
290+
info(
291+
'Async operation completed',
292+
operation: operation,
293+
duration: stopwatch.elapsed,
294+
);
295+
}
296+
}
297+
298+
/// Legacy method for backward compatibility
299+
static void show(String message, {Object? error, StackTrace? stackTrace}) {
300+
debug(message, error: error, stackTrace: stackTrace);
301+
}
302+
303+
/// Reset configuration to defaults
304+
static void reset() {
305+
_tag = _defaultTag;
306+
_currentScreen = '';
307+
_isDebugMode = kDebugMode;
308+
_includeTimestamp = true;
309+
_includeStackTrace = true;
310+
_includeSystemInfo = false;
311+
_minLogLevel = LogLevel.debug;
312+
_useStructuredLogging = true;
313+
_useDeveloperLog = true;
314+
}
315+
}

0 commit comments

Comments
 (0)