//# Logging.h: Send, record, and filter informational messages //# Copyright (C) 1996,1997,2004 //# Associated Universities, Inc. Washington DC, USA. //# //# This library is free software; you can redistribute it and/or modify it //# under the terms of the GNU Library General Public License as published by //# the Free Software Foundation; either version 2 of the License, or (at your //# option) any later version. //# //# This library is distributed in the hope that it will be useful, but WITHOUT //# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or //# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public //# License for more details. //# //# You should have received a copy of the GNU Library General Public License //# along with this library; if not, write to the Free Software Foundation, //# Inc., 675 Massachusetts Ave, Cambridge, MA 02139, USA. //# //# Correspondence concerning AIPS++ should be addressed as follows: //# Internet email: aips2-request@nrao.edu. //# Postal address: AIPS++ Project Office //# National Radio Astronomy Observatory //# 520 Edgemont Road //# Charlottesville, VA 22903-2475 USA //# //# $Id$ #ifndef CASA_LOGGING_H #define CASA_LOGGING_H #include #include #include #include #include #include namespace casacore { //# NAMESPACE CASACORE - BEGIN // // // // Send, record, and filter informational messages. // // //
  • General Casacore utility classes, such as String. // // // // // Logging, as in "log book", or "processing log." // // // // The classes in the logging module have two essential purposes: //
      //
    1. 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 //
    2. 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 // cerr (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: //
      //
    1. What content do you want the message to have; and //
    2. what priority does the message have (DEBUGGING, NORMAL, WARN, SEVERE). //
    // Schematically, application programmers would use the logging system as // follows: // // #include // ... // 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); // ... // } // } // //
      //
    1. 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). //
    2. Set the message and the new line number (optional but encouraged) and // post it. //
    3. 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. //
    // // // 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 &data() {return data_p;} // 4 // const Array &data() const {return data_p;} // 5 // private: // 6 // Vector 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. //
    //
    // // //
      //
    1. Attaching informational messages to datasets to describe their processing // history. //
    2. 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. //
    //
    // //
  • More filtering options? // // } //# NAMESPACE CASACORE - END #endif