Building High-Quality Production Flutter App: A Complete Guide to Flutter Remote Logging with GCP Cloud Logging

Eternity (Isaac Adariku)
11 min readFeb 14, 2024

by Isaac Adariku (LinkedIn, Twitter, Website)

Picture this: Your Flutter app encounters an unexpected behaviour in production, and you’re left scrambling to understand the problem buried within lines of code. Frustrating, isn’t it?

For Backend and Web Developers, sifting through server logs is a common solution. But what about Flutter Developers?

While the popular Firebase Crashlytics or other bug-tracking tools can help offer a solution for app crashes, it has limitations. It only retains the most recent eight exceptions per session, potentially losing valuable insights if your app encounters more exceptions (Source).

So, what’s the solution? How do you debug your Flutter app in production without direct access to users’ devices?

If you prefer to follow a codelab, you can access the Flutter Remote Logging with Cloud Logging Codelab.

By the end of this article, you’ll:

  • Understand the significance of Remote Logging.
  • Set up Remote Logging in your Flutter app with Cloud Logging.
  • Effectively monitor, analyze, alert, and troubleshoot issues in your Flutter app.

What is Remote Logging?

Remote logging is a technique to log events from your app to a remote server. Creating possibilities for monitoring, analyzing, alerting, visualizing, understanding user behaviour, identifying bugs, and optimizing app performance in production environments.

Why is Remote Logging Important?

As a developer, one of the simplest and most popular debugging techniques is examining application logs. Remote logging extends this capability to production environments, allowing you to address issues that may impact your users. It provides valuable insights for fixing bugs and offers real-time alerts when issues occur, as emphasized by Dane Mackier, FilledStacks.

Remote Logging available options for Flutter

While several solutions exist for remote logging, including Sentry, AWS CloudWatch, logging_appenders, and many more, this article focuses on Cloud Logging.

Why Cloud Logging?

It’s a fully managed service designed for storing, searching, analyzing, visualizing and alerting on log data and events, making it ideal for monitoring and optimizing your Flutter app.

Example of remote logs from a Flutter App to Cloud Logging
Example of remote logs from a Flutter App to Cloud Logging

Setting up Remote Logging in Flutter app with Cloud Logging

Prerequisites

To use Cloud Logging, you need a Google Cloud project. If you don’t have one, you can create it in the Google Cloud Console. Additionally, a Firebase project simplifies this process as it’s automatically created with a Google Cloud project.

Authentication with Google Cloud Platform

This is necessary to write logs to Cloud Logging. There are two options:

1. Using the google-sign-in plugin in conjunction with the extension_google_sign_in_as_googleapis_auth package, this approach is suitable for user authentication and authorization.

2. Using a Service Account for autonomous application authentication. This is the approach we’ll explore in this article.

Step 1: Create and Enable Service for Logging

Create a Service Account

Navigate to the Google Cloud Console and select your project.

Selecting a project in the Google Cloud Console
Selecting a project in the Google Cloud Console

In the left-hand navigation, click on the IAM & Admin and then click on Service Accounts.

Navigating to Service Accounts in Google Cloud Console from IAM & Admin
Navigating to Service Accounts in Google Cloud Console
  1. Fill in the service account details and click on CREATE AND CONTINUE.

2. Grant this service account access to the project, click on Select a role and search for logs writer and then click on Logs Writer which gives the service account access to write logs, and click on CONTINUE.

3. We don’t need to grant any users access to this service account, so click on DONE.

4. Create a Key for the service account, click on Keys tab and then ADD KEY and select Create new key. Select JSON and click on CREATE.

The JSON key file will be downloaded to your computer. As seen below.

Enable Cloud Logging API

Navigate to Cloud Logging API and then click on ENABLE.

Step 2: Set up the Flutter app

Create or Open a Flutter Project

Open your Flutter project or create a new Flutter project by running flutter create flutter_remote_logging from a command line.

We will use the googleapis and googleapis_auth packages to authenticate the Flutter app to access Cloud Logging. Also, we will use the logger package to log events from our app to Cloud Logging with severity levels. This enables filtering logs based on severity, such as displaying only ERROR-level logs.

Run the following command from a command line to add the packages to your Flutter app:

flutter pub add googleapis googleapis_auth logger

Step 3: Authenticate the Flutter app to Access Cloud Logging

Create a new service or a new Dart file called google_cloud_logging_service.dart and add the following code:

import 'package:flutter/foundation.dart';
import 'package:googleapis/logging/v2.dart';
import 'package:googleapis_auth/auth_io.dart';

// Define constants for authentication and project identification
const _serviceAccountCredentials = {
// Replace with the content of the JSON key file
"type": "service_account",
// ...
// ...
// ...
};
const _projectId = 'YOUR_PROJECT_ID'; // Replace with your project ID from the JSON key file

