write_logger 0.0.2
write_logger: ^0.0.2 copied to clipboard
Colored console and encrypted daily file logging for Flutter apps.
example/lib/main.dart
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:open_filex/open_filex.dart';
import 'package:write_logger/write_logger.dart';
/// Demo encryption — reverse text so file contents are clearly transformed.
class DemoEncryptionAdapter implements EncryptionAdapter {
@override
Future<String> encrypt(String plainText) async =>
plainText.split('').reversed.join();
@override
Future<String> decrypt(String cipherText) async =>
cipherText.split('').reversed.join();
}
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await WriteLogger.init(
const WriteLoggerConfig(
minLevel: WriteLogLevel.trace,
retentionDays: 7,
enableConsole: true,
enableFile: true,
useColors: true,
useEmojis: true,
// Pass DemoEncryptionAdapter() to encrypt file lines.
),
);
// Emit one sample of every level at startup (check console / log file).
await _logAllTypes(tag: 'Startup');
await _logSampleCurl(tag: 'Startup');
runApp(const WriteLoggerExampleApp());
}
/// Sample cURL string as an app would pass into the logger (e.g. from Dio).
String sampleCurlCommand() {
return "curl -X POST "
"-H 'Content-Type: application/json' "
"-H 'Authorization: ***' "
"--data '{\"patientId\":\"P-1001\",\"slot\":\"2026-08-06T10:00:00Z\"}' "
"'https://api.example.com/v1/appointments'";
}
/// Logs a sample HTTP cURL using the normal debug level + `cURL` tag.
Future<void> _logSampleCurl({String? tag}) async {
final curl = sampleCurlCommand();
await WriteLogger.d(curl, tag: tag ?? 'cURL');
}
Future<void> _logAllTypes({String? tag}) async {
await WriteLogger.t('Trace: fine-grained diagnostic detail', tag: tag);
await WriteLogger.d('Debug: development-only detail', tag: tag);
await WriteLogger.i('Info: normal application event', tag: tag);
await WriteLogger.w('Warning: unexpected but recoverable', tag: tag);
await WriteLogger.e(
'Error: operation failed',
tag: tag,
error: Exception('Demo exception'),
stackTrace: StackTrace.current,
);
await WriteLogger.validate('Validate: input / rule check passed', tag: tag);
await WriteLogger.security('Security: auth or token event', tag: tag);
await WriteLogger.performance('Performance: 42ms API round-trip', tag: tag);
await WriteLogger.delete('Delete: resource removed', tag: tag);
// Generic API covering every level explicitly.
for (final level in WriteLogLevel.values) {
await WriteLogger.log(
'Generic log() for ${level.label}',
level: level,
tag: tag,
);
}
}
/// Opens today's log file with the OS default app.
///
/// Returns a short status message for the UI.
Future<String> openLogFile() async {
if (!WriteLogger.supportsFileLogging) {
return 'File logging is not available on this platform (console-only).';
}
final path = await WriteLogger.getLogFilePath();
if (path == null || !await File(path).exists()) {
return 'No log file found.';
}
final result = await OpenFilex.open(path, type: 'text/plain');
switch (result.type) {
case ResultType.done:
return 'Opened: $path';
case ResultType.noAppToOpen:
return 'No app available to open .txt files.';
case ResultType.permissionDenied:
return 'Permission denied while opening log file.';
case ResultType.fileNotFound:
return 'Log file not found at $path';
case ResultType.error:
return 'Failed to open log file: ${result.message}';
}
}
class WriteLoggerExampleApp extends StatelessWidget {
const WriteLoggerExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'write_logger example',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF0F766E)),
useMaterial3: true,
),
home: const ExampleHomePage(),
);
}
}
class ExampleHomePage extends StatefulWidget {
const ExampleHomePage({super.key});
@override
State<ExampleHomePage> createState() => _ExampleHomePageState();
}
class _ExampleHomePageState extends State<ExampleHomePage> {
String _status = 'Ready — tap a level or “Log all types”.';
String? _logFilePath;
bool _busy = false;
Future<void> _run(Future<void> Function() action, String status) async {
if (_busy) return;
setState(() {
_busy = true;
_status = status;
});
try {
await action();
final path = await WriteLogger.getLogFilePath();
setState(() {
_logFilePath = path;
_status = '$status — done. Check console and log file.';
});
} finally {
if (mounted) setState(() => _busy = false);
}
}
Future<void> _copyPath() async {
final path = _logFilePath;
if (path == null) return;
await Clipboard.setData(ClipboardData(text: path));
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Log file path copied')),
);
}
Future<void> _openLogFile() async {
if (_busy) return;
setState(() {
_busy = true;
_status = 'Opening log file…';
});
try {
final message = await openLogFile();
final path = await WriteLogger.getLogFilePath();
if (!mounted) return;
setState(() {
_logFilePath = path;
_status = message;
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
} finally {
if (mounted) setState(() => _busy = false);
}
}
Future<void> _viewLogFileInApp() async {
if (_busy) return;
setState(() => _busy = true);
try {
if (!WriteLogger.supportsFileLogging) {
if (!mounted) return;
setState(
() => _status =
'File logging is not available on this platform (console-only).',
);
return;
}
final path = await WriteLogger.getLogFilePath();
if (path == null || !await File(path).exists()) {
if (!mounted) return;
setState(() => _status = 'No log file found.');
return;
}
final contents = await File(path).readAsString();
if (!mounted) return;
setState(() => _logFilePath = path);
await Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => LogFileViewerPage(
filePath: path,
contents: contents,
),
),
);
} finally {
if (mounted) setState(() => _busy = false);
}
}
@override
Widget build(BuildContext context) {
final levels = <({String label, Color color, Future<void> Function() onTap})>[
(
label: 'TRACE',
color: Colors.blueGrey,
onTap: () => WriteLogger.t('Manual trace event', tag: 'UI'),
),
(
label: 'DEBUG',
color: Colors.blue,
onTap: () => WriteLogger.d('Manual debug event', tag: 'UI'),
),
(
label: 'INFO',
color: Colors.cyan.shade700,
onTap: () => WriteLogger.i('Manual info event', tag: 'UI'),
),
(
label: 'WARNING',
color: Colors.orange.shade800,
onTap: () => WriteLogger.w('Manual warning event', tag: 'UI'),
),
(
label: 'ERROR',
color: Colors.red.shade700,
onTap: () => WriteLogger.e(
'Manual error event',
tag: 'UI',
error: Exception('UI demo error'),
),
),
(
label: 'VALIDATE',
color: Colors.green.shade700,
onTap: () => WriteLogger.validate('Manual validate event', tag: 'UI'),
),
(
label: 'SECURITY',
color: Colors.purple.shade700,
onTap: () => WriteLogger.security('Manual security event', tag: 'UI'),
),
(
label: 'PERFORMANCE',
color: Colors.teal.shade700,
onTap: () =>
WriteLogger.performance('Manual performance event', tag: 'UI'),
),
(
label: 'DELETE',
color: Colors.brown.shade600,
onTap: () => WriteLogger.delete('Manual delete event', tag: 'UI'),
),
];
return Scaffold(
appBar: AppBar(
title: const Text('write_logger example'),
actions: [
IconButton(
tooltip: 'Open log file',
onPressed: _busy ? null : _openLogFile,
icon: const Icon(Icons.open_in_new),
),
IconButton(
tooltip: 'View log in app',
onPressed: _busy ? null : _viewLogFileInApp,
icon: const Icon(Icons.article_outlined),
),
IconButton(
tooltip: 'Refresh log path',
onPressed: _busy
? null
: () => _run(() async {
_logFilePath = await WriteLogger.getLogFilePath();
}, 'Refreshed log path'),
icon: const Icon(Icons.folder_open),
),
],
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
Text(
'All log types',
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 4),
Text(
'Each button writes one level. “Log all types” fires every helper '
'plus WriteLogger.log() for each WriteLogLevel.',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 16),
FilledButton.icon(
onPressed: _busy
? null
: () => _run(
() => _logAllTypes(tag: 'AllTypes'),
'Logged all types',
),
icon: const Icon(Icons.playlist_play),
label: const Text('Log all types'),
),
const SizedBox(height: 8),
FilledButton.tonalIcon(
onPressed: _busy
? null
: () => _run(
() => _logSampleCurl(tag: 'cURL'),
'Logged sample cURL',
),
icon: const Icon(Icons.terminal),
label: const Text('Log sample cURL'),
),
const SizedBox(height: 8),
Row(
children: [
Expanded(
child: OutlinedButton.icon(
onPressed: _busy ? null : _openLogFile,
icon: const Icon(Icons.open_in_new),
label: const Text('Open file'),
),
),
const SizedBox(width: 8),
Expanded(
child: OutlinedButton.icon(
onPressed: _busy ? null : _viewLogFileInApp,
icon: const Icon(Icons.article_outlined),
label: const Text('View in app'),
),
),
],
),
const SizedBox(height: 16),
Wrap(
spacing: 8,
runSpacing: 8,
children: [
for (final item in levels)
FilledButton.tonal(
style: FilledButton.styleFrom(
foregroundColor: item.color,
),
onPressed: _busy
? null
: () => _run(item.onTap, 'Logged ${item.label}'),
child: Text(item.label),
),
],
),
const SizedBox(height: 24),
Text('Status', style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 8),
SelectableText(_status),
if (_logFilePath != null) ...[
const SizedBox(height: 16),
Text('Log file', style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 8),
SelectableText(_logFilePath!),
TextButton.icon(
onPressed: _copyPath,
icon: const Icon(Icons.copy, size: 18),
label: const Text('Copy path'),
),
],
const SizedBox(height: 24),
Text(
'Tip: run with `flutter run` in a terminal to see ANSI colors.',
style: Theme.of(context).textTheme.bodySmall,
),
],
),
);
}
}
class LogFileViewerPage extends StatelessWidget {
const LogFileViewerPage({
super.key,
required this.filePath,
required this.contents,
});
final String filePath;
final String contents;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(File(filePath).uri.pathSegments.last),
actions: [
IconButton(
tooltip: 'Open externally',
onPressed: () async {
final message = await openLogFile();
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
},
icon: const Icon(Icons.open_in_new),
),
],
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 8),
child: SelectableText(
filePath,
style: Theme.of(context).textTheme.bodySmall,
),
),
const Divider(height: 1),
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: SelectableText(
contents.isEmpty ? '(empty file)' : contents,
style: const TextStyle(fontFamily: 'monospace', fontSize: 12),
),
),
),
],
),
);
}
}