Research: Logging consistency audit in fork-added C/C++ code¶
- Status: Active
- Workstream: PR fix/logging-consistency-audit (this PR)
- Last updated: 2026-05-30
Question¶
libvmaf ships a user-installable log callback vmaf_log() filtered by vmaf_set_log_level(). Fork-added C/C++ code in core/src/ has accumulated direct printf / fprintf(stderr, ...) calls that bypass this surface entirely. Which call sites are real diagnostics that should go through the callback, and which are intentional CLI stdout output that must stay as direct prints?
Sources¶
core/src/log.h— declaresvmaf_log(VmafLogLevel, fmt, ...)and the level setter.core/include/libvmaf/libvmaf.h— public surface forvmaf_set_log_callback(the user contract that direct stderr writes break).- Memory note
feedback_no_lint_skip_upstream— lint upstream-mirror with the same rules as fork-added. - Memory note
feedback_vendored_in_scope— vendored libsvm / cJSON / pdjson are nominally in scope, but fixing must not break upstream sync semantics.
Findings¶
Enumerated all printf / fprintf(stderr, ...) calls in core/src/**/*.{c,cpp} (excluding third_party/, iqa/, 3rdparty/). Classified each site against three axes:
- Is it a diagnostic, or intended CLI output? Diagnostics belong in
vmaf_log. CLI output (e.g.,--list-devices,--print-timing, profiling tables) is the contract of the function and must stay direct. - Is it the log implementation itself?
core/src/log.{c,cpp}contain the default callback; they must use rawfprintfby construction. - Is it inside vendored code with upstream-sync invariants? Vendored libsvm in
core/src/svm.cppcarries its ownfprintfpattern — see deferral below.
| Site | Decision | Reason |
|---|---|---|
core/src/libvmaf.c x5 — vmaf_write_output guards | ROUTE through vmaf_log (ERROR) | Public API error path; users expect their callback to receive the message. |
core/src/sycl/dispatch_strategy.cpp — VMAF_SYCL_NO_GRAPH deprecation | ROUTE (WARNING) | One-shot env-var deprecation, exactly the canonical WARNING use case. |
core/src/sycl/common.cpp — device-enum exception | ROUTE (ERROR) | Exception path during vmaf_sycl_list_devices(). |
core/src/sycl/common.cpp — upload-timing debug print | ROUTE (DEBUG) | Already gated on extractor_timing debug flag; DEBUG level fits. |
core/src/sycl/common.cpp — graph_submit exceptions x3 | ROUTE (ERROR) | All exception paths. |
core/src/sycl/common.cpp — vmaf_sycl_list_devices stdout | KEEP | --list-devices is a CLI surface; stdout is the contract. |
core/src/sycl/common.cpp — vmaf_sycl_print_timing | KEEP | Explicit user-invoked print; stderr stream is the contract. |
core/src/sycl/common.cpp — vmaf_sycl_profiling_print | KEEP | Explicit user-invoked profiling table. |
core/src/log.{c,cpp} | KEEP | The log implementation itself. |
core/src/svm.cpp (libsvm warnings/errors) | DEFER | Vendored libsvm; rerouting through vmaf_log would change the semantics for callers that embed libsvm directly. Worth its own audit + ADR if pursued. |
core/src/feature/{vif,adm,ssim,motion,…}.c printf("error: ...") | DEFER | Upstream Netflix feature-extractor code; same upstream-sync concern as libsvm. Track as follow-up. |
core/tools/vmaf.c and other CLI tools | KEEP | stdout score output IS the CLI interface. |
Decisions¶
- Route the 11 fork-local diagnostic sites through
vmaf_logat the appropriate level (5 ERROR inlibvmaf.c, 1 WARNING + 4 ERROR + 1 DEBUG across SYCL). - Keep CLI-style stdout/stderr prints whose stream IS the intended interface.
- Defer vendored libsvm and upstream-mirror feature-extractor sites to a follow-up — they need an upstream-sync impact analysis first.
Alternatives considered¶
- Reroute everything including vendored libsvm. Rejected for now — changes libsvm-internal warning semantics; bigger surface, deserves its own ADR.
- Route the CLI device-list / profiling prints through
vmaf_log(INFO). Rejected — these are pull-style "print on request" functions whose stream IS the contract. Routing through the callback would silently drop output for callers that haven't installed a callback at INFO level. - Add a separate
vmaf_log_diagnostichelper. Rejected — the existingvmaf_log(level, ...)already covers the routing need; no case for a parallel surface.
Validation¶
meson setup build-log core -Denable_cuda=false -Denable_sycl=false && ninja -C build-logsucceeds.meson test -C build-log --suite=fastpasses 49/49.- SYCL backend builds gated on CI (no host SYCL toolchain here); the
extern "C" { #include "log.h" }wrapping matches the pattern already used incore/src/sycl/common.cpp.
Follow-ups¶
- Vendored
core/src/svm.cppaudit — separate PR, requires upstream libsvm-sync impact note. - Upstream-mirror feature extractors (
vif.c,adm.c,ms_ssim.c,motion.c,ssim.c) — separate PR, port back to Netflix or treat as permanent fork delta.