class GoogleCloudLoggingService {
late LoggingApi _loggingApi; // Instance variable for Cloud Logging API
bool _isSetup = false; // Indicator to check if the API setup is complete

// Method to set up the Cloud Logging API
Future<void> setupLoggingApi() async {
if (_isSetup) return;

try {
// Create credentials using ServiceAccountCredentials
final credentials = ServiceAccountCredentials.fromJson(
_serviceAccountCredentials,
);

// Authenticate using ServiceAccountCredentials and obtain an AutoRefreshingAuthClient authorized client
final authClient = await clientViaServiceAccount(
credentials,
[LoggingApi.loggingWriteScope],
);

// Initialize the Logging API with the authorized client
_loggingApi = LoggingApi(authClient);

// Mark the Logging API setup as complete
_isSetup = true;
debugPrint('Cloud Logging API setup for $_projectId');
} catch (error) {
debugPrint('Error setting up Cloud Logging API $error');
}
}
}

Let’s quickly understand the code above:

  • We created a GoogleCloudLoggingService class that will be responsible for setting up the Cloud Logging API and writing logs to Cloud Logging.
  • Using the JSON key file we downloaded, we defined a constant _serviceAccountCredentials to store the content of the JSON key file. We also defined another constant _projectId to store the project ID from the JSON key file.

Important: Adding the JSON key file content directly to the Dart file is not recommended for security reasons. But since this service account is only used to write logs, it is safe to do so. However, you should follow this best practice for managing service account keys.

  • We are using the late keyword to declare the _loggingApi instance variable because we will initialize it in the setupLoggingApi() method.
  • It includes a method setupLoggingApi() to initialize and authenticate the Cloud Logging API.
  • The class maintains a flag _isSetup to track whether the API setup is complete or not, which is used to prevent multiple setups of the API with the same instance.

If everything is set up correctly, your project structure should look similar to this:

flutter_project
├── android
├── ios
├── lib
│ ├── google_cloud_logging_service.dart
│ └── main.dart
├── test
├── web
├── pubspec.yaml
└── ...

Step 4: Set up Log Writer

Using the Logger package we added, let’s update the google_cloud_logging_service.dart file to include the writeLog() method:

...
import 'package:logger/logger.dart';

...
...
...

class GoogleCloudLoggingService {
...
...
...

void writeLog({required Level level, required String message}) {
if (!_isSetup) {
// If Logging API is not setup, return
debugPrint('Cloud Logging API is not setup');
return;
}

// Define environment and log name
const env = 'dev';
const logName = 'projects/$_projectId/logs/$env'; // It should in the format projects/[PROJECT_ID]/logs/[LOG_ID]

// Create a monitored resource
final resource = MonitoredResource()..type = 'global'; // A global resource type is used for logs that are not associated with a specific resource

// Map log levels to severity levels
final severityFromLevel = switch (level) {
Level.fatal => 'CRITICAL',
Level.error => 'ERROR',
Level.warning => 'WARNING',
Level.info => 'INFO',
Level.debug => 'DEBUG',
_ => 'NOTICE',
};

// Create a log entry
final logEntry = LogEntry()
..logName = logName
..jsonPayload = {'message': message}
..resource = resource
..severity = severityFromLevel
..labels = {
'project_id': _projectId, // Must match the project ID with the one in the JSON key file
'level': level.name.toUpperCase(),
'environment': env, // Optional but useful to filter logs by environment
'user_id': 'your-app-user-id', // Useful to filter logs by userID
'app_instance_id': 'your-app-instance-id', // Useful to filter logs by app instance ID e.g device ID + app version (iPhone-12-ProMax-v1.0.0)
};

// Create a write log entries request
final request = WriteLogEntriesRequest()..entries = [logEntry];

// Write the log entry using the Logging API and handle errors
_loggingApi.entries.write(request).catchError((error) {
debugPrint('Error writing log entry $error');
return WriteLogEntriesResponse();
});
}
}

The method writeLog() is to write log entries to the Cloud Logging. It constructs log entries using the provided Level and message from the Logger package.

Step 5: Set up Logger to Output to Cloud Logging API

In the main.dart file, update with the following code:

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_remote_logging/google_cloud_logging_service.dart';
import 'package:logger/logger.dart';

/// This class is use to override the default
/// Logger Filter. So we can log in release mode.
class CustomFilter extends LogFilter {
@override
bool shouldLog(LogEvent event) {
return true;
}
}

final log = Logger(filter: CustomFilter());
final googleCloudLoggingService = GoogleCloudLoggingService();

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

