Skip to content

Transform liblog into a logging facade #3

Closed
@sfackler

Description

@sfackler

There are a couple of distinct kinds of programs, each of which has different needs from a logger:

  • Short running programs like rustc need a simple logging framework that provides some reasonable level of configurable filtering when trying to debug a problem.
  • Embedded programs or kernels may not have a stderr at all. They may, however, have the ability to log information through a serial port or network interface.
  • Long running programs like servers have more advanced logging needs than the current crate provides. In this case, dynamic reconfiguration of logging filters is very helpful, as is the ability to have multiple "sinks" that can output to the console, rolling files, the syslog, over the network, etc.
  • Programs that don't care about logging shouldn't be spammed with unwanted information from libraries that they use.

The current liblog implementation covers the first and last use cases, but misses the middle two.

There are two competing concerns here: we want a single logging interface that libraries can use - it would be crazy to expect someone to provide different versions of a single crate that only differ by their choice of logging framework! But at the same time, we want the libraries to be usable in all of the contexts mentioned above, with their logging intact.

One solution is to turn liblog into a logging "facade". It provides the infrastructure to allow libraries to send log messages, while allowing the downstream application to chose the actual logging framework that will process the log messages. One example is the slf4j library for Java, which allows libraries to log in a way that will work with any of the many logging frameworks available in Java: java.util.logging, log4j, Logback, etc.

I have a prototype conversion of liblog to a facade in the log-ng project. It consists of three crates:

  • log_ng - the core facade
  • basic - a port of liblog's current RUST_LOG based log implementation
  • log4r - a prototype of a more complex logger in the vein of log4j and Logback.

Documentation is available here: http://sfackler.github.io/log-ng/doc/log_ng/

Changes from liblog

While converting liblog, I did make some changes to the current functionality:

  • The log levels are now an enum instead of a u32, consisting of Off, Error, Warn, Info, Debug, and Trace. Note that Trace is new. I don't really see the use in having 255 distinct levels, and it simplifies parsing and pretty-printing of the level if we can restrict the level to an enum.
  • The logging macros don't call enabled before calling log. It turned out when I was experimenting with logger implementations that log can pretty trivially perform the filtering itself. If argument computation is expensive, the log_enabled! macro is still available, which will call through to enabled.
  • There is now a single global logger instead of thread-local loggers. The current liblog implementation does provide the ability to set the logger, but since it's task local, it's impossible to make sure the right logger is installed in each thread - think of threads started by TaskPool for example. Having thread-local loggers also results in a log of duplicated data and computation. The RUST_LOG environment variable has to be reparsed every time a program logs on a new thread, for example. In addition, it's not totally clear to me that anyone actually wants to have different loggers on different threads. Even if one does, it's pretty easy to make a thread local logger implementation for the logging facade.
  • The ndebug cfg is checked only in debug! and trace!, not in log! as well. I'm not sure what the right thing to do is here, but it seems like completely forbidding the use of the debug and trace levels with optimizations enabled is over-aggressive, especially since the global maximum log level check makes them pretty cheap when those levels are disabled. We may want to have debug! and trace! always enabled, and have separate debug build only versions.

Interesting notes/design decisions

  • The logger may only be initialized once, and isn't torn down when a program exits. It's possible to support logger replacement and teardown with atexit but it would require storing the logger in an Arc and adding a couple extra atomic reads and writes every time a message is logged. I'm not sure what the right answer is here. I don't really think people will want to swap their loggers out dynamically, but cleanup at exit seems like a nice thing to do to avoid spamming Valgrind.
  • Log messages generated when the logger is not initialized are ignored. Some logging facades like slf4j fall back to a simple logging implementation that dumps messages at the higher levels to the console. We could do this, but I'm a bit hesitant to do something that'll clutter the console for programs that don't want logging information as well as slow them down.
  • A logger has to be manually installed. slf4j can use some reflective classpath scanning magic to automatically set itself up to use common logging frameworks if they're present on the classpath, but we don't really have that ability. We may be able to do some dark magick with weak linkage, but I think that explicit is better than implicit here.

Remaining problems/blockers

  • Since the logger has to be manually initialized, it becomes very hard to use when running tests. You could make a Once and use that to initialize the logger in every test, but that adds a huge amount of pain to adding new tests. I think the best solution here is to augment rust's test support to add something like Go's TestMain function or junit's @BeforeClass annotation to allow for global setup before any tests run. This is useful in other contexts as well for other global resource setup.

Thoughts?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions