Skip to content

feat(c-api): add cuOptSetLogCallback and cuOptSetLogLevel - #1636

Open
ramakrishnap-nv wants to merge 3 commits into
mainfrom
feat/c-log-callback
Open

feat(c-api): add cuOptSetLogCallback and cuOptSetLogLevel#1636
ramakrishnap-nv wants to merge 3 commits into
mainfrom
feat/c-log-callback

Conversation

@ramakrishnap-nv

@ramakrishnap-nv ramakrishnap-nv commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Closes #1536, closes #184.

Adds a C API function plus an environment variable for solver log control:

  • cuOptSetLogCallback(settings, callback, user_data) — registers a
    per-line log callback invoked once per solver log line (in addition to
    any console/file sink). Pass NULL to clear.
  • CUOPT_LOG_LEVEL environment variable — overrides log verbosity at
    runtime (TRACE, DEBUG, INFO, WARN, ERROR, CRITICAL, OFF,
    case-insensitive). Read in default_level().

Log level is intentionally not exposed as a public API (per review):
the level enum is developer/debug-facing, so verbosity is controlled via
the env var rather than a cuOptSetLogLevel setter. Note the env var is
bounded by the compile-time LIBCUOPT_LOGGING_LEVEL (default INFO) —
lowering verbosity (e.g. WARN/ERROR/OFF) always works; raising it
above the build level has no effect since those statements are compiled out.

Design: the callback is stored in solver_settings_handle_t and
installed as a rapids_logger::callback_sink_mt via a RAII scope guard in
cuOptSolve, so init_logger_t picks it up without changes to the C++
internal solver-settings structs. It is captured immutably into the logger
guard to avoid a data race with the log bridge.

Adds two new C API functions to address #1536 and #184:

- cuOptSetLogCallback(settings, callback, user_data): registers a
  per-line log callback. Invoked once per log line in addition to any
  console/file sink already enabled. Pass NULL to clear.
- cuOptSetLogLevel(settings, level): overrides log verbosity using the
  new CUOPT_LOG_LEVEL_* constants (TRACE…OFF) in constants.h.

Internally, the callback is stored in solver_settings_handle_t and
installed as a rapids_logger::callback_sink_mt via a RAII scope guard
in cuOptSolve, so init_logger_t picks it up without any changes to the
C++ internal solver-settings structs. The pending globals are protected
by the existing g_guard_mutex that already serialises init_logger_t
construction.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
@ramakrishnap-nv
ramakrishnap-nv requested a review from a team as a code owner July 28, 2026 21:57
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The C API now supports per-solve log callbacks with user data, removes the solver log-level setter, and reads default log levels from CUOPT_LOG_LEVEL. Logger callback state is scoped to logger initialization, with updated logging guards and expanded C API tests.

Changes

Solver logging configuration

Layer / File(s) Summary
Logging API contracts
cpp/include/cuopt/mathematical_optimization/cuopt_c.h, cpp/src/utilities/logger.hpp
Documents callback severity values, retains callback registration, removes log-level setter declarations, and defines pending callback interfaces.
Logger runtime configuration
cpp/src/utilities/logger.cpp, cpp/src/mip_heuristics/*.cu
Parses CUOPT_LOG_LEVEL, forwards log records through captured callbacks, and updates debug/trace guards to RAPIDS logger levels.
Solver settings and solve lifecycle
cpp/src/pdlp/cuopt_c.cpp
Stores callback configuration, applies it during solving, and clears pending callback state after each solve.
C API callback validation
cpp/tests/linear_programming/c_api_tests/*
Tests callback delivery, user-data forwarding, callback clearing, and isolation across solves while replacing log-level tests.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested labels: improvement

Suggested reviewers: nguidotti, rg20, mlubin

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.38% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR adds a C log callback and improves logging controls, satisfying #184 and the logging-visibility goals in #1536.
Out of Scope Changes check ✅ Passed The changes stay focused on logging callback support, log-level control, and related tests; no clear out-of-scope additions stand out.
Title check ✅ Passed The title is related to the change, but it misleadingly says cuOptSetLogLevel was added when it was removed.
Description check ✅ Passed The description matches the implemented callback support and the shift to CUOPT_LOG_LEVEL for log verbosity.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Fix failing CI checks
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/c-log-callback

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cpp/src/pdlp/cuopt_c.cpp`:
- Around line 1077-1096: Add GoogleTest cases covering cuOptSetLogCallback and
cuOptSetLogLevel: verify log callbacks receive messages, can be cleared, and
forward the configured user_data; reject levels outside CUOPT_LOG_LEVEL_TRACE
through CUOPT_LOG_LEVEL_OFF; and confirm the selected level is applied during
cuOptSolve. Reuse the existing C API test fixtures and solver setup.

In `@cpp/src/utilities/logger.cpp`:
- Around line 149-158: Make logging configuration solve-local: in
cpp/src/utilities/logger.cpp lines 149-158, store immutable callback, user-data,
and level state in the sink/logger guard and have user_log_bridge use that state
without reading mutable pending globals; in cpp/src/utilities/logger.cpp lines
209-222, apply configuration for every solve or explicitly serialize/reject
concurrent solve logging rather than reusing an existing guard; in
cpp/src/pdlp/cuopt_c.cpp lines 1174-1195, pass each solve’s logging
configuration directly into the logger lifecycle instead of publishing it
through shared globals.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 493ccf78-693c-4957-a617-764507810f92

📥 Commits

Reviewing files that changed from the base of the PR and between 5dd8df4 and 3756aaf.

📒 Files selected for processing (5)
  • cpp/include/cuopt/mathematical_optimization/constants.h
  • cpp/include/cuopt/mathematical_optimization/cuopt_c.h
  • cpp/src/pdlp/cuopt_c.cpp
  • cpp/src/utilities/logger.cpp
  • cpp/src/utilities/logger.hpp

Comment thread cpp/src/pdlp/cuopt_c.cpp Outdated
Comment on lines +1077 to +1096
cuopt_int_t cuOptSetLogCallback(cuOptSolverSettings settings,
cuOptLogCallback callback,
void* user_data)
{
if (settings == nullptr) { return CUOPT_INVALID_ARGUMENT; }
solver_settings_handle_t* handle = get_settings_handle(settings);
handle->log_callback = callback;
handle->log_callback_user_data = user_data;
return CUOPT_SUCCESS;
}

cuopt_int_t cuOptSetLogLevel(cuOptSolverSettings settings, int level)
{
if (settings == nullptr) { return CUOPT_INVALID_ARGUMENT; }
if (level < CUOPT_LOG_LEVEL_TRACE || level > CUOPT_LOG_LEVEL_OFF) {
return CUOPT_INVALID_ARGUMENT;
}
get_settings_handle(settings)->log_level = level;
return CUOPT_SUCCESS;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Add coverage for the new C logging API.

Add GoogleTest coverage for callback delivery/clearing, user-data forwarding, invalid log levels, and level application during cuOptSolve. As per coding guidelines, contributors must add unit tests for code changes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/src/pdlp/cuopt_c.cpp` around lines 1077 - 1096, Add GoogleTest cases
covering cuOptSetLogCallback and cuOptSetLogLevel: verify log callbacks receive
messages, can be cleared, and forward the configured user_data; reject levels
outside CUOPT_LOG_LEVEL_TRACE through CUOPT_LOG_LEVEL_OFF; and confirm the
selected level is applied during cuOptSolve. Reuse the existing C API test
fixtures and solver setup.

Source: Coding guidelines

Comment thread cpp/src/utilities/logger.cpp Outdated
@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown

CI Test Summary

✅ All 31 test job(s) passed.

Fix a data race flagged in PR review: user_log_bridge was reading
g_pending_callback (a mutable global) without any lock, creating a
potential race against clear_pending_log_callback() called from
cuOptSolve's RAII scope guard.

Fix: capture the callback + user_data immutably into the
logger_config_guard at init_logger_t construction time. The bridge now
reads g_active_log_callback, a stable pointer into the guard's owned
state. The pointer is only nulled after reset_default_logger() removes
the sink (which blocks until all in-flight bridge calls complete), so
there is no race window.

Also adds a @warning to cuOptLogCallback documenting that log message
formatting is not a stable API and the callback is intended for display
purposes, not for parsing solver events programmatically.

Tests added (c_api_test.c):
- log_callback: verifies callback is invoked and user_data is forwarded
- log_callback_cleared: verifies NULL callback produces no calls
- log_level_off: verifies CUOPT_LOG_LEVEL_OFF silences all messages
- log_level_invalid: verifies out-of-range levels are rejected

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cpp/tests/linear_programming/c_api_tests/c_api_test.c`:
- Around line 377-390: Extend the callback lifecycle test around cuOptSolve to
first solve with the registered counting_log_callback and ctx, then create fresh
settings without a callback and solve again. Assert the second solve leaves
ctx.calls unchanged, while preserving the existing status checks and cleanup
flow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: b0ced68f-d15e-420b-ada2-0df32165076f

📥 Commits

Reviewing files that changed from the base of the PR and between 3756aaf and a1ce114.

📒 Files selected for processing (5)
  • cpp/include/cuopt/mathematical_optimization/cuopt_c.h
  • cpp/src/utilities/logger.cpp
  • cpp/tests/linear_programming/c_api_tests/c_api_test.c
  • cpp/tests/linear_programming/c_api_tests/c_api_tests.cpp
  • cpp/tests/linear_programming/c_api_tests/c_api_tests.h
🚧 Files skipped from review as they are similar to previous changes (2)
  • cpp/src/utilities/logger.cpp
  • cpp/include/cuopt/mathematical_optimization/cuopt_c.h

Comment thread cpp/tests/linear_programming/c_api_tests/c_api_test.c
/* @brief QCQP (barrier) scaling hyper-parameters */
#define CUOPT_QCQP_HYPER_RUIZ_EQUILIBRATION "qcqp_hyper_ruiz_equilibration"

/* @brief Log level constants for cuOptSetLogLevel */

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Are we sure we want to expose these log levels to the user?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

We can always set this Error or warn by default and it is upto user what they want to check.

@chris-maes chris-maes left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think we should discuss before merging. I'm supportive of adding an API like
cuOptSetLogCallback. But I don't think we should expose the different log levels to the user. These log levels are really more for developers and debugging. I'm fine with having an environmental variable that can change the log level.

@ramakrishnap-nv ramakrishnap-nv added the do not merge Do not merge if this flag is set label Jul 30, 2026
@ramakrishnap-nv

ramakrishnap-nv commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator Author

I think we should discuss before merging. I'm supportive of adding an API like
cuOptSetLogCallback. But I don't think we should expose the different log levels to the user. These log levels are really more for developers and debugging. I'm fine with having an environmental variable that can change the log level.

Updated, so removed set level and provided an option. in env to choose from existing levels.

Per review (chris-maes): log levels are developer/debug-facing and should
not be part of the public C API. Remove cuOptSetLogLevel and the public
CUOPT_LOG_LEVEL_* constants; keep cuOptSetLogCallback. Verbosity is now
controlled at runtime by the CUOPT_LOG_LEVEL environment variable
(TRACE..OFF, case-insensitive), read in default_level().

- Point two mip_heuristics #if guards at RAPIDS_LOGGER_LOG_LEVEL_* instead
  of the removed CUOPT_LOG_LEVEL_*, fixing a pre-existing latent bug where
  those undefined macros evaluated to 0.
- Replace the log-level unit tests with a cross-solve callback-lifecycle
  regression (CodeRabbit).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cpp/src/utilities/logger.cpp (1)

206-230: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Bind callback configuration to each solve, not global pending state.

A concurrent solve can overwrite g_pending_callback after another solve publishes it but before its init_logger_t consumes it. Also, a second init_logger_t reuses g_active_guard, so its logs can invoke the first solve’s callback/user data. This regresses the previously reported cross-solve configuration race.

  • cpp/src/utilities/logger.cpp#L206-L230: remove process-global pending callback ownership; capture configuration in a solve-scoped logger instance, or explicitly serialize logger configuration for the full solve lifetime.
  • cpp/src/pdlp/cuopt_c.cpp#L1162-L1177: pass the callback configuration directly into that solve’s logger lifecycle rather than publishing it before initialization; add an overlapping-solves regression test.

As per path instructions, “watch for thread-safety issues introduced by module-level/static pending callback storage (HIGH concurrency).”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/src/utilities/logger.cpp` around lines 206 - 230, Remove the
process-global pending callback state and bind callback configuration to each
solve’s logger lifecycle. In cpp/src/utilities/logger.cpp lines 206-230, update
set_pending_log_callback, clear_pending_log_callback, and related initialization
so configuration is solve-scoped or logger configuration is serialized for the
entire solve. In cpp/src/pdlp/cuopt_c.cpp lines 1162-1177, pass the callback and
user data directly into that solve’s logger instead of publishing global state
before initialization, and add an overlapping-solves regression test.

Source: Path instructions

🧹 Nitpick comments (1)
cpp/src/utilities/logger.cpp (1)

90-127: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Add coverage for CUOPT_LOG_LEVEL.

The replacement configuration path has no supplied test for case-insensitive valid values or unset/invalid fallback. Add isolated coverage for those behaviors.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/src/utilities/logger.cpp` around lines 90 - 127, Add isolated tests for
env_log_level covering case-insensitive recognized CUOPT_LOG_LEVEL values and
confirming unset or unrecognized values return std::nullopt. Ensure each test
controls and restores the environment variable so cases remain independent, and
use the existing default_level behavior only where needed to verify fallback.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@cpp/src/utilities/logger.cpp`:
- Around line 206-230: Remove the process-global pending callback state and bind
callback configuration to each solve’s logger lifecycle. In
cpp/src/utilities/logger.cpp lines 206-230, update set_pending_log_callback,
clear_pending_log_callback, and related initialization so configuration is
solve-scoped or logger configuration is serialized for the entire solve. In
cpp/src/pdlp/cuopt_c.cpp lines 1162-1177, pass the callback and user data
directly into that solve’s logger instead of publishing global state before
initialization, and add an overlapping-solves regression test.

---

Nitpick comments:
In `@cpp/src/utilities/logger.cpp`:
- Around line 90-127: Add isolated tests for env_log_level covering
case-insensitive recognized CUOPT_LOG_LEVEL values and confirming unset or
unrecognized values return std::nullopt. Ensure each test controls and restores
the environment variable so cases remain independent, and use the existing
default_level behavior only where needed to verify fallback.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: d0ce21bf-fa81-4a0b-9863-a2225b22dca2

📥 Commits

Reviewing files that changed from the base of the PR and between a1ce114 and adc2f81.

📒 Files selected for processing (9)
  • cpp/include/cuopt/mathematical_optimization/cuopt_c.h
  • cpp/src/mip_heuristics/diversity/diversity_manager.cu
  • cpp/src/mip_heuristics/feasibility_jump/feasibility_jump.cu
  • cpp/src/pdlp/cuopt_c.cpp
  • cpp/src/utilities/logger.cpp
  • cpp/src/utilities/logger.hpp
  • cpp/tests/linear_programming/c_api_tests/c_api_test.c
  • cpp/tests/linear_programming/c_api_tests/c_api_tests.cpp
  • cpp/tests/linear_programming/c_api_tests/c_api_tests.h
💤 Files with no reviewable changes (1)
  • cpp/src/utilities/logger.hpp

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do not merge Do not merge if this flag is set

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Better logging for C and Python layers [FEA] Add support for logging callback in the C interface

3 participants