// Setup Cloud Logging API
await googleCloudLoggingService.setupLoggingApi();

// Use the output listener from the Logger package to write logs to Cloud Logging
Logger.addOutputListener((event) {
if (kReleaseMode) { // Only write logs to Cloud Logging in release mode
googleCloudLoggingService.writeLog(
level: event.level,
message: event.lines.join('\n'), // Join the log lines with a new line, so that it is written as a single message
);
debugPrint('App will log output to Cloud Logging');
}
});

log.i('App started');
runApp(const MyApp());
}

💡 For the sake of this article, I won’t be adding any dependency injection package like get_it to manage the GoogleCloudLoggingService instance. You can use any DI package you like.

As our app is starting, we call the setupLoggingApi() method from the GoogleCloudLoggingService() instance to authenticate our Flutter app to access Cloud Logging. We then use the output listener from the Logger package to write logs to Cloud Logging only in release mode, as this is the main mode we want to log, monitor and analyze activities in the Flutter app.

Quick Note: If you want to test in debug mode, change the if (kReleaseMode) condition to if (!kReleaseMode).

Step 6: Write Logs to Cloud Logging

🥳 We are all set and ready to write logs to Cloud Logging. You can write logs from anywhere in the Flutter app using the Logger package.

💡 In a real-world scenario, you would write logs based on user interactions, network requests, and caught errors.

For this article, I used the counter app to showcase some logs. I added the following logs to the _incrementCounter() method in the main.dart file, such that when you click on the counter button, it logs the following:

  void _incrementCounter() {
log.i('User tapped on the button');
log.e('Error occurred while fetching data');
log.w('Warning: User is offline');
log.f('A fatal error occurred' '$_counter');
log.d('Debug: User data fetched successfully');
log.t('Verbose: User data fetched successfully');
...
...
}

✨ You can now run the Flutter app and if everything is set up correctly, you should see the logs in your console.

✨ Click on the counter button to trigger the logs.

Congratulations! 👏🏾 You have successfully set up remote logging in your Flutter app with Cloud Logging.

Source code is available on Github

Step 7: View Logs in Cloud Logging

You can view logs in Cloud Logging by navigating to the Google Cloud Console and selecting your project. In the left-hand navigation, click on Logging and then Logs Explorer or click on the following link Logs Explorer.

You can filter logs by severity level, log name, resource type, and time range. If at any point a log is not clear, click on the log entry to view the log details and then click on Explain this log entry to get AI-ML-powered insights into the log entry.

You can also create logs-based metrics and alerts to monitor and alert on log entries in the Flutter app. For example, you can create a logs-based metric to count the number of logs with a severity level of CRITICAL and then create an alert to notify you when the count exceeds a certain threshold in Slack, Email, or any other notification channel based on your policy.

Another good use case is to monitor users' behaviour. If you want to know if users are using a feature or not, you can add a log monitoring for that and alert if the usage is low. This is something we cannot achieve with Crashlytics alone, and it is very important to understand user behaviour because it can help us remove unused code. As a developer, less code, little maintenance, fewer bugs, and more quality apps 😋.

For more information, you can refer to the Cloud Logging documentation.

Wrap Up

We’ve explored the setup of a game-changing solution for Flutter remote logging using Cloud Logging. You now understand the significance of remote logging, how to set it up in your Flutter app, and how to effectively monitor and analyze issues.

For clearer and more organized log output, consider leveraging Stacked Architecture. It provides a more organized and structured way to log events in your Flutter app. You can refer to the stackedLoggerGenerator in the stacked_generator package for more information.

Remote logging is indispensable for any high-quality production Flutter app. Research indicates that successful reliability is significantly associated with observability in systems (source). Remote logging facilitates faster and more accurate issue resolution, ultimately enhancing the quality of Flutter apps.

Always remember “High-quality Flutter Apps should be the default” — Eric Seidel Leading Shorebird, Founded Flutter.

I hope you enjoyed this article 😊 Until next time, 💙 Happy Fluttering! 💙

Help contribute to this article by:

📢 Spreading it with your friends, family, and colleagues.

👏🏾 Tapping the clap button below to show your appreciation and motivation.

📝 Sharing your thoughts and inquiries by leaving a comment below.

➕ Follow me on Medium to receive notifications when I release new articles.

😍 Connecting with me on Twitter and LinkedIn and I would be delighted to provide assistance.

📆 Book a 1:1 Session with me! on TopMate

Every day is another opportunity to gain Mastery! 💙 — Isaac Adariku (Eternity)

--

--

Eternity (Isaac Adariku)
Eternity (Isaac Adariku)

Written by Eternity (Isaac Adariku)

GDE Flutter & Dart | Software Craftsman | Organizer FlutterKaduna