astute_logger 2.6.0
astute_logger: ^2.6.0 copied to clipboard
A simple and powerful logger for Flutter apps with support for color-coding, JSON pretty-printing, and performance tracking.
๐ Astute Logger #
A lightweight, configurable logging package for Flutter with support for colored console output, persistent file logging, contextual logging, execution time measurement, structured and free-text sensitive data redaction, and a built-in Dio HTTP interceptor.
โจ Features #
- ๐ Multiple log levels
- ๐ข Debug
- ๐ต Info
- ๐ก Warning
- ๐ด Error
- ๐ฃ Critical
- ๐จ Colored console logs
- ๐ Persistent file logging with an async, serialized write queue
- โ๏ธ Configurable logger settings
- ๐ Automatic caller method detection (VM + web fallback)
- ๐ Context-aware logging using Zones
- ๐ Request ID propagation
- ๐ท๏ธ Additional contextual metadata & tags
- ๐ Pretty JSON logging
- ๐ Sensitive data redaction
- ๐ Authorization tokens & Bearer headers
- ๐ Passwords, secrets & API keys
- ๐ง Email addresses (opt-out via
redactEmails) - ๐ณ Credit card numbers
- ๐งฉ Key-based redaction for nested maps & lists via
redactObject
- โฑ๏ธ Execution time measurement (sync & async)
- ๐จ Error & stack trace logging
- ๐ Built-in Dio
LoggingInterceptorfor HTTP request/response/error traffic - ๐ฑ Cross-platform Flutter support (Android, iOS, Web, Desktop)
๐ฆ Installation #
Add the package to your pubspec.yaml.
dependencies:
astute_logger: ^0.1.0
Install it:
flutter pub get
๐ฅ Import #
import 'package:astute_logger/astute_logger.dart';
// Optional: HTTP interceptor
import 'package:astute_logger/service/logging_interceptor.dart';
๐๏ธ Create a Logger #
final logger = AstuteLogger(
'AppLogger',
config: const LogConfig(
enableConsoleOutput: true,
enableFileOutput: true,
enableColorLogging: true,
enableRedaction: true,
redactEmails: true,
minimumLogLevel: LogLevel.debug,
logFileName: 'app_logs.txt',
),
);
๐ Examples #
๐ข Debug #
await logger.debug("Debug message");
๐ต Info #
await logger.info("Application started");
๐ก Warning #
await logger.warning("API response is slow");
๐ด Error #
await logger.error(
"Unable to load profile",
error: Exception("HTTP 500"),
stackTrace: StackTrace.current,
);
๐ฃ Critical #
await logger.critical(
"Database connection lost",
error: Exception("SocketException"),
stackTrace: StackTrace.current,
);
โน๏ธ Async by default. All write methods (
debug,info,warning,error,critical,json,write) returnFuture<void>. The returned Future completes only after the log line has been persisted to disk (if file output is enabled). Await it whenever you need a durability guarantee.
๐ท๏ธ Logging with Metadata #
await logger.info(
"User logged in",
tag: 'AUTH',
extra: {
"userId": 123,
"role": "Admin",
},
);
๐ Context-Aware Logging #
await LoggerContext.runWithContext(
requestId: "REQ-1001",
extra: {
"screen": "Home",
"feature": "Login",
},
body: () async {
await logger.info("Inside contextual logging");
// Every log within this Zone automatically carries:
// [ReqID: REQ-1001] [Ctx: {screen: Home, feature: Login}]
},
);
โฑ๏ธ Measure Execution Time #
Synchronous #
final sorted = logger.logExecutionTime(
"Sorting List",
() {
final list = List.generate(100000, (i) => 100000 - i);
list.sort();
return list;
},
);
The timing log is emitted even if the callback throws โ the original exception is rethrown unchanged.
Asynchronous #
await logger.logExecutionTimeAsync(
"API Request",
() async {
await Future.delayed(const Duration(seconds: 2));
},
);
๐ Pretty JSON Logging #
Use write(prettyPrint: true) for raw JSON strings, or json() for objects.
Pretty-print a JSON string #
await logger.write(
message: jsonEncode({
"name": "John",
"age": 25,
"roles": ["Admin", "User"]
}),
prettyPrint: true,
level: LogLevel.info,
);
Log a structured object (auto pretty-printed + redacted) #
await logger.json({
"user": "john",
"password": "hunter2", // โ [REDACTED]
"profile": {
"email": "john@example.com", // โ [REDACTED] (unless redactEmails=false)
},
});
๐ Pretty-printed JSON is redacted using key-based rules, not regex. This means
{"password": "p@ss!"}is reliably redacted even when the value contains special characters or when the JSON is multi-line.
๐ Automatic Sensitive Data Redaction #
AstuteLogger offers two complementary redaction engines:
1. Key-based redaction (structured data) #
Applied automatically by json() and by write(prettyPrint: true) when the
message decodes to a Map or List. It also powers the public
redactObject() helper used by the Dio interceptor.
logger.redactObject({
'user': 'alice',
'credentials': {
'password': 'hunter2',
'token': 'abc',
},
});
// => {
// 'user': 'alice',
// 'credentials': {
// 'password': '[REDACTED]',
// 'token': '[REDACTED]',
// },
// }
Manage the sensitive key set at runtime:
AstuteLogger.registerSensitiveKey('ssn');
AstuteLogger.unregisterSensitiveKey('ssn');
AstuteLogger.getSensitiveKeys(); // sorted list
Default keys: password, token, accesstoken, refreshtoken,
authorization, apikey, secret, email.
2. Free-text redaction (regex) #
Applied to every message when enableRedaction: true.
| Rule | Example input | Output |
|---|---|---|
| Quoted key/value | "password": "p@ss!" |
"password": "[REDACTED]" |
| Bearer token | Authorization: Bearer abc-123 |
Authorization: Bearer [REDACTED] |
| Bare key/value | token:abc123 count:5 |
token:[REDACTED] count:5 |
| Email address | contact alice@example.com |
contact [REDACTED] |
| Credit card | card 4111111111111111 |
card [REDACTED] |
Disabling email redaction #
If you need to log emails for debugging:
const LogConfig(redactEmails: false)
Emails are preserved, but credit-card numbers and other rules still apply.
To disable redaction entirely, set enableRedaction: false.
๐ Read Log File #
final file = await AstuteLogger.getLogFile();
if (file != null && await file.exists()) {
final logs = await file.readAsString();
print(logs);
}
Custom file name:
final file = await AstuteLogger.getLogFile(fileName: 'api_logs.txt');
getLogFile()cachesFileinstances per path to avoid repeated platform channel calls and to keep a stable identity across lookups.
๐ท๏ธ Filter Logs by Tag #
final authLogs = await AstuteLogger.getLogsByTag('AUTH');
authLogs.forEach(print);
Custom file name:
final apiLogs = await AstuteLogger.getLogsByTag(
'HTTP',
fileName: 'api_logs.txt',
);
Matching is case-insensitive and ANSI color codes are stripped from the returned lines.
๐ Dio HTTP Logging #
Attach the built-in LoggingInterceptor to any Dio instance:
import 'package:dio/dio.dart';
import 'package:astute_logger/service/logging_interceptor.dart';
final dio = Dio()
..interceptors.add(LoggingInterceptor(
logger: logger,
logRequestHeaders: true,
logRequestBody: true,
logResponseHeaders: true,
logResponseBody: true,
maxBodyLength: 2000, // truncate long bodies
maxHeaderValueLength: 500, // truncate long header values
));
What gets logged #
- Request: method, URL, headers (sensitive ones redacted), body
- Response: status code + message, elapsed time, headers, body
- Error: status code, elapsed time, URL, request/response headers,
request/response bodies, error message,
DioExceptionType
Redaction & safety #
Authorization,Cookie,Set-Cookie,X-Api-Key,X-Access-Token,Access-Token,Bearer,Tokenheaders are replaced with[REDACTED].- JSON bodies are decoded and passed through
redactObject()โ so{"password": "hunter2"}becomes{"password": "[REDACTED]"}. FormDatabodies log only field names and file basenames โ never full paths or raw bytes.- Long values are truncated with a
... [truncated, total N chars]suffix.
โ๏ธ Configuration #
const LogConfig(
enableConsoleOutput: true,
enableFileOutput: true,
enableColorLogging: true,
enableRedaction: true,
redactEmails: true,
minimumLogLevel: LogLevel.debug,
logFileName: 'app_logs.txt',
)
| Field | Default | Description |
|---|---|---|
enableRedaction |
true |
Master switch for all redaction |
redactEmails |
true |
Redact email addresses in free text |
minimumLogLevel |
LogLevel.debug |
Drop messages below this level |
enableConsoleOutput |
true |
Write to dart:developer log() |
enableFileOutput |
true |
Persist to the app documents directory |
logFileName |
'app_logs.txt' |
Target log file name |
enableColorLogging |
true |
ANSI colorize console output |
๐งญ App Modes #
Each log line includes the current Flutter build mode:
logger.appMode; // AppMode.debug | profile | release | unknown
๐งช Testing Hooks #
The following static helpers are exposed for tests and diagnostics:
// Wait for the async write queue to fully drain.
await AstuteLogger.flush();
// Clear the internal File cache (useful between tests).
AstuteLogger.resetFileCache();
These are marked
@visibleForTestingโ prefer not to call them from production code.
๐งต Threading & Durability #
- All log events flow through a single static queue with a bounded capacity (1024 pending events). Producers block when the queue is full.
- Writes are serialized: events are persisted strictly in enqueue order, even across multiple logger instances writing to the same file.
- File handles are opened and closed per write (
openWrite+close()), so log files are never held open by the process โ safe on Windows and in tests.
โค๏ธ License #
MIT License.