Skip to content

Logging

FLEX provides a FlexLogger feature that offers structured, configurable, and colorful logging for Flutter applications. The logging system supports multiple log levels, custom output handlers, and scoped loggers for better organization and debugging.

The FLEX logging system consists of:

  • FlexLogger: Core singleton logger with configurable output
  • ScopedLogger: Component-specific loggers with preset sources
  • FlexLoggerConfig: Configuration for log levels, formatting, and output
  • LoggingFeature: FLEX feature integration for easy setup

Configure logging in your main.dart:

await FlexCore.initialize([
LoggingFeature(
config: const FlexLoggerConfig(
minLevel: kDebugMode ? LogLevel.debug : LogLevel.info,
showEmojis: true,
showTimestamp: true,
showSource: true,
useShortName: true,
enableColors: true,
),
),
// Other features...
]);

FLEX supports six log levels with different visual indicators:

enum LogLevel {
trace, // ⬜ Most verbose - detailed execution flow
debug, // 🟦 Debug information for development
info, // 🟩 General information about app state
warn, // 🟨 Warning messages for potential issues
error, // 🟥 Error conditions that need attention
fatal, // ☠️ Critical errors that may crash the app
}
const FlexLoggerConfig(
// Minimum log level to display
minLevel: LogLevel.info,
// Visual formatting
showTimestamp: true, // Include timestamps: [14:30:25.123]
showSource: true, // Show component/source names
showEmojis: true, // Include emoji indicators
useShortName: false, // Use TRC/DBG instead of TRACE/DEBUG
// Console output
enableConsole: true, // Print to console/debug output
enableColors: true, // Use ANSI colors in console
enableSourcePadding: true, // Pad source names for alignment
// Error handling
includeStackTraces: true, // Include stack traces for errors
// Custom output
customLogger: myCustomLogger, // Send logs to external systems
)
// Get the global logger instance
final logger = FlexCore.logger;
// Get a scoped logger for a specific component
final logger = FlexCore.getLogger('ProductService');
class ProductService {
final logger = FlexCore.getLogger('ProductService');
Future<List<Product>> fetchProducts() async {
logger.debug('Starting product fetch');
try {
logger.info('Fetching products from API');
final products = await _apiCall();
logger.info('Successfully fetched ${products.length} products');
return products;
} catch (error, stackTrace) {
logger.error(
'Failed to fetch products',
error: error,
stackTrace: stackTrace,
);
rethrow;
}
}
}
class ExampleService {
final logger = FlexCore.getLogger('ExampleService');
void demonstrateLogLevels() {
// Trace - Most verbose, execution flow
logger.trace('Entering method demonstrateLogLevels()');
// Debug - Development information
logger.debug('Processing user input: ${userInput}');
// Info - General application flow
logger.info('User successfully logged in');
// Warn - Potential issues
logger.warn('API response time is higher than expected (2.5s)');
// Error - Error conditions
logger.error('Failed to save user preferences', error: exception);
// Fatal - Critical system errors
logger.fatal('Database connection lost', error: error, stackTrace: stack);
}
}

Scoped loggers automatically include a source identifier:

class AuthBloc extends Bloc<AuthEvent, AuthState> {
final logger = FlexCore.getLogger('AuthBloc');
Future<void> login(String email, String password) async {
logger.info('Login attempt for user: $email');
try {
final result = await _authService.login(email, password);
logger.info('Login successful for user: $email');
emit(AuthStateAuthenticated(result));
} catch (error) {
logger.error('Login failed for user: $email', error: error);
emit(const AuthStateError('Login failed'));
}
}
}
class ProductCardWidget extends StatefulWidget {
@override
_ProductCardWidgetState createState() => _ProductCardWidgetState();
}
class _ProductCardWidgetState extends State<ProductCardWidget> {
final logger = FlexCore.getLogger('ProductCard');
@override
void initState() {
super.initState();
logger.debug('ProductCard initialized');
}
void _onAddToCart() {
logger.info('Add to cart button pressed for product: ${widget.product.id}');
try {
// Add to cart logic
logger.info('Product added to cart successfully');
} catch (error) {
logger.error('Failed to add product to cart', error: error);
}
}
}
FlexLoggerConfig getLoggerConfig() {
if (kDebugMode) {
// Development configuration
return const FlexLoggerConfig(
minLevel: LogLevel.trace,
showEmojis: true,
showTimestamp: true,
enableColors: true,
includeStackTraces: true,
);
} else if (kProfileMode) {
// Profile/staging configuration
return const FlexLoggerConfig(
minLevel: LogLevel.info,
showEmojis: false,
showTimestamp: true,
enableColors: false,
includeStackTraces: false,
);
} else {
// Production configuration
return const FlexLoggerConfig(
minLevel: LogLevel.warn,
showEmojis: false,
showTimestamp: false,
enableConsole: false,
customLogger: productionLogger,
);
}
}
void productionLogger(
String message,
LogLevel level,
String source,
Object? error,
StackTrace? stackTrace,
) {
// Send to analytics service
AnalyticsService.logEvent('app_log', {
'message': message,
'level': level.name,
'source': source,
'timestamp': DateTime.now().toIso8601String(),
});
// Send errors to crash reporting
if (level == LogLevel.error || level == LogLevel.fatal) {
CrashReporting.recordError(error, stackTrace, {
'message': message,
'source': source,
});
}
}
class OrderService {
final logger = FlexCore.getLogger('OrderService');
Future<Order> createOrder(OrderRequest request) async {
final orderId = generateOrderId();
logger.info('Creating order: $orderId');
logger.debug('Order details: items=${request.items.length}, total=${request.total}');
try {
final order = await _processOrder(request);
logger.info('Order created successfully: $orderId, amount=${order.total}');
return order;
} catch (error, stackTrace) {
logger.error(
'Order creation failed: $orderId',
error: error,
stackTrace: stackTrace,
);
rethrow;
}
}
}
class CartBloc extends Bloc<CartEvent, CartState> {
final logger = FlexCore.getLogger('CartBloc');
void _onAddItem(AddCartItem event, Emitter<CartState> emit) {
final userId = state.user?.id ?? 'anonymous';
final itemId = event.product.id;
logger.info('Adding item to cart: user=$userId, item=$itemId, qty=${event.quantity}');
try {
final updatedCart = state.cart.addItem(event.product, event.quantity);
logger.info('Item added successfully: cart_size=${updatedCart.items.length}, total=${updatedCart.total}');
emit(state.copyWith(cart: updatedCart));
} catch (error) {
logger.error('Failed to add item to cart: user=$userId, item=$itemId', error: error);
emit(state.copyWith(error: 'Failed to add item to cart'));
}
}
}
class ApiService {
final logger = FlexCore.getLogger('ApiService');
Future<T> _timedRequest<T>(
String endpoint,
Future<T> Function() request,
) async {
final stopwatch = Stopwatch()..start();
logger.trace('Starting request to $endpoint');
try {
final result = await request();
stopwatch.stop();
final duration = stopwatch.elapsedMilliseconds;
if (duration > 2000) {
logger.warn('Slow API response: $endpoint took ${duration}ms');
} else {
logger.debug('API request completed: $endpoint (${duration}ms)');
}
return result;
} catch (error) {
stopwatch.stop();
logger.error('API request failed: $endpoint (${stopwatch.elapsedMilliseconds}ms)', error: error);
rethrow;
}
}
}
class PaymentService {
final logger = FlexCore.getLogger('PaymentService');
Future<PaymentResult> processPayment(PaymentRequest request) async {
final correlationId = uuid.v4();
logger.info('Processing payment: id=$correlationId, amount=${request.amount}, method=${request.method}');
try {
final result = await _chargeCard(request);
logger.info('Payment successful: id=$correlationId, transaction=${result.transactionId}');
return result;
} on PaymentDeclinedException catch (error) {
logger.warn('Payment declined: id=$correlationId, reason=${error.reason}');
rethrow;
} on NetworkException catch (error, stackTrace) {
logger.error('Payment network error: id=$correlationId', error: error, stackTrace: stackTrace);
rethrow;
} catch (error, stackTrace) {
logger.fatal('Unexpected payment error: id=$correlationId', error: error, stackTrace: stackTrace);
rethrow;
}
}
}

For quick logging, use the shorthand methods:

class QuickLoggingExample {
final logger = FlexCore.getLogger('QuickExample');
void demonstrateShorthand() {
logger.t('Trace message'); // trace()
logger.d('Debug message'); // debug()
logger.i('Info message'); // info()
logger.w('Warning message'); // warn()
logger.e('Error message'); // error()
logger.f('Fatal message'); // fatal()
}
}
🟦 [14:30:25.123] DEBUG: User authentication started
🟩 [14:30:25.456] INFO: Token validation successful
🟨 [14:30:25.789] WARN: Session expires in 5 minutes
🟥 [14:30:26.012] ERROR: Failed to refresh token
☠️ [14:30:26.345] FATAL: Authentication service unavailable
[2023-12-25T14:30:25.123Z] WARN: [AuthService] Session expires in 5 minutes
[2023-12-25T14:30:26.012Z] ERROR: [AuthService] Failed to refresh token
[2023-12-25T14:30:26.345Z] FATAL: [AuthService] Authentication service unavailable
void setupProductionLogging() {
FlexLogger.configure(FlexLoggerConfig(
minLevel: LogLevel.info,
customLogger: (message, level, source, error, stackTrace) {
// Send to Firebase Crashlytics
if (level == LogLevel.error || level == LogLevel.fatal) {
FirebaseCrashlytics.instance.recordError(
error ?? message,
stackTrace,
information: [
DiagnosticsProperty('source', source),
DiagnosticsProperty('message', message),
DiagnosticsProperty('level', level.name),
],
);
}
// Send to analytics
FirebaseAnalytics.instance.logEvent(
name: 'app_log',
parameters: {
'level': level.name,
'source': source,
'message': message.length > 100 ? message.substring(0, 100) : message,
},
);
},
));
}

The FLEX logging system provides a powerful foundation for debugging, monitoring, and maintaining Flutter applications with comprehensive logging capabilities that scale from development to production.