8. Error Handling

Document ID:

Revision:

1.3

Last modification:

March 18, 2024

Status:

Released

Repository:

https://gitlab.eso.org/cii/info/cii-docs

File:

error_api.rst

Project:

ELT CII

Owner:

Marcus Schilling

Document History

Revision

Date

Changed/ reviewed

Section(s)

Modification

0.9

9.4.2019

bterpinc

All

Created.

1.0

16.9.2019

bterpinc

All

Updated after internal review.

1.1

10.2.2020

bterpinc

All

Major update of all sections after Topical review comments.

1.2

20.11.2020

bterpinc

2.2

Added explanation about the error severity.

1.3

18.03.2024

mschilli

0

Public doc

Confidentiality

This document is classified as Public.

Scope

This document is a user manual for the error handling system of the ELT Core Integration Infrastructure software.

Audience

This document is aimed at Users and Maintainers of the ELT Core Integration Infrastructure software.

Glossary of Terms

API

Application Programmers Interface

CII

Core Integration Infrastructure

GUI

Graphical User Interface

ICD

Interface Control Document

IDE

Integrated development environment

MAL

Middleware Abstraction Layer

SVN

Subversion version control system

References

  1. Cosylab, Interface Control Document, Specification, CSL-DOC-17-147262, version 1.3

  2. Cosylab, Error handling design document, CSL-DOC-17-167386, version 1.2

  3. ELT Config, Error Handling Transfer document, CSL-DOC-19-172644, version 1.5

  4. ElasticSearch Query DSL, https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html

  5. ElasticSearch, Standard Analyzer, https://www.elastic.co/guide/en/elasticsearch/reference/6.8/analysis-standard-analyzer.html

8.1. Overview

This document is a user manual for CII Error handling system.

8.2. Introduction

Error Handling in CII provides the APIs and tools necessary to conveniently implement a common error handling mechanism across all EELT Control System applications and thus streamline the diagnosis of abnormal behavior of the system.

image7

Figure 2‑1 Interaction diagram of error handling

In general, error handling covers the following error management activities:

Error detection: language-specific try/catch mechanisms provide error detection.

Error handling: errors are based on exceptions. The name of the exception with the namespace defines the error type and error severity. Catching errors is based on the try/catch mechanism of each language. Error grouping is supported by exception inheritance. Error message and description is a part of exception class.

Propagation of error information: Error information is contained inside the exception entities. This information is passed between the different program parts. Passing information over the network is realized with ICD-based exceptions.

Administration and collection of all-important information: Definition of the error information is contained inside the exception class. New classes can be added and changed and it is also possible to change existing exception classes. All error information can be logged to the logging system.

Error messages will be displayed to the user: A QT GUI error display widget provides a GUI component for displaying the error messages. In the case of an error, the error message is displayed in a pop-up dialog.

All components of the error handling are provided for 3 languages: Java, CPP and Python.

8.2.1. Standard workflow

image8

Figure 2‑2 Error code repository and workflow

Figure 2‑2 presents the typical workflow for storing the exception information in a central repository. It is advised to have one or more general maintainers (lead developers) of the error code system. The maintainer is responsible for the error code system integrity and has an overview of all the exceptions in the system (or a specific section).

Whenever the developers need a specific exception, they first checkout the latest version of the SVN module where needed exception is located. They then use grep, find or any other file text search tool to find the desired exception. They can also browse the folder tree to find the exceptions that belong to specific groups. A helper search script searchExceptions.sh is provided to aid the developer when searching. If a developer cannot find the needed exception, one creates a new exception that is stored in a separate development branch. The new exception can be used immediately in the development branch. Then the developer commits the proposed new exception to central storage in the separate branch. The developer informs the maintainer about the new exception. The maintainer reviews the new exception and either merges the exception into the trunk or proposes the use of an existing exception from the trunk to the developer. When a new exception is merged into the system, the maintainer sends the message to the developer. The developer checks out the latest version of the trunk and uses the new exception from the trunk.

If a change to the exception information is needed, the same process as for creating an exception is used; the only difference is that the existing exception is modified and stored to the development branch. Subsequent steps are identical.

8.2.2. CII Exceptions

The CII error handling mechanism is based on CiiException, which is the base exception from which all other exceptions are derived. The CiiException derives from the special ICD defined CiiSerializedException, which provides serialization (2.4). Derived from the CiiException, two additional exceptions are defined as the part of the error handling system: CiiError and CiiBaseException. These two exceptions correspond to the error severity:

  • Normal - CiiBaseException – these exceptions cover the errors which can be recovered, and can be handled at higher software layers.

  • Error - CiiError – these exceptions cover the unrecoverable errors. When unrecoverable error happens, the program shall exist with the corresponding error message.

The severity of the error is defined by the exception name and the parent exception from which all errors of the selected severity are defined. The exception and error severities are pre-defined with exception names. All exceptions defined for severity »error« must contain the word error in the exception class name. All exceptions with »normal« severity must contain the word exception in the class name. Exceptions that define error severity are based on CiiError exception and exceptions with normal severity must derive from CiiBaseException.

All user defined exceptions must derive from the CiiBaseException or from CiiError.

image9

Figure 2‑3 CII base exceptions and errors

Figure 2‑3 shows the class inheritance diagram of the base errors and exceptions. The exceptions defined by the particular CII service all derive from the CiiBaseException for »normal« severity and from CiiError for »error« severity. Every CII service also defines root exceptions for the two severities (e.q., CiiOLDBException, CiiOLDBError). From these two exceptions, all the other exceptions for a particular system are derived.

8.2.2.1. Error stack

Single exception instance can nest exceptions and create an error stack. The stack is created by construction exceptions with the included information about the nested exception. A hierarchy of nested exceptions constructs the exception stack. Every nested exception presents an entry to the exception error stack. Exceptions (entries to the error stack) can only be added to the error stack, they cannot be changed or deleted. The error stack is automatically removed when reference to the exception with all the nested exceptions is lost.

In C++, a special throw mechanism must be used to create a stack of errors.

Whenever there is a need for the stack to be logged, an explicit entry to the log system must be programmed by the developer.

The examples of creating the exception stack can be found in 4.6.1.

8.2.2.2. Indexing Service

The Indexing service is a Java-based program that collects information about the exceptions and stores them in the Elasticsearch. The Indexing Service will traverse the SVN directory where exceptions are stored and crawl over the source code to extract the following information from the exceptions:

