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

redirect log to python console #3090

Merged
merged 10 commits into from
May 20, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
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
2 changes: 2 additions & 0 deletions include/LightGBM/c_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ typedef void* BoosterHandle; /*!< \brief Handle of booster. */
*/
LIGHTGBM_C_EXPORT const char* LGBM_GetLastError();

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 buf_size = 512;
char buf[buf_size];
snprintf(buf, buf_size, "[LightGBM] [%s] ", level_str);
GetLogCallBack()(buf);
vsnprintf(buf, buf_size, 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_
8 changes: 8 additions & 0 deletions python-package/lightgbm/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,21 @@
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='')
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems python 2 don't support end=''. @StrikerRUS @henry0312 any better solution? or we drop the support of python 2?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just add print_function to the 3rd line

from __future__ import absolute_import, print_function


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
6 changes: 6 additions & 0 deletions src/c_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,12 @@ const char* LGBM_GetLastError() {
return LastErrorMsg();
}

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