Skip to content

Commit

Permalink
redirect log to python console (microsoft#3090)
Browse files Browse the repository at this point in the history
* redir log to python console

* fix pylint

* Apply suggestions from code review

* Update basic.py

* Apply suggestions from code review

Co-authored-by: Nikita Titov <nekit94-08@mail.ru>

* Update c_api.h

* Apply suggestions from code review

* Apply suggestions from code review

* super-minor: better wording

Co-authored-by: Nikita Titov <nekit94-08@mail.ru>
Co-authored-by: StrikerRUS <nekit94-12@hotmail.com>
  • Loading branch information
3 people authored and ChipKerchner committed Jun 11, 2020
1 parent 0735837 commit e87e0c8
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 42 deletions.
7 changes: 7 additions & 0 deletions include/LightGBM/c_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ typedef void* BoosterHandle; /*!< \brief Handle of booster. */
*/
LIGHTGBM_C_EXPORT const char* LGBM_GetLastError();

/*!
* \brief Register a callback function for log redirecting.
* \param callback The callback function to register
* \return 0 when succeed, -1 when failure happens
*/
LIGHTGBM_C_EXPORT int LGBM_RegisterLogCallback(void (*callback)(const char*));

// --- start Dataset interface

/*!
Expand Down
104 changes: 64 additions & 40 deletions include/LightGBM/utils/log.h
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
/*!
* Copyright (c) 2016 Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE file in the project root for license information.
* Licensed under the MIT License. See LICENSE file in the project root for
* license information.
*/
#ifndef LIGHTGBM_UTILS_LOG_H_
#define LIGHTGBM_UTILS_LOG_H_

#include <string>
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <exception>
#include <iostream>
#include <stdexcept>
#include <string>

#ifdef LGB_R_BUILD
#define R_NO_REMAP
#define R_USE_C99_IN_CXX
#include <R_ext/Error.h>
#include <R_ext/Print.h>
#define R_NO_REMAP
#define R_USE_C99_IN_CXX
#include <R_ext/Error.h>
#include <R_ext/Print.h>
#endif

namespace LightGBM {
Expand All @@ -30,9 +31,10 @@ namespace LightGBM {
#endif

#ifndef CHECK
#define CHECK(condition) \
if (!(condition)) Log::Fatal("Check failed: " #condition \
" at %s, line %d .\n", __FILE__, __LINE__);
#define CHECK(condition) \
if (!(condition)) \
Log::Fatal("Check failed: " #condition " at %s, line %d .\n", __FILE__, \
__LINE__);
#endif

#ifndef CHECK_EQ
Expand Down Expand Up @@ -60,29 +62,32 @@ namespace LightGBM {
#endif

#ifndef CHECK_NOTNULL
#define CHECK_NOTNULL(pointer) \
if ((pointer) == nullptr) LightGBM::Log::Fatal(#pointer " Can't be NULL at %s, line %d .\n", __FILE__, __LINE__);
#define CHECK_NOTNULL(pointer) \
if ((pointer) == nullptr) \
LightGBM::Log::Fatal(#pointer " Can't be NULL at %s, line %d .\n", \
__FILE__, __LINE__);
#endif

enum class LogLevel: int {
enum class LogLevel : int {
Fatal = -1,
Warning = 0,
Info = 1,
Debug = 2,
};

/*!
* \brief A static Log class
*/
* \brief A static Log class
*/
class Log {
public:
using Callback = void (*)(const char *);
/*!
* \brief Resets the minimal log level. It is INFO by default.
* \param level The new minimal log level.
*/
static void ResetLogLevel(LogLevel level) {
GetLevel() = level;
}
* \brief Resets the minimal log level. It is INFO by default.
* \param level The new minimal log level.
*/
static void ResetLogLevel(LogLevel level) { GetLevel() = level; }

static void ResetCallBack(Callback callback) { GetLogCallBack() = callback; }

static void Debug(const char *format, ...) {
va_list val;
Expand Down Expand Up @@ -113,39 +118,58 @@ class Log {
#endif
va_end(val);

// R code should write back to R's error stream,
// otherwise to stderr
#ifndef LGB_R_BUILD
fprintf(stderr, "[LightGBM] [Fatal] %s\n", str_buf);
fflush(stderr);
throw std::runtime_error(std::string(str_buf));
#else
Rf_error("[LightGBM] [Fatal] %s\n", str_buf);
#endif
// R code should write back to R's error stream,
// otherwise to stderr
#ifndef LGB_R_BUILD
fprintf(stderr, "[LightGBM] [Fatal] %s\n", str_buf);
fflush(stderr);
throw std::runtime_error(std::string(str_buf));
#else
Rf_error("[LightGBM] [Fatal] %s\n", str_buf);
#endif
}

private:
static void Write(LogLevel level, const char* level_str, const char *format, va_list val) {
static void Write(LogLevel level, const char *level_str, const char *format,
va_list val) {
if (level <= GetLevel()) { // omit the message with low level
// R code should write back to R's output stream,
// otherwise to stdout
#ifndef LGB_R_BUILD
// R code should write back to R's output stream,
// otherwise to stdout
#ifndef LGB_R_BUILD
if (GetLogCallBack() == nullptr) {
printf("[LightGBM] [%s] ", level_str);
vprintf(format, val);
printf("\n");
fflush(stdout);
#else
Rprintf("[LightGBM] [%s] ", level_str);
Rvprintf(format, val);
Rprintf("\n");
#endif
} else {
const size_t kBufSize = 512;
char buf[kBufSize];
snprintf(buf, kBufSize, "[LightGBM] [%s] ", level_str);
GetLogCallBack()(buf);
vsnprintf(buf, kBufSize, format, val);
GetLogCallBack()(buf);
GetLogCallBack()("\n");
}
#else
Rprintf("[LightGBM] [%s] ", level_str);
Rvprintf(format, val);
Rprintf("\n");
#endif
}
}

// a trick to use static variable in header file.
// May be not good, but avoid to use an additional cpp file
static LogLevel& GetLevel() { static THREAD_LOCAL LogLevel level = LogLevel::Info; return level; }
static LogLevel &GetLevel() {
static THREAD_LOCAL LogLevel level = LogLevel::Info;
return level;
}

static Callback &GetLogCallBack() {
static THREAD_LOCAL Callback callback = nullptr;
return callback;
}
};

} // namespace LightGBM
#endif // LightGBM_UTILS_LOG_H_
#endif // LightGBM_UTILS_LOG_H_
11 changes: 10 additions & 1 deletion python-package/lightgbm/basic.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# coding: utf-8
"""Wrapper for C API of LightGBM."""
from __future__ import absolute_import
from __future__ import absolute_import, print_function

import copy
import ctypes
Expand All @@ -21,13 +21,22 @@
from .libpath import find_lib_path


def _log_callback(msg):
"""Redirect logs from native library into Python console."""
print("{0:s}".format(decode_string(msg)), end='')


def _load_lib():
"""Load LightGBM library."""
lib_path = find_lib_path()
if len(lib_path) == 0:
return None
lib = ctypes.cdll.LoadLibrary(lib_path[0])
lib.LGBM_GetLastError.restype = ctypes.c_char_p
callback = ctypes.CFUNCTYPE(None, ctypes.c_char_p)
lib.callback = callback(_log_callback)
if lib.LGBM_RegisterLogCallback(lib.callback) != 0:
raise LightGBMError(decode_string(lib.LGBM_GetLastError()))
return lib


Expand Down
7 changes: 6 additions & 1 deletion src/c_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -642,7 +642,6 @@ const char* LGBM_GetLastError() {
return LastErrorMsg();
}


int LGBM_GetDeviceType() {
#ifdef USE_GPU
return 1;
Expand All @@ -653,6 +652,12 @@ int LGBM_GetDeviceType() {
#endif
}

int LGBM_RegisterLogCallback(void (*callback)(const char*)) {
API_BEGIN();
Log::ResetCallBack(callback);
API_END();
}

int LGBM_DatasetCreateFromFile(const char* filename,
const char* parameters,
const DatasetHandle reference,
Expand Down

0 comments on commit e87e0c8

Please sign in to comment.