DUNE-DAQ
DUNE Trigger and Data Acquisition software
|
The Error Reporting System (ERS) software package provides a common API for error reporting in the ATLAS TDAQ system. ERS offers several macro that can be used for reporting pre-defined errors if some conditions are violated at the level of C++ code. ERS also provides tools for defining custom classes that can be used for reporting high-level issues.
The Error Reporting System (ERS) software package provides a common API for error reporting in the ATLAS TDAQ system. ERS offers several macro that can be used for reporting pre-defined errors if some conditions are violated at the level of C++ code. ERS also provides tools for defining custom classes that can be used for reporting high-level issues.
In order to use ERS functionality an application has to include a single header file ers/ers.hpp
ERS provides several convenience macro for checking conditions in a C++ code. If a certain condition is violated a corresponding macro creates a new instance of ers::Assertion class and sends it to the ers::fatal stream. Further processing depends on the ers::fatal stream configuration. By default the issue is printed to the standard error stream.
Here is a list of available macro:
Note: These macro are defined to empty statements if ERS_NO_DEBUG macro is defined at compilation time.
The amount of information, which is printed for an issue depends on the actual ERS verbosity level, which can be controlled via the DUNEDAQ_ERS_VERBOSITY_LEVEL macro. Default verbosity level is zero. In this case the following information is reported for any issue:
One can control the current verbosity level via the DUNEDAQ_ERS_VERBOSITY_LEVEL macro:
where N must be an integer number.
ERS assumes that user functions should throw exceptions in case of errors. If such exceptions are instances of classes, which inherit the ers::Issue one, ERS offers a number of advantages with respect to their handling:
In order to define a custom issue one has to do the following steps:
ERS defines two helper macro, which implement all these steps. The macro are called ERS_DECLARE_ISSUE and ERS_DECLARE_ISSUE_BASE. The first one should be used to declare an issue class that inherits from the ers::Issue as it is shown on the following example:
Note that attribute names may appear in the message expression. Also note a special syntax of the attributes declaration, which must always be declared using a list of ((attribute_type)attribute_name) tokens. All the brackets in this expression are essential. Do not use commas to separate attributes. The only requirement for the type of an issue attribute is that for this type must be defined the output operator to the standard C++ output stream and the input operator from the standard C++ input stream. It is important to note that these operators must unambiguously match each other, i.e. the input operator must be able to unambiguously restore the state of the attribute from a stream, which had been used to save the object's state with the output operator. Evidently all the built-in C++ types satisfy this criteria. The result of the ERS_DECLARE_ISSUE macro expansion would look like:
The macro ERS_DECLARE_ISSUE_BASE has to be used if one wants to declare a new issue class, which inherits from one of the other custom ERS issue classes. For example, the following class inherits from the ers::Assertion class defined above:
The result of the ERS_DECLARE_ISSUE_BASE macro expansion looks like:
The macro ERS_HERE is a convenience macro that is used to add the context information, like the file name, the line number and the signature of the function where the issue was constructed, to the new issue object. This means that when a new issue is created one shall always use ERS_HERE macro as the first parameter of the issue constructor.
Functions, which can throw exceptions must be invoked inside try...catch statement. The following example shows a typical use case of handling ERS exceptions.
This example demonstrates the main features of the ERS API:
The ERS system provides multiple instances of the stream API, one per severity level, to report issues. The issues which are sent to different streams may be forwarded to different destinations depending on a particular stream configuration. By default the ERS streams are configured in the following way:
Note: the letter "l" at the beginning of "lstdout" and "lstderr" names indicates that these stream implementations are thread-safe and can be safely used in multi-threaded applications so that issues reported from different threads will not be mixed up in the output.
In order to change the default configuration for an ERS stream one should use the DUNEDAQ_ERS_<SEVERITY> environment variable. For example the following command:
will cause all the issues, which are sent to the ers::error stream, been printed to the standard C++ error stream and then been thrown using the C++ throw operator.
A filter stream can also be associated with any severity level. For example:
The difference with the previous configuration is that only errors, which have "ipc" qualifier will be passed to the "throw" stream. Users can add any qualifiers to their specific issues by using the Issue::add_qualifier function. By default every issue has one qualifier associated with it - the name of the TDAQ software package, which builds the binary (a library or an application) where the issue object is constructed.
One can also define complex and reversed filters like in the following example:
This configuration will throw all the errors, which come neither from "ipc" nor from "is" TDAQ packages.
ERS provides several stream implementations which can be used in any combination in ERS streams configurations. Here is the list of available stream implementations:
While ERS provides a set of basic stream implementations one can also implement a custom one if this is required. Custom streams can be plugged into any existing application which is using ERS without recompiling this application.
In order to provide a new custom stream implementation one has to declare a sub-class of the ers::OutputStream class and implement its pure virtual method called write. Here is an example of how this is done by the FilterStream stream implementation:
An implementation of the ers::OutputStream::write function must decide whether to pass the given issue to the next stream in the chain or not. If a custom stream does not provide any filtering functionality then it shall always pass the input message to the next stream by using the chained().write( issue ) code.
In order to register and use a custom ERS stream implementation one can use a dedicated macro called ERS_REGISTER_STREAM in the following way:
The first parameter of this macro is the name of the class which implements the new stream; the second one gives a new stream name to be used in ERS stream configurations (this is the name which one can put to the DUNEDAQ_ERS_<SEVERITY> environment variables); and the last parameter is a placeholder for the stream class constructor parameters. If the constructor of the new custom stream does not require parameters then last field of this macro should be left empty.
In order to use a custom stream one has to build a new shared library from the class that implements this stream and pass this library to ERS by setting its name to the DUNEDAQ_ERS_STREAM_LIBS environment variable. For example if this macro is set to the following value:
then ERS will be looking for the libMyCustomFilter.so library in all the directories which appear in the LD_LIBRARY_PATH environment variable.
ERS can be used for error reporting in multi-threaded applications. As C++ language does not provide a way of passing exceptions across thread boundaries, ERS provides the ers::set_issue_catcher function to overcome this limitation. When one of the threads of a software application catches an issue it can send it to one of the the ERS streams using ers::error, ers::fatal or ers::warning functions. If no error catcher thread is installed in this application the new issue will be forwarded to the respective ERS stream implementations according to the stream configuration. Otherwise if a custom issue catcher is installed the issue will be passed to the dedicated thread which will call the custom error catcher function.
An error catcher should be installed by calling the ers::set_issue_catcher function and passing it a function object as parameter. This function object will be executed in the context of a dedicated thread (created by the ers::set_issue_catcher function) for every issue which is reported by the current application to ers::fatal, ers::error and ers::warning streams. The parameter of the ers::set_issue_catcher is of the std::function<void ( const ers::Issue & )> type which allows to use plain C-style functions as well as C++ member functions for implementing a custom error catcher. For example one can define an error catcher as a member function of a certain class:
This error catcher can be registered with ERS in the following way:
Note that the error handling catcher can be set only once for the lifetime of an application. An attempt to set it again will fail and the ers::IssueCatcherAlreadySet exception will be thrown.
To unregister a previously installed issue catcher one need to destroy the handler that is returned by the ers::set_issue_catcher function using delete operator:
There is a specific implementation of ERS input and output streams which allows to exchange issue across application boundaries, i.e. one process may receive ERS issues produces by another processes. The following example shows how to do that:
The MyIssueReceiver instance will be getting all messages, which are sent to the "mts" stream implementation by all applications working in the current TDAQ partition whose name will be taken from the DUNEDAQ_PARTITION environment variable. Alternatively one may pass partition name explicitly via the "mts" stream parameters list:
To cancel a previously made subscription one should use the ers::StreamManager::remove_receiver function and giving it a pointer to the corresponding receiver object, e.g.: