harmony_log

Harmony Logging Mechanism

Installation

Add the following lines to your pubspec.yaml file:

dependencies:
  harmony_log: ^latest.version

import harmony_log.

import 'package:harmony_log/harmony_log.dart';

Usage

Log system contains two main blocks, A logger which is represented using Log class, and an output which is represented using LogOutput class.

Each log request on Log class is transformed to a LogEvent by adding identification and time information, and is redirected to the given LogOutput. LogEvents contain message, error, stackTrace and level (which is represented using LogLevel enum) as well as some extra information like id (which is generated by the given LogId) , time and extra. extra is a general field with type Object which is reserved for adding extra information to a log event.

A LogId is needed for Log class to generate an id for each log event. You can use LogId factories like counter which will simply count from a starting value, constant which will always generate the same given id and custom which you can provide your custom logic for id generation. You can check out on dart docs.

As well as a generic log method on Log class you have access to helper methods v, d, i, w, e and wtf which add level according to their acronyms. You have also access to write method on Log class which is a low level method to directly specify log events. LogLevel enum has 6 different log levels like LogLevel.verbose with significance of 0 and LogLevel.wtf with significance of 5. LogLevels are comparable using compareTo method or comparison operators like >=.

Each Log class should be initialized using init and closed using close. After calling init it should be immediately usable. But after calling close the behaviour is unspecified meaning using log after being closed can actually log events, don't log events without giving an error or throwing an error. Also closing may be finished asynchronously. you must call logging methods after calling init and before calling close. init and close commands are redirected to LogOutput.

Log class is created by providing a LogOutput, LogId and a tag to its constructor. tag is by default null. it is recommended that you create one Log class with null tag for your app log, then create tagged loggers from your logger using tagged method. This has the advantage to using the same output and id generation systems, So by initializing one them all of them are initialized and the same for closing.

LogOutputs are created using its factories. Some of the outputs are responsible to redirect events to other outputs, like filtered to filter events and redirect to other output, multi to output to multiple other outputs, redirect to redirect output to other output conditionally and redirectOnDebug to redirect output to other output only on debug. Some of them are terminal outputs like noop which does not do anything. And there is a special custom output which can be used to create custom outputs by providing functions for init, write and close operations. And a special plain factory which is used to output plain String data. There is also developerLog log output which can be used to log to dart:developer.

Filtering events is done by using special filtered LogOutput factory. You should provide and output for filtered events to go to and a LogFilter to filter events. LogFilters are created using its factories, for example level which will allow levels equal or greater than the specified level, all which will allow all events, none which won't allow any events, debug which will allow events only on debug, and several other factories which you can check out on dart docs. And there is a custom factory which is used to create custom filtering logic. All LogFilters support standard set operations like |, &, - and !.

Plain output is where you will output logs to for example console and file. Outputting plain data is done by using special plain LogOutput factory. You should provide a LogPlainFilter and a LogPlainOutput.

LogPlainFilter is responsible for transforming a log event to a list of string lines. It can be created using its factories like simple which will format a simple single-line output, pretty which will format events in a box like view, json which will format events in json. json formatter is specially good when outputting to a file. LogPlainFilter will also add start and end sequences to log output, for example in json we need [and ] at start and end of file to be meaningful. There is a custom factory which you can use to implement your custom formatting mechanism.

LogPlainOutput is responsible to output a list of string lines. Some of the plain outputs are responsible to redirect events to other plain outputs, like multi to output to multiple other outputs, redirect to redirect output to other output conditionally. Some of them are terminal outputs like noop which does not do anything, console which is used to output data using print statements and file which is used to output data to file in the given directory path. File name is based on creating time with a prefix, postfix and extension which can be changed of needed. File output can not be used on flutter web.

As a note filtering will always pass init and close operations. It is better to use redirect and its dialects for enabling/disabling logging completely on different situations.

A basic example, which only logs on debug and outputs to cosnole using simple formatting:

import 'package:harmony_log/harmony_log.dart';

void main() {
  final log = Log(
    id: LogId.counter(),
    child: LogOutput.redirectOnDebug(
      child: LogOutput.plain(
        format: LogPlainFormat.simple(),
        child: LogPlainOutput.console(),
      ),
    ),
  );
  log.init();
  log.i('hello, there!');
  log.e('bad code!', error: AssertionError());
  log.close();
}

A basic example, which only logs on debug and outputs to cosnole using pretty formatting, and also filtering logs to have at least info level and using counter id generation:


final log = Log(
  id: LogId.counter(),
  child: LogOutput.redirectOnDebug(
    child: LogOutput.filtered(
      filter: LogFilter.level(LogLevel.info),
      child: LogOutput.plain(
        format: LogPlainFormat.pretty(),
        child: LogPlainOutput.console(),
      ),
    ),
  ),
);

An example, which shows logs with level at least debug on cosole only in debug with pretty formatting, saves all logs to a file with json formatting, on release sends logs with level of at least error to a server:


final log = Log(
  id: LogId.counter(),
  child: LogOutput.multi(
    children: [
      LogOutput.redirectOnDebug(
        child: LogOutput.filtered(
          filter: LogFilter.level(LogLevel.debug),
          child: LogOutput.plain(
            format: LogPlainFormat.simple(),
            child: LogPlainOutput.console(),
          ),
        ),
      ),
      LogOutput.plan(
        format: LogPlainFormat.json(),
        child: LogPlainOutput.file(
          path: 'path/to/directory',
        ),
      ),
      LogOutput.redirectOnRelease(
        child: LogOutput.filtered(
          filter: LogFilter.level(LogLevel.error),
          child: LogOutput.custom(
            write: (e) {
              // send event to server
            },
          ),
        ),
      ),
    ],
  ),
);

creating a tagged logger:


final log = Log(/* ... */);

final otherLog = log.tagged('OTHER');

Examples

see example:

Simple Example

Complex Example

Libraries

harmony_log