As the AppException is propagated up the call stack, a list of ErrorInfo objects
are built up inside it. In this text I will examine that list, and how to extract information from it.

The ErrorInfo list can contain more than one ErrorInfo object. So, how do you determine
what type of error the AppException represents? What is the severity? What error message should you show
to the user? Or write to the application log? I mean, given that you could have multiple severities and error types
embedded in the different ErrorInfo objects, how do you determine the real type and severity of the error?

A ConfigFileLoader Example

Here is an example ErrorInfo list for an exception thrown during the loading of an important
configuration file:

errorId

contextId

type

severity

cause

FileLoadError

FileLoader

client

error

FileNotFoundException

ConfigFileLoadError

ConfigFileLoader

internal

fatal

This ErrorInfo list originates from an AppException being thrown by a
FileLoader component. This component only knows that the file loading error, is
of type client (wrong path given), and of severity error.

The FileLoader component was called by a ComfigFileLoader
component. The ConfigFileLoader component knows that if configuration file is not loaded
correctly, the whole application will not work correctly. Therefore the ConfigFileLoader
adds that information to the ErrorInfo list. It sets the error type to internal,
since either the configuration file is missing, or the paths to the configuration file is wrong. This is
an internal error. Additionally, the ConfigFileLoader sets the severity to fatal, since the application
cannot run correctly without the configuration file.

So, which of the two ErrorInfo objects should we use, when notifying users and non-users?

The answer is: A combination of them all.

In the example above, it is the second ErrorInfo
object (ConfigFileLoader) that contains most correct information
about the severity and type of the error. However, the first ErrorInfo
object contains more information about what failed (FileNotFoundException).

So, when it comes to severity and type, use the latest ErrorInfo object.
When it comes to logging diagnostic information, use all the ErrorInfo objects.

A UserFileLoader Example

Here, I will look at a similar example to the one mentioned above:

errorId

contextId

type

severity

cause

FileLoadError

FileLoader

client

error

FileNotFoundException

UserFileLoadError

UserFileLoader

client

warning

The FileLoader component is called by the UserFileLoader, which loads files which
the user of the application provides the paths for. Thus, if an error occurrs during the loading of that file,
it is not an error in the application. It is most likely just the user providing an invalid file path, or
the file pointed to has invalid content. As soon as the user points the application to an existing, valid file,
the error will go away.

A StreamLoader Example

Imagine that the fileLoader() method calls a streamLoader() method underneath.
The streamLoader() method is given a stream to load the file from.
The streamLoader() method does not know where the stream is connected to, but the
calling method most likely does. If the stream loading fails, here is an example of how the
ErrorInfo list could look:

errorId

contextId

type

severity

cause

parameters

StreamLoadError

StreamLoader

client

error

FileLoadError

FileLoader

Notice how the second ErrorInfo object does not update the severity field. It has no
new information about the severity, so it leaves it set to whatever the first ErrorInfo object set it
to.

Of course, in a real application, the fileLoader() method would probably be called from some other
method, which may actually know more about the severity of the error. Like this:

errorId

contextId

type

severity

cause

parameters

StreamLoadError

StreamLoader

client

error

FileLoadError

FileLoader

UserFileLoadError

UserFileLoader

warning

Notice, however, how the last ErrorInfo object does not know anything new about the
errorType, so it leaves it untouched.

Extracting Severity and Error Type from the ErrorInfo List

The severity and error type of a given AppException is used to determine how to handle
the exception, and which group of relevant parties to notify. Since the ErrorInfo
list can contain multiple severity and error type codes, you need to somehow filter them down to
one each. Otherwise, how do you know what to do about a given exception? I mean, if it is both
listed as a warning and an error, and as a service and client error?

If you follow the rule of only setting the error type and severity if you actually know something new about it,
you should use the latest severity and error type. Here is a code example:

After the above two for-loops have executed, the errorType and severity variables
will contain the latest set value of error type and severity.

You may decide to encapsulate the two loops in each their method (I probably would).
You probably won't be able to measure the performance difference, since exceptions do not happen
that often, and since the ErrorInfo list is probably not going to be that deep.

Extracting Error ID from the ErrorInfo List

Extracting the error ID and error descriptions from the ErrorInfo object list is a bit
different than extracting error type and severity.

For error type and severity you are only interested in one value. For error ID and error descriptions
you are interested in all the values. The complete error id is thus composed of all the error id's and
context id's in ErrorInfo list.

Here is a code example method that illustrates how to extract the total error ID:

This method creates a total error ID which is made up of all the context ID's and error ID's of the
ErrorInfo list.

Notice that the list is iterated backwards, from last to first ErrorInfo object. Remember,
the ErrorInfo objects are added to the AppException on the way up the call stack.
The path to the exception in context ID's and error ID's is thus bottom up in the list. By iterating
the list in reverse order, the path is changed to top down, reflecting the context path from the top
of the application down to the location of the error.

Here is an example total error ID:

UserFileLoader:FileLoadError/FileLoader:FileLoadError

Extracting User Error Descriptions

The user should be given a simple error message when something fails in your application. If the
error is caused by the users behaviour, and thus can be corrected by the user, tell the user
what the error was, and how to correct it.

If, on the other hand, the error is caused by something which is out of the users hands, don't
give the user too much detail. Just let the user know that the requested action failed, and
that it has been logged and will be investigated. You can use pretty much the same user error
message for all these types of errors, as giving the user internal error ID's, parameter information
etc. is not going to help the user. On the contrary, you risk giving away information that can
be used by hackers to break into your application.

Error Type Determines User Error Descriptions

You look at the errorType to determine if the error was caused by the user, or
something else. If the error type is "client", that means that the client (the user) has used
the application in a wrong way. By correcting the usage, the error will go away. In this case,
tell the user what went wrong, and tell them how to correct their error.

If the errorType is either service or internal, then you
all you do is to show the user a standard error message, saying that the error has been logged,
and will be investigated.

Extracting Non-User Error Descriptions

The non-user error description is used by the application operators and developers to determine
the cause of the error, and how to correct it. Therefore, it should be as detailed as possible.
The non-user error description should therefore include all the ErrorInfo objects
details, and not just one of ErrorInfo objects.

The full error description becomes quite large, and possibly messy and unstructured.
Therefore, it might make sense to convert it to an XML structure, and log that full structure.
Having an XML structure for the error details may also make it easier for tools to monitor the
log, and extract the bits of information out of it, they need.

Here is an example XML structure for an error report:

<error><fullErrorId>
UserFileLoader:LoadError/FileLoader:FileLoadError
</fullErrorId><errorType>client</errorType><severity>warning</severity><errorInfoList><errorInfo><contextId>FileLoader</contextId><errorId>FileLoadError</errorId><errorType>client</errorType><severity>error</severity><errorDescription>
The file was found, but could not be parsed correctly.
The following error was found in the file: ...
</errorDescription><parameters><parameter name="file">c:\data\myFile.txt</parameter></parameters></errorInfo><errorInfo><contextId>UserFileLoader</contextId><errorId>FileLoadError</errorId><errorType>client</errorType><severity>warning</severity><errorDescription>
An error occurred during the processing of a file which
the user requested the application to process.
</errorDescription><errorCorrection>
Point to a file that actually exists.
</errorCorrection><parameters><parameter name="file">c:\data\myFile.txt</parameter></parameters></errorInfo></errorInfoList></error>