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:
#include <casa/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); ... } }
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.
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.
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 }
LogSink a("mylogtable"); LogSink b(a); LogSink c; c = a; ... c.post(...); // ends up in mylogtable ... b.post(...); // as does thisThis 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.
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 } }
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.
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 }