Logging library

Dependencies

Overview

  • This is the central logging library which handles cloud (e.g. sentry, crashanalytics) and local (e.g. Console/Terminal and Local Files For Device) Logs.
Cloud logging services
  • As of now we have implemented Sentry to log and measure performance. Any other service is not available yet. To setup Sentry please check this.
  • You can check out more details about it here: Sentry and Performance
  • Also to enable sentry; developer will have to pass sentryConfig in environment.
  • So developer should never use individual service in their application, they should always use Logging library for logging any kind of content.
hierarchy
  • To setup Sentry please check this.

Use cases

use cases
  • To easily log content on cloud
  • To easily add another cloud logging service
  • To log transactions. e.g. I want to measure and log time of an api call
  • To see formatted (prettier) objects in the console easily readable by the human eye
  • To easily differentiate between different kinds of events.

Log types

Currently, our logs are of six types.

  1. log
  2. info
  3. success
  4. warning
  5. exception
  6. transaction

For Local Logs: Different types will print logs in different colors. the log will be in grey, info in blue, success in green, warning in yellow, and exception will be in red.

How to use

Call static methods of the Log class to log the events.

Log.log("Log");
Log.info("info");
Log.success("success");
Log.warning("warning");
try {
  // code snippet
} catch (error, stackTrace){
  Log.exception(error, stackTrace: stackTrace);
}

Log Transactions

When developer wants to measure performance of different operations they can use logTransaction funtion. Where developer will have to pass operation for which they wants to log as execute argument. e.g. API send receive requests, getting a file from local storage and parsing data, processing huge data, search queries, etc.

Log.logTransaction(
  execute: () async => {},
  details: const TransactionDetails(
    name: 'idle transaction',
    operation: 'idle',
  ),
);
Log.logTransaction(
  execute: Api.ajax('api.vaah.dev'),
  details: const TransactionDetails(
    name: 'get root route of vaah api',
    operation: 'read',
  ),
);

To print data, as argument pass the data

Map<String, dynamic> data = {
  "first_key": "value",
  "second_key": [
    {"key": "value"},
    {"key": "updated_value"},
  ],
};
Log.info('data', data: data);

Output:

Output

You can disable local and cloud logging for specific log

Log.exception('exception', disableLocalLogging: true);
Log.info('log', disableCloudLogging: true);

For exception you can pass two extra parameters: stackTrace and hint

catch (error, stackTrace){
    Log.exception(
        error,
        stackTrace: stackTrace,
        hint: 'The exception is caught in ---',
    )
}

How to add new cloud service?

Step 1: create a service which implements LoggingService, example of implemented service: Sentry

Step 2: Add that service in _services array, in logging_library.dart

  static final List<Type> _services = [
    SentryLoggingService,
    FirebaseLoggingService,
  ];

Step 3: Call relavent functions of that new service in _logEvent and exception, switch cases

static void _logEvent(
    String text, {
    Object? data,
    EventType? type,
  }) {
    for (var service in _services) {
      switch (service) {
        case SentryLoggingService:
          SentryLoggingService.logEvent(
            message: text,
            level: type?.toSentryLevel,
            data: data,
          );
          return;
        case FirebaseLoggingService:
          FirebaseLoggingService.logEvent(
            message: text,
            type: type,
            data: data,
          );
          return;
        default:
          return;
      }
    }
  }
static void exception(
    dynamic throwable, {
    Object? data,
    dynamic stackTrace,
    dynamic hint,
    bool disableLocalLogging = false,
    bool disableCloudLogging = false,
  }) {
    EnvironmentConfig config = EnvironmentConfig.getEnvConfig();
    if (config.enableLocalLogs && !disableLocalLogging) {
      Console.danger(throwable.toString(), data);
    }
    if (config.enableCloudLogs && !disableCloudLogging) {
      final hintWithData = {
        'hint': hint,
        'data': data,
      };
      for (var service in _services) {
        switch (service) {
          case SentryLoggingService:
            SentryLoggingService.logException(
              throwable,
              stackTrace: stackTrace,
              hint: hintWithData,
            );
            return;
          case FirebaseLoggingService:
            FirebaseLoggingService.logException(
              throwable,
              stackTrace: stackTrace,
              hint: hintWithData,
            );
            return;
          default:
            return;
        }
      }
    }
  }

