----------------------------- ----------------------------- The ZOOM ErrorLogger Package ----------------------------- ----------------------------- Feature Guide and Design notes version 8 6/18/99 The following syntaxes and semantics are based on: * Original design discussions including M. FIschler, J. Linnemann, L. Sexton, and J. Kowalkowski, in Spring 1998. * Realizations occurring during implementation of features, Fall 1998. * Enhancements responding to user input and correction of flaws found during feature coverage testing, SUmmer 1999. * Enhancements responding to user experience and further requests, Summer 2000. As of 9/00 the package was fully implemented. The intent is that neither the physicist NOR THE FRAMEWORKER should need to change any code as the features get implemented, and this has been met in the enhancement cycle. In the original intention, the goal was that none of the headers these users would include were expected to change further, so user code should not be forced to recompile as more ErrorLogger features were enabled. In some cases an enhancement meant coding a new user-level feature was omitted -- there of course the header involved has to change. Various portions of these design notes are suitable for extraction as users guides: The users guide for physicists would consist of the "How the Physicist Logs Errors" sections. The guide for frameworkers would be everything up to the "Customization Hooks" chapter. Change Log: 4/7/99 MF Section 10: Additional constructor for ErrorLog("packageName"). 4/7/99 MF Section 11: Warning about not supplying an ELcout as a stream for a destination. 6/18/99 MF Section 8: ELseverityLevel constructed from string. 6/18/99 MF Section 18: Clarification that if range is entered with start higher than end, nothing happens. 6/18/99 MF Section 5: \n in items, and the nobr manipulator. 6/18/99 MF Section 13: Clarification that if no threshold is set, it is by default ELzeroSeverity. 6/18/99 MF Section 24: newlineAfterItems and newlineBeforeTime. 7/2/99 jv Section 9: automatic attachment of a destination if there is none attached by the first log. 7/2/99 jv Section 13: Default threshold for ELdestControl is ELzeroSeverity not ELwarning. 7/2/99 jv Section 15: Function that suppresses termination summary 7/2/99 jv Section 24: separateTime, separateEpilogue and includeSerial functions added 7/2/99 jv Note 10: Removed the info about ELcout default constructor 9/22/00 mf Removed all references to ELcout, other than leaving note 10 for historical purposes. 9/22/00 mf fundament. Replaced early mention of ELsaveBuffer with the discussion of the ELcollected and ELerrorList classes which obviate it. 9/22/00 mf section 9: Replaced ELsaveBuffer with a discussion of ELcollected and ELerrorList. 9/22/00 mf section 13: Added section 13.5: Filtering by Module. 9/22/00 mf section 21: ELsaveBuffer Local Semantics becomes ELErrorList Local Semantics, which are much simpler. 9/22/00 mf section 26: ELsaveBuffer Semantics and layout is removed. 9/22/00 mf The following features were added after user experience, and are documented on the web pages. We have not modified these design notes to reflect these new capabilities: (1) Filtering by module (2) Collective error logging via ELcollected (3) The ELerrorList destination (obviating ELsaveBuffer) 11/1/00 mf Mods reflecting the above capabilities. 1/15/01 mf Another new capability: per-destination setLineLength(). 2/13/01 mf Another new capability: squelching "ErrorLog Established" 3/15/01 mf Three new capabilities: (1) statisticsMap (2) discardThreshold (3) hex output 4/5/01 mf Enhancement of filerModule capabilities: ignoreModule, respondToModule. 10/19/01 mf Section 5: Eliminate the nobr manipulator. 2/11/03 mf Section 9: getELdestControl 2/11/03 mf Section 13.8 and 2.5: Establishing Debug Verbosity Levels 2/11/03 mf Section 5: Message format contains time zone. 6/23/03 mf Section 7: moduleName() and subroutineName() 6/24/03 mf Section 23.8: changing ostream for an ELoutput Contents -------- Fundamentals Purpose and Scope Design Considerations Classes and Concepts ErrorLogger Semantics and Syntax How the Physicist Logs Errors 1) How the Physicist has access to the logger 2) How a physicist issues a log message 2.5) Messages for Debugging 3) How a physicist can issue a log message in multiple statements: 4) How an instance of ErrorObj may be formed 5) What will the error message output look like 6) What summary information will be available 7) How the physicist can indicate what is being done 8) A list of the available severity levels Frameworker's Basics of Setting up and Using Error Loggers 9) How the frameworker sets up for logging 10) How the Module (or Package) Sets Up errlog 11) Global ErrorLog Instantiation 12) ELcontextSupplier: How the run and event numbers are indicated: 13) Basics of Thresholds and Limits 13.5) Filtering by Module or multiple modules 13.7) DiscardThreshold 13.8) DebugVerbosityLevel 14) Controlling abort behavior 15) Obtaining error statistics summaries 16) Clearing statistics and/or limits Further Options for the Frameworker 17) Checking on errors 18) Error counts 19) Traces and control over whether traces are logged 20) Time span in conjunction with limits 21) ELerrorList Local Semantics 22) Accessing Saved Error Messages 23) How ELstatistics information is kept 23.5) Access to the statistics map 24) Formatting control available in ELoutput 24.5) Hex output formatting via ErrorLog Customization Hooks and Issues Relating to Collective Logging 25) What an ELdestination Must Do 26) (removed) 27) Avoiding lists, strings, streams, and templates 28) Table Limitations 29) Direct logging to one destination 30) Custom Severity Levels? Example of use of the package Technical Design Design Patterns and C++ Techniques Used Rejected Alternatives Minutiae and Notes Known Concerns Stubs and intended implementation order ****************** * Fundamentals * ****************** Purpose and Scope ----------------- The purpose of this package is: 1) to let user code issue ("log") error and warning messages; 2) to provide a uniform syntax and set of concepts for logging, across the different experiments and different groups in an experiment; 3) to provide a uniform and sensible logger behavior, in terms of information output and formatting; 4) to provide means of controlling multiple log destinations, output limitations, and other behavior; 5) to allow for (to the greatest reasonable extent) customizations tailoring to various environmental and system consideration. Equally important is the time scale for achieving this: 6) Items 1, 2 and a basic semblance of behavior 3 should be in place within a few weeks, so that physicists can begin inserting error logging into their code, without concern that those lines will have to change later. 7) At that point of release, all header files defining the interface, which would be included either by physicist code, module base classes, or the framework, should be frozen. This avoids the burden of extra compilations of user code, and guarantees stability of the interface. 8) The full behavior 3 and 4 should be in place within a month of that point. This has no particular justification in terms of coding needs, but provides a criterion by which to limit complexity of the feature set. 9) Tailoring for special environments, and other non-essential support work, may proceed after the package has been delivered. This support would include integrating traceback capabilities into the log information, and creation of ZMexception subclasses that log via this ErrorLogger mechanism. -- Purpose and Scope -- In terms of concepts explained below, a "physicist" inserts logging statements into the code. These work because a "frameworker" has set up the logger, specified one or more log destinations, and established various behavior controls. A standard form of behavior is performed when errors occur or summaries are requested. Ease and cost of use is critical: 9) Overall, if only the standard features and and basic controls are used, the package ought to be very easy for the physicist to use, clear and simple for the frameworker to set up, and not a heavy burden in terms of code to compile or executable code pulled in. A "customizer" may, by introducing derived classes, change that behavior. For instance, the formatting of the error text may be modified beyond the ways the framework controls permit; or the customization may avoid C++ features and system support assumptions that may not be desired in a Level 2 context. Having said what the package intends to do, we set some boundaries by listing issues this package does not intend to address: A) Beyond providing clear ways to derive classes for customization, the issue of implementing these customizations is not addressed. B) This package addresses issues of "collective logging," that is, coordination by some central entity of error log destinations stemming from multiple processes, in the simplest possible manner: 1. The collection mechanism leaves the details of interprocess communication completely up to the experiment. 2. A way for code to access a history of error messages is provided in the form of a (flitered) std::list of error message objects. If this list is desired, the user code is responsible for periodically examining and clearing it. Design Considerations --------------------- In the text of this section, whenever a class which is part of this package is mentioned for the first time, we will indicate this by hyphens. All classes are briefly described in the next section ("Classes and Concepts") and detailed later in this document. Let us define a "physicist" as anyone coding "on top of" some framework level, whose code may want to issue log messages. And define a "frameworker" as the person providing the means for the physicists to do things (like error logging) WITHOUT extraneous knowledge of the mechanism; the frameworker, in a sense, controls the flow of the job. The structure of the framework we have in mind is that there is a collection of classes, which for the purpose of this document we shall refer to as "modules" (D0 calls these packages). Each module is a class derived from a standard Module class; and the overall framework has some way of registering all the modules and seeing that some key method of each one is invoked when appropriate. Anything in the framework above the level of modules, or in the Module base class itself, we will say is in the realm of the frameworker; anything in methods of the derived specific modules is said to be physicist code. The fundamental idea is that the framework instantiates an --ELadministrator-- object, and through its methods attaches various forms of sinks derived from --ELdestination--. Then the base Module class will have an instance of --ErrorLog-- as a protected (or public) variable; for illustrations in this document we assume that is assigned the name "errlog." Since this is a variable at module scope, all methods of the specific module class can use errlog, but methods of different modules will be using different ErrorLog instances; that is, an ErrorLog can own information about the module. The variable "errlog" makes the logger is made available to the user, who can issue errors in a syntax like errlog (ELerror, "Too much energy") << "E = " << totalEnergy << endmsg; The frameworker can control aspects of the behavior of the logger and the individual destinations, such as limits on how may times a given message is to be output. Those fundamentals, combined with the details of what aspects of behavior can be controlled, would dictate the (simplest) design of the package, were it not for the requirement that customization be accommodated. Three possible customizations we have in mind are: 1) It should be possible to link in error logging without requiring true streams and STL streams, and without "post-initialization" use of memory allocation on the heap. (But this should not stop the normal form of the package from using these useful concepts!) 2) It should be possible to derive from the ELdestination class in such a way that things like centralized log collection can be implemented. 3) To the farthest extent practical (BUT NO FURTHER), the design should allow for customization of such behaviors as message formatting, save buffering, and so forth. It turns out that the same design philosophy helps with both of these. In areas where flexibility is wanted, we define classes that encapsulate the use and behavior of each concept, and derive from these classes that implement our intended behaviors. For example, ELdestination defines what we mean by a destination object, while --ELoutput-- and --ELerrorList-- are two specific classes derived from that base. The encapsulation classes will carefully specify: * Which methods must be implemented with behavior agreeing with our expectations; * Which methods must be present but may be "ignored" when invoked, or may safely be implemented with very different behavior than what we had in mind; * Guarantees concerning the ways the package will use the classes. Also, in doing our implementations, we will try to isolate behaviors that we anticipate a customizer may want to modify, such as the use of std::strings, from the bulk of the code, which the customizer can likely make use of. Classes and Concepts -------------------- Each of these is discussed in the Syntax and Semantics discussion but here I wish to enumerate them to orient the reader: ELadministrator Object providing methods for overall control of the error logger. This class uses the "Singleton" pattern. The framework should instance() one ELadministrator, and through it, attach various ELdestination sinks for log message to flow to. ELadministratorX ErrorLog Encapsulation of the fundamental behavior supporting issuing of messages. ErrorLogX Fundamental object supporting issuing of messages to the error logger. The ErrorLog class provided will use templating to allow the physicist to use operator<< with arbitrary classes; a different derived class might avoid templates and be more restrictive ELdestination Encapsulation of the behavior of a "sink" for the logged information to go to. ELdestinationX Derived from ELdestination: ELoutput A specific class implementing our agreed behavior and formatting for sending messages to an ostream (which could be cout, cerr, or an ofstream for a file). ELcollected A destination derived from ELouput, which invokes a user-supplied transport mechanism to send the formatted message which ELoutput would have streamed, to a central message logger. ELerrorList A destination derived from ELouput, which inserts each ErrorObj onto a std::list. User code can use methods of std::list to access the objects and/or clear the list, and can use methods of ErrorObj to get information about each message. ELstatistics A specific class, implementing storage and output of message frequency statistics and summary tables. ELdestControl An object to act as a proxy for an ELdestination which is attached to the logger, faithfully providing the full spectrum of public methods available from the destination object. ELdestControlX ELseverityLevel A class allowing for the definition of a fixed set of severity levels. The package provides the levels. Each severity level is a an instance of this class, and contains methods delivering its unique level number, symbol, and name. -- Classes and Concepts -- ErrorObj An error message object. While the ErrorObj class provided uses templating to allow operator<< with arbitrary classes, a custom substitute might provide operator<< only from an ELstring. ErrorObjX Provides implementation and non-interface material so that ErrorObj may be frozen at an early stage. ELcontextSupplier An interface for an object providing three methods (which can be equivalent) to obtain info about run, event, and other framework-wide concepts. ELstring Because quite a few methods throughout this package work with strings of characters, it is very useful to have a data type with the semantics of std::string. To allow for substitution of a different class, the interfaces and use ELstring instead; our provided classes will typedef this to std::string. ELlimitTable Class implementing the logic and table storage needed to allow a destination to decide whether or not to ignore an error based on having seen too many of that type of error. ELextendedID The combination of id, severity, process, module, subroutine. This defines one type of message, for the purposes of summary statistics. It does NOT include additional text, timestamp, or context. **************************************** * The ErrorLogger Syntax and Semantics * **************************************** How the Physicist Logs Errors ----------------------------- 1) How the Physicist has access to the logger --------------------------------------------- Both experiments have the concept of an organizational step in the course of processing, under control of some responsible person or group. One experiment calls this a "package," another a "module"; but in both cases it is implemented in code as a set of structural rules that the framework requires its various major components to follow. In particular, there is an object encapsulating each entire package (or module); and this is derived from some framework-provided base class which we will call Module. Each module has an object at scope of the Module class, which for illustration sake, we will name "errlog." The question of what the writer of the base Module class must do to establish this log object is answered below in "How the Module Sets Up errlog." So the physicist working in a methods of that module sees "errlog" as an instance of --ErrorLog--. A physicist coding for a different module will see some other instance of ErrorLog (with the same name "errlog" if things are set up as we recommend). But these are thin shells attached to a single instance of an --ELadministrator-- object that represents the actual logger. A logger can be associated with one or more destinations. Each destination represents a "sink" for error information; it is a class with methods to accept and deal with error message information, and to allow the frameworker to control various behavior aspects. In any event, the frameworker has established the logger and set up the destinations (possibly driven by information from a job control file). The physicist logging errors need not be concerned with the destinations which have been set up. To get the headers allowing the use of ErrorLog, the compilation unit has to include one header: #include "ErrorLogger/ErrorLog.h". However, since the physicist code is going to be part of a class that is derived from a Module base class, and the Module base sets up errlog, that will already have included the necessary ErrorLog.h. So in normal use the physicist does not have to include that line; nothing extra need be included to get access to errlog. Also, since this product is namespace protected, the user must specify that namespace zmel is in general use: using namespace zmel; ZOOM provides a mechanism to use if you wish the use of namespaces to be disabled/enabled depending on a define. Instead of "using namespace zmel": ZM_USING_NAMESPACE( zmel ) /* using namespace zmel; */ -- How the Physicist Logs Errors -- 2) How a physicist issues a log message: ---------------------------------------- errlog (ELerror, "Too much energy") << "E = " << totalEnergy << endmsg; a b c d e f Here is the breakdown of this syntax: a) The ErrorLog object has a () method which returns something you can treat like an ostream, that is, you can do << to it. It takes two mandatory arguments -- a severity and a message ID. The presence of these arguments has the effect of saying that this is the start of an error message. b) ELerror is one of the ErrorLogger severity levels. They all start with EL (they are listed below). They are all instances (provided by the ErrorLogger package) of the class --ELseverityLevel--. Invoking this operator() of the ErrorLog object has the effect of starting a new error message, and establishing the severity of that error. c) The second argument is a string (or char*), and determines the message identifier (ID). Although the entire content of the string will go into the output text, the message ID is considered for statistics and limits purposes to be the leading 20 characters of this string, padded with trailing blanks. d) There follow an arbitrary number of further outputs to the message. These will together form an informational string, which most destinations will print after the ID, with suitable line-break insertions. These further outputs are optional. e) Notice that the further arguments need not be strings (although you could, if you wish, format them all up into one string by using a stringstream). In the example, the double totalEnergy is put to the stream. Any data type or class that could be streamed to cout can be sent to the log message in this way. f) The endmsg at the end of the message is treated specially, and indicates that the message is over. Only one endmsg should be placed in a message; the user is free to insert explicit "\n" characters and/or endl should control of multiple lines be desired. See Note 1 about endmsg. An alternative to doing log (sev, id) is to use the provided ERRLOG macro: ERRLOG ( sev, id ) equivalent to errlog ( sev, id ) << __FILE__ <<":" << __LINE__ << " " This assumes that the ErrorLog available is named errlog; a second macro is ERRLOGTO ( logname, sev, id ) equivalent to logname ( sev, id ) << __FILE__ <<":" << __LINE__ << " " -- How the Physicist Logs Errors -- 2.5) Messages for Debugging: ---------------------------- An alternative way to issue a message is to provide to errlog(), instead of a severity and id, a single integer representing a debugging level. errlog (3) << "Some key information" << endmsg; If the framework for the job has set the debug verbosity level to this value or higher, then the message will emerge. But if the verbosity level is lower than the integer provided -- and by default if the framework does nothing the level is considered to be zero -- then the message will be ignored with little runtime overhead cost. -- How the Physicist Logs Errors -- 3) How a physicist can issue a log message in multiple stamements: ------------------------------------------------------------------ errlog (ELerror, " "Too much energy") << "E = " << totalEnergy; if ( condition ) { errlog << "more stuff"; } errlog << "yet more stuff" << endmsg; g h g) The ErrorLog object also has a direct << method. This streams more into its message string, without declaring a new error or establishing a severity level. Thus you may continue to build up the message, by having repeated lines which do not use endmsg until the last one. h) Although key error information (id, severity, timestamp, context, and so forth) is captured immediately when the log () method is done, the --ErrorObj-- object remains "open" to additional data until either the endmsg is encountered, or some other invocation of errlog () occurs. If the physicist forgets to start a new message with log (severity, id) and instead then does errlog << stuff, then assuming the previous message was closed off with an endmsg (or this is the first one for this log), a new message will be opened. The severity assigned to such a message will be ELunspecified. An error message is dispatched to each of the potential logging destinations when it is closed. If upon termination there is still an active message being issued, the destinations and ELadministrator will terminate it, thus sending it to each destination, and preventing information from being lost. See note 12: "Termination issues," for an outline of the technical steps needed to accomplish this. -- How the Physicist Logs Errors -- 4) How an instance of ErrorObj may be formed independently ---------------------------------------------------------- An --ErrorObj-- is an object representing an error message. Although normally neither the physicist nor the frameworker works with an ErrorObj directly, one can form such an object and later send it to a log. This might be done, for example, if you want to build up some history of what was done, but only log it if some ghastly condition later occurs. ErrorObj myMsg ( ELwarning, "Suspicious Pt" ); j k l errlog (myMsg); m myMsg = ErrorObj ( ELsevere, "Out of space" ); myMsg << "was doing step" << 20 ; n p errlog (myMsg); j) You can instantiate some pre-defined ErrorObj and, when and if an error happens, use it. k,l) The constructor for ErrorObj takes the two mandatory fields: severity and id. m) We support sending one of these formed messages to the log. n) ErrorObj has operator<< so you can pump further info into it, just as in the case of pumping more into the log. p) Notice in both this and the previous example we did not put endmsg. endmsg is inappropriate in ErrorObj's, and ought not to compile. When an ErrorObj is supplied to a log (as in errlog(myMsg)) this is known to be complete and the implied endmsg is supplied automatically. -- How the Physicist Logs Errors -- 5) What will the error message output look like: ------------------------------------------------ Although each possible destination may do different things with an error message, the ErrorLogger package supplies a standard --ELoutput-- destination which will output (to a stream or file) as follows: %ERLOG-w Too much energy: E = 834.750032 d0L2proc5 CTCDRVmodule CTCTRKsubr 10-Jul-1999 14:49:03 CST run=234 event=543 This is the default formatting; if some portion of the message would overrun the end of an 80-column line, it will instead be started on the next line, indenting to align with the first character of the id. A space is inserted to separate each item (each object put to the log via the << operator) from the next. The user can force line breaks by putting a \n as the first or last character in an item. (If \n appears in the middle of an item, indentation will not be done and the column formatter may introduce an unneeded new line when it thinks 80 columns would be exceeded.) The following nobr feature has been eliminated fromthe requirements because nobody was feeling its lack and implementation would be a fair amount of work: ---- The user can prevent the formatter from inserting a line break between two items by using the nobr manipulator: errlog << item1 << nobr << item2 << item3; In the above example, item1 and item2 will appear on the same line, even if that would extend past column 80. Each nobr applies only once: item3 could be placed on the next line. ---- The frameworker can control some aspects of the format produced by ELoutput (discussed below). A custom ELdestination can make more complex format changes. 6) What summary information will be available --------------------------------------------- There will be a method (normally invoked by the framework at the end of a job or run) to deliver a summary, either to one or more of the log destinations or to another ostream or file. The summary information will be a table by message type (message type means the combination of ID, process, module, subroutine, and severity) of count. If there were any occurrences of a message that were not logged anywhere due to limits, that is indicated by an asterisk. A second part supplies up to 3 example contexts for each message type: The first two occurrences, and the last one. In a third part there will be information about total counts at each severity level. Counts are given since the last clear, and also a total for the whole job. The responsibility of triggering the output of summary information belongs to the frameworker rather than the individual physicists. An illustration of the format of the summary information is given in the section on "Obtaining error statistics summaries." -- How the Physicist Logs Errors -- 7) How the physicist can indicate what is being done: ----------------------------------------------------- The physicist may OPTIONALLY declare the name of the subroutine currently executing. When an error is logged, this name will go into the message, and also into the overall statistics. The user will generally only declare fairly large steps, to avoid some overhead. errlog.setSubroutine( "myName" ); An alternative is available: If after the ID string, an item is a string of the form "@SUB=myname", then myname is treated as the subroutine name, regardless of what setSubroutine() has set up. errlog ( ELsevere, "Bank Confusion" ) << "@SUB=prepare_IO" << endmsg; In most frameworks, the framework will automatically set up the module (package) and in some frameworks, the framework may automatically set the subroutine at key points. The physicist can find out the name of the module and/or subroutine: std::string modname = errlog.moduleName(); std::string subname = errlog.subroutineName(); 8) A list of the available severity levels: ------------------------------------------- Each severity level has a corresponding ELseverityLevel object, for example, ELwarning. Each such object has two relevant behaviors: a) Methods getSymbol() and getName() to get its symbol and full name respectively; the destinations will use these methods to prepare text. b) There is a comparison to ask whether one ErrorSeverity is more severe than another. So the concept of logging everything above a certain severity is meaningful. The ErrorSeverity objects -- instantiated globally by the package -- are: Severity object Symbol Full name Intention --------------- ------ --------- --------- ELzeroSeverity -- -- used for thresholds only ELincidental .. .. flash this on a screen ELsuccess -! Success report reaching a milestone ELinfo -i Info information ELwarning -w Warning warning ELwarning2 -W Warning! more serious warning ELerror -e Error error detected ELerror2 -E Error! more serious error ELnextEvent -n Next advise to skip to next event ELunspecified ?? ?? severity was not specified ELsevere -s Severe future results are suspect ELsevere2 -S Severe! more severe ELabort -A Abort! suggest aborting ELfatal -F Fatal! strongly suggest aborting! ELhighestSeverity !! !! used for thresholds only The frameworker can control whether declaring severe, abort, or fatal errors actually will abort the job. The intentions listed for all the error types are only advisory; it is up to the framework to do what the experiment intends. If no abort threshold is set, then even ELfatal errors will not automatically abort the job. ELzeroSeverity and ELhighestSeverity are not supposed to be used in forming error messages but ae available to the frameworker when setting up various thresholds. An ELseverityLevel may be constructed from a string (or char*). This allows a user or framework to accept run-time input specifying what severity to assign to a given possible error. ELstring input; cin >> input; ELseverityLevel mysev (input); // ... errlog (mysev, "this error"); The options accepted for a given level are its symbol, its full name, the name of its severity object -- these are as listed in the above table -- and all-caps names: "ZERO" "INCIDENTAL" "SUCCESS" "INFO" "WARNING" "WARNING2" "ERROR" "ERROR2" "NEXT" "UNSPECIFIED" "SEVERE" "SEVERE2" "ABORT" "FATAL" "HIGHEST" The translation used is case-sensitive. If no match is found, then ELunspecified will be used. Frameworker's Basics of Setting up and Using Error Loggers ---------------------------------------------------------- 9) How the frameworker sets up for logging ------------------------------------------- The ErrorLog objects that modules set up and that users see require that an ELadministrator exist. This encapsulates all the framework control over how logging is done; in particular, the ELadministrator holds the information as to what destinations should be used, and the way to get context information when needed. The framework, outside the context of any ordinary module, should get an instance by invoking the ELadministrator::instance() method. It will then use methods of this to establish how to get context information, and to attach various ELdestination sinks. So first the framework instantiates the ELadministrator: #include "ErrorLogger/ELadministrator.h" ZM_USING_NAMESPACE( zmel ) /* using zmel; */ ELadministrator * logger = ELadministrator::instance(); Notice that the Singleton pattern returns a pointer to the single instance. Next the framework provides the ELcontextSupplier. This is done by creating a class derived from ELcontextSupplier and overriding the pure virtual methods such as context(). An instance of this class is passed to logger.setContextSupplier(). MyContextObj contx; // See below for how the derived context // supplier looks. logger->setContextSupplier (contx); Providing a context supplier is optional; it provides a way for the logger to get at the run and event numbers when an error is logged. If no supplier is provided, no run/event information will be associated with messages. Further details are given below in "ELcontextSupplier: How the run and event numbers are indicated." The frameworker may also declare the name of the overall process (or job, or node in a farm situation). When an error is logged, this name will go into the message. This could be used to distinguish among many cooperating nodes which share the same output for destinations. logger->setProcess( "PCfarm81" ); -- Frameworker's Basics -- Having instantiated logger and told it how to get context information, the framework must attach one or more destinations -- sinks for the messages and statistics to be sent to. An error logger with no sinks is probably useless. The logged information can go to each destination attached to the logger. But not every message will be acted on by every destination; each is subject to thresholds and limits, as discussed below in "Basics of Thresholds and Limits." Each destination must be derived from ELdestination. Four classes derived from ELdestination are provided by the ErrorLogger package: --ELoutput-- is constructed taking an ostream, and is the typical way to associate a log with either cout, cerr, or an ofstream for a file. It has two forms of constructors: ELoutput xx(ostream &); // An ostream (e.g. cerr). ELoutput xx("xfile"); // A file name. // This is mostly a convenience, since // one could have supplied an ofstream. // See Note 9: "ELoutput (fileName)" // for details. --ELcollected-- may be constructed taking an object of class derived from ELsender, which implements the framework's chosed transport mechanism. A frameworker in a multi-procss job could put one or more ELcollecteds on the list to route messages to one or more collection points. --ELerrorList-- may be constructed taking a list of ErrorObj's. A frameworker could put an ELerrorList to be able to have code periodically examine any ErrorObjs that were logged. --ELstatistics-- is a table of message frequencies and sample contexts, which contains methods for generating run summaries. It may also have an associated ostream, to which unreported statistics would be sent when a job terminates. The usual framework will use ELstatistics and one or more ELoutput sinks. To attach a destination, the frameworker must instantiate it, and attach the destination to the ELadministrator. Attaching the destination returns an --ELdestControl-- object, which is a handle for controlling the behavior of that destination. If no destination is attached to the ELadministrator then one is attached automatically to "cout" when the first error is logged. ELoutput logfileD("myfile.log"); ELdestControl logfile = logger->attach( logfileD ); Later, the ELdestControl may be used to control the behavior of this destination, as in logfile.setLimit("*", 20); Sometimes it is desirable to modify the behavior of a destination, from code other than the method which attached that destination to the logger. In that case, it is likely that the ELdestControl handle will have gone out of scope. To be able to recover this handle, there is an optional second aregument to attach, meant to take the name of the destination. ELdestControl logfile = logger->attach( logfileD, "logfile" ); Subsequently, code can recover a handle to logfileD: ); // in later code, where that logfile has been lost ELdestControl logfile bool handleFound; handleFound = errlog.getELdestControl ("myLogFile", logfile); if (!handleFound) { ... oops, wrong name ... } A bit about object ownership and such is given in note 5: "ELdestControl details." The important thing to know is that all actions that apply to the ELdestination class can be invoked off the ELdestControl; it is a faithful proxy. Such methods include setting limits, thresholds, and time spans, triggering summary output, and even directly sending an ErrorObj. -- Frameworker's Basics -- Sometimes, a framework may wish to permanently or temporarily "turn a destination off." This can be done by logfile.setThreshold(ELhighestSeverity) A typical example of the entire setup code in a framework, with a log to a file, one to the screen, and a save buffer, might be (see note 8 about includes): ZM_USING_NAMESPACE( zmel ) /* using zmel; */ ELadministrator * logger = ELadministrator::instance(); class MyContextObj : public ELcontextSupplier { public: ZM_COVARIANT_TYPE(ELcontextSupplier *, myContextObj *) clone() const { return new myContextObj( *this ); } ELstring context () { ostringstream ost; ost << "run= " << getRun() << " event= " << getEvent(); return ELstring(ost.str()); } ELstring summaryContext () { ostringstream ost; ost << getRun() << "/" << getEvent(); return ELstring(ost.str()); } ELstring fullContext () { return context(); } } MyContextObj contx; logger->setContextSupplier (contx); ELdestControl logcerr = logger->attach ( ELoutput(cerr) ); ELdestControl logfile = logger->attach ( ELoutput("myFileName.log") ); ELdestControl logstats = logger->attach ( ELstatistics() ); std::list theList; ELdestControl errbuf = logger->attach ( ELerrorList(theList) ); logfile.setThreshold(ELwarning).setLimit("*", 10); logcerr.setThreshold(ELinfo).setLimit("*",20); logstats.setThreshold(ELwarning); errbuf.setThreshold(ELerror); // See "Basics of Thresholds and Limits" Note that although many modules may instantiate many ErrorLog objects, only one instance of ELadministrator actually will be formed. This is achieved by making ELadministrator a singleton pattern; see Note 4 about "ErrorLog and the Singleton ELadministrator" for details. In this example, the last two destinations attached were the ELstatistics and then the save buffer. This is the recommended order. See "How ELstatistics information is kept" for an explanation of why ELstatistics should come after the ordinary destinations. -- Frameworker's Basics -- 10) How the Module (or Package) Sets Up errlog ---------------------------------------------- (Although in these design notes we use the CDF term "Module" for the base class that a package of physicists' codes is associated with, in D0 the same concept is called a Package.) The class for each module should allow its methods to log errors by containing a variable object of the type ErrorLog. This should be done by giving the base class Module, which all modules classes derive from, an instance of ErrorLog. The name of this object should be known to the various physicists coding parts of the module. We assume that it will be "errlog." This should be placed in the protected section, not private, so that methods of the derived actual module classes can make use of errlog. The ErrorLog instance will contain a small amount of module-specific information, and depend heavily on the ELadministrator set up by the framework. For instance, the ELadministrator will hold the list of destinations for messages sent to the log. Since there is inherently only one instance of ELadministrator, the many instances of ErrorLog will send to destinations in a coherent and unified way. After instantiating errlog, the Module class should provide a module name, to use when methods of this class issue an error message to the log. (Module classes in the framework generally have a way to know their name.) Destinations provided in the ErrorLogger package utilize the first 16 characters of this name wherever a brief module identification is wanted. Thus in the Module base class, the lines setting up the ErrorLog could look something like: class Module { public: Module(module_selection) { ... errlog.setModule( "moduleName" ); }; protected: ... ErrorLog errlog; } (An additional routine, setPackage(name), is identical in operation to setModule(name).) If this setup is used, the classes derived from Module, which contain the physicists' code, need do nothing further to be able to issue messages to errlog. An alternative constructor is available which would let a package declare at global scope an ErrorLog with a given package name: myerrlog = ErrorLog("myPackageName"); There will be only one ELadministrator for the whole process. The mechanism for seeing that the base class Module knows the name associated with the derived class that is causing it to be instantiated -- the mysterious module_selection in the above example -- is discussed in Note 7: Module Name. 11) Global ErrorLog Instantiation --------------------------------- Here, we discuss how to have an error log for code which is not underneath a module. We show how to do this whether (a) you would want to assign some universal "utility" module name to such a log, or (b) you want the module name to change depending on the actual Module that originated the call to that external "utility code." Also, we will show how to hook the cout and cerr used in the utility (or anywhere in your code) to the log, so that lines streamed to those will come out through the error logger. This should be considered a measure to avoid having to scan through large chunks of code utilizing cout or cerr, rather than a general alternative to the error log mechanism. (a) Illustrations in this document (other than this section) assume a structuring into Module classes, with each Module instantiating an ErrorLog. However, if necessary, one can instantiate a log at global scope. For programs not completely organized into Modules, this permits any piece of code to issue log messages. The penalty for working that way is that the mechanism for pulling in a different module identification string for each class where a log message might originate is bypassed: Whatever module is indicated for the global ErrorLog will be there for any use of that log. Of course, the techniques can be combined. If a framework involves several modules, plus universally available "utilities" that are shared among modules and are not encapsulated by a class of their own, the latter might use a global ErrorLog to allow them to issue error messages. (For programs involving multiple compilation units, each one can instantiate an ErrorLog, or sources which are not in the same compilation unit as the one that instantiates the ErrorLog could declare that as an extern. Because of the underlying Singleton pattern for the ELadministrator, the effect is the same.) #include "ErrorLogger/ErrorLog.h" ErrorLog errlog; main() { // The main framework ... errlog.setModule ("Framework Level"); errlog (ELsevere, "Event Sequence Bad") << "Events went from" << prior_event << " to " << event" << endmsg; ... } -- Frameworker's Basics -- (b) The problem with using a global errlog as shown above can be seen from the following scenario: Module Tracker (with name "TRACKER") calls the utility chiSqFit which is at global scope (not a member function of Tracker). chiSqFit detects a problem, and having been instrumented for logging, does errlog (ELwarning, "bad fit") << ndegrees << endmsg; where now errlog is a global scope logger which we assume has been set up with errlog.setModule("Framework Level"). This message will appear in the log as having come from module "Framework Level" rather than module "TRACKER." The frameworker may well judge that the identity of the originating module is much more important to know, particularly since the utility does have the opportunity to identify the subroutine in which the problem occurred. The solution to this dilemma lies in explicitly doing a setModule(name) to the global errlog when entering a new module. (You could conceivably automate this, by doing ::errlog.setModule in the Module code before the key function is invoked; but for simplicity we will illustrate doing this explicitly.) Thus: main() { // The main framework ... errlog.setModule ("Framework Level"); errlog (ELsevere, "Event Sequence Bad") << "Events went from" << prior_event << " to " << event" << endmsg; ... errlog.setModule("FindTracks"); // In case of use of the global errlog FindTracks.doit(event); // The FindTracks module. errlog.setModule("DoPhysics"); // In case of use of the global errlog DoPhysics.doit(event); // The DoPhysics module. ... } Notice that those setModule calls will not be necessary if all the error logging happens in places where the errlog in scope is that of the module; in that case, the automatic mechanism gets the proper module name with no extra concern for the frameworker. -- Frameworker's Basics -- 12) ELcontextSupplier: How the run and event numbers are indicated: ------------------------------------------------------------------- The run and event are printed in the text of every error message. However, the framework does not call some setEvent() method every time a new event is started. In order to avoid overhead for every event (when errors might be logged in only a tiny fraction of those events), this information is not pre-supplied by the framework. Instead, when an error is being logged, the logger asks for the run and event names at that point. It asks by invoking the context() function of the --ELcontextSupplier-- object set up for the logger. The frameworker should create a class derived from ELcontextSupplier and pass an instance of that class to the constructor of the ELadministrator, as shown above ("How the frameworker sets up for logging"). The context supplier is specified via the setContextSupplier() method of ELadministrator. If this method is not invoked, no context information will be associated with error messages, no user function massage error objects will be invoked, and no trace information will be output. An ELcontextSupplier defines the following simple interface: class ELcontextSupplier { public: virtual ELcontextSupplier* clone() const = 0; virtual ELstring context () const = 0; virtual ELstring summaryContext () const = 0; virtual ELstring fullContext () const = 0; virtual void editErrorObj(ErrorObj & msg) const; virtual ELstring traceRoutine() const; } As seen from this, the class derived from ELcontextSupplier **must** define the clone() method (a trivial recipe for this is given below) and the three virtual methods returning ELstring, though some may of those be identical to others. Although destinations are free to call for and use whichever context string they wish, the intent of the three forms is as follows: context() is the form used by the typical output-to-a-log-file destination, for example ELoutput. Here the string ought not to be too long, to avoid clutter in the log; but no limitation or truncation is imposed per se. A typical string would be "run= 1234 event= 12345". summaryContext() is the form used by ELstatistics to get a string suitable for insertion into a table. This is length-critical, and will be truncated at 16 characters in the statistics summary. A typical string would be 1234/12345. fullContext() is a form intended for use where extra info may be useful and length is not a big consideration. ELoutput can enable fullContext() instead of context() on a per-destination basis. fullContext() would typically just return context(), but another example might be "run= 1234 event= 12345 reco version 3.2" If a custom form of ELstring is used which may have a fixed length limit, the context supplier should protect against over-writing past the end of the available space. Another mandatory method is the clone() method. This, however, is just a trivial copy into new memory. The following "mantra" for writing a context supplier can be followed: class myContextSupplier : public ELcontextSupplier { public: myContextSupplier * clone() const; ELstring context() const; ELstring fullContext() const; ELstring summaryContext() const; }; // myContextSupplier myContextSupplier::clone() const {return new myContextSupplier( *this )} ELstring myContextSupplier::context() const { ostringstream ev; /* whatever code it takes to form context string in ev */} return ev.str(); } ELstring myContextSupplier::fullContext() const { return context(); } ELstring myContextSupplier::summaryContext() const { return context(); } This mantra assumes that fullContext and summaryContext are the same as the context string; it should be obvious how to put different code there. Those familair with Visual C++ on NT will note that the abouve mantra will not work, since clone() is declared to return an ELcontextSupplier* in the base class ELcontextSupplier*. Standard C++, but not Microsoft, supports covariant return types; VC++ requires exactly matching return types for virtual functions. For this purpose, ZMenvironment.h provides a macro which resolves to ELcontextSupplier* on NT: ZM_COVARIANT_TYPE(ELcontextSupplier *, myContextSupplier *) clone() const; We recommend this be used to promote portability to NT while keeping type safety wherevere possible. However, none of the functionality of the ErrorLogger package is hindered by the assumption (used on NT) that clone() returns an ELcontextSUpplier*. The remaining routines are optional and need not be supplied in the user-specific class derived from ELcontextSupplier: The editErrorObj() routine will be called when the message is started. It provides a hook to modify the module or subroutine information, the id, or any part of the message other than the actual text items (which would not have been established yet). This method is optional; the editErrorObj() method of the base ELcontextSupplier class is an inline method doing nothing. (The editErrorObj() routine also provides a backdoor by which the framework can cause a specific routine to be called every time an error message is started. It will be called exactly once for each message instantiated, and the message id, module and subroutine, and severity will be available at that point.) The traceRoutine() routine will be called for each destination that is going to log a trace (see section (19) Traces and control over whether traces are logged). It must return an ELstring, containing the text to be appended as the trace. This method is optional; if it is not present, the traceRoutine() method of the base ELcontextSupplier class provides a trace as best the package knows how. Currently, we don't know how to provide a trace, so the string returned will be empty. This may change for some or all systems in the future. (Obviously, traceRoutine() is another possible hook for a framework to tie handling routines, but since it is called only for destinations with appropriate trace thresholds, and may be called more than once for a given message, it may be unwise to use it this way.) -- Frameworker's Basics -- 13) Basics of Thresholds and Limits ---------------------------------- Some nomenclature: Thresholds and limits both apply to particular destinations. When we say "limit", we shall always mean some count of messages of a particular description, beyond which some action will cease to happen. A limit can be specified by message id, or for multiple id's in two ways: A general wild card, or all messages of some severity level. When we say "threshold", we shall always mean some ELseverityLevel, at or above which some action would happen. One can set a limit or a threshold for each individual ELdestination. To do this, you use methods of the associated ELdestControl, which we will call "dest" for these examples. The effects are as follows: Method Effect ------ ------ dest->setThreshold (severity) Suppress logging or acting on messages below this severity, for this destination. dest->setLimit (id, n) dest->setLimit (severity, n) dest->setLimit ("*", n) For this destination, don't log past n instances of any given exception id matching the specified type. In case one has established two or more applicable limits, the limit used is the most specific applicable case: Specified ID before specific severity level, and both before wild card "*". (See Note 6: "Limit Semantics" for further details.) As implied by the above chart, each ELdestination owns a threshold level (if no threshold is explicitly set, the threshold is ELzeroSeverity). Each ELdestination also owns a general limits table (indexed by severity level) and a limits table (indexed by message id). Each limits table entry contains as data the limit and the count. Notice that the ELerrorList and ELcollected are just particular destinations. That implies that filtering by severity and throttling by frequency for those are set up via setThreshold and setLimit, just as for other destinations. Also notice that ELstatistics is also a particular destination. That means that filtering by severity is set up via setThreshold. In the case of an ELstatistics destination, however, setLimit has no effect: Once an error message id gets into that table, there is absolutely no cost to incrementing its count, so a limit would be useless. -- Frameworker's Basics -- The typical framework code related to these routines might look like: stats.setThreshold ( ELerror ); // Don't keep stats on messages // with severity below ELerror. screen.setThreshold (ELincidental); // Send messages of severity ELincidental // and up to the screen. logfile.setThreshold (ELwarning); // Don't send to logfile unless severity // is at least ELwarning. logfile.setLimit ( "*", 10 ); // Generally don't log past the 10th // occurrence of a given message id. logfile.setLimit("Bank short",50); // But log up to 50 occurrences of // "Bank short" for the logfile. savebuf.setLimit( "ELwarning", 6 ); // Don't save more than 6 occurrences of // any given warning in save buffer. Although a limit conceptually may or may not have been set, and if no limit whatsoever applies, no messages will be throttled out, the threshold severity level always has a value. By default, in each individual destination, this threshold starts as ELzeroSeverity. For convenience, the ELadministrator has setThresholds() and setLimits() methods. The meaning of those is to invoke the corresponding method for EVERY destination currently attached to the logger. (That might change some limits previously established.) logger->setThresholds (severity) logger->setLimits (id, n) logger->setLimits (severity, n) logger->setLimits ("*", n) A limit can be made "infinite" by setting it to -1. Finally, two features slightly soften the concept of throttling via a limit. The first is that a count is past its limit, action does not cease completely: If the applicable limit for a given error would be L, then all if there is an excess E = count-L, instances with E/L = 2**N for any non-negative integer N, will be logged. Thus if the limit is 5, you will see numbers 1, 2, 3, 4, 5, 10, 15, 25, 45, 85, and so forth. The second concept applies when a large bunch of errors of some type is followed by a long period with no such errors. At that point the count toward the limit may be reset, so that a new cluster of these errors can again be logged. This is explained below in "Time spans on Limits." 13.5 Filtering by Module or multiple modules ------------------------------------------- Individual destinations can filter the messages they will react to by module. The typical framework code related to this routines might look like: logfile.filterModule("Tracking"); // Ignore messages except those from the // Tracking module. The model is that there is a `respondToModule' list and an `ignoreModule' list. At any given time, either the `respondToModule' list or the `ignoreModule' list may be active, but not both. Then there are two ways to call each of two methods to influence these: dest.ignoreModule("*"); Clear `respondToModule' list, and filter out all messages, except those coming from any module that is later added to the `respondToModule' list. dest.respondToModule("name"); Add this module to the `respondToModule' list, and remove it (if present) from the `ignoreModule' list. dest.ignoreModule("name"); Add this module to the `ignoreModule' list, and remove it (if present) from the `respondToModule' list. dest.respondToModule("*") Clear `ignoreModule' list, and respond to all messages, except those coming from any module that is later added to the `ignoreModule' list. Two other available methods are: dest.filterModule("a") has the same effect as dest.ignoreModule("*"); dest.respondToModule("a"); dest.excludeModule("a") has the same effect as dest.respondToModule("*"); dest.ignoreModule("a"); 13.7 DiscardThreshold --------------------- The action taken when a message is sent to an ErrorLog includes formatting, timestamping, and "shopping" the message to each destination. Even if no destination reacts to the message, this can take a fair aomount of time. The frameworker (or for that matter any user with access to errlog) can short-ciruit this by doing errlog.setDiscardThreshold (sev) The meaning is that any messages sent through the usual mechanism of errlog ( severity, id ) << items << endmsg, which have severity less than sev, will be discarded with the absolute minimum possible work. The message will not be shopped to destinations, and each operator<< (until another errlog(sev, id) is started) will simply return, doing no work. The net effect is a speedup of null-message processing by a factor of 30. The discardThreshold works like other thresholds in that if a message severity is greater or equal to the threshold, it can be reacted to. The discardThreshold takes precedence over individual destination's thresholds. The default discardThreshold is ELzeroSeverity. -- Frameworker's Basics -- 14) Controlling abort behavior ------------------------------ The higher severity levels are used by physicists to indicate that they consider this error so severe as to warrant terminating the job. However, ultimate control of this rests in the hands of the framework. The control is associated with the logger (the ELadministrator) rather than any specific destination. logger->setAbortThreshold (ELfatal); By default, the abort threshold is ELabort. If the physicist issues a log message with severity as at or above the abort threshold, UPON COMPLETION OF THE LOG MESSAGE and dispatch of that to the various destinations, the logger will terminate the job by invoking exit (severityLevel). Assuming the framework had established an atexit() handler, this would be called as the job exits. A plausible philosophy is to set the abort threshold at ELhighestSeverity, so that NO user message aborts the job. In that case, we advise periodically checking for the presence of severe errors (see "Checking on errors"). -- Frameworker's Basics -- 15) Obtaining error statistics summaries ---------------------------------------- Assuming the frameworker has attached an instance of the provided ELstatistics destination to the logger, statistics summaries may be obtained. ELstatistics has no visible reaction to being sent an error message, but places information into a table, which can be formatted and output. For the purposes of the illustrations below, we will assume that the logger has attached an ELstatistics destination, with an ELdestControl called "stats." The summary of error statistics can be obtained as a string. It can also be sent to a destination device (as a series of summary lines); ELoutput places these lines into its associated stream to output the summary. Other destinations may treat these lines differently; in particular, ELcollected and ELstatistics ignore them. To obtain the summary: stats->summary(dest); // Sends the summary to this ELdestination. stats->summary(os); // Sends the whole summary string as a char*, // to some arbitrary ostream os. stats->summary(s&); // Sends the whole summary string to ELstring s. Except when the framework supplies a string& s, the summary will be sent using char* arguments, a single line (with a fixed maximum length) at a time. Each of the above forms also has an optional last argument, containing a char* to insert into the first line of the summary string. stats->summary(dest, "summary title"); stats->summary(os, "summary title"); stats->summary(s&, "summary title"); ELoutput puts up 40 characters of this title in the first line of the summary output. (Custom destinations should protect themselves against misbehaving if sent an arbitrarily long title.) There is an additional means of having the summary sent to an ostream: If the constructor of ELstatistics is supplied an ostream argument, then when the job completes the destructor for that ELstatistics will check to see if any information has changed since the last summary request. If so, one final summary will automatically be sent to that ostream. (This is safe assuming the user does not explicitly delete the ostream before the statistics destination goes away.) If Elstatistics was constructed without specifying this terination ostream, then it will default to use cerr as this "termination ostream." To suppress the termination summary altogether: stats.noTerminationSummary(); Not every message will make it to become a summary entry: The frameworker can set a threshold for the ELstatistics destination, filtering out errors below the severity of below (say) ELerror. Also, ELstatistics can be constructed with a maximum number of error IDs it will keep track of. -- Frameworker's Basics -- The format of the summary information is that of three parts: The first part lists the errors which have occurred, with their frequencies. The second part re-lists just the identification, and supplies the contexts (that is, the run/event or up to 17 characters of the context string) of the first two and last examples of occurrences of each error. The third part lists the count of occurrences of each severity level. To illustrate: %ERLOG =============== SUMMARY === summary title ========================== Process job123a type message id sev module subroutine count total ---- -------------------- - ----------------- ---------------- ----- ----- 1 Bank Error E central tracker refine_candidate 97 97 2 Bank Error E central tracker prepare_sigma 5 5 3 Bank Error E muon tracker prepare_sigma 5 5 4 Energy overflow S CALhadron sanityChecks 1 1 5 Too many tracks w track matching start_seeds 11* 11 6 Too many stray trax w CTKtrack countTracks 32 32 7 Too many stray trax E CTKtrack checkTracks 4 4 * Some occurrences of this message were suppressed in all logs, due to limits. Process job123b type message id sev module/package subroutine count total ---- -------------------- - ----------------- ---------------- ----- ----- 8 Bank Error E central tracker refine_candidate 91 91 ... 12345 abcdefghijklmnpqrst -w abcdefghijklmnop abcdefghijklmnop 123456* 1234567 type message id Examples: run/evt run/evt run/evt ---- ------------------- ------- ------- ------- 1 Bank Error 14231/54101 14231/54103 14239/59350 2 Bank Error 14231/54215 14232/55506 14238/58190 3 Bank Error 14231/54200 14232/55606 14239/59195 4 Energy overflow 14235/60305 5 Too many tracks 14231/54101 14231/54506 14238/59190 6 Too many stray trax 14231/54164 14231/54171 14239/59195 7 Too many stray trax 14231/54100 14231/54191 14239/60012 8 Bank Error 15282/64102 15282/64106 15288/69358 12345 abcdefghijklmnpqrst ABCDEFGHIJKLMNOP ABCDEFGHIJKLMNOP ABCDEFGHIJKLMNOP Severity Number of occurences Total -------- -------------------- --------- .. 275984 512902 SUCCESS 21000 42000 INFO 2100 4200 WARNING 325 604 ERROR 4 6 ERROR! 1 1 SEVERE 1 1 The last lines of the first parts in this illustration demonstrate the maximum number of characters each field will display. A customizer is free to alter this format by providing a custom destination similar to ELstatistics. -- Frameworker's Basics -- 16) Clearing statistics and/or limits ------------------------------------- The method to clear the counts in the statistics being kept in the destination whose ELdestControl is "stats" is stats->clearSummary(); This clears both the individual statistics (kept by message ID) and the counts for the various severity levels. It would normally be used after having invoked stats->summary(..) in some form to stream the information somewhere. clearSummary() does not zero the aggregate counts for individual error IDs or for severity levels. It also does not affect any limits set, or wipe the knowledge of the message IDs from the statistics tables. (Thus there would remain a bunch of errors with counts of zero; when statistics are output these do not appear.) There is a more sweeping method: stats->wipe(); This clears everything -- counts and aggregate counts for individual ID's and for severity levels. The statistics is wiped clean, which may be relevant if memory management issues have imposed a limit on the number of message ID entries in the statistics table. The same routine also wipes out any information in the limits table. This includes values which have been supplied by setLimit() (or setTimespan()), and counts of the individual instances of each message ID. For the ELstatistics destination this is moot (since limits do not apply) but for ELoutput or ELerrorList this may be useful when a maximum number of message ID entries in the limits table has been imposed. dest->wipe(); logger->wipe(); // Applies wipe() to all attached destinations This clears everything -- counts and aggregate counts for severity levels and for individual ID's, as well as any limits established. (This includes the limits for "*" all messages.) The table is wiped clean, which may be relevant if memory management issues imposes a limit on the number of message ID entries in the statistics table. Finally, there is a way to zero the counts going toward the limits of all IDs in a destination. This does not affect ELstatistics (which does not use limits) and will not compactify the limits table (see Note 6: "Limit Semantics"). It also does not affect aggregate counts. dest->zero(); **************************************** * Further Options for the Frameworker * **************************************** 17) Checking on errors ---------------------- Since the severity levels above ELnext indicate advice that the flow of processing ought to be modified or ceased, it is advisable (in all but the most time-critical applications) that the framework check for the presence of such errors, between modules or between events. To make this inexpensive, a routine checkSeverity() is provided. ErrorSeverity highest = logger->checkSeverity(); if ( highest >= ELnext ) { do whatever }; This routine provides the severity level of the highest error declared since the last checkSeverity(), or ELzeroSeverity if none have been declared since the last check. 18) Error counts ---------------- You can obtain a cumulative count of errors of a given severity -- including those which occurred but were not sent to a destination or saved. logger->severityCount(ELerror); logger->severityCount(ELsevere, ELfatal); The two-argument form adds the counts of the severities from the first to the second level; for example, severe1, severe2, abort and fatal. These counts are can be reset to zero by logger->resetSeverityCount(ELerror); logger->resetSeverityCount(ELerror, ELfatal); logger->resetSeverityCount(); The latter resets all the severity counts. Note that these methods of the ELadministrator "logger" have nothing to do with the counts kept in ELstatistics for summary purposes. In the two-argument forms of these routines, if the first is a higher level than the second argument, the count will be zero and nothing will be reset. -- Further Options -- 19) Traces and control over whether traces are logged ----------------------------------------------------- The package has the ability to send trace information to be output by destinations. A trace is a string which might have the sort of stack and argument trace one can get from the system plus debug information, or might have other information as specified by the traceRoutine() member of the ELcontextSupplier. Obviously, if a message is not logged at some destination (because it misses the severity threshold or has occurred more times than its limit) no trace is sent to that destination either. But even if the message is logged, you may not want the additional information (and clutter) of a trace unless the message is sufficiently severe: dest->setTraceThreshold(severity); -- For errors logged to this destination, include the trace (if available) if severity is at least this level. The default provided is setTraceThreshold(ELhighestSeverity), that is, unless a threshold is explicitly set, the trace routine is not called and no traces are output. Presently, there is no mechanism for obtaining the good traceback information in a platform-independant way. Until useful traceback routines are made available for each platform, the default is for the traceback to be null. However, as described in section 12 on the ELcontextSupplier, the user may supply a custom trace routine, to be invoked for each destination having a suitably low trace threshold. If there is useful experiment-dependant trace-like information available, this may be a reasonable mechanism for outputting (logging) that when errors occur. -- Further Options -- 20) Time span in conjunction with limits --------------------------------------- Sometimes, you may have a large bunch of errors logged in a short period of time, due to a specific problem within, say, a peculiar event. The limit mechanism prevents an output destination from being swamped with more than N of these identical messages. If the messages continue to arrive on a regular basis, then this throttling should continue to apply. But sometimes, there may be a burst of errors, hitting the limit, followed by a long hiatus, followed by another instance or burst of that type of error. In that case, you may well wish to see the errors immediately after the dry spell. The way this is provided is by the concept of a time span. A time span is a number of seconds associated with a given error type. When a message arrives to ELoutput or ELerrorList (which use the same ELlimitTable class to implement their throttling behavior), the count (toward throttling based on the limit) might be zeroed. The count is zeroed if the number of seconds between the previous occurrence of this type of error and the present occurrence exceeds the time span for this type of error. Of course, if the count is zeroed, this and the next N errors will not be suppressed. (On systems where getting time information is impractical, time span will be moot.) The semantics of setting time spans, and choosing which time span is applicable, is exactly the same as for setting limits. However, a time span is expressed as a float number of seconds t. dest->setTimespan (id, t) dest->setTimespan (severity, t) dest->setTimespan ("*", t) logger->setTimespans (id, t) logger->setTimespans (severity, t) logger->setTimespans ("*", t) A time span (or a limit) can be "unset" by supplying a honking large value that will never be reached. -- Further Options -- 21) ELerrorList Local Semantics -------------------------------- The ELerrorList is intended to support something that was done in Run I: ] At least one experiment had the framework examine errors AFTER an event was completed. One could imagine storing the error information in a data bank tied to the event, for example. The ELerrorList records an ErrorObj by pushing in onto a std::list using push_back(theErrorObj). The list it works with is that list supplied to its constructor. ELerrorList will add one item to the ErrorObj: It invokes fullContext() to get the full run/event context and adds that as a last item before pushing the ErrorObj onto the list. The list remains the property of (and the responsibility of) the framework. Thus the ELerrorList Local Semantics is merely the semantics of std::list. Of relevance: * The user can iterate through the list to get at each ErrorObj, and can examine each one. * The user can invoke list.clear() between events to keep the list size minimal. * IMPORTANT -- The list must not be allowed to destruct while errors might still be logged. For example, the following is wrong and will likely core dump: ErrorLog errlog; int main() { setup(); doThingsThatMightLogErrors(); } void setup() { // .. std::list theList; logger->attach(ELerrorList(theList)); } void doThingsThatMightLogErrors() { // things had better **not** log errors, because the ELerrorList // will still want to record them, but theList is not valid here. } The operations which come from the user or frameworker, to impact ELerrorList mostly come from the fact that it is an ELdestination subclass: setThreshold() Thresholds, limits and time spans are set setLimit() as for any other ELdestination class. setTimespan() -- Further Options -- 22) Accessing Saved Error Messages ---------------------------------- For the program to get at information contained in saved error objects, you need to know how to get at the items in the ELerrorList list, and having gotten an ErrorObj, how to get at its individual fields of data. The ELerrorList puts ErrorObjs onto its supplied list using the usual list semantics. To get at an item, you declare a const_iterator and use it. std::lit::const_iterator err; for ( err = theList->begin(); err != theList->end(); ++err ) { if ( err->severity() == ELabort ) { think_about_aborting(); } } The methods for forming and adding information to an ErrorObj were already described in "How an instance of ErrorObj may be formed." ErrorObj also has methods to get the various pieces of information, both those captured when the object was created, and those supplied directly by a user. ELseverityLevel severity(); ELstring id(); ELstring text(); ELstring process(); ELstring module(); ELstring subroutine(); ELstring context(); ELstring verboseContext(); time_t timeStamp(); ELstring fullMessage(); int sequenceNumber(); The last item bears explaining: ErrorObjs sent and not ignored by ELsaveBuffer are assigned sequential numbers, so that a non-local entity can detect any skipped messages. Only ErrorObj's obtained from the save buffer have a meaningful sequence number; if a user just constructs an ErrorObj it will have sequence number 0. Notice that only information associated with the error message is available in this way. This would not include information describing how a specific destination (including an ELstatistics destination) would treat the error. In particular, there is no way to get from the count of how many times this type of error has occurred, directly from an ErrorObj. -- Further Options -- 23) How ELstatistics information is kept ---------------------------------------- The error statistics kept by the ELstatistics destination are logically a map. The combination of 20-byte message id, severity, process, module and subroutine, which we for convenience combine to form --ELextendedID--, acts as the key for this map. The data is a (zero-able) count and an aggregate count for each type of message, plus the (brief) contexts (run/event) of the two first and one latest instance of occurences of such a message with non-null contexts. A last piece of data for each entry is a flag telling whether any message of this type has been throttled out of all the destinations because of limits. In "How the framework sets up for logging," it was mentioned that ELstatistics should be attached after all the ordinary destinations. ELdestControl logfile = logger->attach ( ELoutput(cerr) ); ELdestControl logcerr = logger->attach ( ELoutput("myFileName.log")); ELdestControl logstats = logger->attach ( ELstatistics(cout) ); ELdestControl errbuf = logger->attach ( ELsaveBuffer(20000) ); This is the recommended order, for the following reason: Each destination indicates whether it has ignored a given message because of a limit or threshold. An ELstatistics, when passed an error object, notes whether any destination has YET actually logged the error message; if not, it will mark that error type as having an instance that was ignored by every destination due to limits. So if you want that asterisk in the statistics summary to be meaningful, attach ELstatistics after all the destinations which might output the message. In the above example, we attached errbuf after ELstatistics because we want the * to appear in the summary if an error was neither logged to logfile nor logcerr. For instance, one could imagine a attaching a scrolling screen that will get millions of messages AFTER logstats, saying that if the instance only was output there, we want to note in the summary that some instance appears in no relevant destination logs. In addition to the information kept in the individual error table, a count and an aggregate count of errors by severity level is kept, so that that information may be supplied in the summary. You can access the ELstatistics information by the stats.summary(..) methods. The ELstatistics class provides the default constructor (using cerr as its ostream), and also constructors taking two pieces of information. The first is a limit on the number of distinct entries for which statistics will be kept. The second is an ostream to be used to output a final statistics summary upon job termination (or whenever the ELstatistics object is destructed) if statistics have changed singe the last requested summary - see Note 12: Termination issues. ELstatistics( int spaceLimit=-1); ELstatistics( std::ostream osp ); ELstatistics( int spaceLimit=-1, std::ostream & osp ); The example above, ELdestControl logstats = logger->attach ( ELstatistics(cout) ); sets up statistics with no limit on the number of entries, and when logstats is destructed, if it has changed since the last summary, it would send its summary as a string to cout. 23.5 Access to the statistics map --------------------------------- The frameworker can obtain a std::map containing the same information available when an ELstatistics summary is requested. The difference is that for puposes within a program, it may be easier to extract information from the ELextendedID keys and StatsCount structures than to parse summary output lines. ELdestControl logstats = logger->attach ( ELstatistics() ); ... std::map m = logstats.statisticsMap(); To use this, one should know about the ELextendedID struct from ELextendedID.h, and the StatsCount struct from ELmap.h. (There is no guarantee that ELstatistics keeeps this information in the form of a map. However, if it is kept in another form, then statisticsMap() will do the transformation and supply a map.) 23.8 Changing the ostream used by an ELoutput --------------------------------------------- The frameworker can switch ostreams used by an ELoutput destination. This is done, of course, via the associated destControl. Three related signatures are supplied: dest->changeFile(os) dest->changeFIle("name") dest->flush() 24) Formatting control available in ELoutput -------------------------------------------- The ELoutput destination provided allows the frameworker to control some aspects of the format outputted for each error message. The methods in the second column of this chart reflect the default behavior. dest->suppressTime() dest->includeTime() dest->suppressModule() dest->includeModule() dest->suppressSubroutine() dest->includeSubroutine() dest->suppressText() dest->includeText() dest->suppressContext() dest->includeContext() dest->includeSerial() dest->suppressSerial() dest->useFullContext() dest->useContext() Note that these methods should be called as methods of the destination control obtained when the ELoutput was attached to the logger. Users can use \n at the start or end of items sent to the log, to control line formatting. ELoutput also supports a couple of routines to force newlines into the "epilogue" of context and time infomation appended after the last item: dest->separateEpilogue() dest->attachEpilogue() dest->separateTime() dest->attachTime() The former places a newline after the last user-supplied item so that the run/event context, module name, and and time stamp come out on a fresh line. The latter forces the timestamp to come out at the start of its own line (after the usual indent). These may help with easy scanning of the log output. Their inverse functions in the second column can be called to reverse the process. Another flexibility is setting line length: The ELoutput destination by default formats messages lines by starting a new line whenever an item would go past column 80. One can change that line length: dest->setLineLength(len) dest->getLIneLength() Note that this is done separately for each destination, thus a log might have 132-column formating while a screen output sticks to 80. Note also that for the ELstatistics destination, the formatting -- which assigns fixed column widths for various fields -- is unaffected by this line length. Another flexibility is squelching the "ErrorLog Established" message that comes out at the top of the log. This message can be very useful in cases where a log was appended to an existing file. Nonetheless, it is sometimes desirable that a program with no problematic occurances emit absolutely nothing to the error logging stream. This is done when constructing the ELoutput destination. A second argument is accepted; this is a boolean which defaults to true, but if false will squelch the ErrorLog Established message. ELdestControl logcerr = logger->attach ( ELoutput(cerr, false) ); ELdestControl logfile = logger->attach ( ELoutput("myFileName.log", false) ); The default constructor for ELoutput does not provide this flexibility. For further flexibility, a custom ELdestination must be created. 24.5 Hex output formatting via ErrorLog --------------------------------------- When data of integer type is output, it is occasionally preferable to see the value in hex. The package does not support streaming the hex manipulator into an ErrorLog, because technical considerations involving the details of signature of hex make doing this in a portable manner too difficult. Instead, the framworker or a user can set an ErrorLog to print all integer items larger that some trigger value in a format like 258 [0x00000102]: errlog.setHexTrigger (int n); The rules are as follows: The logic determining whether to use the hex form is on a per-ErrorLog basis. Hex output is done whenever an int, long, or short (or the unsigned form of one of those) is streamed to the errlog, and the absolute value of that integer is greater than or equal to the hexTrigger established for that ErrorLog. The default value of hexTrigger is negative. A negative value turns off hex formatting. ***************************************************************** * Customization Hooks and Issues Relating to Collective Logging * ***************************************************************** 25) What an ELdestination Must Do ---------------------------------- In order for a custom ELdestination to work properly in the context of how the physicists and frameworker can use it and how the logger will interact with it, the destination class (and its associated ELdestControl) must support the following methods: The destination class must have: bool log ( const ErrorObj & ) This is allowed (and encouraged, based on limits and thresholds) to decide to ignore or act upon this message. If the message is acted upon, log() should return true so that statistics can know somebody logged it. bool output(ELstring, ELseverityLevel) If the severity level is at or above threshold, write this item out (into the log). void summary( ELdestination & destination, const ELstring & title="" ); void summary( std::ostream & destination, const ELstring & title="" ); void summary( ELstring & destination, const ELstring & title="" ); These are present for the purpose of producing formatted summary information and forwarding it to a destination. Any given destination is free to ignore these. void summarization( const ELstring & title, const ELstring & summaryInfo ); This function is responsible for emitting the provided summary information, and may emit additional headers, timestamps, footers, etc., as desired. ELdestination * clone(); Pure virtual in ELdestination; must be supplied. This must new a copy of the destination object and return its address. See note 5 "ELdestControl Details" for the ownership of destinations issue. -- Customization Hooks -- The ELdestControl object has the following methods, which the destination class must support: setThreshold (ELseverityLevel sv) The destination is free to ignore these, setLimit (ELstring s, int n) as does ELstatistics which does not setLimit (ELseverityLevel sv,int n) apply limits or time spans. If these are NOT ignored, then the string "*" setTimespan (ELstring s, int n) must be treated as a wild card, and setTimespan (ELseverityLevel sv,int n) the semantics of precedence described in this document must apply. setTableLimit (int n) Impose a limit on the number of entries in any given table. See note 3. clearSummary(); Ignore clearSummary except for statistics wipe(); destination; see "clearing Statistics and/or zero(); Limits" behavior definitions. setPreamble (ELstring preamble) Establishes a substitute for the default %ERLOG start of message output. Pre-ambles of up to 6 chars will work properly with the standard formatter. setNewline (ELstring nl) Establishes a substitute for the default \n as a newline character in the log. Provided in case NT needs this. The control object for a destination intended as a statistics gatherer must also support: summary(dest, ELstring title=""); These methods should be ignored except in summary(os, ELstring title=""); the case of the controller of a statistics summary(s&, ELstring title=""); destination, which will output the summary as directed. These are present (as immediate returns) in the base class ELdestControl. The ELdestControl class has three protected pointers, ELdestination * d; ELdestinationX * dx; ELdestControlX * x; It is possible to derive off this class, placing values do customized subclasses of ELdestination and/or ELdestinationX into these pointers in the constructor of the derived ELdestControl subclass. This would minimize the amount of new code one needs to write, and also may eliminate in some cases the need to recompile framework compilation units when the header of the new customized class changes. -- Customization Hooks -- 26) ELsaveBuffer Semantics and Layout -- REMOVED ------------------------------------- -- Customization Hooks -- 27) Avoiding lists, maps, strings, streams, and templates ---------------------------------------------------------- When first designing this with D0 L2 in mind, the following concerns came up: Due to the lack of post-startup memory allocation, and desired avoidance of C++ library classes, there several types of worries: A Structures that may in principle grow indefinitely. B Use of C++ strings. C Use of stringstreams to build strings. D Use of streams other than stringstreams. E Use of maps. F Use of templates penetrating to user interface Ideally, we would like to feel free to use all of these in a basic implementation, yet have clean ways to avoid using any of them if necessary. Here are the places where there can be concern: A1 - The errorStats table could have unlimited entries A2 - The errorLimits table could have unlimited entries A3 - The save buffer ELbuffer might grow arbitrarily A4 - The control structure for ELbuffer might grow arbitrarily A5 - Capture buffers used to flush the ELbuffer need to be limited A6 - The list of destinations could grow indefinitely B1 - An item sent to log by << can be arbitrarily long B2 - The formatted message string can be arbitrarily long B3 - The date/time might be a C++ string B4 - Part of the Context object might be a string B5 - Objects contained in a class (e.g. ELerrorStats) might be strings C1 - Use of stringstream to implement operator<< D1 - An ELdestination might inherently be associated with a stream E1 - Use of maps in the limitsTable E2 - Use of maps in ELstatistics F1 - The ErrorLog and ErrorObj classes use templates for operator<<. We have followed several design principles to allow a customizer to resolve any or all of these concerns. These include keeping functionality that we do insist on implementing via full C++ in subclasses below interface definitions; the customizer can replace any of those subclasses which violate the conditions of the limited environment. The following techniques can be followed to avoid (or avoid using in a post-init dynamic memory manner) the std:: concepts of list, string, and stream: -- To avoid using lists and maps: We use various ELlist_XXX as typedef-ed to std::list, for instance, typedef std::list ELlist_destI so one option is to replace those with your own container classes, but we do assume list semantics. Similarly, we use various sorts of ELmap_XXX as typedef-ed to std::map, and there we assume map semantics. To try to avoid or put limits on those semantics: ELadministrator To avoid lists altogether you could supply a custom ELadministrator. But if you can tolerate lists if they do not grow after init time, using the provided one should be fine. ELstatsTable The errorLogger will have an additional set of constructors taking an instance of ELstatsTableI & which will allow substituting a derived class that does not do any undesired operations. ELlimitsTable The ELdestination derived classes can utilize any limits table or mechanism desired. ELsavebuffer A custom ELdestination may easily be put in place of ELsaveBuffer on the list of sinks (or however the ErrorLoggerI accepts sinks). -- To avoid using strings: The ErrorLogger package uses ELstring which is typedef-ed to std::string, so one can easily substitute any class with similar semantics. In particular, a string class with some hacked allocator to avoid dynamic memory allocation after some startup epoch would be a plausible substitute. The following classes would be places to check for whether your string semantics are adequate: ErrorLog This one is easy: All string-like arguments are done as C-strings via char*, and all internal strings with the exception of the error summary are stored as ELstrings with specified maximum lengths. ErrorSummary When a summary is sent to a destination, it is sent one fixed-length char* representing a single line at a time. So unless the framework explicitly does log.summary(string&), no strings or arbitrarily long char arrays which might have to be malloc-ed are used. ErrorObj Our provided class does use a string to hold the formatted error message string, however, the customizer can substitute a different class or use a different form of ELstring. ELlimitsTable ELstatsTable Our provided classes for these do not use strings. ELdestination Our provided class for ELdestination uses strings in an essential way. One could derive off ELdestination to form sinks that do not use strings, or rely on a different form of ELstring. ELsavebuffer Our ELsaveBuffer does not use strings, since it strives for a clean and remote-accessible fixed format. -- Customization Hooks -- -- To avoid using streams: ErrorLogger ErrorLogger does not actually use streams, though the syntax is patterned to some extent after the string << syntax. ELdestination Our provided ELdestination uses streams extensively. A customizer would have to create a custom derived class to avoid this. -- To avoid using templates: Templates are a trickier matter. We have avoided using templates in all but two essential circumstances: a) The std::list and std::map make use of templates, so to avoid templates altogether one would have to custom derive custom ELlist_dest and similar classes defined in ELlist.h and ELmap.h. b) ErrorLog accepts items of information supplied to it by operator<<. It formats these into ELstrings using an ostringstream to format the message, and sends them to the list of ELdestination and the error message object. Both the accepting and the formatting via ostringstream use templated operator<<, and this HAS TO BE, because otherwise the user would not be free to << any type of string, number or object to the logger! To avoid the use of templates, one would have to substitute a custom file in ErrorLog.icc, supporting the creation of ELstrings from some restricted set of object types. By out rules, an ErrorLog must have << from at least char*, so at least that trivial one must be present. Similarly, one would have to substitute a custom file for ErrorObj.icc, supporting operator<< from presumedly the same restricted set of types. 28) Table Limitations --------------------- In implementations that allocate a fixed amount of space for tables, such as a fixed number of ids in limit tables, the customizer needs to define the behavior when these limits are exceeded. We suggest the following: - When a statistics table (which is a pure map) cannot add another key of error, log that fact (the first time) as an ELwarning2 and ignore the overflow. - When the id-only part of a limits table gets full, additional setLimit commands log an ELwarning2 and become no-ops. - When the count part of a limits table, which is keyed off extended id, becomes full, log the first time as an ELerror2. Additional types of errors are simply not limited. -- Customization Hooks -- 29) Direct logging to one destination -------------------------------------- Although in general all logging is done through ErrorLog objects, which work through the ELadministrator, the frameworker may log a formed ErrorObj directly to an ELdestControl. bool reacted = logfile.log ( ErrorObj & myMsg ); As implied, the destination's thresholds and limits still apply; the log() method returns false if the destination did not react to this ErrorObj. If this backdoor mechanism is used, it has bypassed the ErrorLog functions. In particular, ErrorLog normally provides module and subroutine strings. To restore this ability, the ErrorObj class has two further methods: myMsg.setModule ( "whatever" ); myMsg.setSubroutine ( "whatever" ); Note that (unless the destination happens to be ELstatistics) error messages logged in this manner will not be reflected in the statistics. Generally, it is a better idea to send things to all destinations using errorlog (myMsg); and let each destination filter out what it does not want. Another point for custom destinations: In ELdestination.h we have defined the method log() which is used in two senses. Generally, it is the method by which errlog sends the full message to a dest (e.g., ELstatistics) that wants the full message at once. However, it can also be invoked directly as a way to log the message to a destination via a back door. The problem is that the former usage implies NOT processing individual items, because they assumedly have been processed. The latter implies processing items if that is how they get output. To allow for this, the destination must be prepared for a backdoor log, if it has an appropriate action to take. In some cases (ELoutput, for instance) it can know absolutely that log() was coming through the backdoor because skipMessageObject was set so the ELadministrator would never call its log(). Then it can know to process the individual items. I can imagine a dest that reacts both to items and to the overall message. In that case, one should check msg.isFromELadministrator to see if the backdoor mechanism was used. 30) Custom Severity Levels? --------------------------- The intent is that all assigned severity levels fall into one of the 14 levels provided. (ELsev_highestSeverity and ELsev_zeroSeverity are not counted here because they are intended only for establishing thresholds.) In our experience, even allowing 14 levels of granularity is generally met with derision. Accordingly, we do not permit users to invent additional levels. ********************************* * Example of use of the Package * ********************************* In this example, we assume a relatively simple framework in which a sequence of physicists' modules, inheriting off a base class Module, are invoke for each event. The relevant files are: frame1.h and .cc The framework, including main() Module.h and .cc The module base class FindTracks.h and .cc The first actual module to call DoPhysics.h and .cc The second actual module to call We also assume that the way the framework calls the activity of a module is to invoke its operator()(event &). This is a syntactically elegant way, but is far from the only possibility, and indeed both CDF and D0 use more sophisticated schemes in their frameworks. We will annotate this code in places relevant to the ErrorLogger package. We omit various things such as code guards from this example; they appear in the actual test files. // DoPhysics1.h -- Physicist header in module DoPhysics #include "frame1.h" #include "Module1.h" #include "ZMutility/iostream" using std::cout; using std::endl; class Event; class DoPhysics : public Module { // Since DoPhysics is a Module, all member functions // of DoPhysics, in particular operator()(event), // have full access to all instance variables of // the underlying Module instance, in particular, // errlog. public: DoPhysics (const string & name); void operator() (Event & event); }; -------------- // DoPhysics1.cc -- Physicist code in module DoPhysics #include "ZMutility/ZMenvironment.h" #include "frame1.h" #include "DoPhysics1.h" #include "ZMutility/iostream" using std::cout; using std::endl; DoPhysics::DoPhysics (const string & name) : Module(name) {} // When the Module base class // is initialized with name, this // will do errlog.setModule(name) void DoPhysics::operator() (Event & event) { event.data *= 3; errlog ( ELerror, "wrong data") << event.data << endmsg; // A sample error message issued. int i; for (i = 0; i<6; i += 2) { errlog ( ELinfo, "i ="); errlog << i; event.data += i; errlog << " data = " << event.data; errlog << endmsg; // A message issued in several pieces } errlog ( ELsuccess, "Did step 2") << endmsg; } -------------- // FindTracks1.h and .cc are much the same as DoPhysics1.h and .cc -------------- // Module.h -- the Module base class supplied by the framework #include "ZMutility/ZMenvironment.h" #include "ZMutility/iostream" using std::cout; using std::endl; #include "ErrorLogger/ErrorLog.h" #include "frame1.h" class Event; class Module { public: Module (const string & name); virtual void operator()(Event& e) = 0; // do the processing for this module! protected: // Note that this is the only line ErrorLog errlog; // inserted because of the ErrorLogger // mechanism! It creates an instance data // member errlog, which will contain the module // name and which is available to all the member // functions of the Module subclasses. }; // Module -------------- // Module.cc -- the Module base class supplied by the framework #include "Module1.h" Module::Module (const string & name) { errlog.setModule(name); // This is how the errlog variable knows // the name of the module being executed. } --------------- // frame1.h -- the framework's header #include "ZMutility/ZMenvironment.h" #include "ZMutility/iostream" #include "ErrorLogger/ELcontextSupplier.h" #include "ZMutility/sstream" using std::ostringstream; class Event { // This is a rather lame mock-up of what an Event looks like. The framework // will pass an Event from one module to the next. public: int data; Event(int d) : data(d) {} }; // Event class RunEventContext : public ELcontextSupplier { public: string context(); string fullContext(); string summaryContext(); }; // RunEventContext --------------- // frame1.cc -- the framework #include "ZMutility/ZMenvironment.h" #include "ErrorLogger/ELadministrator.h" #include "ErrorLogger/ELdestControl.h" #include "ErrorLogger/ELoutput.h" #include "ZMutility/iostream" #include "ZMutility/sstream" using std::ostringstream; using std::cout; using std::endl; #include "Module1.h" #include "FindTracks1.h" #include "DoPhysics1.h" string RunEventContext::context() { ostringstream ev; ev << "event = " << FindTracks::event_counter(); return ev.str(); // This is how an error message, when it is issued, // finds out the context. Thus you don't have do do // some setContext for each new event. } string RunEventContext::fullContext() { return context(); } string RunEventContext::summaryContext() { return context(); } // The framework: // * Defines a class Event (above) // * Provides an ELcontextSUpplierI (above) // * Sets up a pure virtual base class Module (in module1.cc) // * Knows which classes derived from Module exist // * Contains main() which instantiates and invokes each of the // various modules // // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void main() { ELadministrator * logger = ELadministrator::instance(); // ELadministrator is a singleton: // Any and all loggers use the same actual // instance. RunEventContext recontext; logger->setContextSupplier (recontext); logger->setAbortThreshold (ELabort); ELoutput logfileD ( "myLogFile.frame1" ); ELdestControl logfile = logger->attach(logfileD); logfile.setThreshold (ELsuccess); ELoutput outputD ( cout ); ELdestControl output = logger->attach(outputD); output.setThreshold (ELwarning); ErrorLog errlog; errlog.setModule ("framework"); // Here we have created a global-scope // errlog for any utilities which are NOT // methods of a module with its own errlog. // The following is a list of the modules in this program: FindTracks findTracks("TRACK FINDER"); DoPhysics doPhysics("PHYSICS CODE"); int n; for ( n=1; n<10; n++) { Event event(n); errlog (ELinfo, "Event Number ") << n << endmsg; // This demonstrates use of the global errlog. findTracks(event); doPhysics(event); cout << "data returns as " << event.data << "\n\n"; } errlog ( ELabort, "Time to abort " ) << "n = " << n << endmsg; } ******************** * Technical Design * ******************** Design Patterns and C++ Techniques Used --------------------------------------- ELadministrator "Destructable Singleton" ---------------------------------------- Logging is to be done under a unified, centralized control. However, each individual ErrorLog instance wants a pointer to an administrator. For all the reasons given in Gamma et al, it is better to use a singleton pattern than to create a global instance of the administrator. However, as noted by Vlissides, this does not allow for destructing the single true instance. Vlissides in C++ Reports "To Kill a Singleton" shows a pattern which does allow for this. The pattern used for ELadministrator is that same one, modified with a suggestion due to Kowalkowski that makes it more robust against compiler quirkiness. Blind Adapters at the User and Framework Interfaces ---------------------------------------------------- One of the goals of the earliest implementation was that the headers seen by both the physicist and the frameworker should not change after the initial limited prototype release. The concern is that recompiling all the framework units is a big deal; so even if the interface remains stable, changing anything in the headers is bad. (Re-linking as more functions are enabled, on the other hand, is OK.) Since we had (by consensus or by fiat) firm definitions of the functionality, this ought to be possible by defining interface headers in the right way. The problem that arose with the naive approach was as follows: The header file for the class contains public methods, and protected data and perhaps supporting methods. While the methods available to the user, can be considered absolutely stable, it would be foolish to suppose that before implementing 90% of the features, we can anticipate every piece of protected data required. The first reaction to this is to say that the actual class is derived off the pure interface class; the user includes just the interface header. The drawback to this is that now the constructor is in a header that the user is not supposed to be concerned with or even including! For instance, if ErrorLog is the interface and the user has to declare an ErrorLogX, that is bad. The classic solution is to use the Adapter pattern C++ implementation suggested in Gamma+3. There ErrorLog would be a public ErrorLogI and a private ErrorLogX. Aside from solvable issues of multiple inheritance, this is a no-go for us: When ErrorLogX changes the user compilation units have to re-compile. The solution chosen is what I call a "Blind Adapter" pattern. The header seen by the user provides an interface class (ErrorLog in our example) with ONLY: * The agreed public methods * A protected pointer to a struct of some type the user should not be concerned with (ErrorLogX) Note that this pointer is declared by a forward declaration: class ErrorLog { public: methods(); protected: struct ErrorLogX; ErrorLogX * x; } Now ErrorLog does not need to include ErrorLogX.h, so when something changes in the data structure needed, user and framework code is COMPLETELY ISOLATED from those changes in terms of re-compilation. The cost is that there is an extra level of indirection in some dispatched methods. This cost is quite small, and the same as the cost of virtual methods, but the compiler is more likely to find optimizations in the latter case. I call this pattern a blind adapter, but it is also the "Cheshire Cat" special case of the "Bridge" pattern in Gamma+3. As such, it has further possibilities: For example, run-time choice of implementation is in principle possible. This pattern is used for all the classes physicist or framework code ought to talk to directly: ELadministrator ELadministratorX ELdestControl ELdestControlX ErrorLog ErrorLogX, ELadministrator, ELadministratorX {1} ELoutput ELoutputX ELstatistics ELstatisticsX ELsaveBuffer ELsaveX ELdestination ELdestinationX {2} {1} The ErrorLog is itself a decorated proxy for ELadministrator. Rather than go thru yet another level of indirection, we put the ELadministratorX pointer into the ErrorLog protected area as well. Similarly, the {2} Actually, the user would not typically mention ELdestination, but instead a specific subclass like ELoutput. There is one shortfall of this pattern, where implementation actually must be in ErrorLog.h: The ability to accept << from any type implies a template mechanism, and this has to be defined in a file that the user program including ErrorLog.h will depend upon. What that means is that the template has to be implemented very carefully, to get it right the first time. Fortunately, it is quite a simple template. Rejected Alternatives --------------------- The particular syntaxes and features selected were picked in concert with the CDF and D0 people, and represent a consensus. We may have omitted things that are worth doing. However, we do not wish to re-trace ground covered by decisions already made once. Here, I list some of the proposed syntaxes, so that when somebody says "why not change it to do this" we can say we considered that and rejected it. *** How a physicist issues a log message We considered log << ELerror << "id" << ... This was narrowly rejected because the accepted form makes it tough to accidentally use a data-dependent string for the id, e.g. log << ELerror << energy << " is too much energy" <setContext(co); ELdestControl logfile = logger->attach(logfileD); Note 5: ELdestControl details ----------------------------- To avoid any possibility of a frameworker attaching a destination object to the logger, then deleting the destination object and trapping the logger into faulting when the next message tries to invoke methods of the deleted object, the logger has to make a copy of the destination object. But then, the framework can't directly talk to the instance of the object in use by the logger! So logger->attach() returns an ELdestControl object, a hook to control the destination. The destination instantiated by the user can be deleted or allowed to go out of scope. The control object ought to stick around, though the only consequence of letting that go out of scope would be that the framework no longer has a way to affect the behavior of the destination. We ensure that the control object faithfully accepts all methods applicable to the ELdestination. This includes setting limits, and directly sending a log message. If a custom destination contains methods outside the normal set defined by ELdestination, then the customizer will also need to provide a custom control object for that type of destination, inheriting off ELdestControl but adding dispatchers for the new custom methods. Then when attach returns the ELdestControl, the framework can use that to construct an instance of the derived control class. The ELdestControl contains, of course, an ELdestination* pointing to the particular destination object that was created by the clone() method when logger->attach was called. All of its methods dispatch through this pointer. Since they are all virtual methods in class ELdestination, they will go through the derived class virtual table, hence getting the correct versions. Note 6: Limit Semantics ----------------------- If an instance of a message with a particular ID is sent to a destination before setLimit(ID) has been invoked for that ID, but while there is an applicable wild card limit (for either "*" or the message's severity level), then that ID is assigned an entry in the limits table, and the limit used IS THE APPLICABLE WILD CARD LIMIT. Changing a wild card limit will not affect this entry once it is in the table. For example, dest->setLimit ("*", 8); log ( ELerror, "Bank Limit"); // will use 8 as limit for Bank Error dest->setLimit ("*", 2); log ( ELerror, "Bank Limit"); // eight instances of Bank Error will log ( ELerror, "Bank Limit"); // be output -- not just 2. log ( ELerror, "Bank Limit"); ... log ( ELerror, "Bank Limit"); A consequence of this is that when dest->zero() is called, the entry for Bank Error would remain in the limits table (still with a value of 8) as if dest->setLimit("Bank Error", 8) had been called. Note 7: Module Name -------------------- In the example in "How the Module Sets Up errlog" we assumed that the Module class has a constructor taking as an argument some information that would allow it to know the name by which this (derived) instance is to be known. We called this information module_selection; it could be as simple as a string, or involve other information that the error logger would not care about. At any rate, the info would be saved and the Module::getModuleName() method would use this info to produce the name. Thus we are assuming that each particular class is providing that information in the initializer list of its constructor, as in: class CentralTracker : public Module { public: CentralTracker() : Module("CENTRAL TRACKER") { // any other construction needed } virtual doTheWork(Event& e){ ... } // whatever other methods and data CentralTracker needs }; class Module { public: Module (string name) { errlog.setModule(name); // This line was added } virtual doTheWork(Event& e) = 0; protected: Errorlog errlog; // This line was added }; Note that very few lines were added to make this Module class support the ErrorLogger mechanism. Initially, I thought a bit more complexity was needed. Here is text from an earlier version of this note: class CentralTracker : public Module { public: CentralTracker() : Module("CENTRAL TRACKER") { // any other construction needed } virtual doTheWork(Event& e){ ... } // whatever other methods and data CentralTracker needs }; class Module { public: Module (char* name) : name_(name) { errlog.setModule(getModuleName()); } ELstring getModuleName() {return name_;} virtual doTheWork(Event& e) = 0; protected: ELstring name_; Errorlog errlog; }; There are more clever-looking ways to accomplish this, but I have found none that actually work. For example, you can't directly supply the module name to the instance of ErrorLog instantiate as a class variable in the base class Module -- it has no way of knowing that name at that point! And you could not make getModuleName a pure virtual in Module, relying on each derived module class to supply its own version which simply returns the name you want -- at the point this function is called, you are initializing the Module member of the derived class, so the virtual mechanism will not apply. Note 8: Includes ----------------- People using the ErrorLogger classes should include the header for each class they are using directly. Thus the Physicist and the Module base class need only #include "ErrorLogger/ErrorLog.h" while the framework probably needs a collection like #include "ErrorLogger/ELadministrator.h" #include "ErrorLogger/ELcontextSupplier.h" #include "ErrorLogger/ELdestControl.h" #include "ErrorLogger/ELoutput.h" #include "ErrorLogger/ELstatistics.h" #include "ErrorLogger/ELsaveBuffer.h" #include "ErrorLogger/ErrorLog.h" And everyone has to say using namespace zmel; or ZM_USING_NAMESPACE( zmel ) /* using namespace zmel; */ Note 9: ELoutput (fileName) ---------------------------- Constructing ELoutput from a string holding the file name is a convenience, but it presents a couple of subtle issues. - What mode should the file be opened in (trunc to overwrite any existing file, app to append to the end if a file exists, or fail if the file exists)? - If the file is opened for append, how do we avoid logs from two jobs from looking like one longer error log? - What to do if the attempt to open the file fails. - Who owns (and deletes when appropriate) the ofstream created to represent this file? We take the following positions: + The file is opened for append. (If the file does not exist, it is effectively opened for simple writing.) + To separate two logs, we always place lines of ====== as divider strings before a log starts (even if it is the first thing in the file). + If the attempt to open the file fails, this destination instead logs to cerr -- we output a "meta error message" to indicate this switch. + If the ELoutput is constructed from a file name, it makes a new ofstream to represent that file. It owns that for purposes of deleting *d at the end. + When a destination is attached to the ELadministrator, a new copy is made of the ELoutput object (to prevent any possibility of a user causing another user's error message to do a segfault by wiping out the destination object). The clone() method ***also transfers ownership of the ofstream*** if it was constructed from a file name. So the copy owned by the ELadministrator is the one that will delete the ofstream when it goes away. When ELoutput is constructed from an ostream (which can of course be an ofstream), it is assumed the framework has opened that stream with the desired mode (app or trunc) and that if appending, has sent any desired divider strings. And it is assumed that any deleting or destructing of the stream is handled by the frameworker or the system. Note 10: "ELcout: Replacing cout and/or cerr." ----------------------------------------------- OVERRIDING NOTE: Expert users pointed out several further subtleties associated with ELcout, and were of the opinion that using tis mechanism was always the wrong thing to do. Therefore, we have removed ELcout from the package. The following discussion is here for historical reasons only. Although the ErrorLogger package is meant to augment rather than replace cout and cerr, the package supports hooking cout and/or cerr to the logger instead. In section (11) we outlined how to do to this by declaring cout and/or cerr to be variables of type ELcout. Obviously, ELcout has to react appropriately to the << syntax, routing the string it gets to ELdestinations. That sweeps a lot of questions under the rug. For example, there is no natural analogue, in the context of ostream semantics, of supplying the severity level and id. Here, we answer those questions: a) What are the constructors for ELcout? The constructor accepts one argument: An ELseverityLevel. This severity level is used (at least by standard logging destinations) only to compare to their threshold for deciding whether to react to or to ignore an output item sent to them. b) How does the code use this mechanism (after constructing the ELcout)? This one is set by the very purpose of the mechanism: You leave all your code sending things to cout (or cerr) unchanged. Thus the ELcout type accepts all the stuff that can be streamed to an ostream. c) What about special operations to the cout stream? The intent is to help the user whose existing code does a lot of ordinary cout output. Manipulators, which have meaning when sent via << to ostringstreams, will be accepted, but have no affect on output appearance. Direct methods on cout (and in fact all methods other than via <<) are NOT SUPPORTED for ELcout. For example, if cout is an ELcout, then cout.flush(), cout.good(), cout.setf(ios_base::showbase) will not compile. Almost nobody uses these routinely. d) How do we get the right cout in scope? If the compilation unit contains, in any compiled file, at global scope, the declaration of ELcout cout (level); instead of using std::cout; this will make the ELcout form available to all code in that compilation unit. Some compilers and/or includes may violate the standard and either have cout at anonymous global scope, or place using std::cout; into a header where it should not be. Either of these would clash with the ELcout cout declaration. One possible fix is to put that declaration into a namespace (say "mine") and explicitly say using mine::cout. A last-ditch brute-force approach if a name class becomes bothersome is to do ELcout myCout (level); and globally substitute myCout for cout. e) How do we route an ELoutput destination to the REAL cout? In the compilation unit that includes the main framework, the frameworker will often be instantiating an ELoutput destination with the ostream cout in its constructor. If cout is declared as an ELcout, then there is the potential for a terrible mess: The logger sends a message to ELoutput, which outputs text to cout, which turns into output sent to ELoutput, and so forth. The solution is to explicitly say std::cout in the constructor of ELoutput: ELoutput outputD ( std::cout ); To avoid utter confusion in the case of a mistake, ELcout declares a private (hence inaccessible) constructor taking an ELcout. f) How do the standard destinations react to these? Of the standard destinations, ELstatistics and ELsaveBuffer will ignore items sent via the output() method. Only ELoutput will react to them. ELoutput will send the unvarnished text of all items it gets in output() to its associated ostream. g) Will things sent to ELcout appear in multiple places? If multiple destinations are attached to the log, a single cout<< can appear in each one. However, two things may prevent a given output item from appearing at a given destination: - If the severity level appearing in the constructor of the ELcout is lower that the threshold for that destination, the item will not appear. - The base class ELdestinationI has a public bool data member skipOutputItems. If this is set false, no output items will appear at that destination. h) What about endl? Each item sent to an ELcout gets put into an ostringstream via <<. The endl behaves in a sensible way: It inserts \n or whatever the appropriate end-of-line sequence is for the particular machine. The same is true for flush and ends. Since ELoutput merely sends on output() items as it gets them, the cout behavior is "what you do is what you get". That is, if many outputs are done without doing any endl's or \n, then they will be strung together into a long line. i) What about building messages in multiple steps? Again, ELoutput vomits one output() item at a time. It makes no difference whether these are chained on one line or placed all over the code. However, one ought not mix cout into multi-step formation of a true message to errlog. For instance: ELcout cout; errlog ( ELwarning, "track length" ) << "too long: "; cout << track.length() << "explanation"; // better would be: errlog << track.length() << "explanation"; errlog << endmsg; would embed the cout stuff into the error message, but might screw up the decisions about formatting, line breaks, and so forth. j) What does this imply about the nature of a generic ELdestination? The burden on ELdestination is that now it has a bool skipOutputItems, and must have an output(const ELstring &) method -- which will be a nop in the base class and can be overridden in ELoutput. k) Does this type of output produce real ErrorObjs? NO. Even though a severity is implied, that is only for the purpose of ELoutput deciding whether to suppress the items because of its threshold. We thought about creating a true message if the first would sent ends in a colon (and treating that word as the id); this opens up too large a can of worms. l) How does the initialization of the ELcout instance work? Since each severity level is an instance of ELseverityLevel, and since ELcout cout (ELerror) would be done at global scope in most cases, one has to worry that the severity object (ELerror in this case) may not have been constructed before the ELcout is instantiated. To take care of this worry: The constructor actually takes and stores a reference rather than an object: ELcout (const ELseverityLevel &). The various levels are declared as externs in ELseverityLevel.h. So ELcout cout (ELerror) produces a reference to what at compilation time is an unresolved external; this is resolved at link time. At the instant cout is constructed, this reference points to memory set aside for ELerror -- it may not as yet hold meaningful information. But by the time any code is entered, ELerror will have been constructed, and thus the reference will be valid. m) What is going to happen before ELcout is fully implemented? In order to avoid forcing user code that does ELcout cout (level) to recompile as ELcout evolves, we will keep ELcout as a proxy, with ELcoutX dealing with implementation needs. However, even at the start this proxy had better send its stuff to ELoutput (and ELoutput had better output it); otherwise, the stub behavior would be to "swallow up" everything streamed to an ELcout -- this would be unacceptable. Note 11: Reasons for last round of header changes -------------------------------------------------- The concept of a destination reacting immediately to items sent to the log, rather than to the full message after completion, had two tempting benefits. The first is that if the job somehow goes disastrously wrong, you get possibly valuable information. The second, which became moot, is that this made it easier to hack together a temporary mock-up of the mechanism. This second turned out to be a complete red herring. The first benefit is real, but overall, the complexity price paid for this capability exceeds the benefit. ----------------------- The choice of making ELseverityLevel an object, rather than an enum with associated tables of name and symbol, was dictated by the idea of immediately getting the syntax going by having the >> method for a severity output its name. Given that we later abandoned that early step, we could have made it an enum. But by then we had committed to allow user-defined extra levels. In retrospect, we should have done the simpler enum+tables approach, where we would not do so much string copying and where comparisons would be more straightforward. This would cost us nay chance at user-defined levels, but that would not have been a big price. ----------------------- The choice of making ELseverityLevel an object, rather than an enum with associated tables of name and symbol, was dictated by the idea of immediately getting the syntax going by having the >> method for a severity output its name. Given that we later abandoned that early step, we could have made it an enum. But by then we had committed to allow user-defined extra levels. In retrospect, we could have done the simpler enum+tables approach, where we would not do so much string copying and where comparisons would be more straightforward. This would cost us any chance at user-defined levels, but that would not have been a big price. The solution adopted is to create a very lightweight ELseverityLevel class, with a table of names and symbols and << defined in those terms. ----------------------- In the ErrorLog class I used log() to refer to logging one item that arrived via <<. This name is confusing, since in destinations, log means taking the whole message. However, ErrorLog was frozen before I noticed this confusion. ----------------------- The use of the blind adaptor pattern should be made more uniform: If a user sees class Abc, its implementation auxiliary class should be AbcX, and the pointer to it should always be called x. Note 12: Termination Issues On termination, if there is still an "active" message (a message started by errlog() and not yet completed by endmsg), it has to be sent to the destinations. There is a problem with this: If the sending is done as part of the ELadministratorX destructor, then there is no guarantee that the destinations are still around. And if it is done as part of the destination destructor, then naively, there is no guarantee that the ELadministrator -- which has the active message object, as well as the flag indicating that is the case -- has not already disappeared. That is, the user may have instantiated the destination before or after the first instance() of the ELadministrator singleton was invoked. Upon further reflection, we know that the destinations ON THE LIST IN THE ELadministrator were instantiated after the administrator, because they are copies of the user-instantiated destination objects, made when attach() is done. In fact, the list is a list of ELdestination*, so these won't ever go away till we explicitly delete them in a loop! Other termination activities include output of a final statistics if things have changed. Again, we need to know the destination objects are still around. So the strategy is: In the destructor for ELadministrator, if there is an active message, use the same technique that endmsg would use to send that to every destination. In the destructor for an ELstatistics destination, emit a summary to the designated termination ostream under the title "Termination Summary", but only if changes have occurred since the last summary was generated. Finally, walk the list of attached destinations, and delete each. Stubs and intended implementation order --------------------------------------- This list is not meant to be inclusive; it covers those items which occurred to coders because code nearby to the stubbed code was being implemented. Note that these are implementation matters only. For example, setModule can already be used, but till its stub is corrected, it will have no effect. 1: Immediate cleanup: -------------------- 1.1 Close (and dispatch) active ErrorObj, if any, in ErrorLog.cc. 1.2 Make sure ErrorObj m will stick around! 2: First thing -------------- 2.1 setThresholds 2.2 setAbortThreshold 3: Early on ----------- 3.1 setProcess 3.2 setContextSupplier 3.3 setModule 3.4 setPackage not yet implemented \n"; 3.5 Put the date/time in the log header 4: First wave of implementation ------------------------------- 4.1 setSubroutine 4.2 checkSeverity 4.3 setLimits 4.4 add to limits table 4.5 ELstatistics is currently skipping the message object 4.6 ELstatistics destination 4.7 ELstatistics::summary 5: Second wave -------------- 5.1 log(ErrorObj) 5.2 ELadministrator::wipe() 5.3 setLimit(id) 5.4 setLimit(severity) 6: Early Basic functionality set --------------------------------- 6.1 severityCount 6.2 resetSeverityCount 7: Basic functionality set --------------------------- 7.1 backdoor log to destination 7.2 clearSummary() 7.3 wipe() 7.4 zero() 7.5 conditionalize formats 7.6 Time stamp 7.7 option of using fullContext() 7.8 wipe() in ELoutput 7.9 zero() in ELoutput 7.10 clearSummary() in ELstatistics 7.11 wipe() in ELstatistics 7.12 zero() in ELstatistics 7.13 detach 7.14 logic in ErrorObj to tell whether message was acted on by any dest 8: Refinements -------------- 8.1 setTimespans 8.2 wipe() to limits table 8.3 setTImespan(severity) 9: Later Refinements -------------------- 9.1 setTableLimit 9.2 ELstatistics destination with limit