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 theCPLDebug(). Debug messages are not visible to users by default, unless they install a custom error handler, or set theCPL_DEBUGconfiguration 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 withabortafter 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 recoverableCE_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.
CPLE_None: no error. Category used byCPLDebug()CPLE_AppDefined: Application defined error.CPLE_OutOfMemory: Out of memory error.CPLE_FileIO: File I/O error.CPLE_OpenFailed: Open failed.CPLE_IllegalArg: Illegal argument.CPLE_NotSupported: Not supported.CPLE_AssertionFailed: Assertion failed.CPLE_NoWriteAccess: No write access.CPLE_UserInterrupt: User interrupted.CPLE_ObjectNull: NULL object.
Network filesystem related errors:
CPLE_HttpResponse: HTTP response.CPLE_BucketNotFound: Corresponds toVSIE_BucketNotFoundCPLE_ObjectNotFound: Corresponds toVSIE_ObjectNotFoundCPLE_AccessDenied: Corresponds toVSIE_AccessDeniedCPLE_InvalidCredentials: Corresponds toVSIE_InvalidCredentialsCPLE_SignatureDoesNotMatch: Corresponds toVSIE_SignatureDoesNotMatchCPLE_ObjectStorageGenericError: Corresponds toVSIE_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:
CPLGetLastErrorType(): return the level of the last errorCPLGetLastErrorNo(): return the category of the last errorCPLGetLastErrorMsg(): 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
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.