SpringLog

SpringLog is a named logger, and its static members are the switches of logging as a whole: levels, the handler and listeners.

Why use it

Code logs through a SpringLog; a backend decides where the records go. This keeps libraries and applications on one API, and lets you set levels per logger name without touching code.

Example

import 'package:spring_core_dart/spring_core_dart.dart';
import 'package:neon/spring_gen/app.spring.dart'; // the generated file

@Service()
@Log(topic: 'neon.checkout') // logger name chosen explicitly
class Checkout {
  Checkout(this.gateway);
  final PaymentGateway gateway;

  void pay(Order order) {
    log.info('order placed', fields: {'order': order.id});
    log.debug(() => 'basket: ${order.dump()}'); // built only if enabled
    try {
      gateway.charge(order);
    } on PaymentException catch (e, s) {
      log.error('payment failed', error: e, stackTrace: s);
    }
  }
}

// `dart:math` also declares `log`, so pick another getter name there.
@Component()
@Log(fieldName: 'logger') // logger 'neon.Payments'
class Payments {
  void refund() => logger.warning('refund');
}

Levels, fields and listeners in code:

const log = SpringLog('neon.jobs');

void configureLogging() {
  SpringLog.level = SpringLogLevel.warning; // root level
  SpringLog.setLevel('neon.checkout', SpringLogLevel.debug); // and below
}

Future<void> runJob(String jobId) =>
    // Every record logged inside, also after an `await`, carries `job`.
    SpringLog.withFields({'job': jobId}, () async {
      log.info('started');
      await Future<void>.delayed(const Duration(seconds: 1));
      log.info('finished');
    });

void captureInTest() {
  final records = <SpringLogRecord>[];
  final stop = SpringLog.listen(records.add); // after the handler
  log.warning('disk almost full');
  stop();
}

@Log writes the log getter into the .spring.dart file of the @ComponentScan that scans the class. Import that file in the class's library. The default logger name is <package>.<Class>.

Members

@Log:

Name Type Default Meaning
topic String? null The logger name; <package>.<Class> when null.
fieldName String 'log' The name of the generated getter.

SpringLog instance members:

Name Meaning
SpringLog(name) Creates a logger; usually const.
name The logger name, used to look up its level.
trace(message, {fields}), debug(...), info(...) Log at that level.
warning(message, {error, stackTrace, fields}), error(...) Log at that level, with an optional error and stack trace.
log(level, message, {error, stackTrace, fields}) Log at a level chosen at run time.
isEnabled(level) Whether this logger emits records of level now.

message may be a function returning the text; it is called only when the record is emitted. Any other object is turned into text with toString.

SpringLog static members:

Name Meaning
level The root level; SpringLogLevel.info unless set.
setLevel(name, level) Sets the level of a logger and the loggers below it; null makes it inherit again.
levelOf(name) The level that applies to a logger name.
configuredLevels, resetLevels() The levels set with setLevel; remove them all.
handler Where records go: a SpringLogHandler. printLogHandler unless a backend replaces it.
listen(listener) Also sends every emitted record to listener; returns a function that stops it.
withFields(fields, body) Runs body with fields added to every record logged inside, including asynchronous work.
currentFields The fields set by withFields for the code running now.
hold() Holds back records until release() on the returned SpringLogHold; used by startup code before levels are known.

SpringLogLevel: trace, debug, info, warning, error, and off, which silences a logger.

SpringLogRecord (what handlers and listeners receive): level, loggerName, message, error, stackTrace, fields, time, sequence.

Other helpers: printLogHandlerColours forces colours on or off; parseSpringLogLevel(text) reads a level name as written in configuration (warn and warning both work); LogFormatUtils.formatValue(value) quotes a value from outside and replaces line breaks and control characters, so it cannot forge log lines.

In Spring (Java)

// Spring's commons-logging facade
private static final Log log = LogFactory.getLog(Checkout.class);
log.info("order placed");

// Lombok
@Slf4j(topic = "neon.payments")
class Payments { void refund() { log.warn("refund"); } }

Differences that matter:

See also