error type,

error message,

error description,

author.

Extraction uses the flowing rules:

The error type is extracted from the exception name and folder path.

The error description is extracted from the class comment. The description starts with the @desc tag.

The author of the exception is extracted from the class comment. It starts with the @author tag.

The error message is extracted from the exception class member variable named message. The variable is of type string.

The indexing service expects all information other than the description to be stored in one line.

To provide namespace information for the error type, the directory structure information is used. As the exceptions must be properly structured in the named folders that reflect the namespace, this information will provide the proper information for the error type namespace.

8.2.3. Conditions API

Error handling also provides conditions API. The purpose of the API is to provide a convenience API to enable checking of pre and post conditions, and method invariants. The API also supports enabling and disabling conditions and fail-fast checks. API provides static methods for verifying arguments, null values, states and valid index positions in lists and arrays. The methods, in general, accept one or more arguments for checking the condition and an argument that defines the exception type. The API interacts with the error handling system.

Whenever the condition is not met, the API will throw an exception. API contains an option for setting the fail-fast mode. When the fail-fast mode is enabled, the language-specific root exception will be thrown. This will cause the application to exit with the defined error message.

The error data is centrally stored in SVN. Chapter A.3 contains a description of Conditions API methods.

8.2.4. Serialization

Transportation of the exceptions over the distributed processes, written in different programming languages is supported with exception serialization. Serialized exceptions are transferred over MAL, which provides serialization of exceptions into plain objects. Therefore, exceptions are passed over the network with information serialized to string.

MAL uses ICD schema to define the exception structure. Custom user-defined ICD exceptions with desired information can be created. For these exceptions, there is no mechanism to implicitly transfer the data from the CiiException based exceptions to the ICD exception. Exception data can be read from the CiiException based exceptions and used to fill the ICD user-defined member fields. When an ICD based exception is transferred over the network, it is the developer’s responsibility to catch the exception and manage the data transfer and pass the exception to the higher levels of error handling.

For simplicity of use, the ICD CiiSerializedException is defined. This is a special ICD exception with hard-coded additions. This exception has predefined member fields, which carry the data of the exception. CiiSerializedException is also a base exception for all CiiExceptions. This special exception simplifies transferring exceptions over the network from the sender side, as no data copying between exceptions is needed. All CiiExceptions are derived from the CiiSerializedException, this means that CiiSerializedException will automatically catch all user-defined CiiExceptions. All exceptions that use CiiException will be automatically transferred over the network as CiiSerializedException.

8.3. Installation

Error Handling provides the following components that need to be installed:

  • Error indexer

  • Error handling dialog widget

  • Conditions API

See Configuration and Error Handling Transfer Document [3] for instructions to install the error handling system.

8.3.1. Prerequisites and waf modules

Error Handling uses SVN as central storage for exceptions. Error Handling also uses Elasticsearch as an engine for more structured searching of the exceptions. The two products must be installed and accessible from the local hosts to use the Error Handling:

  • SVN server,

  • Elasticsearch.

Error handling uses the following CII modules:

  • elt-mal: MAL, MAL ZPB.

  • client-api: Conditions API, Error Handling API.

  • srv-error-indexer: Error indexer, search script.

  • elt-qt-widgets: Error handling dialog widget.

8.4. Usage

The example in this manual is based on CLI SVN interface. Standard language IDE’s (Eclipse, IntelliJ Idea, QT Creator) can be used for writing exception definitions and to communicate with the SVN server. Examples of IDE use are not part of this manual.

8.4.1. Includes/Imports

For basic usage of the Error Handling, the following programming language imports must be added:

8.4.1.1. Java

import elt.error.CiiError;
import elt.error.CiiBaseException;
import elt.error.CiiConditions;

8.4.1.2. CPP

#include <CiiException.hpp>
#include <CiiConditions.hpp>

8.4.1.3. Python

import elt.error

8.4.1.4. QT

#include <CiiException.hpp>
#include <CiiExceptionDialog.hpp>

8.4.2. Creating an SVN repository

To use the error codes system an SVN repository must be created, which will act as a storage for all CII Exceptions. It is expected that this repository is already created by the lead developer or repository maintainer. The developer should consult the responsible person for the information about the location.

In this manual we will create a repository in an SVN server hosted at https://svnServer, at /pathToErrorCodes/errorCodes path. The repository can be created with the following command:

svn mkdir -m "Create error codes directory" https://svnServer/pathToErrorCodes/errorCodes --username myusername

In the errorCodes repository, the basic error codes system structure must be created. Folders can be created in the same manner as the root folder, or a local copy of structure can be prepared and committed to the server. The following structure must be created:

errorCodes/trunk/java

errorCodes/trunk/cpp

errorCodes/trunk/python

errorCodes/branches

Note: The root folder in SVN for the exceptions can be changed to the specific path (e.g. p8/trunk/CONTROL-SYSTEMS/CentralControlSystem/CII/CODE/srv-error-indexer/sampleErrorCodeTestRepo/). However, the structure of folders after the root folder is fixed and cannot be changed in order for Indexing service (6) to properly collect the error codes from SVN repository. Indexing service expects trunk folder and subfolders java, cpp and python. The names of the subfolder’s are a part of indexing service source code. A small change in Indexing service code must be made to use the different names for the folder structure.

8.4.3. Creating the exceptions

Exceptions must be created in the folder reflecting the namespace and with a file name reflecting the exception class name. Multiple exceptions with the same name are allowed, if they are put in separate namespaces. Exception name together with the namespace defines the error type and severity.

The workflow of error codes is described in section 2.1. Shortly, a developer must create exceptions in his own branch, which is then checked by the maintainer and merged into the trunk.

Note: It is expected that the development environment was properly set and that the variable $INTROOT points to the root location where all the CII components are installed (command “module load introot eeltdev” was run).

If the SVN errorCodes folder is not present on local developer’s computer, we first need to check-out the errorCodes directory from the SVN.

cd $INTROOT/sources/
svn co https://svnServer/pathToErrorCodes/errorCodes --username myusername

New exceptions, defined by a developer, should be created in their own branch. It is suggested to create this branch with a folder named after developer username. For each language a separate folder is required (java, cpp, python).