Enviroment variables which control logging

  • depending on environment variables enableLocalLogs and enableCloudLogs, the content is logged. e.g. if enableLocalLogs in the environment is set to false then no local logs will be printed. if enableCloudLogs is set to false then no local logs will be printed.

logging_library.dart Source code:

import './_cloud/firebase_logging_service.dart';
import './_cloud/logging_service.dart';
import './_cloud/sentry_logging_service.dart';
import './_local/console_service.dart';
import '../../env.dart';

class Log {
  static final List<Type> _services = [
    SentryLoggingService,
    // FirebaseLoggingService,
  ];

  static void log(
    dynamic text, {
    Object? data,
    bool disableLocalLogging = false,
    bool disableCloudLogging = false,
  }) {
    EnvironmentConfig config = EnvironmentConfig.getEnvConfig();
    if (config.enableLocalLogs && !disableLocalLogging) {
      Console.log(text.toString(), data);
    }
    if (config.enableCloudLogs && !disableCloudLogging) {
      _logEvent(text.toString(), data: data, type: EventType.log);
    }
  }

  static void info(
    dynamic text, {
    Object? data,
    bool disableLocalLogging = false,
    bool disableCloudLogging = false,
  }) {
    EnvironmentConfig config = EnvironmentConfig.getEnvConfig();
    if (config.enableLocalLogs && !disableLocalLogging) {
      Console.info(text.toString(), data);
    }
    if (config.enableCloudLogs && !disableCloudLogging) {
      _logEvent(text.toString(), data: data, type: EventType.info);
    }
  }

  static void success(
    dynamic text, {
    Object? data,
    bool disableLocalLogging = false,
    bool disableCloudLogging = false,
  }) {
    EnvironmentConfig config = EnvironmentConfig.getEnvConfig();
    if (config.enableLocalLogs && !disableLocalLogging) {
      Console.success(text.toString(), data);
    }
    if (config.enableCloudLogs && !disableCloudLogging) {
      _logEvent(text.toString(), data: data, type: EventType.success);
    }
  }

  static void warning(
    dynamic text, {
    Object? data,
    bool disableLocalLogging = false,
    bool disableCloudLogging = false,
  }) {
    EnvironmentConfig config = EnvironmentConfig.getEnvConfig();
    if (config.enableLocalLogs && !disableLocalLogging) {
      Console.warning(text.toString(), data);
    }
    if (config.enableCloudLogs && !disableCloudLogging) {
      _logEvent(text.toString(), data: data, type: EventType.warning);
    }
  }

  static void exception(
    dynamic throwable, {
    Object? data,
    dynamic stackTrace,
    dynamic hint,
    bool disableLocalLogging = false,
    bool disableCloudLogging = false,
  }) {
    EnvironmentConfig config = EnvironmentConfig.getEnvConfig();
    if (config.enableLocalLogs && !disableLocalLogging) {
      Console.danger(throwable.toString(), data);
    }
    if (config.enableCloudLogs && !disableCloudLogging) {
      final hintWithData = {
        'hint': hint,
        'data': data,
      };
      for (var service in _services) {
        switch (service) {
          case SentryLoggingService:
            SentryLoggingService.logException(
              throwable,
              stackTrace: stackTrace,
              hint: hintWithData,
            );
            return;
          case FirebaseLoggingService:
            FirebaseLoggingService.logException(
              throwable,
              stackTrace: stackTrace,
              hint: hintWithData,
            );
            return;
          default:
            return;
        }
      }
    }
  }

  static void _logEvent(
    String text, {
    Object? data,
    EventType? type,
  }) {
    for (var service in _services) {
      switch (service) {
        case SentryLoggingService:
          SentryLoggingService.logEvent(
            message: text,
            level: type?.toSentryLevel,
            data: data,
          );
          return;
        case FirebaseLoggingService:
          FirebaseLoggingService.logEvent(
            message: text,
            type: type,
            data: data,
          );
          return;
        default:
          return;
      }
    }
  }
}