Skip to content

Lumberjack

Federico Sossai edited this page Sep 19, 2024 · 19 revisions

Using Lumberjack

Lumberjack is a reusable log manager. It offers a single point of control for the desired verbosity.

Overview

There are only two classes you need to know: Logger and Lumberjack.

A Logger object is used to generate log messages and refers to one Lumberjack object. A Lumberjack object dictates whether messages generated by Loggers that rely on it should appear or not. Verbosity can be set to the desired level for each Logger, independently. A Lumberjack object is bound to a JSON file that represents the single point of control for the user of Noelle.

Verbosity levels

Noelle has only one globally available Lumberjack instance called NoelleLumberjack. Verbosity levels are 1 (called info) and 2 (called debug). Verbosity levels can be edited through a JSON file located at $(noelle-config --logs) that looks like this:

{
  "default_verbosity": 1,
  "separator": ": ",
  "verbosity_override": {
    "MyLogger1": 2,
    "MyLogger2": 0
  }
}

The "default_verbosity" level applies for all loggers that are not mentioned in the "verbosity_override" dictionary.

  • 0: Logs are disabled
  • 1: Only log.info() are enabled
  • 2: Both log.debug() and log.info() are enabled

Initialization

Logger log(NoelleLumberjack, "JohnDoe");

// now, you can edit the JSON file and override the verbosity for "JohnDoe"

Messages

log.level(LOG_BYPASS) << "This message is printed no matter what\n";
log.level(LOG_INFO) << "Info message\n";
log.level(LOG_DEBUG) << "Debug message\n";

// or more concisely:

log.bypass() << "This message is printed no matter what\n";
log.info() << "Info message\n";
log.debug() << "Debug message\n";

will produce:

JohnDoe: This message is printed no matter what
JohnDoe: Debug message
JohnDoe: Info message

Sections

Sections objects follow lexical scope, that is, are closed by their destructors.

log.info() << "Start\n";
log.info() << "Message 1\n";
{
  auto s1 = log.namedSection("Details");
  log.debug() << "Message 2\n";
  {
    auto s2 = log.indentedSection();
    log.debug() << "Message 3\n";
    log.debug() << *V->getType() << "\n";
    log.debug().noPrefix() << *V->getType() << "\n";
  }
  log.debug() << "Message 4\n";
}
log.info() << "End\n";

with verbosity level 2 (debug+info) will produce:

JohnDoe: Start
JohnDoe: Message 1
JohnDoe: Details: Message 2
JohnDoe: Details:   Message 3
JohnDoe: Details:   i32
i32
JohnDoe: Details: Message 4
JohnDoe: End

and with verbosity level 1 (info only) will produce:

JohnDoe: Start
JohnDoe: Message 1
JohnDoe: End

For more involed control flows, sections can be opened and closed manually:

log.openIndentedSection();
log.closeIndentedSection();
log.openNamedSection("Name");
log.closeNamedSection();

Objects with custom print() methods

Some objects (e.g. arcana::noelle:SCC) cannot be directly printed through raw_ostream::operator<<() because they are not native LLVM types, but they might be equipped with one or both of the following methods:

  • void print(llvm::raw_ostream &stream)
  • void print(llvm::raw_ostream &stream, std::string prefix)

Lumberjack detects these possibilities, making objects printable with no additional API complications. When calling, say, log.info() << obj, the logger will automatically invoke obj.print(...) passing an appropriate prefix.

scc->print(errs(), "JohnDoe: ");  // avoid this
log.info() << *scc;               // use this instead

For printing multi-line objects that don't have a dedicated method for printing with a prefix (e.g. BasicBlock) it is recommended to hide Lumberjack automatic prefix:

BasicBlock &BB = ...;
log.info().noPrefix() << BB << "\n";

Lambdas

Lambdas are conditinally invoked. The return value of a lambda will be computed only if the message will be displayed.

int i = 0;
auto heavyLambda = [&i]() {
  i++;
  return "This string took long to compute";
};

log.debug() << "Result: " << heavyLambda << "\n";

In the example above, if debug messages are disabled, i will be zero after the call. Notice that object being passed is the lambda variable and not it's return value.

When building on Noelle ...

.. you should instantiate a new Lumberjack, so that Loggers belonging to different codebases are kept seperate and controlled through different JSON files.

Lumberjack MyProjectLumberjack("path/to/Lumberjack.json", llvm::errs());