cd branches/
mkdir developerName
cd developerName
mkdir java cpp python

Exceptions source code must be structured as shown in the following examples. Exception files must be placed in the folders, whose names reflect the namespace. Each single exception is placed in own file with the filename named the same as the exception class.

In the example below we create CiiFileHandlingException, which is in the elt.error.common package. We create this exception for all 3 languages. Folders for this package must be created:

mkdir -p java/src/elt/error/common
mkdir -p python/src/elt/error/common
mkdir -p cpp/elt/error/src/include

Information from the exceptions is parsed by the indexing service. In order for the indexing service to properly parse the exceptions, the exceptions must be placed in the described folders. Also, the structure of exceptions source code must follow the structure presented in the following chapters (4.3.1, 4.3.2, 4.3.3). Information about the author and description of the exception is parsed from the source code comments. The information about the exception message is parsed from the message string.

Examples of the exceptions SVN repository can be found in the srv-error-indexer module under the sampleErrorCodesRepo directory. The following sections show the programming language specific examples.

8.4.3.1. Java

To create a Java exception, create CiiFileHandlingException.java file inside the java/src/elt/error/common folder with the desired exception specifics. The following example shows the Java exception structure:

package elt.error.common

/**
 * @desc This is the description of file handling error.
 * @author Somebody@eso.org
 */

public class CiiFileHandlingException extends CiiBaseException {
  public static final String MESSAGE = "Error while handling file: %s";

  public CiiFileHandlingException(String path){
    super(String.format(MESSAGE, path));
  }
}

8.4.3.2. CPP

For CPP exceptions to be browsable, they must follow the Java style for exception filenames. Each exception is created in its own .hpp file, with the file named the same as the exception class name. Exception details (description and author) must be added inline in the header code. CPP exceptions are header only based, no .cpp files shall be created.

To create a CPP exception, create CiiFileHandlingException.hpp file inside the cpp/elt/error/common/src/include folder with the desired exception specifics. The following example shows the CPP exception structure:

/**
 * @desc This is the description of file handling error.
 * @author Somebody@eso.org
 */

namespace elt {
namespace error {
namespace common {

class CiiFileHandlingException : public CiiBaseException {

const std::string getMessage(){
  static const std::string& message = "Error while handling file: %s";
  return message;
  //other option for message definition
  //return std::string("Bad version number %i");
}
public:
  CiiFileHandlingException(const char* fileName):
                   CiiException(getMessage(), fileName){}
};

//other option std::string
//if the argument passed to the CII Exception is string it has to be converted to const char*
/*
public:
  CiiFileHandlingException(const std::string& fileName):
                   CiiException(getMessage(), fileName.c_str()){}
}; */
}
}
}

8.4.3.3. Python

For Python exceptions to be browsable, they must follow the Java style for exception filenames. Each exception is created in its own .py file, with the file named the same as the exception class name.

To create a Python exception, create CiiFileHandlingException.py file inside the python/src/elt/error/common folder with the desired exception specifics. The following example shows the Python exception structure:

"""
@desc This is the description of file handling error.
@author Somebody@eso.org
"""
class CiiFileHandlingException(CiiBaseException):
  _message = "Error while handling file: %s"
  def __init__(self, arg1):
    super().__init__(_message % arg1)

After the exceptions are created in a separate branch, the developer commits the exceptions to the SVN server and informs the exceptions maintainer about the existence of new exceptions. The maintainer then merges the exception into the trunk.

It is the maintainer’s responsibility to check that the exception structure (folders and namespace) conforms to the convention.

8.4.4. Wrapping native exceptions

As native language exceptions follow their own inheritance tree, they cannot be directly caught by the CiiException. For this reason, all the native exceptions must be caught and rethrown with the details passed to the variant of the CiiException. This way information provided by the native exceptions can be part of the CII Exception.

The following example shows how to pass the native exception information to CII Exceptions in Java:

try {
  testList.get(arrayIndex);
} catch (IndexOutOfBoundsException ex) {
   throw new CiiBadIndexException(arrayIndex, ex);
}

Same mechanism should be used in Python and CPP.

8.4.5. Search script

Search script searchExceptions.sh is a simple bash script providing capabilities of automatic SVN update, local search and remote search. The script is a wrapper script around the Linux find and grep commands. The script automatically checks out the latest version of errorCodes from the SVN server and executes a search. It also provides search for indexed exceptions in Elasticsearch. While the functionality of the search in Elasticsearch is practically the same as for other find and grep style searches, it provides more structured output with more detailed information about the exceptions.

The script is intended to be used locally by developers. All the settings are embedded directly in the script.

8.4.5.1. Configuring the script

To configure the script, open it in a text editor:

nano $INTROOT/bin/searchExceptions.sh

Change the following settings:

  • REMOTE_REPO: location of the root of remote SVN repository

  • SVN_USERNAME: username with SVN permissions

  • SVN_PASSWORD: password for the SVN user

  • LOCAL_REPO: location of the root of local SVN repository

Example:

REMOTE_REPO="https://svnhq8.hq.eso.org/p8/trunk/CONTROL-SYSTEMS/CentralControlSystem/CII/CODE/srv-error-indexer/sampleErrorCodesRepo"
SVN_USERNAME="myusername"
SVN_PASSWORD="mypassword"
LOCAL_REPO="myLocalRepoRoot/sampleErrorCodesRepo"

8.4.5.2. Usage of the search script

A search without a special flag argument will perform a grep style search:

./searchExceptions.sh "Error while handling file:"

The search with the -f argument will execute a find style search:

./searchExceptions.sh -f "CiiFileHandlingException.java"

Direct Elasticsearch index searches can be executed with the -r argument. The following example shows how to search the exception index using the Elasticsearch query:

searchExceptions.sh -r "file error"

This searches for the exceptions that contain either word file or word error in the following fields of the Elasticsearch index:

  • Name

  • Folder

  • Namespace

  • Author

  • Description

  • Message

  • Language

  • Comment

A more relevant match (i.e. all or many search words are present) appear sooner in the returned list of results.

To see all functions supported by the script, the -h option can be used:

searchExceptions.sh -h

8.4.6. Exceptions examples

The examples show usage of the CII error handling system for Java, CPP and Python. As the majority of native language exception handling is similar across all three languages, a general example is shown only for Java:

public class ErrorHandlingExample {

