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 cpl_error.h header file (cpl_error.h).

Error levels

GDAL defines several error levels in the 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 CPLDebug(). Debug messages are not visible to users by default, unless they install a custom error handler, or set the 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 CPLErrorNum type. While most errors belong to the following categories, drivers are allowed to emit custom codes.

Network filesystem related errors:

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:

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 CPLGetErrorCounter() before and after that call.

Custom error handlers

Applications can install a global custom error handler using CPLSetErrorHandler(), or CPLSetErrorHandlerEx() if they need to attach an arbitrary user data that can be retrieved with 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 CPLPushErrorHandler(), or CPLPushErrorHandlerEx() if they need to attach an arbitrary user data that can be retrieved with CPLGetErrorHandlerUserData() in the error handler. It must be paired with a call to CPLPopErrorHandler() (from the same thread that called CPLPushErrorHandler()).

For example:

#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 CPLQuietErrorHandler() can be used to completely silence error messages.

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

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 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:

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 osgeo.gdal.DontUseExceptions().

For GDAL developers

Emitting debug messages and errors

Errors are reported using the CPLError() function.

Example:

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 CPLDebug() function.

Example:

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 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 CPLErrorStateBackuper

// 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++)

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

#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();
class CPLErrorAccumulator

Class typically used by a worker thread to store errors emitted by their worker functions, and replay them in the main thread.

An instance of CPLErrorAccumulator can be shared by several threads. Each thread calls InstallForCurrentScope() in its processing function. The main thread may invoke ReplayErrors() to replay errors (and warnings).

Since

3.11

Public Functions

CPLErrorAccumulator() = default

Constructor.

Context InstallForCurrentScope()

Install a temporary error handler that will store errors and warnings.

inline const std::vector<CPLErrorHandlerAccumulatorStruct> &GetErrors() const

Return error list.

void ReplayErrors()

Replay stored errors.

struct Context

Object returned by InstallForCurrentScope() during life-time of which, errors are redirected to the CPLErrorAccumulator instance.

class CPLErrorHandlerAccumulatorStruct

Class that stores details about an emitted error.

Returned by CPLErrorAccumulator::GetErrors()

Since

3.11

Public Functions

inline CPLErrorHandlerAccumulatorStruct()

Default constructor.

inline CPLErrorHandlerAccumulatorStruct(CPLErr eErrIn, CPLErrorNum noIn, const char *msgIn)

Constructor.

Public Members

CPLErr type

Error level.

CPLErrorNum no

Error number.

CPLString msg = {}

Error message.