Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add configurable error level handling and custom loggers #20

Merged
merged 8 commits into from
May 28, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add configurable error level handling and custom loggers
  • Loading branch information
tbonfort committed May 27, 2021
commit 48e99f0d191157754c96c7ad01f205b633eb5c58
69 changes: 69 additions & 0 deletions godal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ extern "C" {
extern long long int _gogdalSizeCallback(char* key, char** errorString);
extern int _gogdalMultiReadCallback(char* key, int nRanges, void* pocbuffers, void* coffsets, void* clengths, char** errorString);
extern size_t _gogdalReadCallback(char* key, void* buffer, size_t off, size_t clen, char** errorString);
extern void godalLogger(int loggerID, CPLErr lvl, const char *msg);
}

char *cplErrToString(CPLErr err) {
Expand Down Expand Up @@ -1428,3 +1429,71 @@ char* VSIInstallGoHandler(const char *pszPrefix, size_t bufferSize, size_t cache
return nullptr;
}

typedef struct {
char **message;
CPLErr failLevel;
int loggerID;
} ErrorHandler;

static void godalErrorHandler2(CPLErr e, CPLErrorNum n, const char* msg) {
ErrorHandler *eh = (ErrorHandler*)CPLGetErrorHandlerUserData();
assert(eh!=nullptr);
if(e >= eh->failLevel) {
if (*eh->message == nullptr) {
*eh->message = (char *)malloc(strlen(msg) + 1);
strcpy(*eh->message, msg);
} else {
*eh->message = (char *)realloc(*eh->message, strlen(*eh->message) + strlen(msg) + 3);
strcat(*eh->message, "\n");
strcat(*eh->message, msg);
}
} else if( eh->loggerID != 0) {
godalLogger(eh->loggerID, e, msg);
}
}

static void godalWrap2(int debugEnabled, ErrorHandler *eh, char **options) {
CPLPushErrorHandlerEx(&godalErrorHandler2,eh);
if(options!=nullptr) {
for(char* option=*options; option; option=*(++options)) {
char *idx = strchr(option,'=');
if(idx) {
*idx='\0';
CPLSetThreadLocalConfigOption(option,idx+1);
*idx='=';
}
}
}
if (debugEnabled) {
CPLSetThreadLocalConfigOption("CPL_DEBUG","ON");
}
}

static void godalUnwrap2(int debugEnabled, char **options) {
CPLPopErrorHandler();
if(options!=nullptr) {
for(char* option=*options; option; option=*(++options)) {
char *idx = strchr(option,'=');
if(idx) {
*idx='\0';
CPLSetThreadLocalConfigOption(option,nullptr);
*idx='=';
}
}
}
if (debugEnabled) {
CPLSetThreadLocalConfigOption("CPL_DEBUG",nullptr);
}
}

char *test_godal_error_handling(int debugEnabled, int loggerID, CPLErr failLevel) {
char *msg = nullptr;
ErrorHandler eh{&msg, failLevel, loggerID};
godalWrap2(debugEnabled, &eh,nullptr);
CPLDebug("godal","this is a debug message");
CPLError(CE_Warning, CPLE_AppDefined, "this is a warning message");
CPLError(CE_Failure, CPLE_AppDefined, "this is a failure message");
godalUnwrap2(debugEnabled,nullptr);
return msg;
}

103 changes: 103 additions & 0 deletions godal.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,16 @@ const (
CFloat64 = DataType(C.GDT_CFloat64)
)

type ErrorCategory int

const (
CE_None = ErrorCategory(C.CE_None)
CE_Debug = ErrorCategory(C.CE_Debug)
CE_Warning = ErrorCategory(C.CE_Warning)
CE_Failure = ErrorCategory(C.CE_Failure)
CE_Fatal = ErrorCategory(C.CE_Fatal)
)

// String implements Stringer
func (dtype DataType) String() string {
return C.GoString(C.GDALGetDataTypeName(C.GDALDataType(dtype)))
Expand Down Expand Up @@ -1839,11 +1849,104 @@ func AssertMinVersion(major, minor, revision int) {
}
}

var loggerMu sync.Mutex
var loggerIndex int
var loggerFns = make(map[int]func(ErrorCategory, string))

func registerLogger(fn func(ErrorCategory, string)) int {
loggerMu.Lock()
defer loggerMu.Unlock()
loggerIndex++
for loggerIndex == 0 && loggerFns[loggerIndex] != nil {
loggerIndex++
}
loggerFns[loggerIndex] = fn
return loggerIndex
}

func getLogger(i int) func(ErrorCategory, string) {
loggerMu.Lock()
defer loggerMu.Unlock()
return loggerFns[i]
}

func unregisterLogger(i int) {
loggerMu.Lock()
defer loggerMu.Unlock()
delete(loggerFns, i)
}

func init() {
compiledVersion := LibVersion(C.GDAL_VERSION_NUM)
AssertMinVersion(compiledVersion.Major(), compiledVersion.Minor(), 0)
}

//export godalLogger
func godalLogger(loggerID C.int, ec C.int, msg *C.char) {
lfn := getLogger(int(loggerID))
lfn(ErrorCategory(ec), C.GoString(msg))
}

type logCallback struct {
logFn func(ec ErrorCategory, message string)
debugEnabled bool
}

func (lcb logCallback) setErrorAndLoggingOpt(elo *errorAndLoggingOpts) {
elo.logFn = lcb.logFn
if lcb.debugEnabled {
elo.debugEnabled = 1
}
}

func Logger(logFn func(ec ErrorCategory, message string), debugEnabled bool) interface {
errorAndLoggingOption
} {
return logCallback{logFn, debugEnabled}
}

type failureLevel struct {
ec ErrorCategory
}

func (fl failureLevel) setErrorAndLoggingOpt(elo *errorAndLoggingOpts) {
elo.ec = fl.ec
}

func FailureLevel(ec ErrorCategory) interface {
errorAndLoggingOption
} {
return failureLevel{ec}
}

type errorAndLoggingOpts struct {
logFn func(ec ErrorCategory, message string)
debugEnabled int
ec ErrorCategory
}

type errorAndLoggingOption interface {
setErrorAndLoggingOpt(elo *errorAndLoggingOpts)
}

func testErrorAndLogging(opts ...errorAndLoggingOption) error {
ealo := errorAndLoggingOpts{nil, 0, CE_Warning}
for _, o := range opts {
o.setErrorAndLoggingOpt(&ealo)
}
var logIdx int
if ealo.logFn != nil {
logIdx = registerLogger(ealo.logFn)
defer unregisterLogger(logIdx)
}
errmsg := C.test_godal_error_handling(C.int(ealo.debugEnabled), C.int(logIdx), C.CPLErr(ealo.ec))
if errmsg != nil {
defer C.free(unsafe.Pointer(errmsg))
return errors.New(C.GoString(errmsg))
}
return nil
}

// Version returns the runtime version of the gdal library
func Version() LibVersion {
cstr := C.CString("VERSION_NUM")
Expand Down
2 changes: 2 additions & 0 deletions godal.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ extern "C" {
char *godalGeometryTransform(OGRGeometryH geom, OGRCoordinateTransformationH trn, OGRSpatialReferenceH dst);

GDALDatasetH godalBuildVRT(char *dstname, char **sources, char **switches, char **error, char **config);

char *test_godal_error_handling(int debugEnabled, int loggerID, CPLErr failLevel);
#ifdef __cplusplus
}
#endif
Expand Down
45 changes: 45 additions & 0 deletions godal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2732,3 +2732,48 @@ func TestVSIGCSNoAuth(t *testing.T) {
t.Error("ENOENT not raised")
}
}

type errLogger struct {
logs []struct {
ec ErrorCategory
msg string
}
}

func (e *errLogger) Log(ec ErrorCategory, message string) {
e.logs = append(e.logs, struct {
ec ErrorCategory
msg string
}{ec, message})
}
func TestErrorHandling(t *testing.T) {
err := testErrorAndLogging()
assert.EqualError(t, err, "this is a warning message\nthis is a failure message")

err = testErrorAndLogging(FailureLevel(CE_Failure))
assert.EqualError(t, err, "this is a failure message")

err = testErrorAndLogging(FailureLevel(CE_Fatal))
assert.NoError(t, err)

el := &errLogger{}
err = testErrorAndLogging(FailureLevel(CE_Failure), Logger(el.Log, true))
assert.EqualError(t, err, "this is a failure message")
assert.Equal(t, []struct {
ec ErrorCategory
msg string
}{
{CE_Debug, "godal: this is a debug message"},
{CE_Warning, "this is a warning message"},
}, el.logs)

el.logs = nil
err = testErrorAndLogging(FailureLevel(CE_Failure), Logger(el.Log, false))
assert.EqualError(t, err, "this is a failure message")
assert.Equal(t, []struct {
ec ErrorCategory
msg string
}{
{CE_Warning, "this is a warning message"},
}, el.logs)
}