  //initialize logger
  private static Logger logger =
                 CiiLogManager.getLogger("ErrorHandlingUsageExample");

  public static void main(String[] args) {

    /* Main method is the on the highest level (first) of exception handling.
     * All unhandled exception will end the application.
     */

    //initialize example object
    ErrorHandlingExample ex = new ErrorHandlingExample();

    /* Handle the exception from the runProductionArrayElementCode() and
     * log the current exception to log (Level.WARNING).
     */
    try {
      ex.runProductionArrayElementCode();
    } catch (CiiException e) {
      /* Exception is treated as warning. Current exception stack
       * is logged on a warning.
       * Exception is not passed to higher level, so the stack is cleared.
       */
      logger.warn(CiiLogManager.formatException(e));
    }

    /* Error will not be logged at this level.
     * Stack will also contain entries from the ex.runProductionFileOpen();
     */
    try {
      ex.runProductionFileOpen();
    } catch (CiiException e) {

      /*
       * Access individual information
       */
      System.out.println(e.getTypeName()); //errorTypeID
      System.out.println(e.getCreationDate()); //detectionTimestamp
      System.out.println(e.getStackTraceAsString());//localization information
      System.out.println(e.getMessage()); //message


      /* This will exit the application with CiiException as the exception
       * is thrown from the main method.
       */

      throw e;
    }

  }

  /**
   * Methods on ErrorHandlingExample present the 2 level of exception
   * handling.
   */
  public void runProductionArrayElementCode() {

    /*
     * Error from lower level is handled as non-critical and logged
     * as warning.
     * The super-type CiiException also catches CiiArrayOutOfRangeException
     */
    ProductionCode pcHandled = new ProductionCode();
    try {
      pcHandled.codePartThatGetsArrayElement();
    } catch (CiiException ex) {
      logger.warn(CiiLogManager.formatException(ex));
    }

    /*
     * Error from lower level is handled but passed also to higher level.
     */
    ProductionCode pc = new ProductionCode();
    try {
      pc.codePartThatGetsArrayElement();
    } catch (CiiBadIndexException ex) {
      /*
       * Stack will contain details form CiiBadIndexException
       * Error will be propagated to upper level
       */
      throw ex;
    }
  }

  public void runProductionFileOpen() {
    /*
     * Non handled, error will be handled in the higher level.
     */
    ProductionCode pcNonHandled = new ProductionCode();
    pcNonHandled.codePartThatTriesToOpenFile();
  }

  private class ProductionCode {

    /**
     * Application level 3 error handling
     */
    public void codePartThatGetsArrayElement() {

      final ArrayList<Integer> testList =
            new ArrayList(Arrays.asList(1, 2, 3, 4, 5));
      final int arrayIndex = 9;

      /*
       * Example of array out of range error, where java native exception
       * is wrapped into
       * CiiArrayOutOfRangeException. The error is propagated to higher level.
       */

      try {
        testList.get(arrayIndex);
      } catch (IndexOutOfBoundsException ex) {
        /*
         * Internal exception will be caught, but nothing will be passed.
         * IndexOutOfBoundsException is native java exception.
         */
      }

      try {
        testList.get(arrayIndex);
      } catch (IndexOutOfBoundsException ex) {
        /* Internal exception will be caught, and passed
         * as a CiiBadIndexException
         *
         * Error message example:  "Index 9 is out of bounds!
         * Error will be propagated to higher level.
         */
        throw new CiiBadIndexException(arrayIndex, ex);
      }
    }


    public void codePartThatTriesToOpenFile() {

      /*
       * Non existing file is logged as info.
       */
      final String path = "data.txt";
      final File myDatafile = new File(path);
      if (!myDatafile.exists()) {
        /*
         * We treat this as non - error, we only log the information
         * to logger.
         */
        logger.log(Level.INFO, "File %s could not be opened", path);
      }

      /*
       * Non existing file is threaded as error and passed to higher level.
       */
      if (!myDatafile.exists()) {
        /*
         * Pass the exception to higher level.
         * Exception will have path added to the message.
         */
        throw new CiiFileHandlingException(path);
      }
    }
  }

Presented example will produce the following log messages:

14:53:41.056 [main] INFO ErrorHandlingUsageExample - Index 9 is out of bounds!, ErrorTypeName=elt.error.CiiBadIndexException, ErrorDateTime=1579445621042, ErrorMessage=Index 9 is out of bounds!, ErrorTrace=elt.error.CiiBadIndexException
     at elt.error.ErrorHandlingExample$ProductionCode.codePartThatGetsArrayElement(ErrorHandlingExample.java:127)
     at elt.error.ErrorHandlingExample.runProductionArrayElementCode(ErrorHandlingExample.java:75)
     at elt.error.ErrorHandlingExample.main(ErrorHandlingExample.java:41)
Caused by: java.lang.IndexOutOfBoundsException: Index: 9, Size: 5
     at java.util.ArrayList.rangeCheck(ArrayList.java:657)
     at java.util.ArrayList.get(ArrayList.java:433)
     at elt.error.ErrorHandlingExample$ProductionCode.codePartThatGetsArrayElement(ErrorHandlingExample.java:120)
     ... 2 more
,
14:53:41.069 [main] WARN ErrorHandlingUsageExample - File data.txt could not be opened
Exception in thread "main" elt.error.CiiFileHandlingException: Error while handling: data.txt
     at elt.error.ErrorHandlingExample$ProductionCode.codePartThatTriesToOpenFile(ErrorHandlingExample.java:154)
     at elt.error.ErrorHandlingExample.runProductionFileOpen(ErrorHandlingExample.java:90)
     at elt.error.ErrorHandlingExample.main(ErrorHandlingExample.java:49)

Source code of this example can be found in the client-api module under elt-common/java/common/test/elt/error/ErrorHandlingExample.java

8.4.6.1. Building an Error Stack

The following examples show sample code of building the exception stack in different languages. The exception stack is built by nesting the exceptions:

8.4.6.1.1. Building Error Stack in Java
try {
     try {
             throw CiiOldbDpExistsException("testDPUri");
             } catch (CiiOldbDpExistsException ex) {
                     throw (CiiBadIndexException(10, ex));
             }
     } catch (CiiBadIndexException ex) {
             ex.getCiiExceptionStackAsString();
     }
}
8.4.6.1.2. Building Error Stack in Python
try:
    try:
        raise CiiInvalidTypeException("")
    except CiiException as x:
        raise CiiBadIndexException(x, 10)
