Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v0.9.2 #52

Closed
wants to merge 11 commits into from
Closed
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
## 0.9.2
- New MultiOutput: allow multiple simultaneous outputs.
- New AsyncOutput: performs output in asynchronous mode.
- New LogPlatform: identifies the runtime platform, and it's capabilities.
- New LoggerFactory: A `Logger` factory with predefined properties.
- ConsoleOutput: now merging multiple lines to one String to avoid multiple IO calls.
- MemoryOutput: overrides `[]` operator for events buffer access.
- SimplePrinter, PrettyPrinter, PrefixPrinter: added optional `name` property.
- Logger.log(): `try` `catch` while sending events to output,\
to avoid `Logger` influence over the main software.
- README.md: fixed and improved badges.

gmpassos marked this conversation as resolved.
Show resolved Hide resolved
## 0.9.1
- Fix logging output for Flutter Web. Credits to @nateshmbhat and @Cocotus.

Expand Down
28 changes: 20 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Logger

[![Travis](https://img.shields.io/travis/com/leisim/logger/master.svg)](https://travis-ci.com/leisim/logger) [![Version](https://img.shields.io/pub/v/logger.svg)](https://pub.dev/packages/logger) ![Runtime](https://img.shields.io/badge/dart-%3E%3D2.1-brightgreen.svg) ![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)
[![pub package](https://img.shields.io/pub/v/logger.svg?logo=dart&logoColor=00b9fc)](https://pub.dartlang.org/packages/logger)
[![CI](https://img.shields.io/github/workflow/status/leisim/logger/Dart%20CI/master?logo=github-actions&logoColor=white)](https://github.com/leisim/logger/actions)
[![Last Commits](https://img.shields.io/github/last-commit/leisim/logger?logo=git&logoColor=white)](https://github.com/leisim/logger/commits/master)
[![Pull Requests](https://img.shields.io/github/issues-pr/leisim/logger?logo=github&logoColor=white)](https://github.com/leisim/logger/pulls)
[![Code size](https://img.shields.io/github/languages/code-size/leisim/logger?logo=github&logoColor=white)](https://github.com/leisim/logger)
[![License](https://img.shields.io/github/license/leisim/logger?logo=open-source-initiative&logoColor=green)](https://github.com/leisim/logger/blob/master/LICENSE)

Small, easy to use and extensible logger which prints beautiful logs.<br>
Inspired by [logger](https://github.com/orhanobut/logger) for Android.
Expand Down Expand Up @@ -79,6 +84,7 @@ If you use the `PrettyPrinter`, there are more options:
```dart
var logger = Logger(
printer: PrettyPrinter(
name: 'MyName', // name to use in events of this Logger.
methodCount: 2, // number of method calls to be displayed
errorMethodCount: 8, // number of method calls if stacktrace is provided
lineLength: 120, // width of the output
Expand All @@ -89,15 +95,21 @@ var logger = Logger(
);
```

### Auto detecting
### Auto Detecting Platform

With the `io` package you can auto detect the `lineLength` and `colors` arguments.
Assuming you have imported the `io` package with `import 'dart:io' as io;` you
can auto detect `colors` with `io.stdout.supportsAnsiEscapes` and `lineLength`
with `io.stdout.terminalColumns`.
The class `LogPlatform` tries to automatically identify `lineLength` and `colors`
support for console output, depending on the detected platform (VM or Web).

You should probably do this unless there's a good reason you don't want to
import `io`, for example when using this library on the web.
If in VM platform it uses `dart:io`
(`colors` with `io.stdout.supportsAnsiEscapes` and
`lineLength` with `io.stdout.terminalColumns`).

If you want to allow output in `stderr` (if present in the runtime platform),
you should enable it:

```dart
LogPlatform.enableSTDERR = true ;
```

## LogFilter

Expand Down
6 changes: 6 additions & 0 deletions lib/logger.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,23 @@ export 'src/filters/production_filter.dart';
export 'src/outputs/console_output.dart';
export 'src/outputs/stream_output.dart';
export 'src/outputs/memory_output.dart';
export 'src/outputs/multi_output.dart';
export 'src/outputs/async_output.dart';

export 'src/printers/pretty_printer.dart';
export 'src/printers/logfmt_printer.dart';
export 'src/printers/simple_printer.dart';
export 'src/printers/hybrid_printer.dart';
export 'src/printers/prefix_printer.dart';

export 'src/platform/platform.dart';

export 'src/log_output.dart'
if (dart.library.io) 'src/outputs/file_output.dart';

export 'src/log_filter.dart';
export 'src/log_output.dart';
export 'src/log_printer.dart';
export 'src/log_factory.dart';
export 'src/logger.dart';

71 changes: 71 additions & 0 deletions lib/src/log_factory.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import 'dart:collection';
gmpassos marked this conversation as resolved.
Show resolved Hide resolved

import 'package:logger/logger.dart';

/// A [Logger] factory.
class LoggerFactory {
/// Name to use in printer.
final String name;

/// The [Logger] base [Level].
final Level level;

/// The [Logger] [LogFilter].
final LogFilter filter;

/// The [Logger] [LogPrinter].
final LogPrinter printer;

/// The outputs ([LogOutput]) of [Logger]. If necessary uses [MultiOutput].
final List<LogOutput> outputs;

/// The main [LogOutput] of [Logger]. If necessary uses [MultiOutput] to merge
/// with [outputs].
final LogOutput output;

LoggerFactory(
{this.name, this.level, this.filter, this.printer, this.outputs, this.output});

/// Returns a new instance of [Logger].
///
/// You can overwrite any constructor parameter passing it again.
Logger get(
{String name,
LogFilter filter,
Level level,
LogPrinter printer,
List<LogOutput> outputs,
LogOutput output}) {
name ??= this.name;
filter ??= this.filter;
printer ??= this.printer ?? PrettyPrinter(name: name);
level ??= this.level;
outputs ??= this.outputs ?? <LogOutput>[];
output ??= this.output;

if (name != null && name.isNotEmpty && printer is LogPrinterWithName) {
var printerWithName = printer as LogPrinterWithName ;
name = name.trim() ;
if (name.isNotEmpty && printerWithName.name != name) {
printer = printerWithName.copy(withName: name) ;
}
}

// Ensure that order is preserved:
// ignore: prefer_collection_literals
var allOutputs = LinkedHashSet<LogOutput>() ;

if (output != null) allOutputs.add(output);
allOutputs.addAll( outputs.where((e) => e != null) ) ;

LogOutput theOutput;

if (allOutputs.isNotEmpty) {
theOutput = allOutputs.length == 1
? allOutputs.single
: MultiOutput(allOutputs.toList());
}

return Logger(level: level, filter: filter, printer: printer, output: theOutput);
}
}
8 changes: 8 additions & 0 deletions lib/src/log_printer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,11 @@ abstract class LogPrinter {

void destroy() {}
}

abstract class LogPrinterWithName extends LogPrinter {

String get name ;

LogPrinterWithName copy({String withName}) ;

}
gmpassos marked this conversation as resolved.
Show resolved Hide resolved
29 changes: 28 additions & 1 deletion lib/src/logger.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@ enum Level {
nothing,
}


const _levelsNames = {
Level.debug: 'DEBUG',
Level.verbose: 'VERBOSE',
Level.wtf: 'WTF',
Level.info: 'INFO',
Level.warning: 'WARNING',
Level.error: 'ERROR',
};
gmpassos marked this conversation as resolved.
Show resolved Hide resolved


/// Returns the [Level] name in upper case.
String getLevelName(Level level) => _levelsNames[level];

class LogEvent {
final Level level;
final dynamic message;
Expand All @@ -31,6 +45,11 @@ class OutputEvent {
final List<String> lines;

OutputEvent(this.level, this.lines);

@override
String toString() {
return '[$level] ${ lines.join('\ ') }';
}
}

@Deprecated('Use a custom LogFilter instead')
Expand Down Expand Up @@ -116,7 +135,15 @@ class Logger {

if (output.isNotEmpty) {
var outputEvent = OutputEvent(level, output);
_output.output(outputEvent);
// Issues with log output should NOT influence
// the main software behavior.
try {
_output.output(outputEvent);
}
catch(e,s) {
print(e);
print(s);
}
gmpassos marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
Expand Down
141 changes: 141 additions & 0 deletions lib/src/outputs/async_output.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import 'dart:collection';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In principle I really like this. Logging actions should never block the main app.
But I wonder if the same functionality could be achieved with the existing StreamOutput (I'm not sure this compiles):

class AsyncOutput extends LogOutput {
  final LogOutput mainOutput;
  StreamOutput _streamOutput;
  AsyncOutput(this.mainOutput): _streamOutput = StreamOutput() {
      _streamOutput.listen((e) => mainOutput.output(e));
  }

  void output(OutputEvent event) {
     _stream.output(event);
  }

  void destroy() {
     _stream.destroy();
  }
}

Copy link
Contributor Author

@gmpassos gmpassos Jul 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since Dart doesn't have yeld and sleep, any code in the thread will run straightforward (specially in the browser). The concept to release CPU of the current thread doesn't exists for Dart. If you use a Stream, doesn't mean that it will yeld and release CPU for the main software, or really parallelize anything . Also streams won't control priority of events.

This is why AsyncOutput has _maximumFlushTime. If method flush takes more than _maximumFlushTime ms, it stops flushing and schedules a call to flush some ms later, mimicking a yeld behaviour of the thread.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dart does have a yield keyword. With which you can give control back and the scheduler can schedule a different micro task (or not).

I think it would be a good idea to deal with this contribution in a different pull request. Can you open a new PR for this particular output?


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

/// Logs asynchronously to [mainOutput].
///
/// Useful when you wan't to avoid interference of log operations in the main
/// software, specially of IO operations, like writing to files and network.
class AsyncOutput extends LogOutput {
/// The main [LogOutput].
final LogOutput mainOutput;

AsyncOutput(this.mainOutput);

@override
void output(OutputEvent event) {
_outputAsync(event);
}

/// The event queue.
final ListQueue<OutputEvent> _eventsQueue = ListQueue(32);

/// Returns [true] if has some event in queue to flush ([eventsQueueLength] > 0).
bool get hasEventToFlush => _eventsQueue.isNotEmpty;

/// Returns [true] if events queue is empty ([eventsQueueLength] == 0).
bool get isFullyFlushed => _eventsQueue.isEmpty;

/// Returns the length of the events queue.
int get eventsQueueLength => _eventsQueue.length;

void _outputAsync(OutputEvent event) {
_eventsQueue.addLast(event);
_scheduleFlush();
}

bool _scheduledFlush = false;

void _scheduleFlush() {
if (_scheduledFlush) return;
_scheduledFlush = true;
Future.microtask(flush);
}

int _maximumFlushTime = 100;

/// The maximum amount of time in milliseconds that a single flush operation
/// can take.
///
/// If a flush operation takes more than [maximumFlushTime]
/// it will stop and resume after some milliseconds, releasing time for
/// the main software.
int get maximumFlushTime => _maximumFlushTime;

set maximumFlushTime(int value) {
if (value == null) {
value = 100;
} else if (value < 1) {
value = 1;
}
_maximumFlushTime = value;
}

int _consecutiveIncompleteFlushCount = 0;

/// Flushes the queue of events.
/// Returns the amount of events sent to [mainOutput].
///
/// You don't need to call directly this method, since internally it's
/// automatically scheduled.
///
/// If you really want, you can call this at any time.
int flush() {
var eventCount = 0;

int init;

while (true) {
var length = _eventsQueue.length;
if (length == 0) break;

var event = _eventsQueue.removeFirst();

if (length == 1) {
_mainOutput(event);
eventCount++;
break;
} else {
init ??= DateTime.now().millisecondsSinceEpoch;
_mainOutput(event);
eventCount++;
var elapsedTime = DateTime.now().millisecondsSinceEpoch - init;
if (elapsedTime > _maximumFlushTime) {
break;
}
}
}

if (_eventsQueue.isNotEmpty) {
_scheduledFlush = true;
_consecutiveIncompleteFlushCount++;

var delay = _consecutiveIncompleteFlushCount * 2;
if (delay > 100) delay = 100;

Future.delayed(Duration(milliseconds: delay), flush);
} else {
_consecutiveIncompleteFlushCount = 0;
_scheduledFlush = false;
}

return eventCount;
}

/// Fully flushes the events queue.
int fullyFlush() {
var eventCount = 0;

while (_eventsQueue.isNotEmpty) {
var event = _eventsQueue.removeFirst();
_mainOutput(event);
eventCount++;
}

_consecutiveIncompleteFlushCount = 0;
_scheduledFlush = false;

return eventCount;
}

/// Outputs to the [mainOutput].
void _mainOutput(OutputEvent event) {
try {
mainOutput.output(event);
} catch (e, s) {
print(e);
print(s);
}
}
}
12 changes: 10 additions & 2 deletions lib/src/outputs/console_output.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import 'package:logger/src/logger.dart';
import 'package:logger/src/log_output.dart';
import 'package:logger/src/logger.dart';
import 'package:logger/src/platform/platform.dart';

/// Default implementation of [LogOutput].
///
/// It sends everything to the system console.
class ConsoleOutput extends LogOutput {
@override
void output(OutputEvent event) {
event.lines.forEach(print);
var linesLength = event.lines.length;
if (linesLength == 0) return;

var level = event.level;
var allLines = LogPlatform.joinLines(event.lines, level);
var printer = LogPlatform.getConsolePrinter(level);

printer(allLines);
}
}