write_logger 0.0.5 copy "write_logger: ^0.0.5" to clipboard
write_logger: ^0.0.5 copied to clipboard

Colored console and encrypted daily file logging for Flutter apps.

example/lib/main.dart

import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:open_filex/open_filex.dart';
import 'package:write_logger/write_logger.dart';

// Sample adapter: app_encryption_adapter.dart
// import 'app_encryption_adapter.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await WriteLogger.init(
    const WriteLoggerConfig(
      enableConsole: kDebugMode,
      retentionDays: 7,
      enableFile: true,
      // encryption: AppEncryptionAdapter(),
    ),
  );

  // 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),
              ),
            ),
          ),
        ],
      ),
    );
  }
}
0
likes
160
points
18
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Colored console and encrypted daily file logging for Flutter apps.

Repository (GitHub)
View/report issues

Topics

#logging #flutter #console #file

License

MIT (license)

Dependencies

flutter, path, path_provider

More

Packages that depend on write_logger