except CiiException as cix:
        print(cix.get_stack_trace())
8.4.6.1.3. Building Error Stack in CPP
try
{
  try
  {
    throw elt::oldb::CiiOldbDpExistsException("testDPUri");
  } catch (const elt::error::CiiOldbException& e) {
    // build exception stack of nested exceptions
    // CiiOldbDpExistsException is nested in CiiBadIndexException
    CII_THROW_WITH_NESTED(elt::error::CiiBadIndexException, e, 10);
  }
} catch (const elt::error::CiiException& e) {
  // flatten all nested exceptions and dump to string
  std::string nestedDetails = e.dumpWithNested();
}

8.4.6.2. Throwing a CPP Exception with Extra Information

CPP API provides additional macros which add line number and code filename to the error messages. Section A.2 in Appendix A contains the details.

The following example shows throwing a CPP exception with details (line number, function, file name, etc.). Standard CPP exception what() method (inherited from std::exception) shall be used to obtain the exception message. CPP API also provides the extra Dump() method (see details in section A.1).

try
{
  CII_THROW(elt::error::CiiFileHandlingException, "myfileName");
}
catch(const elt::error::CiiException& e)
{
  std::cout << e.What(); //error message
  std::cout << e.GetDetails(); //error details
  std::cout << e.Dump(); //error dump
}

8.4.6.3. Exception Serialization from CPP to Java

The following code shows an example of transferring the exception from CPP to Java. All the exceptions are serialized using the MAL CiiSerializableException.

//CPP method with CiiException on the sender side
std::string methodWithException(const std::string &par1) override
{
  throw elt::error::CiiInvalidDataTypeException(par1);
  return par1;
}

//Java - Receiving exception on the reciever side
try {
  final String unexpectedReply = serialzation.methodWithException(“Test”);
} catch (CiiSerializableException e) {
  logger.info(String.format("Type: %s", e.getCiiType()));
  logger.info(String.format("Host name: %s", e.getCiiHostName()));
  logger.info(String.format("File name: %s", e.getCiiFileName()));
  logger.info(String.format("Class name: %s", e.getCiiClassName()));
  logger.info(String.format("Method name: %s", e.getCiiMethodName()));
  logger.info(String.format("Message: %s", e.getCiiMessage()));
  logger.info(String.format("Creation Date: %d", e.getCiiCreationDate()));
  logger.info(String.format("Stack trace: %s", E.getCiiStackTrace()));
  logger.info(String.format("Exception stack: %s", e.getCiiExceptionStack()));
  logger.info(String.format("Details: %s", e.getCiiDetails()));
}

8.4.7. Using the Condition API

The following example shows the use of the condition API. Since the use is similar for Java, CPP and Python, only a Java example is presented.

public static void main(String[] args) {
  // Enable condition API for the process.
  CiiConditions.setEnabled(true);

  ErrorConditionsExample ex = new ErrorConditionsExample();

  try{
     ex.setRotation(250);
  } catch (CiiException x){
     System.out.println(x.getMessage());
  }
  ex.getListValue(10);
}

// get value from array of float
public float getListValue(int index){
  // check is element is in range.
   CiiConditions.checkElementIndexRange(index, list.length,
                new CiiBadIndexException(index));
   return list[index];
}

public void setRotation(int degrees){
  //pre condition check -- check if rotation in smaller than 360 deg.
  CiiConditions.checkArgument(degrees <= 360, new CiiOverRotatedException());
  axis.setRotation(degrees)
  axis.rotate()
  final int readRotation = 200;
  // post condition check -- check if axis is properly rotated.
  CiiConditions.checkArgument(readRotation == degrees,
      new CiiNotProperlyRotatedException());
}

8.5. Using the QT Error Dialog Widget

To use the error dialog widget, CiiExceptionDialog.hpp must be included into the source code. The dialog is located under the elt::error::gui namespace. The CiiExceptionDialog.hpp derives from the QDialog. The method exec() is therefore directly inherited from this QDialog class.

Exception dialog constructor takes a CiiException as an input parameter. All exception details are automatically parsed into the dialog, based on the CiiException type.

To display the widget, exec() method must be called. The following code shows the typical usage:

#include <CiiExceptionDialog.hpp>

try {
  throw elt::error::CiiInvalidTypeException();
} catch (const elt::error::CiiException& e) {
  gui::CiiExceptionDialog w(e);
  w.exec();

}

Figure 5‑1 shows an example of the Error widget dialog screen output. The widget displays all the important error information: Exception class (error type), exception message, time of the occurrence, thread ID, process ID, host, line number and stack trace.

image10

Figure 5‑1 Error widget dialog screen output

8.6. Indexing service

Error indexer (2.2.2) is a tool that collects the data of exceptions and stores the exceptions data to Elasticsearch index. Using Elasticsearch for searching the exceptions provides structured and powerful search. Data provided by indexing service is used by the search script (4.5), when using the ElasticSearch option. As ElasticSearch returns data in JSON format, the data returned by the queries can be used for fast integration with other services.

8.6.1. Configuring indexing service

Error indexer uses configuration localDB for setting up the needed configuration. The configuration instance file must be created and deployed for error indexer to work properly. Use the following template to create the config file:

config:
  instance:
    __comment__: Configuration for error-indexer
    data:
      "@type": CiiErrorIndexerConfigClass
      fields:
        elasticSearchServiceAddress:
           "@type": MdString
           metadataInstance: ConfStringStd
           value: "localhost"
        elasticSearchIndexName:
           "@type": MdString
           metadataInstance: ConfStringStd
           value: "errors"
        centralErrorCodesRepositoryHost:
           "@type": MdString
           metadataInstance: ConfStringStd
           value: "https://svnhq8.hq.eso.org"
        centralErrorCodesRepositoryPaths:
           "@type": MdStringArray
           metadataInstance: ConfStringArrayStd
           value:
             - "p8/trunk/CONTROL-SYSTEMS/CentralControlSystem/CII/CODE/srv-error-indexer/sampleErrorCodesRepo/"
        lastProcessedRevision:
           "@type": MdInt32
           metadataInstance: ConfInt32Std
           value: 0
        centralErrorCodesPassword:
           "@type": MdString
           metadataInstance: ConfStringStd
           value: "myPass"
        centralErrorCodesUserName:
           "@type": MdString
           metadataInstance: ConfStringStd
           value: "myUsername"

