ROSE
0.11.96.11
|
Controls diagnostic messages from ROSE.
ROSE uses the free Sawyer library to emit diagnostic messages for events such as debugging, program tracing, informational messages, warning and error messages, progress reports, and runtime logic assertions. The Sawyer message support can be found in the Sawyer::Message name space in "Sawyer/Message.h", and the run-time logic assertions can be found in the Sawyer::Assert name space in "assert/Assert.h" (which also defines a number of C preprocessor macros whose names begin with "ASSERT_".
Sawyer supports multiple instances of messaging facilities (Sawyer::Message::Facility or Rose::Diagnostics::Facility) each of which defines a std::ostream
object for each of a number of message importance levels. ROSE defines one global library-wide facility, Rose::Diagnostics::mlog whose string name (used in output) is simply "rose". Software layers within ROSE may define their own facilities and give them names indicative of the software layer, like "Rose::BinaryAnalysis::StackDelta. Tools that use ROSE can also define and register their own facilities. All these facilities are then grouped together into a single Sawyer::Message::Facilities object, Rose::Diagnostics::mfacilities, so they can be controlled collectively or individually from the ROSE command-line (e.g., the frontend
call).
ROSE looks for the command-line switch (e.g., "-rose:log" for source analysis tools or "--log" for binary analysis tools) that takes one argument: what. If what is the word "help" then usage information is displayed; if what is the word "list" then log settings are displayed. Otherwise, what is expected to be a string to pass to the Rose::Diagnostics::mfacilities.control function. See the output from any tool that supports "--log help" for details about the syntax.
Note: this section is about adding a new logging facility to the ROSE library proper. Adding a facility to a tool that uses ROSE is simpler and is described in a later section.
As an example, let's say that a programmer wants to convert the BinaryLoader class to use its own logging facilities. The first step is to declare a static data member for the facility. Adding a logging facility to a namespace or file is similar except the mlog
would be declared in the namespace or as a file-scope (static or anonymous namespace) variable.
If we consistently name message facilities "mlog" then any unqualified reference to mlog
in the source code will probably be the most appropriate (most narrowly scoped) facility. Adding a new facility to a software component that already uses the rose-wide "mlog" is mostly a matter of defining a more narrowly scoped mlog
, and the existing code will start using it (you probably also need to search for places where mlog
usage was qualified). Specifically, we avoid using "log" as the name of any logging facility because it can conflict on some systems with the logorithm function log
.
The second step is to define the mlog
static variable. Since the variable is statically allocated, it must be constructed only with the default constructor. Sawyer also has other Facility constructors, but these generally can't be used for static variables because there is no portable way to ensure that the C++ runtime is initialized before the Facility initialization happens. If the initializations occur in the wrong order then bizarre behavior may result, including segmentation faults in STL container classes.
The third step is to initialize the mlog
static variable after we're sure that the C++ runtime has been initialized. We can do this by defining a class method (a.k.a., static function member) that can be called from ROSE's initialization phase. This is the point at which the mlog is given a name and connected to a destination(s). The name is usually the fully qualified name of the component for which the mlog
object serves as the logging facility, and may contain typical C++ symbol names, dots, and "::". This name is used to enable/disable the facility from the command-line, and will show up as part of the output for any message emitted using this facility. It is recommended that you also provide a comment in order to help people that are not ROSE developers to use ROSE-based tools. The comment should start with a gerund (verb ending with "ing"), be uncapitalized, and unpunctuated.
The second, optional half of this step is to register the facility with the ROSE library so it can be controlled from the command-line. Although unregistered facilities are still useful, components of the ROSE library should almost always register their facility. The easiest way to do this consistently is to use initAndRegister like this:
If the class, namespace, or file containing you initDiagnostics
function is conditionally compiled (e.g., binary analysis is not always enabled in ROSE), you'll want to add a dummy version of initDiagnostics
that does nothing (preferrable to using conditional compilation in the next step).
The fourth and final step is to add a call to BinaryLoader::initDiagnostics
from Diagnostics::initialize. This function is defined in "src/roseSupport/Diagnostics.C". You probably don't need to include your entire header file in Diagnostics.C; a declarations should be sufficient and faster to compile.
Tools that are built on top of the ROSE library can use the same Sawyer messaging support, and the tool's Facility objects can be registered with ROSE and thus controlled along with the library's facilities. Doing so is even easier than using the facility in part of the ROSE library: declare the facility, and initialize and register it. Tools can create as many facilities as they like, although the description below assumes only one.
The facility is usually declared as a static object so it can be available to all parts of the tool. As with static facilities in the ROSE library itself (described above), it should be only default constructed. Also, instead of using namespace Rose::Diagnostics, the tool can optionally use namespace Sawyer::Message::Common. This avoids the ambiguity for mlog
that would be created by importing Rose::Diagnostics or Sawyer::Message (the types and enums in these two namespaces are equivalent).
The second step, initialization and registration, is usually near the beginning of main
right after initializing the ROSE library but before command-line processing.
If you want to globally adjust which levels of diagnostics are output by default (later modified by the command-line) you can do that by passing a string to the same function that's used by the command-line processing, such as:
Although initialization is required, the registering step is optional. Registering causes the tool's diagnostics to be conrollable from ROSE's command-line switches. If you want to only initialize and not register, then use this:
When using a message facility within the ROSE library source code, it is generally desirable to have a "using namespace
Rose::Diagnostics" in effect. Not only does this alleviate the need to qualify the important levels (e.g., INFO
, WARN
, etc), but it also brings Rose::Diagnostics::mlog, the library-wide logging facility, into scope. Doing so will allow any software component to perform logging using the library-wide facility, and once a more local mlog
is declared the more local symbol is automatically used.
When using a message facility within a tool, importing the Sawyer::Message::Common instead of Rose::Diagnostics will prevent an ambiguity between the tool's global mlog
and Rose::Diagnostics::mlog. You can do which ever you prefer.
Regardless of whether you're writing a ROSE library component or a tool, all one needs to do to use a facility is to write a C++ std::ostream-style output statement whose left-hand operand is a Sawyer::Message::Stream. Since streams are held in a Sawyer::Message::Facility and since facilities usually have the C++ name "mlog", the statement will look something like this:
The default configuration will cause the message to be emitted on the standard error stream along with information about the name of the executable, the thread ID, the time in seconds relative to the start of the process, the name of the facility, and the message importance. For example:
ROSE also defines a global macro mprintf
that is a drop-in replacement for printf
. This macro uses whatever mlog[DEBUG]
is in scope. The mfprintf function can be used as a replacement when a specific logging stream is desired.
Sometimes one wants the right hand side of the output statements to be evaluated only in certain circumstances for performance reasons, and there are multiple ways to do that. If you already use conditional compilation or "if" statements to generate diagnostic output, continue to do the same. You can also use an "if" statement whose condition is the diagnostic stream since streams evaluated in a Boolean context return true or false depending on whether the stream is enabled or disabled:
Sawyer also has a SAWYER_MESG() macro that's used like this:
Another thing you can do is construct a new locally-declared stream with a shorter name. Some parts of the ROSE library do things like this:
Not only does this reduce typing a little, but since the function is using its own private message stream, partial messages emitted to that stream won't interfere with partial messages emitted to mlog[DEBUG]
by called functions (see next section).
Another debugging trick is to create a local stream like above, and then explicitly enable or disable it based on some condition you're trying to debug:
A partial message is any message for which the linefeed, "\n", hasn't been inserted. If a message stream is unbuffered and another message is emitted to a different stream and ends up going to the same ultimate destination (e.g., standard error), then any previous partial message is interrupted, usually by appending "...", and re-emitted later when more text is added.
Since each message stream creates messages sequentially in an incremental manner, we sometimes want to "save" a partial message, allow other messages to be emitted from the same stream, and then complete the original message later. This most often happens when one function creates a partial message, and then calls another function that might use the same stream before the partial message is completed. The best way to accomplish this is by creating a new, temporary message stream and moving any partial messages to the new stream. The copy constructor can be used, like the example above, but even more conveniently, if the source stream in the copy constructor has an outstanding partial message then that partial message is moved (not copied) to the newly constructed stream. This allows a partial message to be emitted in the same statement that constructs the local stream:
The documentation for Sawyer::Message has additional hints and examples.
Each diagnostic message has a prefix that contains, by default, the process ID and time, among other things. Sometimes this information should be suppressed in order to more easily compare diagnostic messages between tools. This can be done by adjusting the Rose::Diagnostics::mprefix, which is a pointer to a Sawyer::Message::Prefix object. The adjustments should be done after the ROSE library is initialized (e.g., via ROSE_INITIALIZE, normally the first thing in "main").
Diagnostic output always goes to standard error by default in ROSE and ROSE-based tools. If you want to send your tool's output to a file instead, you can do something like follows instead of the usual call to Rose::Diagnostics::initAndRegister.
Be sure to flush the file by closing it explicitly or implicitly since we didn't configure it to be unbuffered or line buffered. Failure to do any of these steps could result in your file not actually getting any diagnostic messages.
Classes | |
class | StreamPrintf |
Intermediate class for printing to C++ ostreams with a printf-like API. More... | |
Functions | |
ROSE_DLL_API void | initialize () |
Initialize diagnostics-related global variables. More... | |
ROSE_DLL_API bool | isInitialized () |
Returns true if diagnostics-related global variables have been initialized. | |
ROSE_DLL_API void | initAndRegister (Facility *mlog, const std::string &name) |
Initialize and register a logging facility. More... | |
ROSE_DLL_API void | deregister (Facility *mlog) |
Deregister a facility. More... | |
StreamPrintf | mfprintf (std::ostream &stream) |
Print to a C++ stream using a printf-like API. More... | |
Variables | |
ROSE_DLL_API Sawyer::Message::DestinationPtr | destination |
Default destination for ROSE diagnostics. More... | |
ROSE_DLL_API Sawyer::Message::PrefixPtr | mprefix |
Default line prefix for message sinks created in ROSE. More... | |
ROSE_DLL_API Sawyer::Message::Facility | mlog |
Diagnostic facility for the ROSE library as a whole. | |
ROSE_DLL_API void Rose::Diagnostics::initialize | ( | ) |
Initialize diagnostics-related global variables.
This should be called before any of the global variables in the Rose::Diagnostics name space are used. It doesn't hurt to call this more than once.
ROSE_DLL_API void Rose::Diagnostics::initAndRegister | ( | Facility * | mlog, |
const std::string & | name | ||
) |
Initialize and register a logging facility.
Initializes the specified facility by giving it a name and pointing it to the same place as the other ROSE diagnostic streams. Then register the facility with ROSE's global list of facilities so it can be controlled from the command-line. Registering a facility does not copy it–it only saves a reference to it. Therefore, either facilties should be statically declared or they should be de-registered before they go out of scope.
ROSE_DLL_API void Rose::Diagnostics::deregister | ( | Facility * | mlog | ) |
Deregister a facility.
Do this before your facility goes out of scope. In general, you don't need to deregister a Facility object from a Facilities collection when the Facility object is destroyed because Sawyer will notice the destruction automatically. However, the recognition is heuristic based on magic numbers manipulated by constructors and destructors and could potentially fail. The general safe, rule is that if a registered Facility object is destroyed other than at program exit, you should explicitly deregister it before it's destroyed.
StreamPrintf Rose::Diagnostics::mfprintf | ( | std::ostream & | stream | ) |
Print to a C++ stream using a printf-like API.
The mfprintf function is a partial function whose return value is the real function. It's argument can be any C++ std::ostream
although it's intended mainly for Sawyer::Message::Stream streams. It's used like this:
The mprintf
macro always uses mlog[DEBUG]
without any name qualification in order to resolve to the most locally defined mlog
. Therefore, a "using namespace Rose::Diagnostics" probably needs to be in effect.
ROSE_DLL_API Sawyer::Message::DestinationPtr Rose::Diagnostics::destination |
Default destination for ROSE diagnostics.
The user may set this explicitly before Rose::Diagnostics::initialize is called, otherwise that function will create a destination that points to standard error and uses the optional Rose::Diagnostics::mprefix.
ROSE_DLL_API Sawyer::Message::PrefixPtr Rose::Diagnostics::mprefix |
Default line prefix for message sinks created in ROSE.
For instance, if the library needs to create a default destination (Rose::Diagnostics::destination) then this prefix is used, and if null at that time then a default prefix is created and assigned to this variable. The user may assign a prefix before calling Rose::Diagnostics::initialize.