inline_logger
A powerful inline logger for Flutter that lets you log anywhere in your widget tree without breakpoints.
Maintained by Akshay Chand T (akshaychandt)
โจ Why inline_logger?
Traditional logging requires you to break your code flow, add print statements, and often rebuild your UI. With inline_logger, you can log any value inline - directly in your widget tree, method chains, or anywhere else - without disrupting your code!
// โ Traditional way
final userName = user.name;
print('User name: $userName');
return Text(userName);
// โ
With inline_logger
return Text(user.name.log('User name'));
๐ Features
- ๐ Chainable inline logging - Log any value without breaking code flow
- ๐ฏ Multiple log levels - Debug, Verbose, Info, Success, Warning, Error, Critical
- ๐ Clickable source locations - IDE-clickable links to the exact call site
- ๐งน Low-noise by default - ~24 columns of chrome, not ~74, so your message stops wrapping
- ๐ท๏ธ Tag in the IDE's own gutter - your subsystem name replaces the log prefix instead of following it
- ๐จ Color-coded output - Different colors for each log level in console
- ๐จ Emoji indicators - Visual log levels (opt-in)
- โบ Repeat collapsing - fold a retry loop's identical lines into
โบ xN(opt-in, lossless) - โก Zero performance impact - Automatically disabled in release mode
- ๐ Log history - Store important logs for crash reporting
- ๐ Stack trace support - Capture stack traces for errors
- ๐ณ Widget tree logging - Log anywhere in your build methods
- ๐ Extensibility hooks - Custom formatters and record sinks
- โ๏ธ Highly configurable - Customize timestamps, emojis, colors, log levels
๐ฆ Installation
Add this to your pubspec.yaml:
dependencies:
inline_logger: ^0.3.0
Then run:
flutter pub get
๐ฏ Quick Start
Import the package
import 'package:inline_logger/inline_logger.dart';
Basic Usage
1. Inline Widget Tree Logging
The most powerful feature - log directly in your widget tree:
@override
Widget build(BuildContext context) {
return Column(
children: [
// Log user data inline without breaking the widget tree
Text(userData.log('User data').name),
// Log calculations inline
Text('Total: ${(price * quantity).logInfo('Calculated total')}'),
// Chain multiple logs
Text(user.log('Full user').email.logDebug('Email address')),
],
);
}
2. Method Chaining
// Chain logs on any expression
final result = apiCall()
.log('API Response')
.data
.logSuccess('Data extracted')
.firstWhere((item) => item.id == 5)
.logDebug('Found item');
3. Direct Logging Methods
Logger.debug('Debugging info');
Logger.info('General information');
Logger.success('Operation completed successfully!');
Logger.warning('Warning message');
Logger.error('Error occurred', 'Context', stackTrace);
Logger.critical('Critical failure!');
๐ Log Levels
inline_logger supports 7 log levels with color-coded output:
| Level | Color | Emoji | Method | Use Case |
|---|---|---|---|---|
| Debug | Gray | ๐ | .logDebug() |
Debugging information |
| Verbose | Cyan | ๐ | .logVerbose() |
Detailed logs |
| Info | Blue | โน๏ธ | .logInfo() |
General information |
| Success | Green | โ | .logSuccess() |
Successful operations |
| Warning | Yellow | โ ๏ธ | .logWarning() |
Warnings |
| Error | Red | โ | .logError() |
Errors |
| Critical | Bright Red | ๐จ | .logCritical() |
Critical failures |
๐จ Advanced Features
Configuration
// Set minimum log level (only warnings and above)
LoggerConfig.minLevel = LogLevel.warning;
// Timestamps โ off by default, because DevTools and both IDE consoles
// render their own time column. Turn them on for a plain terminal.
LoggerConfig.showTimestamp = true;
LoggerConfig.timestampStyle = TimestampStyle.clock; // 14:23:28.751
LoggerConfig.timestampStyle = TimestampStyle.iso; // the 0.2.x form
// Level token โ a fixed-width 3-char abbreviation by default, so the
// message always starts at the same column.
LoggerConfig.levelStyle = LevelStyle.short; // ERR
LoggerConfig.levelStyle = LevelStyle.full; // [ERROR]
LoggerConfig.levelStyle = LevelStyle.emoji; // โ (glyph replaces the text)
LoggerConfig.levelStyle = LevelStyle.none;
// Emojis โ off by default. Severity is already carried by the colour
// and the level token, and emoji cell widths differ between levels,
// which is what stops columns from lining up. This flag *appends* the
// glyph after the text token; to have the glyph *replace* the token,
// use LevelStyle.emoji above instead.
LoggerConfig.showEmoji = true;
// Enable/disable color-coded output.
LoggerConfig.useColors = true; // Default is true
// How much of the line the level's colour covers.
LoggerConfig.colorScope = ColorScope.body; // default: whole line
// except the location
LoggerConfig.colorScope = ColorScope.line; // include the location
LoggerConfig.colorScope = ColorScope.level; // only the ERR/WRN token
// The trailing location is dimmed so it recedes. Without this it renders
// in the console's default foreground โ a bright amber in the VS Code
// Debug Console, which makes the least important part of the line the
// loudest. Set to '' to leave it un-styled.
LoggerConfig.locationStyle = AnsiColors.gray; // default
// Disable logging completely
LoggerConfig.enabled = false;
// Reset every field to its shipped default (also clears history).
LoggerConfig.reset();
Where the tag comes from
dart:developer renders a [name] gutter on every log line, and the prefix cannot be removed โ an empty name is substituted with the literal log by the SDK debug adapter, Dart-Code, flutter-intellij and DevTools alike. So inline_logger puts it to work: by default your log's key becomes the gutter, replacing the constant prefix rather than following it.
Logger.error('syncPending failed', 'VideoProgressRepo');
// [VideoProgressRepo] ERR syncPending failed (package:my_app/repo.dart:176:7)
// ^^^^^^^^^^^^^^^^^ drawn by the console host โ costs zero columns of
// the line's own width, and gives DevTools per-
// subsystem filtering via `k:VideoProgressRepo`
Records with no key fall back to LoggerConfig.developerLogName ('IL'). To get the 0.2.x @key form back:
LoggerConfig.keyPlacement = KeyPlacement.inline;
LoggerConfig.developerLogName = 'IL';
// [IL] ERR @VideoProgressRepo syncPending failed (package:โฆ:176:7)
Collapsing repeated logs
A retry loop or polling timer that logs the same failure every few minutes fills the console with identical lines. Opt in to collapse them:
LoggerConfig.collapseRepeats = true;
LoggerConfig.repeatWindow = const Duration(minutes: 10);
// [VideoProgressRepo] ERR syncPending failed (package:โฆ/repo.dart:176:7)
// [VideoProgressRepo] ERR โบ x3 syncPending failed
Suppression is console-only and lossless:
onRecordandlogHistoryrun above the collapser and always see 100% of records, so Crashlytics/Sentry forwarding is unaffected.- A pending count is never discarded โ it is surfaced as a
โบ xNsummary on the next log after the window elapses, when a different message interrupts the run, when the entry is evicted pastrepeatMemory, whencollapseRepeatsis turned back off, or on an explicitLogger.flushRepeats(). There is no timer, so a run that is still open when your app goes quiet needsLogger.flushRepeats()to report โ call it from a lifecycle hook. - The window is measured from a run's first occurrence, so a message repeating every 5 minutes reports in every 10 rather than staying hidden forever.
- It is skipped entirely whenever
LoggerConfig.formatteris set.
Console recipes
// โโ Android Studio / IntelliJ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// The IntelliJ Run console does not decode ANSI for dart:developer.log,
// so colours arrive as literal escape codes. Trade them for emoji.
LoggerConfig.useColors = false;
LoggerConfig.showEmoji = true;
// โโ Plain `flutter run` terminal โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// No timestamp column of its own, so add one.
LoggerConfig.showTimestamp = true;
// โโ Bullet-proof click-to-source โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
LoggerConfig.locationPlacement = LocationPlacement.ownLine;
// โโ Noisy retry loops โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
LoggerConfig.collapseRepeats = true;
// โโ Quiet: keep logs, drop the paths โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
LoggerConfig.useClickableLinks = false;
// โโ Restore the 0.2.x look โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
LoggerConfig.developerLogName = 'InlineLogger';
LoggerConfig.keyPlacement = KeyPlacement.inline;
LoggerConfig.showTimestamp = true;
LoggerConfig.timestampStyle = TimestampStyle.iso;
LoggerConfig.levelStyle = LevelStyle.full;
LoggerConfig.showEmoji = true;
LoggerConfig.showColumnNumber = false;
// Note: the emoji now renders *after* the level token rather than
// before it, so that it cannot shift the fixed-width gutter.
๐ Clickable Source Locations
Every log line automatically includes the exact call site (file path + line + column) appended at end-of-line in parentheses. Click it in your IDE's Run/Debug console to jump straight to that line.
[MyRepo] INF Counter updated โ 5 (package:my_app/main.dart:42:23)
The leading [MyRepo] gutter is drawn by the console host, not by this package โ see Where the tag comes from.
No extra arguments needed โ source location is captured automatically via StackTrace.current, and the package intelligently skips its own internal frames to find your code.
What the IDE link scanners actually require
The trailing (...) segment is always the last text on its physical line, and its characters are contiguous โ no ANSI escape ever appears between the parentheses. Styling that wraps the segment from the outside is safe, which is why the location can be dimmed by default. The rules being satisfied are:
- VS Code (Dart-Code) requires both
:line:columnโ a baremain.dart:42is not linkified. It matches the first.dartoccurrence on a line, so a.dartsubstring earlier in your message wins instead. SetLoggerConfig.locationPlacement = LocationPlacement.ownLineto make the match unambiguous. Style escapes do not interfere: they fall outside the pattern's character class, and the console parses ANSI into styled spans before link detection runs. - IntelliJ / Android Studio treats the column as optional, but requires a non-alphanumeric character immediately before the
package:/file:scheme โ which is what the opening parenthesis provides. A style escape sits before that parenthesis, not between it and the scheme.
Both resolve package: URIs through your package config, so eliding directories inside one breaks resolution and kills the link. The location is therefore never abbreviated.
Where the location goes
// Default โ appended to the message line.
LoggerConfig.locationPlacement = LocationPlacement.inline;
// INF Counter updated โ 5 (package:my_app/main.dart:42:23)
// On its own row. Costs a line, but makes the link bullet-proof: no
// `.dart` in your message can steal it, and the row can never exceed
// Dart-Code's 1000-character parse limit (which `Logger.json` does hit).
LoggerConfig.locationPlacement = LocationPlacement.ownLine;
// INF Counter updated โ 5
// โณ (package:my_app/main.dart:42:23)
// Omit it entirely (same as useClickableLinks = false).
LoggerConfig.locationPlacement = LocationPlacement.none;
Link Formats
For frames under lib/ โ which is nearly all of them โ auto, packageUri, fileUri and projectRelative all emit the same package: string. Only bareAbsolute differs, and only for non-package: URIs such as files under test/.
// Recommended default โ package: URIs are clickable in both VS Code and
// Android Studio. Falls back to fileUri for files outside lib/.
LoggerConfig.clickableLinkFormat = LinkFormat.auto;
// Explicit package URI (same fallback behaviour as `auto`).
LoggerConfig.clickableLinkFormat = LinkFormat.packageUri;
// Output: (package:my_app/main.dart:42:23)
// Absolute file URI โ clickable in both VS Code and Android Studio.
LoggerConfig.clickableLinkFormat = LinkFormat.fileUri;
// Output: (file:///Users/akshay/app/lib/main.dart:42:23)
// Bare absolute path โ kept for older IntelliJ plugin versions.
// Not clickable in VS Code, which requires a URI scheme.
LoggerConfig.clickableLinkFormat = LinkFormat.bareAbsolute;
// Output: (/Users/akshay/app/lib/main.dart:42:23)
// Deprecated: project-relative paths are NOT clickable in any IDE.
// Silently falls back to packageUri/fileUri.
// LoggerConfig.clickableLinkFormat = LinkFormat.projectRelative;
Source Location Configuration
// Master switch (default: true in debug, false in release)
LoggerConfig.showSourceLocation = true;
// These two have no *individual* effect โ a clickable link needs both a
// path and a line, so only the both-false case suppresses the segment.
// To hide it, use useClickableLinks or LocationPlacement.none.
LoggerConfig.showFilePath = true;
LoggerConfig.showLineNumber = true;
// The rendered string ALWAYS includes `:column` (VS Code requires it).
// This flag controls only whether that column is the real one from the
// stack frame (`true`, the default since 0.3.0) or a forced `:1`.
LoggerConfig.showColumnNumber = true;
LoggerConfig.showMemberName = false; // Off by default
// Omit the location segment from the rendered string entirely.
// The location is still attached to `LogRecord.source` for
// `onRecord` / `logHistory` consumers.
LoggerConfig.useClickableLinks = false;
Zero-Cost in Production
Source location capture is completely free in release builds. StackTrace.current is never called unless both LoggerConfig.enabled and LoggerConfig.showSourceLocation are true, and the message passes the minLevel filter. Filter first, capture second.
Color-Coded Console Output
inline_logger automatically adds ANSI color codes to your console output, making it easy to distinguish between different log levels at a glance:
- Debug logs appear in gray
- Verbose logs appear in cyan
- Info logs appear in blue
- Success logs appear in green
- Warning logs appear in yellow
- Error logs appear in red
- Critical logs appear in bright red
By default the level's colour covers the line body, and the location carries its own dim span so it recedes:
\x1B[31mERR boom\x1B[0m \x1B[90m(package:app/main.dart:9:4)\x1B[0m
Both spans sit strictly outside the parentheses, so the segment's text reaches the IDE link scanners as one unbroken run.
ColorScope.line extends the level's span over the location too; ColorScope.level narrows it to just the ERR token. Note that an ANSI reset returns the foreground to the terminal default, which is not necessarily the colour a console uses for un-styled text โ that is why an un-styled location shows up amber in the VS Code Debug Console, and why ColorScope.level renders the text before and after the token in two different colours.
Colors work in most modern IDEs and terminals that support ANSI escape codes. You can disable colors if needed:
LoggerConfig.useColors = false;
๐ Extensibility Hooks
Custom Record Sink
Forward every log record to external services:
LoggerConfig.onRecord = (record) {
// Forward to Crashlytics
FirebaseCrashlytics.instance.log(record.message);
// Forward to Sentry
Sentry.addBreadcrumb(Breadcrumb(message: record.message));
// Access full metadata
print('Level: ${record.level}');
print('Source: ${record.source}');
print('Time: ${record.time}');
};
Custom Formatter
Replace the built-in console formatter:
LoggerConfig.formatter = (record) {
// Use renderLocation so the segment stays IDE-clickable โ it applies
// the mandatory `:line:column` and the URI rules for you. Building
// the string by hand is how you end up with a dead link.
final source = record.source != null
? ' ${ConsoleFormatter.renderLocation(record.source!)}'
: '';
return '${record.level.label}: ${record.message}$source';
};
Note that setting a custom formatter also disables repeat collapsing, so your formatter always receives every record.
API Logging
// Log API requests
Logger.apiRequest(
endpoint: '/api/users',
method: 'POST',
headers: {'Authorization': 'Bearer token'},
body: {'name': 'John'},
);
// Log API responses with duration
Logger.apiResponse(
endpoint: '/api/users',
statusCode: 200,
data: responseData,
duration: Duration(milliseconds: 234),
);
Navigation Logging
Logger.navigation('HomeView', 'ProfileView');
State Logging
Logger.state('isLoading', true);
Logger.state('userData', userObject);
Lifecycle Logging
Logger.lifecycle('initState', 'ViewModel initialized');
Logger.lifecycle('dispose', 'Cleaning up resources');
Log History
Log history now stores full LogRecord objects with source locations:
// Access stored logs (warnings, errors, critical)
final history = LoggerConfig.logHistory;
// Each record includes source location
for (final record in history) {
print('${record.level}: ${record.message}');
print('Source: ${record.source}');
}
// Clear history
LoggerConfig.clearHistory();
// Configure history size
LoggerConfig.maxHistorySize = 50;
Structured Logging
Logger.header('USER AUTHENTICATION');
Logger.info('Starting process...');
Logger.success('Completed!');
Logger.divider();
๐ก Real-World Examples
ViewModel with inline logging
class HomeViewModel extends ChangeNotifier {
List<User> _users = [];
Future<void> loadUsers() async {
Logger.header('LOAD USERS');
_isLoading = true.logDebug('isLoading');
notifyListeners();
try {
final stopwatch = Stopwatch()..start();
Logger.apiRequest(endpoint: '/api/users', method: 'GET');
final response = await _api.getUsers();
stopwatch.stop();
Logger.apiResponse(
endpoint: '/api/users',
statusCode: 200,
data: response,
duration: stopwatch.elapsed,
);
_users = response.logSuccess('Users loaded');
} catch (e, stackTrace) {
Logger.error('Failed to load users: $e', 'Error', stackTrace);
} finally {
_isLoading = false.logDebug('isLoading');
notifyListeners();
Logger.divider();
}
}
}
Widget with inline logging
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length.log('Item count'),
itemBuilder: (context, index) {
final item = items[index].logDebug('Current item');
return ListTile(
title: Text(item.name.log('Item name')),
subtitle: Text(item.price.toString().logInfo('Price')),
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => DetailView(
id: item.id.log('Selected item ID'),
),
),
).then((_) => Logger.navigation('DetailView', 'HomeView')),
);
},
);
}
๐ง Configuration Guide
Production Setup
void main() {
// Disable in production
if (kReleaseMode) {
LoggerConfig.enabled = false;
}
// Or set minimum level to only show errors
LoggerConfig.minLevel = LogLevel.error;
runApp(MyApp());
}
Development Setup
void main() {
// Show everything in debug
LoggerConfig.minLevel = LogLevel.debug;
LoggerConfig.useColors = true;
LoggerConfig.maxHistorySize = 100;
// Add a timestamp if your console has no time column of its own.
LoggerConfig.showTimestamp = true;
// Source locations are enabled by default in debug mode
// Customize format for your IDE:
LoggerConfig.clickableLinkFormat = LinkFormat.auto;
runApp(MyApp());
}
๐ API Reference
Extension Methods (Chainable)
All these methods can be chained on any object:
.log([String key, LogLevel level])- Log with custom level.logDebug([String key])- Log as debug.logVerbose([String key])- Log as verbose.logInfo([String key])- Log as info.logSuccess([String key])- Log as success.logWarning([String key])- Log as warning.logError([String key])- Log as error.logCritical([String key])- Log as critical
Static Methods
Logger.debug(value, [name])- Log debugLogger.verbose(value, [name])- Log verboseLogger.info(value, [name])- Log infoLogger.success(value, [name])- Log successLogger.warning(value, [name])- Log warningLogger.error(value, [name, stackTrace])- Log errorLogger.critical(value, [name, stackTrace])- Log criticalLogger.apiRequest({...})- Log API requestLogger.apiResponse({...})- Log API responseLogger.navigation(from, to)- Log navigationLogger.state(name, value)- Log state changeLogger.lifecycle(event, [details])- Log lifecycle eventLogger.divider([title])- Log dividerLogger.header(title)- Log headerLogger.flushRepeats()- Emit any pendingโบ xNrepeat summaries immediately
New Types
SourceLocation- Represents a source code location. Stores the originalUri(sopackage:URIs are preserved verbatim) plus line, optional column, and optional enclosing member. The legacyfilePathgetter and string constructor remain for back-compat.SourceLocationResolver- Resolves call sites from stack tracesLinkFormat- Enum for IDE-clickable link formatsLogRecord- Structured log event recordConsoleFormatter- Formats LogRecords for console output.renderLocationbuilds a clickable segment;formatPlainrenders an ANSI-free single line.TimestampStyle/LevelStyle/LocationPlacement/KeyPlacement- Console layout styles (0.3.0)
Configuration
LoggerConfig.enabled- Master switch (default:kDebugMode)LoggerConfig.minLevel- Minimum log levelLoggerConfig.showTimestamp- Show timestamps (default:false)LoggerConfig.timestampStyle-clock(default) orisoLoggerConfig.levelStyle-short(default),full,emoji, ornoneLoggerConfig.showEmoji- Append an emoji after the text token (default:false); ignored underLevelStyle.emojiLoggerConfig.useColors- ANSI color outputLoggerConfig.colorScope-body(default),line, orlevelLoggerConfig.locationStyle- ANSI style for the location segment (default:AnsiColors.gray;''to disable)LoggerConfig.locationPrefix- prefix for theownLinerow (default:'โณ ')LoggerConfig.dividerWidth- width ofLogger.dividerrules (default:60)LoggerConfig.keyPlacement-developerLogName(default) orinlineLoggerConfig.developerLogName- The[name]gutter, and the fallback for keyless records (default:'IL')LoggerConfig.dividerWidth- Width ofLogger.dividerrules (default:60)LoggerConfig.consoleStackTraceFrames- Frames forwarded to the console (default:8;nullfor all)LoggerConfig.showSourceLocation- Source location capture (default:kDebugMode)LoggerConfig.locationPlacement-inline(default),ownLine, ornoneLoggerConfig.locationPrefix- Prefix for theownLinerow (default:'โณ ')LoggerConfig.showFilePath/showLineNumber- No individual effect; only the both-false case suppresses the locationLoggerConfig.showColumnNumber- Real captured column (default:true) vs a forced:1LoggerConfig.showMemberName- Show enclosing member nameLoggerConfig.useClickableLinks- Whenfalse, omits the location segment entirelyLoggerConfig.clickableLinkFormat- Link format (default:LinkFormat.auto)- Deprecated, superseded byLoggerConfig.linkAnsiStylelocationStyle. It styled the segment's interior, which does break the IDE scanners;locationStylewraps it from outside, which does not.LoggerConfig.collapseRepeats- Collapse identical console lines (default:false)LoggerConfig.repeatWindow/repeatMemory/repeatSummaryExcerpt- Collapser tuningLoggerConfig.onRecord- Custom record sinkLoggerConfig.formatter- Custom formatter overrideLoggerConfig.maxHistorySize- Max history entriesLoggerConfig.reset()- Restore every field to its shipped default
๐ค Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
๐ License
This project is licensed under the MIT License - see the LICENSE file for details.
๐ Credits
Created with โค๏ธ for the Flutter community.
Special thanks to aswinbbc for contributing ideas to this project.
๐ Support
- ๐ Report bugs
- ๐ก Request features
- โญ Star on GitHub
Libraries
- inline_logger
- A powerful inline logger for Flutter that lets you log anywhere in your widget tree without breakpoints. Chain logging calls directly on any expression.