After the file is created (we assume it is saved under ErrorIndexerConfig.cdi.yaml), it has to be deployed to the localDB. If localDB is not initialized, it should be initialized first.

config-tool init #LocalDb initialization.
config-tool deploy -i localDbConfiguration/ErrorIndexerConfig.cdi.yaml -a cii.config://services/CiiErrorIndexer

Following properties are set:

  • elasticSearchServiceAddress: Address of the elastic search server.

  • elasticSearchIndexName: Index name, which will hold the indexed data.

  • centralErrorCodesRepositoryHost: SVN hostname of central repository.

  • centralErrorCodesRepositoryPaths: Array of paths in the repository. Indexer will look for exception code under these subfolders.

  • lastProcessedRevision: Internally, last processed revision is stored. The setting should initially be set to 0.

  • centralErrorCodesPassword: password credential for SVN.

  • centralErrorCodesUserName: user name credential for SVN.

8.6.2. Running the indexing service

To manually run the indexer, execute the following command:

error-indexer

It is proposed to run the indexer every 30 minutes. A Linux time-based utility cron shall be used for this. To define the schedule to use the cron, put the following line into the /etc/crontab (change the {INTROOT} to proper location of the CII root installation folder – varriable $INTOOT cannot be used here):

30 * * * * {INTROOT}/bin/error-indexer

Another option (not documented in this manual) is to hook running of indexer on every SVN update.

8.6.3. Accessing the indexed data

Elasticsearch provides more powerful search capabilities of the exception definitions and more structured output searched data. As the data is returned in JSON format, it is more suitable for integration with other services, for example logging. Elasticsearch uses REST API to access the data. Any REST client can be used to access the errors data. For the following examples we will use curl.

Displaying all the errors in the index:

curl -XPOST 'http://ciielastichost:9200/errors/_search?pretty'

Elasticsearch provides Query DSL language [4], with powerful search capabilities. The following fields can be used, when searching the error index:

  • folder,

  • author,

  • namespace,

  • description,

  • language,

  • message,

  • version,

  • revision,

  • timestamp.

Elasticsearch supports different types of searches. When using the wildcard type of search, only lower case is supported (ElasticSearch standard analyzer is used [5]). Data in index can be in camel case, but the search will only work if the lower-case words are used in the search query. The search will not distinguish between lower and upper case.

Getting the error data based on the exception name:

curl -XPOST 'http://ciielastichost:9200/errors/_search?pretty' -d '{"query":{"match":  {"name": " CiiFileHandlingException"}}}' -H "Content-Type: application/json"

Wildcard search based on exception name:

curl -XPOST 'http://ciielastichost:9200/errors/_search?pretty' -d '{"query":{"wildcard":  {"name": "ciifile*"}}}' -H "Content-Type: application/json"

Searching the errors with the desired description:

curl -XPOST 'http://ciielastichost:9200/errors/_search?pretty' -d '{"query":{"match":  {"description": "This is the description"}}}' -H "Content-Type: application/json"

Searching the errors with the desired description and name:

curl -XPOST 'http://ciielastichost:9200/errors/_search?pretty' -d '{"query": {"bool": {"should": [{ "match": { "description": "This is the description"}}, { "match": { "message": "Error while"}}]}}}' -H "Content-Type: application/json"

Wildcard search with the desired description and name:

curl -XPOST 'http://ciielastichost:9200/errors/_search?pretty' -d '{"query": {"bool": {"should": [{ "wildcard": { "description": "* is the description"}}, { "wildcard": { "message": "*while"}}]}}}' -H "Content-Type: application/json"

Search with the desired language and name:

curl -XPOST 'http://ciielastichost:9200/errors/_search?pretty' -d '{"query": {"bool": {"must": [{ "match": { "language": "JAVA"}}, { "match": { "name": " CiiFileHandlingException"}}]}}}' -H "Content-Type: application/json"

Return the last record of wildcard search sorted by timestamp:

curl -XPOST 'http://ciielastichost:9200/errors/_search?pretty' -d '{"query":{"wildcard":  {"name": "ciifile*"}},"sort": [ { "timestamp": { "order": "desc" } } ], "size": 1}' -H "Content-Type: application/json"

8.7. API

8.7.1. CII Exception methods

CII-specific fields and methods are added to CiiException to provide the required functionalities:

stack trace: (execution stack trace - back trace) is provided by language-specific trace mechanisms in Java and Python. CPP uses the boost libbacktrace module. The getStackTraceAsString method provides a stack trace in a string format.

creation date: creation date/time is implicitly set by CiiException. The date/time is stored in milliseconds since Epoch. The getCreationDate method will return the number.

type name: type name is implicitly set by the exception definition. The getCiiType method will provide the type of the exception class.

The CPP CiiException class provides additional functionalities:

The following fields are provided as member fields with getter and setter methods:

threadId

lineNumber

functionName

filename

processId

hostname

details: additional exception trace details can be added to CPP exceptions. This attribute should be explicitly set by the developer. The getDetails and setDetails methods should be used to retrieve and store the information.

The two additional methods are added:

dump: method that returns string formatted information about all exception details and values.

dumpWithNested: method that returns string formatted information about all nested exceptions details and values.

8.7.2. CPP macros

CPP API provides macros which add details (file, function and line number) to the error messages. If these macros are not used, the exceptions will not contain the mentioned fields. The following macros are provided:

CII_THROW(exceptionType_t, …)

Throws the desired exception with additional information. First parameter is the exception type. All other parameters are variadic, and depend on the number of parameters the exception class defines.

CII_THROW_WITH_NESTED (exceptionType_t, nested_exception, …)

Throws the desired exception with additional information and nested exception.
The first parameter is the exception type and the second parameter is the exception to be nested.

All other parameters are variadic, and depend on the number of parameters the exception class defines.

8.7.3. Conditions API

The conditions API provides static methods for verifying arguments, null values, states and valid index positions in lists and arrays. It provides the following methods:

setEnabled(bool enabled)

getEnabled()

Methods for enabling and disabling the Conditions API. If Conditions API is disabled, checks will not be executed. The getEnabled method will return the current state of the enabled flag.

setFailFastMode(bool enabled)

