-
Notifications
You must be signed in to change notification settings - Fork 35
Lumberjack
Lumberjack is a reusable log manager. It offers a single point of control for the desired verbosity.
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 Logger
s 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.
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. It correspondes toLOG_DISABLED
-
1
: Onlylog.info()
are enabled. It correspondes toLOG_INFO
-
2
: Bothlog.debug()
andlog.info()
are enabled. It correspondes toLOG_DEBUG
Logger log(NoelleLumberjack, "JohnDoe");
// now, you can edit the JSON file and override the verbosity for "JohnDoe"
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 objects follow lexical scope, that is, are closed by their destructors. A guard let you defer a log to the end of the object lifetime. Sections provide the onExit()
method too.
log.info() << "Start\n";
auto g = log.guard();
g.onExit(LOG_INFO, "End\n"); // called by dtor
log.info() << "Message 1\n";
{
auto s1 = log.namedSection("Details");
log.debug() << "Message 2\n";
{
auto s2 = log.indentedSection();
s2.onExit("Goodbye");
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"; // avoid this, use a guard instead
with verbosity level 2 (debug+info) will produce:
JohnDoe: Start
JohnDoe: Message 1
JohnDoe: Details: Message 2
JohnDoe: Details: Message 3
JohnDoe: Details: i32
JohnDoe: Details: Goodbye
i32
JohnDoe: Details: Message 4
JohnDoe: End
and with verbosity level 1 (info only) will produce:
JohnDoe: Start
JohnDoe: Message 1
JohnDoe: End
Notice how the Guard
object g
will print Exit
irrespective of where we leave its lexical scope.
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 if possible
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 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.
Note: Don't write side-effectful lambda to be used with the logger! You don't want the logic of the program to change with the verbosity, do you..
.. 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());