Skip to content
Open
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
40 changes: 40 additions & 0 deletions include/LightGBM/c_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,19 @@ typedef void* ByteBufferHandle; /*!< \brief Handle of ByteBuffer. */
#define C_API_FEATURE_IMPORTANCE_SPLIT (0) /*!< \brief Split type of feature importance. */
#define C_API_FEATURE_IMPORTANCE_GAIN (1) /*!< \brief Gain type of feature importance. */

/*!
* \brief Log level constants for use with ``LGBM_RegisterLogCallbackWithLevel``.
* \note Severity ordering: Lower numeric values indicate more severe messages.
* ``C_API_LOG_LEVEL_FATAL`` (-1) is most severe, and ``C_API_LOG_LEVEL_DEBUG`` (2) is least severe.
* This follows the internal ``LogLevel`` enum and differs from Python logging conventions.
* Callbacks should be careful not to use comparisons like ``level >= C_API_LOG_LEVEL_WARNING``
* to filter "warnings and above", as that would include Info and Debug instead.
*/
#define C_API_LOG_LEVEL_FATAL (-1) /*!< \brief Fatal log level, for use with ``LGBM_RegisterLogCallbackWithLevel``. */
#define C_API_LOG_LEVEL_WARNING (0) /*!< \brief Warning log level, for use with ``LGBM_RegisterLogCallbackWithLevel``. */
#define C_API_LOG_LEVEL_INFO (1) /*!< \brief Info log level, for use with ``LGBM_RegisterLogCallbackWithLevel``. */
#define C_API_LOG_LEVEL_DEBUG (2) /*!< \brief Debug log level, for use with ``LGBM_RegisterLogCallbackWithLevel``. */

/*!
* \brief Get string message of the last error.
* \return Error information
Expand All @@ -73,6 +86,33 @@ LIGHTGBM_C_EXPORT int LGBM_DumpParamAliases(int64_t buffer_len,
*/
LIGHTGBM_C_EXPORT int LGBM_RegisterLogCallback(void (*callback)(const char*));

/*!
* \brief Register a callback function for log redirecting, with log level information.
* \note
* The callback receives the log level as an int (see ``C_API_LOG_LEVEL_*`` constants)
* and the fully-assembled message body (without prefix or trailing newline).
* Each log event produces exactly one callback invocation.
* If both ``LGBM_RegisterLogCallback`` and this function are called on the same thread,
* this callback takes precedence and the old callback is suppressed on that thread
* (both for normal log messages and for fatal errors).
* The callback must not throw; throwing from a C callback is undefined behaviour.
* The callback is only invoked on the thread that called this function;
* log messages from OpenMP worker threads will bypass the callback.
* \param callback The callback function to register
* \return 0 when succeed, -1 when failure happens
*/
LIGHTGBM_C_EXPORT int LGBM_RegisterLogCallbackWithLevel(void (*callback)(int, const char*));

/*!
* \brief Unregister the leveled log callback.
* \note
* This function resets the leveled callback pointer to ``nullptr`` on the calling thread.
* This is useful for cleanup in multi-threaded scenarios or in test fixtures to avoid
* dangling function pointers after the callback's lifetime ends.
* \return 0 when succeed, -1 when failure happens
*/
LIGHTGBM_C_EXPORT int LGBM_UnregisterLogCallbackWithLevel(void);

/*!
* \brief Get number of samples based on parameters and total number of rows of data.
* \param num_total_row Number of total rows
Expand Down
54 changes: 38 additions & 16 deletions include/LightGBM/utils/log.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ enum class LogLevel : int {
class Log {
public:
using Callback = void (*)(const char *);
using LeveledCallback = void (*)(int, const char *);
/*!
* \brief Resets the minimal log level. It is INFO by default.
* \param level The new minimal log level.
Expand All @@ -97,6 +98,8 @@ class Log {

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

static void ResetCallBackWithLevel(LeveledCallback callback) { GetLogCallBackWithLevel() = callback; }

static void Debug(const char *format, ...) {
va_list val;
va_start(val, format);
Expand All @@ -121,7 +124,7 @@ class Log {
char str_buf[kBufSize];
va_start(val, format);
#ifdef _MSC_VER
vsnprintf_s(str_buf, kBufSize, format, val);
vsnprintf_s(str_buf, kBufSize, _TRUNCATE, format, val);
#else
vsnprintf(str_buf, kBufSize, format, val);
#endif
Expand All @@ -130,8 +133,13 @@ class Log {
// 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);
if (GetLogCallBackWithLevel() != nullptr) {
GetLogCallBackWithLevel()(static_cast<int>(LogLevel::Fatal), str_buf);
// leveled callback is sole output on this thread — old callback and stderr suppressed
} else {
fprintf(stderr, "[LightGBM] [Fatal] %s\n", str_buf);
fflush(stderr);
}
#else
REprintf("[LightGBM] [Fatal] %s\n", str_buf);
R_FlushConsole();
Expand All @@ -140,13 +148,39 @@ class Log {
}

private:
// 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 Callback &GetLogCallBack() {
static THREAD_LOCAL Callback callback = nullptr;
return callback;
}

static LeveledCallback &GetLogCallBackWithLevel() {
static THREAD_LOCAL LeveledCallback callback = nullptr;
return callback;
}

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
if (GetLogCallBack() == nullptr) {
if (GetLogCallBackWithLevel() != nullptr) {
const size_t kBufSize = 1024;
char buf[kBufSize];
#ifdef _MSC_VER
vsnprintf_s(buf, kBufSize, _TRUNCATE, format, val);
#else
vsnprintf(buf, kBufSize, format, val);
#endif
GetLogCallBackWithLevel()(static_cast<int>(level), buf);
} else if (GetLogCallBack() == nullptr) {
printf("[LightGBM] [%s] ", level_str);
vprintf(format, val);
printf("\n");
Expand All @@ -168,18 +202,6 @@ class Log {
#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 Callback &GetLogCallBack() {
static THREAD_LOCAL Callback callback = nullptr;
return callback;
}
};

} // namespace LightGBM
Expand Down
11 changes: 10 additions & 1 deletion python-package/lightgbm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@

# .basic is intentionally loaded as early as possible, to dlopen() lib_lightgbm.{dll,dylib,so}
# and its dependencies as early as possible
from .basic import Booster, Dataset, Sequence, register_logger
from .basic import (
Booster,
Dataset,
Sequence,
register_logger,
register_leveled_logger,
unregister_leveled_logger,
)
from .callback import EarlyStopException, early_stopping, log_evaluation, record_evaluation, reset_parameter
from .engine import CVBooster, cv, train

Expand Down Expand Up @@ -36,6 +43,8 @@
"CVBooster",
"Sequence",
"register_logger",
"register_leveled_logger",
"unregister_leveled_logger",
"train",
"cv",
"LGBMModel",
Expand Down
Loading
Loading