getFailFastMode()

Methods for enabling and disabling the fail-fast mode. If fail-fast is enabled, failing the condition will cause the application to exit with an error. The getFailFastMode method will return the current state of the enabled flag.

checkArgument(bool expr, Exception ex)

Checks that the parameter expr is true. It can be used for validating arguments in methods. If the expression evaluates to false, the given exception is thrown.

checkNotNull(T value, Exception ex)

Checks that the value is not null. It returns the value directly, so you can use checkNotNull(value, ex) inline. In case of null, the given type exception is thrown.

checkState(boolean value, Exception ex)

Checks some state of the object, not dependent on the method arguments. For example, an iterator might use this to check that method next() has been called before any call to remove() method. If the expression evaluates to false, the given exception is thrown.

checkElementIndexRange (int index, int size, Exception ex)

Checks that index is a valid element index in a list, string, or array with the specified size. An element index may range from 0 inclusive to size exclusive. Instead of passing the whole list, string, or array, only it’s size is passed as parameter. If the index exceeds the size, the given exception is thrown.

The condition API for specific languages (Java, CPP and Python) is shown in the following sections.

8.7.3.1. Java

public class CiiConditions {
    public static boolean getEnabled() {...}

    public static void setEnabled(boolean enabled) {...}

    public static boolean getFailFastMode(){...}

    public static void setFailFastMode(boolean failFastMode) {...}

    public static <E extends Exception> void checkArgument(boolean expr, E ex) throws E {...}

    public static <T, E extends Exception> void checkNotNull(T value, E ex) throws E {...}

    public static <E extends Exception> void checkState(boolean value, E ex) throws E {...}

    public static <E extends Exception> void checkElementIndexRange(int index, int size, E ex) throws E {...}


}

8.7.3.2. CPP

class CiiConditions {
public:

    static bool GetEnabled();

    static void SetEnabled(bool enabled);

    static bool GetFailFastMode();

    static void SetFailFastMode(bool failFast);

    template<typename E, class T, typename ... Args>
    static void CheckArgument(bool expr, Args ... args);

    template<typename E, class T,  typename ... Args>
    static void CheckNotNull(T value, Args ... args)

    template<typename E, class T, typename ... Args>
    static void CheckState(T value, Args ... args)

    template<typename E, class T, typename ... Args>
    static void CheckElementIndexRange(size_t index,
                 size_t size, Args ... args);

};

8.7.3.3. Python

class CiiConditions:

   @staticmethod
  def get_enabled():
      pass

  @staticmethod
  def set_enabled(enabled):
      pass

  @staticmethod
  def get_fail_fast_mode():
      pass

  @staticmethod
  def set_fail_fast_mode(failFastMode):
      pass

  @staticmethod
  def check_argument(expr, ciiException):
      pass

  @staticmethod
  def check_not_null(value, ciiException):
      pass

  @staticmethod
  def check_state(expr, ciiException):
      pass

  @staticmethod
  def check_element_index_range(index, size, ciiException):
      pass

8.8. Serialization code example

The following code presents example of serialization of exceptions over MAL. The example consists of MAL CPP server and Java client. Examples for other languages can be found in the client-api module source code:

  • Java: client-api/elt-common/java/errorNonUnitTests

  • CPP: client-api/elt-common/cpp/error/errorSerialClnt, errorSerialSrv

  • Python: client-api/elt-common/runScripts/testZpbClientPythonException, testZpbServerPythonException

8.8.1. CPP serialization server code

#include <CiiSerializableException.hpp>
#include <CiiSerializationTest.hpp>
#include <CiiInvalidDataTypeException.hpp>

#include <mal/rr/qos/QoS.hpp>
#include <mal/Cii.hpp>
#include <mal/Mal.hpp>
#include <mal/utility/LoadMal.hpp>
#include <mal/rr/ServerContextProvider.hpp>
#include <mal/rr/ServerContext.hpp>
#include <mal/rr/ServerAmi.hpp>
#include <mal/rr/RrEntity.hpp>

#include <stdexcept>
#include <memory>
#include <iostream>
#include <boost/thread/future.hpp>

namespace {

const std::string EXCEPTION_CALL = "error";

/**
 * Simulate some work
 */
static void simulateWork() {
  std::this_thread::sleep_for(std::chrono::seconds(2));
}

class CiiSerializationTestImpl : public virtual elt::error::icdTest::CiiSerializationTest
{
  std::string methodWithException(const std::string &par1) override
  {
    std::cout << "Got exception call with parameter: " << par1 << std::endl;
    if (par1 == EXCEPTION_CALL)
    {
      elt::mal::throw_exception(elt::error::CiiInvalidDataTypeException(par1));
    }
    return par1;
  }

  std::shared_ptr<::elt::mal::rr::Ami<std::string>>
  amiMethodWithException(const std::string& par1)
  {
    // Obtain ptr to ServerAmi
    std::shared_ptr<::elt::mal::rr::Ami<std::string>> ami = elt::mal::rr::ServerContextProvider::
        getInstance<elt::mal::rr::ServerContext<std::string>>().createAmi();

    std::cout << "Ami call with param: " << par1 << std::endl;

    if (par1 != EXCEPTION_CALL)
    {
      auto future = boost::async(boost::launch::async, [=]() {
        simulateWork();
        // first response
        ami->complete("ONE");

        simulateWork();
        // second response
        ami->complete("TWO");

        simulateWork();
        // third response
        ami->completed("THREE");
      });
    }
    else
    {
      auto future = boost::async(boost::launch::async, [=]() {
        simulateWork();
        // first response
        ami->complete("ONE");

        simulateWork();
        // second response
        ::elt::error::CiiInvalidDataTypeException e = elt::error::CiiInvalidDataTypeException(par1);
        e.setFileName(boost::filesystem::path(__FILE__).filename().string());
        e.setFunctionName(__FUNCTION__);
        e.setLineNumber(__LINE__);
        e.setClassName(boost::core::demangle(typeid(*this).name()));
        ami->completeExceptionally(e);

        simulateWork();
        // third response
        ami->completed("THREE");
      });
    }
    return ami;
  }
};

} // namespace

