loggy
An opinionated library for developing and testing rust applications that use logging.
This is inspired by simple-logging implementation, with additional features focusing on development of applications (as opposed to libraries).
Building and Testing
To build run either cargo build
or cargo make build
.
To test, run either RUST_TEST_THREADS=1 cargo test
or cargo make test
.
Single thread testing is required due to the rust log
facade mandating
the use of a single global logger.
Examples
The main program will need to set up the logger:
extern crate loggy;
extern crate log;
set_logger.unwrap;
set_max_level; // Or whatever level you want.
To provide user control over handling issues:
extern crate loggy;
let is_issue_an_error = decide_based_on_command_line_arguments;
if did_issue_occur else
To deal with errors in processing stages:
extern crate loggy;
continue_to_next_processing_stage;
To test code that emits log messages;
extern crate loggy;
test_loggy!;
Motivation
This library was written to support the development of a non-trivial Rust application that uses logging. The functionality provided here was factored out, both to keep it isolated from the application itself, and in the hope it might prove useful to others.
Technically, this library is an implementation for the Rust log facade, with a few additional features thrown in. The implementation and features were designed to support a specific development workflow.
Features
As an implementation of the log facade, this library is pretty basic and
standard. Messages are emitted to the standard error stream. The message format
is <prefix>[<thread>]: <time> [<level]>] <module>: <message>
, where the time
may be omitted.
Additional features reflect the library's opinionated nature:
Logging Features
- Logging multi-line messages (that contain
\n
) will generate multiple log lines. The first line will include the log level in upper case (e.g.,[ERROR]
), all the following will specify it in lower case (e.g.,[error]
). The time stamp, if included, will be identical for all these lines. This makes log messages easilygrep
-able, countable, etc.
-
It is assumed that either the program is single-threaded, or, if multi-threaded, then the main thread spawns off worker threads slaved to the main one, to perform transient tasks. Therefore, the thread index is only reported for log messages generated from the worker threads.
-
In debug builds, debug messages are always emitted, regardless of the setting of the log level threshold (other messages do obey the threshold). This is under the assumption that debug builds are only used for, well, debugging. In contrast, in release builds, the threshold applies to debug messages as well. It is assumed that there would be none (or that they would be very rare), since release builds are meant for, well, release rather than debugging.
-
The format of the debug messages replaces the
<module>:
name with<file>:<line>:
. This is under the assumption that such messages would hardly ever be seen by users. Developers, on the other hand, would benefit from having the exact code location generating each message.
Development Features
-
An additional
todox!
macro is provided, which behaves exactly likedebug!
. If using thecargo todox
extension, this prevents leftover debugging messages from being inadvertently left in the code. -
An additional
note!
macro is provided, which behaves either likeerror!
orwarn!
, depending on the value of its first (additional) parameter. This boolean parameter is typically derived from a command line argument (ideally, this should be automated as well). This makes it easy to allow the users to determine whether different conditions warrant aborting the program. -
Every call to
error!
(including calls vianote!
), from any thread, is counted. Theerrors
function returns the total number of errors, and thehad_errors
function just returns whether any such calls occurred. This allows the program to easily decide on its final exit status. -
In addition, using
ErrorsScope
allows counting the errors that occur in specific regions of the code (in the current thread), also providingerrors
count and ahad_errors
test. This allows the code to easily report multiple errors from some processing stage, deferring aborting the program until the whole processing stage is done.
Testing Features
-
A
test_loggy!
macro allows creating a test for code that emits log messages. All messages (except for debug messages) are captured to a buffer. The test should useassert_log
to examine this buffer, orclear_log
to explicitly discard it. Examining the log is an effective way to gain insights and verify the behavior of the tested code. -
Setting the
LOGGY_MIRROR_TO_STDERR
environment variable to any non-empty value will cause all messages to be emitted to the standard error stream, together with any debug messages. This places the debug messages in the context of the other messages, helping in debugging.Note that the standard error contents are only reported for failing tests. Well, actually, the rust mechanism for capturing the standard error seems to not work properly when the test spawns new threads, so any debug or mirrored messages emitted from worker threads will be visible even for passing tests. This isn't a show stopper given such messages and the
LOGGY_MIRROR_TO_STDERR
variable are only used when actively debugging an issue. -
The rust
log
facade mandates using a single global logger. This, combined withloggy
handling multiple threads at once (counting errors, capturing messages), means thattest_loggy!
tests must run withRUST_TEST_THREADS=1 cargo test
. This can be automated by providing anenv
section inMakefile.toml
and runningcargo make test
from the command line, and similarly by providing anenv
section in the.travis.yml
file (both methods are used by this package).
License
loggy
is licensed under the MIT License.