Analyzing Logs with Flutter Logs Package

Effective logging is an indispensable part of software development, offering critical insights into application behavior, helping debug issues, and monitoring performance. For Flutter developers, the flutter_logs package is a powerful tool that simplifies the process of generating, managing, and analyzing logs. This package provides a comprehensive set of features, including custom log levels, file writing, filtering, and even sending logs to remote servers.

What is the Flutter Logs Package?

The flutter_logs package is a versatile logging library for Flutter applications. It allows developers to log messages with various severity levels, write logs to files, customize log output, and send logs to remote servers. This makes it easier to track application behavior, debug issues, and monitor performance in both development and production environments.

Why Use the Flutter Logs Package?

  • Easy Integration: Simple to incorporate into existing Flutter projects.
  • Customizable: Supports different log levels and output formats.
  • File Writing: Can write logs to local files for later analysis.
  • Remote Logging: Enables sending logs to remote servers for centralized monitoring.
  • Filtering: Provides mechanisms to filter logs based on severity levels and tags.

How to Use the Flutter Logs Package

To effectively analyze logs using the flutter_logs package, follow these steps:

Step 1: Add the Dependency

First, add flutter_logs to your pubspec.yaml file:

dependencies:
  flutter_logs: ^4.0.0 # Use the latest version

Then, run flutter pub get to install the package.

Step 2: Configure Flutter Logs

Configure the logging settings when your app starts. You can customize various options such as log levels, timestamps, file writing, and remote logging. Example:

import 'package:flutter/material.dart';
import 'package:flutter_logs/flutter_logs.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await FlutterLogs.init(
    logLevelsEnabled: [
      LogLevel.INFO,
      LogLevel.WARNING,
      LogLevel.ERROR,
      LogLevel.SEVERE
    ],
    timeStampType: TimeStampType.TIME_FORMAT_FULL,
    directoryStructure: DirectoryStructure.SINGLE_FILE,
    logTypesEnabled: ["network", "ui", "database"],
    isDebuggable: true,
    filename: "app_log.txt",
    isSequential: true,
    csvDelimiter: ',',
  );

  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Logs Example',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Logs Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            ElevatedButton(
              onPressed: () {
                FlutterLogs.logInfo("ui", "Button Pressed", "User pressed the button");
              },
              child: Text('Log Info'),
            ),
            ElevatedButton(
              onPressed: () {
                FlutterLogs.logWarning("network", "API Timeout", "API request took too long");
              },
              child: Text('Log Warning'),
            ),
            ElevatedButton(
              onPressed: () {
                FlutterLogs.logError("database", "Query Failed", "Failed to execute database query");
              },
              child: Text('Log Error'),
            ),
            ElevatedButton(
              onPressed: () {
                FlutterLogs.logSevere("app", "Fatal Error", "Application encountered a fatal error");
              },
              child: Text('Log Severe'),
            ),
          ],
        ),
      ),
    );
  }
}

Explanation of the configurations:

  • logLevelsEnabled: Specifies which log levels will be recorded.
  • timeStampType: Defines the format of the timestamp.
  • directoryStructure: Configures how log files are structured (single file or hierarchical).
  • logTypesEnabled: Allows you to categorize your logs by type (e.g., network, UI, database).
  • isDebuggable: Indicates whether debugging mode is enabled.
  • filename: The name of the log file.
  • isSequential: Option to have sequentially numbered log files
  • csvDelimiter: Specifies the delimiter for CSV formatted log files.

Step 3: Log Messages in Your Code

Use the FlutterLogs methods to log messages with appropriate severity levels throughout your application:

FlutterLogs.logInfo("ui", "Button Pressed", "User pressed the button");
FlutterLogs.logWarning("network", "API Timeout", "API request took too long");
FlutterLogs.logError("database", "Query Failed", "Failed to execute database query");
FlutterLogs.logSevere("app", "Fatal Error", "Application encountered a fatal error");

Step 4: Analyzing Logs

Analyzing the logs is where you extract useful insights from the recorded messages. You can retrieve logs in different ways:

Retrieve Logs from File

To read the logs from the file:

Future<List<Log>> getLogsFromFile() async {
  List<Log> logs = await FlutterLogs.getLogs();
  return logs;
}
Filter Logs

Filtering logs helps you focus on specific issues. You can filter logs based on log levels, tags, or keywords:

Future<List<Log>> getFilteredLogs() async {
  List<String> logLevels = [LogLevel.ERROR.toString(), LogLevel.SEVERE.toString()];
  List<Log> logs = await FlutterLogs.getFilteredLogs(
    logLevels: logLevels,
    logTypes: ["database", "network"],
    searchTerm: "failed",
  );
  return logs;
}

Step 5: Implement Real-Time Log Monitoring

For real-time monitoring, you can periodically read the log file and display the latest entries in your app. This can be useful during development and testing.

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_logs/flutter_logs.dart';

class LogMonitor extends StatefulWidget {
  @override
  _LogMonitorState createState() => _LogMonitorState();
}

class _LogMonitorState extends State<LogMonitor> {
  List<Log> _logs = [];
  Timer? _timer;

  @override
  void initState() {
    super.initState();
    _timer = Timer.periodic(Duration(seconds: 5), (Timer t) {
      _fetchLogs();
    });
  }

  Future<void> _fetchLogs() async {
    List<Log> logs = await FlutterLogs.getLogs();
    setState(() {
      _logs = logs;
    });
  }

  @override
  void dispose() {
    _timer?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Real-Time Log Monitor'),
      ),
      body: ListView.builder(
        itemCount: _logs.length,
        itemBuilder: (context, index) {
          final log = _logs[index];
          return ListTile(
            title: Text(log.logType + ": " + log.message),
            subtitle: Text(log.timestamp.toString() + " - " + log.logLevel.toString()),
          );
        },
      ),
    );
  }
}

This example shows how to use a Timer to periodically fetch logs and update the UI.

Step 6: Remote Logging

For production environments, sending logs to a remote server can provide centralized monitoring. The flutter_logs package allows you to send logs via HTTP.

Configure Remote Logging
Future<void> configureRemoteLogging() async {
  await FlutterLogs.init(
    logLevelsEnabled: [
      LogLevel.INFO,
      LogLevel.WARNING,
      LogLevel.ERROR,
      LogLevel.SEVERE
    ],
    timeStampType: TimeStampType.TIME_FORMAT_FULL,
    directoryStructure: DirectoryStructure.SINGLE_FILE,
    logTypesEnabled: ["network", "ui", "database"],
    isDebuggable: true,
    filename: "app_log.txt",
    isSequential: true,
    csvDelimiter: ',',
    remoteLogConfig: RemoteLogConfig(
      url: "https://your-remote-server.com/logs",
      httpMethod: "POST",
      headers: {"Authorization": "Bearer YOUR_API_KEY"},
      batchLogsToSend: 10,
      sendBatchInterval: 60,
    ),
  );
}
Implement Sending Logs

Configure the RemoteLogConfig with the URL of your server, the HTTP method, headers, and the frequency of sending log batches.

Example: Integrating with Dio for Network Logging

If you’re using the dio package for network requests, you can create an interceptor to log requests and responses:

import 'package:dio/dio.dart';
import 'package:flutter_logs/flutter_logs.dart';

class LoggingInterceptor extends Interceptor {
  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    FlutterLogs.logInfo("network", "Request", "Request ${options.method} ${options.uri}");
    super.onRequest(options, handler);
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    FlutterLogs.logInfo("network", "Response", "Response ${response.statusCode} ${response.requestOptions.uri}");
    super.onResponse(options, handler);
  }

  @override
  void onError(DioException err, ErrorInterceptorHandler handler) {
    FlutterLogs.logError("network", "Error", "Error ${err.message} ${err.requestOptions.uri}");
    super.onError(err, handler);
  }
}

// Usage
final dio = Dio();
dio.interceptors.add(LoggingInterceptor());

Add this interceptor to your Dio client to automatically log network requests and responses.

Conclusion

The flutter_logs package is a powerful tool for logging and analyzing application behavior in Flutter. By following these steps, you can effectively integrate logging into your Flutter apps, making it easier to debug issues, monitor performance, and gain insights into your application’s behavior in both development and production environments. Proper logging ensures maintainability, reliability, and continuous improvement of your Flutter projects.