inline_logger 0.3.1
inline_logger: ^0.3.1 copied to clipboard
A powerful inline logger for Flutter. Log anywhere in your widget tree without breakpoints. Chain logging calls directly on any expression.
import 'package:flutter/material.dart';
import 'package:inline_logger/inline_logger.dart';
void main() {
// This example runs on the 0.3.0 defaults so the console shows what a
// new user actually gets: the log's key in the IDE's own gutter, a
// fixed-width level token, and a dimmed clickable location.
LoggerConfig.minLevel = LogLevel.debug;
LoggerConfig.useColors = true; // Enable colored console output
// Both default to false in 0.3.0. Uncomment for a plain `flutter run`
// terminal, which has no time column of its own.
// LoggerConfig.showTimestamp = true;
// LoggerConfig.showEmoji = true;
// NEW: Source location is on by default in debug mode.
// These are the defaults, shown here for demonstration:
LoggerConfig.showSourceLocation = true;
LoggerConfig.clickableLinkFormat = LinkFormat.auto;
LoggerConfig.useClickableLinks = true;
// Optional: Hook into every log record
LoggerConfig.onRecord = (record) {
// You could forward to Crashlytics, Sentry, etc.
// print('Record: ${record.level.label} - ${record.message}');
// print('Source: ${record.source}');
};
// Direct call — the clickable link will point to THIS line:
Logger.info('App starting', 'main');
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'inline_logger Example',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const ExampleHomePage(),
);
}
}
class ExampleHomePage extends StatefulWidget {
const ExampleHomePage({super.key});
@override
State<ExampleHomePage> createState() => _ExampleHomePageState();
}
class _ExampleHomePageState extends State<ExampleHomePage> {
int _counter = 0;
List<String> _items = [];
bool _isLoading = false;
@override
void initState() {
super.initState();
Logger.lifecycle('initState', 'ExampleHomePage initialized');
}
void _incrementCounter() {
Logger.header('INCREMENT COUNTER');
setState(() {
// Inline call — the clickable link will point to THIS line:
_counter = (_counter + 1).logSuccess('New counter value');
});
Logger.divider();
}
Future<void> _simulateApiCall() async {
Logger.header('SIMULATE API CALL');
setState(() {
_isLoading = true;
Logger.state('isLoading', _isLoading);
});
Logger.apiRequest(
endpoint: '/api/items',
method: 'GET',
);
// Simulate network delay
final stopwatch = Stopwatch()..start();
await Future.delayed(const Duration(seconds: 2));
stopwatch.stop();
final mockData = ['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5'];
Logger.apiResponse(
endpoint: '/api/items',
statusCode: 200,
data: mockData,
duration: stopwatch.elapsed,
);
setState(() {
_items = mockData.logSuccess('Items loaded');
_isLoading = false;
Logger.state('isLoading', _isLoading);
});
Logger.divider();
}
void _demonstrateLogLevels() {
Logger.header('LOG LEVELS DEMO');
Logger.debug('This is a debug message');
Logger.verbose('This is a verbose message');
Logger.info('This is an info message');
Logger.success('This is a success message');
Logger.warning('This is a warning message');
Logger.error('This is an error message');
Logger.critical('This is a critical message');
Logger.divider();
}
void _demonstrateInlineLogging() {
Logger.header('INLINE LOGGING DEMO');
// Example 1: Simple inline logging
'John Doe'.log('User name');
// Example 2: Chained logging
[1, 2, 3, 4, 5]
.log('Initial list')
.map((e) => e * 2)
.toList()
.logInfo('Doubled list')
.where((e) => e > 5)
.toList()
.logSuccess('Filtered list');
// Example 3: Different log levels
const age = 25;
age.logDebug('User age');
age.logVerbose('User age verbose');
age.logInfo('User age info');
age.logSuccess('User age success');
age.logWarning('User age warning');
Logger.divider();
// Show snackbar to confirm
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Check console for inline logging examples!'),
duration: Duration(seconds: 2),
),
);
}
}
void _demonstrateSourceLocations() {
Logger.header('SOURCE LOCATION DEMO');
// Each of these shows a clickable link to THIS file.
// The shipped default: the key becomes the console's own gutter.
Logger.info('Direct call from _demonstrateSourceLocations', 'Demo');
'inline value'.logDebug('Inline call');
// Note: every LinkFormat except bareAbsolute renders identically for
// files under lib/, because those frames are already package: URIs.
LoggerConfig.clickableLinkFormat = LinkFormat.bareAbsolute;
Logger.info('bareAbsolute — the only format that differs here', 'Demo');
LoggerConfig.clickableLinkFormat = LinkFormat.auto;
// The location on its own row: no `.dart` in a message can steal
// the click, and the row can never blow past Dart-Code's parse cap.
LoggerConfig.locationPlacement = LocationPlacement.ownLine;
Logger.info('location on its own line', 'Demo');
LoggerConfig.locationPlacement = LocationPlacement.inline;
// Emoji level token — the glyph replaces DBG/VRB/INF/… entirely.
LoggerConfig.levelStyle = LevelStyle.emoji;
Logger.info('emoji level token', 'Demo');
Logger.warning('emoji level token', 'Demo');
Logger.error('emoji level token', 'Demo');
LoggerConfig.levelStyle = LevelStyle.short;
// The 0.2.x layout, for comparison.
LoggerConfig.keyPlacement = KeyPlacement.inline;
LoggerConfig.developerLogName = 'InlineLogger';
LoggerConfig.showTimestamp = true;
LoggerConfig.timestampStyle = TimestampStyle.iso;
LoggerConfig.levelStyle = LevelStyle.full;
LoggerConfig.showEmoji = true;
Logger.info('the 0.2.x layout', 'Demo');
// Location segment omitted entirely.
LoggerConfig.useClickableLinks = false;
Logger.info('no location segment', 'Demo');
// Restore field by field rather than calling LoggerConfig.reset(),
// which would also null the onRecord hook installed in main() and
// clear the history the "Log History" button displays.
LoggerConfig.keyPlacement = KeyPlacement.developerLogName;
LoggerConfig.developerLogName = 'IL';
LoggerConfig.showTimestamp = false;
LoggerConfig.timestampStyle = TimestampStyle.clock;
LoggerConfig.levelStyle = LevelStyle.short;
LoggerConfig.showEmoji = false;
LoggerConfig.useClickableLinks = true;
Logger.divider();
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Check console for source location examples!',
),
duration: Duration(seconds: 2),
),
);
}
}
void _showLogHistory() {
Logger.header('LOG HISTORY');
final history = LoggerConfig.logHistory;
Logger.info('Total logs in history: ${history.length}');
// Rendered through ConsoleFormatter.formatPlain: one ANSI-free,
// single-line entry per record, ready for a file sink.
for (final line in LoggerConfig.logHistoryStrings) {
Logger.verbose(line);
}
Logger.divider();
if (mounted) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Log History'),
content: Text(
history.isEmpty
? 'No logs in history yet.\n\n'
'Try triggering errors or warnings!'
: 'Found ${history.length} logs.\n\n'
'Each log includes source location.\n\n'
'Check console for details.',
),
actions: [
TextButton(
onPressed: () {
LoggerConfig.clearHistory();
Logger.success('History cleared');
Navigator.pop(context);
},
child: const Text('Clear History'),
),
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Close'),
),
],
),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('inline_logger Example').log('AppBar title'),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Counter Section
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
const Text(
'Counter Example',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
'Count: ${_counter.log('Current counter')}',
style: Theme.of(context).textTheme.headlineMedium,
),
const SizedBox(height: 8),
ElevatedButton.icon(
onPressed: _incrementCounter,
icon: const Icon(Icons.add),
label: const Text('Increment'),
),
],
),
),
),
const SizedBox(height: 16),
// API Call Section
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
const Text(
'API Call Example',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
if (_isLoading)
const CircularProgressIndicator()
else if (_items.isEmpty)
const Text('No items loaded yet')
else
Column(
children: _items
.map((item) => ListTile(
leading: const Icon(Icons.check_circle),
title: Text(item.log('List item')),
))
.toList(),
),
const SizedBox(height: 8),
ElevatedButton.icon(
onPressed: _isLoading ? null : _simulateApiCall,
icon: const Icon(Icons.cloud_download),
label: const Text('Fetch Items'),
),
],
),
),
),
const SizedBox(height: 16),
// Logging Examples
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text(
'Logging Examples',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
ElevatedButton.icon(
onPressed: _demonstrateLogLevels,
icon: const Icon(Icons.format_list_bulleted),
label: const Text('Show All Log Levels'),
),
const SizedBox(height: 8),
ElevatedButton.icon(
onPressed: _demonstrateInlineLogging,
icon: const Icon(Icons.link),
label: const Text('Inline Logging Demo'),
),
const SizedBox(height: 8),
ElevatedButton.icon(
onPressed: _demonstrateSourceLocations,
icon: const Icon(Icons.location_on),
label: const Text('Source Location Demo'),
),
const SizedBox(height: 8),
ElevatedButton.icon(
onPressed: _showLogHistory,
icon: const Icon(Icons.history),
label: const Text('View Log History'),
),
],
),
),
),
const SizedBox(height: 16),
// Info Card
Card(
color: Colors.blue.shade50,
child: const Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.info_outline, color: Colors.blue),
SizedBox(width: 8),
Expanded(
child: Text(
'Open your console to see logs with '
'clickable source locations!',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
),
],
),
SizedBox(height: 8),
Text(
'All logging is visible in your IDE console '
'or terminal. Click on the file paths in the '
'log output to jump directly to the source code. '
'Try clicking the buttons above to see different '
'logging features in action.',
style: TextStyle(fontSize: 12),
),
],
),
),
),
],
),
),
);
}
@override
void dispose() {
Logger.lifecycle('dispose', 'ExampleHomePage disposed');
super.dispose();
}
}