int main(int ac, char* av[]) {
  std::cout << "CPP ZPB server test start." << std::endl;

  // Load DDS MAL mapping
  std::shared_ptr<::elt::mal::Mal> ddsMal = elt::mal::loadMal("zpb", {{"dds.domain", "100"}});
  ::elt::mal::CiiFactory &factory = elt::mal::CiiFactory::getInstance();
  factory.registerMal("zpb", ddsMal);

  elt::mal::Uri uri("zpb.rr://0.0.0.0:12081/CiiSerializationTest");

  try {
    // Create server, default QoS.
    std::unique_ptr<elt::mal::rr::Server> server = factory.createServer(uri, elt::mal::rr::qos::QoS::DEFAULT, {});

    // Register the service.
    std::shared_ptr<elt::mal::rr::RrEntity> exSer(new CiiSerializationTestImpl());
    server->registerService<elt::error::icdTest::CiiSerializationTest, true>("inst_1", exSer);

    // Process commands until server->shutdown() is called.
    server->run();
  } catch (std::exception& exc) {
    std::cerr << "EXCEPTION: " << exc.what() << std::endl;
  }

  std::cout << "Server stopped." << std::endl;
  return 0;
}

8.8.2. Java serialization client code

import java.net.URI;
import java.util.Properties;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import org.apache.logging.log4j.Logger;
import elt.error.icd.CiiSerializableException;
import elt.error.icdTest.CiiSerializationTestSync;
import elt.log.CiiLogManager;
import elt.mal.CiiFactory;
import elt.mal.Mal;
import elt.mal.rr.Ami;
import elt.mal.rr.qos.QoS;
import elt.mal.rr.qos.ReplyTime;
import elt.mal.zpb.ZpbMal;

public class ExceptionSerializationClientTest {

  private static final Logger logger = CiiLogManager.getLogger(ExceptionSerializationClientTest.class.getName());
  private static final String NO_EX_CALL = "first";
  private static StringBuilder outData = new StringBuilder();

  public static void main(String[] args) {
    logger.debug("Java test zpb client started.");

    final CiiFactory factory = CiiFactory.createInstance();

    final URI uri = URI.create("zpb.rr://127.0.0.1:12081/CiiSerializationTest/inst_1");
    logger.debug("URI: " + uri.toString());

    Mal mal = new ZpbMal();
    factory.registerMal("zpb", mal);

    try (CiiSerializationTestSync serialzation =
        factory.getClient(uri, new QoS[] {new ReplyTime(3, TimeUnit.SECONDS)}, new Properties(),
            CiiSerializationTestSync.class)) {

      logger.info("Perfomrming simple call test.");
      try {
        final String reply = serialzation.methodWithException(NO_EX_CALL);
        if (!reply.equals(NO_EX_CALL)) {
          logger.error("Error!!!! Did not get expected string.");
          System.exit(1);
        }
        final String unexpectedReply =
            serialzation.methodWithException(ExceptionSerializationServerTest.EXCEPTION_CALL);
        logger.error("Error!!!! Should not be here.");
      } catch (CiiSerializableException e) {
        final CiiException ex = CiiException.fromSerializable(e);
        logger.debug("Got CiiSerializableException.");
        logger.info(String.format("Type: %s", ex.getCiiType()));
        logger.info(String.format("Host name: %s", ex.getCiiHostName()));
        logger.info(String.format("File name: %s", ex.getCiiFileName()));
        logger.info(String.format("Class name: %s", ex.getCiiClassName()));
        logger.info(String.format("Method name: %s", ex.getCiiMethodName()));
        logger.info(String.format("Message: %s", ex.getCiiMessage()));
        logger.info(String.format("Creation Date: %d", ex.getCiiCreationDate()));
        logger.info(String.format("Stack trace: %s", ex.getCiiStackTrace()));
        logger.info(String.format("Exception stack: %s", ex.getCiiExceptionStackAsString()));
        logger.info(String.format("Details: %s", ex.getCiiDetails()));

        //the data to be checked by the integration test
        outData.append("Type -- ").append(e.getCiiType()).append(";");
        outData.append("Message -- ").append(e.getCiiMessage()).append(";");
        outData.append("Exception stack -- ").append(ex.getCiiExceptionStackAsString());

      }

      logger.info("Perfomrming Ami call test.");
      try {
        logger.info("OK call.");
        final Ami<String> okTasks = serialzation.amiMethodWithException(NO_EX_CALL);
        for (CompletableFuture<String> reply : okTasks) {
          final String result = reply.get();
          logger.info("Reply: " + result);
          logger.info("Task complete: " + okTasks.isDone());
        }

        logger.info("Error call.");
        final Ami<String> errorTasks = serialzation.amiMethodWithException(ExceptionSerializationServerTest.EXCEPTION_CALL);
        for (CompletableFuture<String> reply : errorTasks) {
          final String result = reply.get();
          logger.info("Reply: " + result);
          logger.info("Task complete: " + errorTasks.isDone());
        }
        logger.error("(Ami) Error!!!! Should not be here.");
      } catch (InterruptedException e) {
        logger.error("Unexpected error: ", e);
      } catch (ExecutionException e) {
        final Throwable th = e.getCause();
        if (th instanceof CiiSerializableException) {
          final CiiSerializableException se = (CiiSerializableException) th;
          final CiiException ex = CiiException.fromSerializable(se);
          logger.debug("Got CiiSerializableException.");
          logger.info(String.format("Type: %s", ex.getCiiType()));
          logger.info(String.format("Host name: %s", ex.getCiiHostName()));
          logger.info(String.format("File name: %s", ex.getCiiFileName()));
          logger.info(String.format("Class name: %s", ex.getCiiClassName()));
          logger.info(String.format("Method name: %s", ex.getCiiMethodName()));
          logger.info(String.format("Message: %s", ex.getCiiMessage()));
          logger.info(String.format("Creation Date: %d", ex.getCiiCreationDate()));
          logger.info(String.format("Stack trace: %s", ex.getCiiStackTrace()));
          logger.info(String.format("Exception stack: %s", ex.getCiiExceptionStackAsString()));
          logger.info(String.format("Details: %s", ex.getCiiDetails()));
        } else {
          logger.error("Unexpected error: ", e);
        }
      } catch (Throwable e) {
        logger.error("Unexpected throwable: ", e);
      }

    } finally {
      mal.close();
      factory.close();
    }
    logger.debug("Client done.");
    System.out.print(outData.toString());
    System.out.print("PASS");
  }

}