Module Logging
Changes made in the current development cycle can be
found in the changelog.
Logging.h: Send, record, and filter informational messages.
Prerequisite
- General AIPS++ utility classes, such as String.
Review Status
- Reviewed By:
- wbrouw
- Date Reviewed:
- 1996/08/21
- Programs:
- Demos:
- Tests:
Etymology
Logging, as in "log book", or "processing log."
Synopsis
The classes in the logging module have two essential purposes:
- To attach processing logs to datasets to retain a permanent history of
informational messages that describe how the dataset arrived at its
present state; and
- To inform the user about the progress and decisions made by various
algorithms, such as those used in the Measures system.
The two fundamental classes in the Logging module are the
LogMessage and
LogSink classes.
However, the class which is most of interest to application programmers is
the LogIO class since it forms the usual
interface to logging.
A LogMessage consists of an informational message tagged with the
time, a priority (DEBUGGING, NORMAL,, WARN, or
SEVERE) and the source code location of the origin of the message
(for use in debugging primarily).
The LogSink is used to send the LogMessage to its
destinations. Usually the message will be sent to both a global sink,
intended for user information (e.g., a GUI window), and to a sink associated
with the dataset(s) which are being modified. In practice, the application
programmer does not need to worry about where the global messages go, that
policy is implemented by the Tasking system. In practice, global messages
will be sent to Glish, where they will appear in a GUI, unless Glish is
not available, in which case SEVERE messsages (only) will be sent to
stdout. However, the principle is that "ordinary" application programmers
shouldn't worry about the details of the global sink - they should just
use it.
A LogFilter can be used to filter
messages (based on priority only at the moment) before they are sent to
their appropriate sink(s).
The LogIO class puts an ostream like
interface on top of the loggins system. Basically, the application
programmer just has to create messages using and os << items
type interface.
The first issue that the application programmer has to decide is whether
to use logging at all, or if instead he should put his messages into a
String or an ostream. It is
never wrong to use log messages, however it is reasonable for low level
classes to use Strings or ostreams, since the
caller of that class will have the opportunity to put the text in a log
message if he decides that's the most appropriate thing to do. Note that
it is always wrong to write directly to cout or (other
then for debugging) - use an ostream, so the caller can replace
it with, for example, an ostringstream.
Once you decide to use logging, the application programmer only has
to decide at every location he wants to log:
- What content do you want the message to have; and
- what priority does the message have (DEBUGGING, NORMAL, WARN, SEVERE).
Schematically, application programmers would use the logging system as
follows:
#include <aips/Logging.h>
...
void MyClass:myFunction(LogIO &os)
{
os << LogIO::NORMAL << LogOrigin("MyClass", "myFunction()", WHERE); // 1
...
os << WHERE << "An informative message") << LogIO::POST; // 2
if (error()) {
os << WHERE << LogIO::SEVERE << "Error!" << LogIO::POST; // 3
sink.post(msg);
...
}
}
- Set up the location where log messages come from. WHERE will expand
into the file name and line number (useful for debugging). Set the
priority to NORMAL (this is the default, but you don't know what
the state of os is when it is passed in to this function).
- Set the message and the new line number (optional but encouraged) and
post it.
- Change the priority to SEVERE and post an error message.
When a dataset is created from several other datasets, their input
"histories" should be merged if possible. This can be done if the
local log sink is in fact a Table. The way you do this is by interrogating
the local sink to find out if it is in fact a TableLogSink. If it is, you
can use a concatenate method of TableLogSink. Schematically this would be
implemented as follows in some DataSet class that has a logSink method that
returns a LogIO reference:
void merge(DataSet &out, const DataSet &in1, const DataSet &in2) {
... copy the data from in1 and in2 to out
if (out.logSink().localSink().isTableLogSink()) { // can write to out
if (in1.logSink().localSink().isTableLogSink()) {
out.logSink().localSink().castToTableLogSink().concatenate(
in1.logSink().localSink().castToTableLogSink());
}
if (... the same for in2 ...)
}
Of course, DataSet might provide some convenience function for merging
histories. However the point is that given a sink, you can safely determing
whether or not it is in fact a TableLogSink, and if it is you can call
its concatenate function, which takes another TableLogSink.
Example
The following example code is checked into the system as
dLogging.cc. It is found in the Logging test directory.
class DataClass
{
public:
DataClass(const IPosition &shape, const LogSink &sink); // 1
void set(Int toWhat); // 2
LogIO &sink() return os_p;} // 3
Array<Int> &data() {return data_p;} // 4
const Array<Int> &data() const {return data_p;} // 5
private: // 6
Vector<Int> data_p; // 7
LogSink log_sink_p; // 8
LogIO os_p; // 9
};
This toy class is meant to represent one which is to have "attached" logging
information. Generally, these classes would be fairly high level
astronomical classes, e.g. Image, not Array. Note that
only operations which change the data should be logged in the internal log.
Operations which only read the data should be logged either globally, or in
the class that is taking the results and modifying its own data.
- 1.
- Depending on the application, the LogSink to be used might
either be handed in to the class, as is the case here, or it might
be created by the class. For example, a MeasurementSet
will have a processing log table with a known name.
- 2.
- A sample function that changes the state of the class. Here,
it just sets all the elements of the internal array to
toWhat.
- 3.
- Return the LogIO that is used by this object. A member function like this
should be provided for use by global functions which manipulate the object.
Note that it is non-const --- the internal sink should be modified only
by functions which CHANGE the object, otherwise the global sink should be
used.
- 4.
- Return the internal data. Arguably this should be logged at at least
DEBUGGING level.
- 5.
- Non-const version of the above. Note that it should not be logged since
the state cannot be changed with this function.
- 7.
- The internal data member.
- 8.
- The location to which log mesages are sent.
- 9.
- The LogIO object that will be the actual interface to the logging
system.
DataClass::DataClass(const IPosition &shape, const LogSink &sink)
: log_sink_p(sink), os_p(log_sink_p) // 1
{ // 2
os_p << LogOrigin("DataClass", // 3
"DataClass(const IPosition &shape, const LogSink &sink)"); // 4
// 5
if (shape.nelements() != 1) { // 6
os_p << LogIO::SEVERE << WHERE << // 7
"Illegal Shape! Must be one dimensional." << LogIO::EXCEPTION; // 8
} // 9
// 10
data_p.resize(shape(0)); // 11
os_p << "Inital shape " << shape << "and value 2" << // 12
LogIO::NORMAL << LogIO::POST; // 13
// 14
set(2); // 15
}
- 1.
- The private LogSink data member is initialized with one that
the caller provides. Note that LogSink uses reference semantics, so
that if another "copy" of the sink is made then all the log messages
will go to the same place. For example:
LogSink a("mylogtable");
LogSink b(a);
LogSink c;
c = a;
...
c.post(...); // ends up in mylogtable
...
b.post(...); // as does this
This can be useful if several classes might be modifying the same data,
or if a data is spread over several objects.
Also, os_p is intialized from the sink.
- 3.
- For a member function, the first argument to LogOrigin is the class name.
- 4.
- The next argument is the function name. You should use the full name with
arguments so that you can use the argument name in your messages. Leave
off the return type. Cutting and pasting is easier than typing!
- 7.
- WHERE is a predefined macro that gives the file name and line number.
- 8.
- Create a SEVERE level error message, post it and throw an exception.
- 11.
- This will post the message locally and globally, and then throw
an exception. Another possibility would be to call
postGloballyThenThrow() if you only wanted to send the
message to the global sink (for example, if the object is hopelessly
corrupted, or if the problem occurs in a read-only operation). The
thrown exception is an AipsError. The
post*Throw() functions will always set the priority to
SEVERE, however it doesn't hurt to show your intentions
- 12.
- Create and send a NORMAL priority message.
- 15.
- Call set() from the constructor to give the data values
an initial value.
void DataClass::set(Int toWhat)
{
os_p << LogIO::NORMAL << LogOrigin("DataClass", "set(Int toWhat)"); // 1
os_p << "Setting data values to " << toWhat << WHERE << LogIO::POST; // 2
uInt n = data_p.nelements(); // 3
for (uInt i=0; i < n; i++) { // 4
#ifdef AIPS_DEBUG // 5
os_p << LogIO::DEBUGGING << WHERE << // 6
"Setting element " << i << " to " << toWhat << LogIO::POST; // 7
#endif // 8
data_p(i) = toWhat; // 9
}
}
- 2.
- This and the previous line set up and send a normal priority log message
much as we did previously.
- 7.
- LogMessages are relatively expensive to produces and consume. Use of
them in a very tight loop should either be #ifdef'd out as
in this example, or like:
if (aips_debug_on) {
... set up and send log message ...
}
The advantage of this code is that it's always available - so, for
example, you can turn it on and off by manipulating the global variable
aips_debug_on. However very tight loops cannot even afford
this extra if, and should prefer the ifdef.
Normally the DEBUGGING messages are "boring but low-volume",
and you should just send them normally.
void square(DataClass &object)
{
object.sink() << LogIO::NORMAL << WHERE << // 1
LogOrigin("square(DataClass &object)") << "Squaring data elements" // 2
<< LogIO::POST; // 3
object.data() *= object.data(); // 4
}
This function shows how a global function that modifies an object can send
log messages to that objects LogSink using a function of that
object to get access to its sink.
float sum(const DataClass &object)
{
LogIO global(LogOrigin("sum(const DataClass &object)")); // 1
float theSum = sum(object.data()); // 2
global << WHERE << "Sum of object is: " << theSum; // 3
return theSum; // 4
}
This is an example of a global function that only reads -- does not change --
an object.
- 3.
- Since we are not changing the data object, we only post the message
globally, we don't write it to the data object's log sink. The caller
of sum() might log the message somewhere else if the return
value is used to modify data in some other object. Instead we send it
to the global sink. Here we don't POST the message ourselves, we rely
on the LogIO destructor to do it for us.
int main()
{
LogSink::globalSink().filter(LogMessage::DEBUGGING); // 1
LogSink logger(LogMessage::NORMAL, "dLogging_messages_tmp"); // 2
// 3
IPosition legalShape(1, 10); // 4
DataClass dc(legalShape, logger); // 5
// 6
square(dc); // 7
// 8
Float total = sum(dc); // 9
// 10
return 0; // 11
}
- 1.
- Change the priority of messages to display on the global sink's
filter to
DEBUGGING from the default NORMAL. The default
global sink logs to cerr. The global sink can be replaced with
LogSink::globalSink().
- 2.
- Create the sink that we are going to use. This constructor will use
a Table. If the table doesn't exist
it will be created. If it does exist, new log messages will be appended
to the end.
- 5.
- Create an object with the provided sink. The alternative strategy, which
will be used with classes like
MeasurementSet is for the object
to make it's own LogSink if it knows where it wants its
messages to go.
- 7.
- Changes the data - log messages go to its local sink.
- 9.
- Reads the data - log messages go only to the global sink.
Motivation
- Attaching informational messages to datasets to describe their processing
history.
- Informational messages to inform the user about the progress and
parameters of algorithms - for example those used for reference frame
conversions in the Measures module.
To Do
- LogFilter -- Filter LogMessages on message priority. (full description)
- LogFilterExpr -- Class to deal with a TaQL expression to filter messages. (full description)
- LogFilterInterface -- Abstract base class for filtering LogMessages. (full description)
- LogFilterTaql -- Filter LogMessages using a TaQL expression. (full description)
- LogHolderIter -- Class doing the actual iteration through an LoggerHolder. (full description)
- LogHolderIterEntry -- Class representing an entry in a LoggerHolder. (full description)
- LogIO -- ostream-like interface to creating log messages. (full description)
- LogMessage -- Informational log messages with with time, priority, and origin. (full description)
- LogMessage_ostream -- Write a LogMessage to an ostream. (full description)
- LogOrigin -- LogOrigin: The source code location of the originator of a LogMessage. (full description)
- LogOrigin_ostream -- Write a LogOrigin to an ostream. (full description)
- LogSink -- Distribute LogMessages to their destination(s) (full description)
- LogSinkInterface -- Accepts LogMessages and posts them to some destination (full description)
- LoggerHolder -- Class holding a hierarchy of loggers. (full description)
- LoggerHolderIterator -- Class to iterate through an LoggerHolder. (full description)
- LoggerHolderRep -- Representation of the class holding a hierarchy of loggers. (full description)
- MemoryLogSink -- Save log messages in memory. (full description)
- NullLogSink -- Throw away all messages. (full description)
- SourceLocation -- Helper struct to get the source line. (full description)
- StreamLogSink -- Send log messages to an ostream. (full description)
- TableLogSink -- Save log messages in an AIPS++ Table (full description)
- command -- Functions to send commands to a LogIO object. (full description)
- output -- Functions to accumulate text in the output message. (full description)