isolate_logger 1.0.14
isolate_logger: ^1.0.14 copied to clipboard
A logger utility that uses Isolates under the hood, that logs messages to the console and files, helping users track user events, exceptions, and other relevant activities.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:isolate_logger/isolate_logger.dart';
import 'package:share_plus/share_plus.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await IsolateLogger.instance.initLogger(
timeFormat: LogTimeFormat.userFriendly,
wantConsoleLog: true,
logFileNamePrefix: "EXAMPLE_APP",
logFileExtension: LogFileExtension.text,
);
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
void dispose() {
IsolateLogger.instance.dispose();
super.dispose();
}
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Isolate Logger',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: const MyHomePage(title: 'Isolate Logger'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
final _customFilePathController = TextEditingController(
text: "service_info/login_api_123.log",
);
final _customMessageController = TextEditingController();
@override
void initState() {
super.initState();
IsolateLogger.instance.logInfo(
tag: "MyHomePage",
subTag: "initState()",
message: "App Started",
);
}
@override
void dispose() {
_customFilePathController.dispose();
_customMessageController.dispose();
super.dispose();
}
void _incrementCounter() {
IsolateLogger.instance.logInfo(
tag: "EXAMPLE",
subTag: "_incrementCounter",
message: "Previous count : $_counter, New count : ${_counter + 1}",
);
setState(() {
_counter++;
});
}
void _showSnackBar(String message) {
if (!mounted) return;
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(
SnackBar(
content: Text(message),
behavior: SnackBarBehavior.floating,
),
);
}
void _logIntoCustomFile() {
final filePath = _customFilePathController.text;
final message = _customMessageController.text.trim();
if (message.isEmpty) {
_showSnackBar("Please enter a message to log.");
return;
}
try {
IsolateLogger.instance.logIntoCustomFile(
filePath: filePath,
message: message,
);
_customMessageController.clear();
_showSnackBar("Logged into AppLogs/$filePath");
} on ArgumentError catch (e) {
_showSnackBar("Invalid file path : ${e.message}");
}
}
Future<void> _exportLogs({String? zipPrefix}) async {
final result = await IsolateLogger.instance.exportLogs(
exportType: LogsExportType.all,
logZipFilePrefix: zipPrefix,
);
if (result?.isNotEmpty ?? false) {
await SharePlus.instance.share(
ShareParams(files: [XFile(result!)]),
);
} else {
_showSnackBar("No logs available to export.");
}
}
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Scaffold(
appBar: AppBar(
backgroundColor: colorScheme.inversePrimary,
title: Text(widget.title),
centerTitle: true,
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
_SectionCard(
title: "Event logging",
icon: Icons.touch_app_outlined,
children: [
ListTile(
contentPadding: EdgeInsets.zero,
title: const Text("Button taps logged so far"),
subtitle: const Text("Each tap emits a logInfo entry"),
trailing: Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
color: colorScheme.primary,
fontWeight: FontWeight.bold,
),
),
),
Wrap(
spacing: 4,
runSpacing: 4,
children: [
FilledButton.tonalIcon(
onPressed: _incrementCounter,
icon: const Icon(Icons.add),
label: const Text("Increment"),
),
FilledButton.tonalIcon(
onPressed: () => throw (Exception("Fake-Error")),
icon: const Icon(Icons.bug_report_outlined),
label: const Text("Throw fake-exception"),
),
],
),
],
),
const SizedBox(height: 12),
_SectionCard(
title: "Custom file logging",
icon: Icons.drive_file_move_outline,
children: [
Text(
"Logs the message as-is into AppLogs/<file path>, "
"creating directories if needed.",
style: Theme.of(context).textTheme.bodySmall,
),
const SizedBox(height: 16),
TextField(
controller: _customFilePathController,
decoration: const InputDecoration(
labelText: "Relative file path",
hintText: "service_info/login_api_123.log",
prefixIcon: Icon(Icons.folder_outlined),
border: OutlineInputBorder(),
),
),
const SizedBox(height: 12),
TextField(
controller: _customMessageController,
maxLines: 3,
keyboardType: TextInputType.multiline,
decoration: const InputDecoration(
labelText: "Message",
hintText: "Anything you want stored in that file",
prefixIcon: Icon(Icons.notes_outlined),
border: OutlineInputBorder(),
),
onSubmitted: (_) => _logIntoCustomFile(),
),
const SizedBox(height: 12),
FilledButton.icon(
onPressed: _logIntoCustomFile,
icon: const Icon(Icons.save_alt),
label: const Text("Log into custom file"),
),
],
),
const SizedBox(height: 12),
_SectionCard(
title: "Manage logs",
icon: Icons.settings_outlined,
children: [
Wrap(
spacing: 8,
runSpacing: 8,
children: [
OutlinedButton.icon(
onPressed: () => _exportLogs(),
icon: const Icon(Icons.ios_share),
label: const Text("Export logs"),
),
OutlinedButton.icon(
onPressed: () => _exportLogs(
zipPrefix: "myPersonalDeviceID_QA",
),
icon: const Icon(Icons.drive_folder_upload_outlined),
label: const Text("Export with prefix"),
),
OutlinedButton.icon(
onPressed: () async {
await IsolateLogger.instance.clearAllLogs();
_showSnackBar("All logs cleared.");
},
icon: const Icon(Icons.delete_outline),
label: const Text("Clear logs"),
),
OutlinedButton.icon(
onPressed: () => IsolateLogger.instance.dispose(),
icon: const Icon(Icons.power_settings_new),
label: const Text("Dispose isolate"),
),
],
),
],
),
],
),
);
}
}
/// A simple titled card used to group related actions in the example app.
class _SectionCard extends StatelessWidget {
const _SectionCard({
required this.title,
required this.icon,
required this.children,
});
final String title;
final IconData icon;
final List<Widget> children;
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Card(
elevation: 0,
color: colorScheme.surfaceContainerLow,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: BorderSide(color: colorScheme.outlineVariant),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(icon, color: colorScheme.primary),
const SizedBox(width: 8),
Text(
title,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
),
),
],
),
const SizedBox(height: 12),
...children,
],
),
),
);
}
}