.. _error_handling:

Error handling in GDAL
======================

Overview
--------

GDAL provides a centralized error reporting system used across the
C and C++ APIs, and also available through SWIG binding languages.

C++ exceptions do not reach callers of the C++ public API,
unless otherwise stated in the documentation of the methods.

A global error handler is installed by default, which redirects non-debugging
warning and error messages, to the standard error stream. It is possible to
install a custom error handler to modify that default behavior.

An error is composed of an error level (warning, failure, etc.), an error category
(I/O error, memory error, etc.) and an error message.

For GDAL users
--------------

Include file
++++++++++++

GDAL error handling routines are available by including the public :file:`cpl_error.h`
header file (:source_file:`port/cpl_error.h`).

Error levels
++++++++++++

GDAL defines several error levels in the :cpp:enum:`CPLErr` enumeration:

- ``CE_None``: no error. This level is only used as a potential return code
  for functions.

- ``CE_Debug``: Debug message. This error level is generated by the
  :cpp:func:`CPLDebug`. Debug messages are not visible to users by default,
  unless they install a custom error handler, or set the :config:`CPL_DEBUG`
  configuration option.

- ``CE_Warning``: Warning. This error level corresponds to a non-nominal
  situation that is worth bringing to the attention of the user, but that does
  not prevent the ongoing operation to complete.

- ``CE_Failure``: Error that prevents the current operation to succeed. Other
  following GDAL operations might succeed.

- ``CE_Fatal``: Fatal unrecoverable error. The process is terminated with
  ``abort`` after it is emitted. Such errors are very rarely encountered, and
  are typical of a situation where a dynamic memory allocation fails on a very
  small allocation. Failures to allocate a large amount of memory will result
  instead to a recoverable ``CE_Failure``.


Error category
++++++++++++++

The error category is reported as an integer value aliased to the :cpp:type:`CPLErrorNum`
type. While most errors belong to the following categories, drivers are allowed
to emit custom codes.

- :c:macro:`CPLE_None`: no error. Category used by :cpp:func:`CPLDebug`

- :c:macro:`CPLE_AppDefined`: Application defined error.

- :c:macro:`CPLE_OutOfMemory`: Out of memory error.

- :c:macro:`CPLE_FileIO`: File I/O error.

- :c:macro:`CPLE_OpenFailed`: Open failed.

- :c:macro:`CPLE_IllegalArg`: Illegal argument.

- :c:macro:`CPLE_NotSupported`: Not supported.

- :c:macro:`CPLE_AssertionFailed`: Assertion failed.

- :c:macro:`CPLE_NoWriteAccess`: No write access.

- :c:macro:`CPLE_UserInterrupt`: User interrupted.

- :c:macro:`CPLE_ObjectNull`: NULL object.

Network filesystem related errors:

- :c:macro:`CPLE_HttpResponse`: HTTP response.

- :c:macro:`CPLE_BucketNotFound`: Corresponds to :c:macro:`VSIE_BucketNotFound`

- :c:macro:`CPLE_ObjectNotFound`: Corresponds to :c:macro:`VSIE_ObjectNotFound`

- :c:macro:`CPLE_AccessDenied`: Corresponds to :c:macro:`VSIE_AccessDenied`

- :c:macro:`CPLE_InvalidCredentials`: Corresponds to :c:macro:`VSIE_InvalidCredentials`

- :c:macro:`CPLE_SignatureDoesNotMatch`: Corresponds to :c:macro:`VSIE_SignatureDoesNotMatch`

- :c:macro:`CPLE_ObjectStorageGenericError`: Corresponds to :c:macro:`VSIE_ObjectStorageGenericError`

Retrieving error codes and messages
+++++++++++++++++++++++++++++++++++

Several warnings or errors can be emitted during the call of a GDAL public C/C++
function, in the current thread. Users can retrieve the last error with:

- :cpp:func:`CPLGetLastErrorType`: return the level of the last error

- :cpp:func:`CPLGetLastErrorNo`: return the category of the last error

- :cpp:func:`CPLGetLastErrorMsg`: return the message of the last error. The
  returned string is short-lived and must not be freed.

It is also possible to detect if one or several errors have been emitted during
the call of a function/method by comparing the value returned by
:cpp:func:`CPLGetErrorCounter` before and after that call.

Custom error handlers
+++++++++++++++++++++

Applications can install a global custom error handler using :cpp:func:`CPLSetErrorHandler`,
or :cpp:func:`CPLSetErrorHandlerEx` if they need to attach an arbitrary user data
that can be retrieved with :cpp:func:`CPLGetErrorHandlerUserData` in the error handler.
Such global custom error handler is mostly meant for applications that control
all uses of GDAL, and not for library code that may coexist with other application
or library code that also uses GDAL.

A per-thread temporary error handler can be installed with :cpp:func:`CPLPushErrorHandler`,
or :cpp:func:`CPLPushErrorHandlerEx` if they need to attach an arbitrary user data
that can be retrieved with :cpp:func:`CPLGetErrorHandlerUserData` in the error handler.
It must be paired with a call to :cpp:func:`CPLPopErrorHandler` (from the same
thread that called :cpp:func:`CPLPushErrorHandler`).

For example:

.. code-block:: c

    #include <cpl_error.h>

    struct ErrorContext
    {
        const char* context;
    };

    /* note: CPL_STDCALL needed for Windows 32-bit builds */
    static void CPL_STDCALL MyErrorHandler(CPLErr eErr, CPLErrorNo nErrorNum, const char* pszMessage)
    {
        const ErrorContext* context = (const ErrorContext*) CPLGetErrorHandlerUserData();
        fprintf(stderr, "context %s: error level=%d, error num=%d, msg=%s\n", eErr nErrorNum, pszMessage)
    }

    void MyFunc()
    {
        ErrorContext ctxt;
        ctxt.context = "Called from MyFunc";
        CPLPushErrorHandlerEx(MyErrorHandler, &ctxt);
        GDALClose(GDALOpen("i_do_not_exist", GA_ReadOnly));
        CPLPopErrorHandler();
    }

A special error handler :cpp:func:`CPLQuietErrorHandler` can be used to completely
silence error messages.

To ensure that :cpp:func:`CPLPushErrorHandler` and :cpp:func:`CPLPopErrorHandler`
are always paired, C++ users can instantiate :cpp:class:`CPLErrorHandlerPusher`. The
above example can be modified as:

.. code-block:: c++

    void MyFunc()
    {
        ErrorContext ctxt;
        ctxt.context = "Called from MyFunc";
        {
            // Create a scope to limit the scope of the temporary error
            // handler.
            CPLErrorHandlerPusher oCurrentHandler(MyErrorHandler, &ctxt);
            GDALClose(GDALOpen("i_do_not_exist", GA_ReadOnly));
        }
    }


Error handling from Python
++++++++++++++++++++++++++

Most GDAL functions do not emit a Python exception by default, unless the
:py:meth:`osgeo.gdal.UseExceptions` method is called.

When exceptions are enabled, ``CE_Failure`` errors are turned into a Python
exception.

It is possible to install a custom error handler like in the following example:

.. code-block:: python

    from osgeo import gdal

    gdal.DontUseExceptions()

    class MyErrorHandler:
        def __init__(self):
            pass

        def handler(self, level, error_num, message):
            print(f"Error level {level}, num {error_num}: {message}")

        def __enter__(self):
            gdal.PushErrorHandler(self.handler)

        def __exit__(self, *args):
            gdal.PopErrorHandler()

    with MyErrorHandler():
        gdal.Open("/i/do/not/exist")


Note that such custom error handler will only catch warnings when exceptions
are enabled. To also catch ``CE_Failure`` error, which are translated as Python exceptions
when exceptions are enabled, exceptions must not be enabled, or explicitly
disabled with :py:meth:`osgeo.gdal.DontUseExceptions`.


For GDAL developers
-------------------

Emitting debug messages and errors
++++++++++++++++++++++++++++++++++

Errors are reported using the :cpp:func:`CPLError` function.

Example:

.. code-block:: cpp

    CPLError(CE_Failure, CPLE_AppDefined,
             "Something went wrong during processing of %s", pszFilename);


For an error message that must be emitted only once during the lifetime of the
process, ``CPLErrorOnce`` can be used.


Debug messages are reported using the :cpp:func:`CPLDebug` function.

Example:

.. code-block:: cpp

    CPLDebug("GTiff", "Tile %d is empty", nTileId);

Debug messages that must appear only in GDAL Debug builds (typically because
they are emitted in a performance critical loop) can be emitted with
:c:macro:`CPLDebugOnly`. Those calls are completely omitted in release builds.
For debug messages that must be emitted only once during the lifetime of the
process, ``CPLDebugOnce`` can be used.


Preserving the error state (C++)
++++++++++++++++++++++++++++++++

It is sometimes desirable to avoid the error state from being modified by a
call to another function. This can be accomplished with an instance of
:cpp:class:`CPLErrorStateBackuper`

.. code-block:: cpp

    // 4 below statements just to demonstrate the effect of CPLErrorStateBackuper
    const std::string osLastErrorMsg = CPLGetLastErrorMsg();
    const CPLErr eLastErrLevel = CPLGetLastErrorType();
    const CPLErrorNum nLastErrorNum = CPLGetLastErrorNo();
    const GUInt32 nLastErrorCount = CPLGetErrorCounter();

    {
        CPLErrorStateBackuper oErrorBackuper(CPLQuietErrorHandler);
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Something went wrong but nobody will notice...");
    }

    // 4 below statements just to demonstrate the effect of CPLErrorStateBackuper
    CPLAssert(osLastErrorMsg == CPLGetLastErrorMsg());
    CPLAssert(eLastErrLevel == CPLGetLastErrorType());
    CPLAssert(nLastErrorNum == CPLGetLastErrorNo());
    CPLAssert(nLastErrorCount == CPLGetErrorCounter());


Accumulating errors (C++)
+++++++++++++++++++++++++

:source_file:`port/cpl_error_internal.h` provides a :cpp:class:`CPLErrorAccumulator`
class typically used by a worker thread to store errors emitted by their
worker functions, and replay them in the main thread.

.. code-block:: cpp

    #include "cpl_error_internal.h"

    CPLErrorAccumulator oErrorAccumulator;

    std::thread my_thread([&oErrorAccumulator]() {
        // Catches errors emitted in the current thread
        CPLErrorAccumulator::Context oInstalledAccumulator = oErrorAccumulator.InstallForCurrentScope()
        CPLError(CE_Failure, CPLE_AppDefined, "Something went wrong");
    });
    my_thread.join();

    // Re-emit errors from the calling thread
    oErrorAccumulator.Replay();


.. doxygenclass:: CPLErrorAccumulator
   :project: api
   :members:

.. doxygenclass:: CPLErrorHandlerAccumulatorStruct
   :project: api
   :members:
