Logging - best practices proposal
I am converting a lot of prints into CLOG in my branch and would like to gather some feedback, introduce certain style for logs.
Reduce use of G.debug
and G.debug_value
Simple cases
// catastrophy:
if (G.debug_value > 0)
printf("cloth_free_modifier_extern\n");
// antipattern:
if (G.debug & G_DEBUG) {
CLOG_INFO(&LOG, 1, "redo_cb: operator redo %s", op->type->name);
}
// better just:
CLOG_INFO(&LOG, 1, "redo_cb: operator redo %s", op->type->name);
// antipattern:
if (G.debug & G_DEBUG) {
// allocate memory, prepare data to be printed
// printf(...)
// free memory
}
// better:
static CLG_LogRef LOG = {"bke.anim_sys"};
if (CLOG_CHECK_LEVEL(&LOG, 0)) { // in master called CLOG_CHECK
// allocate memory, prepare data to be printed
// CLOG_INFO(&LOG, 0, "why is 6 afraid of 7?")
// free memory
}
Rationale: CLOG_INFO
is macro which introduces implicit check: to print this info logger LOG
must be enabled and log level must be minimum 1. There is no performance improvement (to my knowledge) and if
with debug check is doubling on functionality (you need to enable this log anyway)
G.debug
valid usecases
So when to use flags?
- use flags when there is some kind of system to init, especially if it can be done only on startup:
if (G.debug & G_DEBUG_GPU) gpu_debug_init()
– now when you have flag, every log you introduce need to be guarded if (G.debug & G_DEBUG_GPU) CLOG_INFO(...)
- additionally notice how little some debug flags are used in code (eg.
G_DEBUG_JOBS
)
After studying flags usage in blender code:
- most times debug is a gurad for print and can be converted to log
- these flags change logic (incomplete list):
– G_DEBUG
changes mouse grab behaviour
– G_DEBUG_WM
adds internal operators in F3 search,
– G_DEBUG_DEPSGRAPH_NO_THREADS
use only 1 thread
– G_DEBUG_GPU_MEM
gpu memory in status bar -
– G_DEBUG_GPU
does some magic with GPU debugging
– G_DEBUG_IO
enables sanity checks on file write
– G_DEBUG_GPU_FORCE_WORKAROUNDS
- some magic with GG
parameters
Should G.debug flags use reports or logs?
In my humble opinion user should not see any more Reports if debug flags are enable, but I am open minded about it. Example to study:
bool BLO_main_validate_libraries(Main *bmain, ReportList *reports) {
// ...
BKE_reportf(reports,
RPT_ERROR,
"ID %s is in local database while being linked from library %s!",
id->name,
id->lib->filepath);
// ...
}
// usage:
if (G.debug & G_DEBUG_IO && mainvar->lock != NULL) {
BKE_report(reports, RPT_INFO, "Checking sanity of current .blend file *BEFORE* save to disk");
BLO_main_validate_libraries(mainvar, reports);
BLO_main_validate_shapekeys(mainvar, reports);
}
Debug functions
To prety print struct use function *_sprintfN
which returns (newy allocated) char *
(consistent with BLI_sprintfN
& friends).
void DM_debug_print_cdlayers(CustomData *data) { prinf("...") }
char *DM_debug_sprintfN_cdlayers(CustomData *data) { // use DynStr to create and return cstring */ }
// usage 1:
CLOG_STR_INFO_N(&LOG, 1, DM_debug_sprintfN_cdlayers(sth)); // frees memory
// usage 2:
char *message = DM_debug_sprintfN_cdlayers(sth);
CLOG_INFO(&LOG, 1, "sth=%s", message);
MEM_freeN(message);
// passing log reference seems also ok:
void ED_undo_object_set_active_or_warn(ViewLayer *view_layer,
Object *ob,
const char *info,
CLG_LogRef *log);
// usage:
static CLG_LogRef LOG = {"ed.undo.mball"};
ED_undo_object_set_active_or_warn(CTX_data_view_layer(C), us->elems[0].obedit_ref.ptr, us_p->name, &LOG);
Rationale: CLOG
logs the function you are calling from. It would be useless to always have it called from DM_debug_print_cdlayers
, unless you have traces enabled. Not sure how much it impacts performance, feel free to comment.
Clearly separate reports from logging
Assume that reports are for users, logs are for devs. Unfortunately this is is not clear.
If you want logs, you need to enable them. Right now it is really messed up around the code base. Also:
- remove debug report type - in the codebase there is one usage of it. You usually do not want to feed user with debugs. Just use log.
- remove
storelevel
from ReportList
- this was a feature allowed to not store your reports, but only print it to console. Again, use logs. Instead introduce logger that will log reports to console (already done in my branch soc-2020-info-editor
)
Avoid dumping information to console
// meh:
if (error & NTREE_DOVERSION_NEED_OUTPUT) {
BKE_report(fd->reports, RPT_ERROR, "Eevee material conversion problem. Error in console");
printf(
"You need to connect Principled and Eevee Specular shader nodes to new material "
"output "
"nodes.\n");
}
// better
BKE_report(fd->reports,
RPT_ERROR,
"Eevee material conversion problem. \n"
"You need to connect Principled and Eevee Specular shader nodes to new material "
"output nodes");
}
This printf
was introduced, because window manager is not initialized yet and report can not be displayed or saved. In branch (possibly) fixed that problem.
Local debug macros
// at beginng of file defining custom macro
// #define PATH_DEBUG
// usage in code
#ifdef PATH_DEBUG
printf("\t%s found: %s\n", __func__, targetpath);
#endif
// at the end
#ifdef PATH_DEBUG
# undef PATH_DEBUG
#endif
// or using macro that comes from build settings
#if defined(DEBUG)
#endif
Rationale: Using macro improves performance but I would avoid it, unless there is a good reason to use it. Good reason are ex. TIMEIT
functions.
Local PRINTF
// antipattern:
#define PRINTF(message) printf(message)
void very_long_function() { PRINTF("blah"); }
// better:
static CLG_LogRef LOG = {"ed.file_name"};
#define CLOG_FILE_NAME(message) CLOG_INFO(&LOG, message);
void very_long_function() { CLOG_FILE_NAME("blah"); }
Rationale: Well if you have to use macro, I don’t mind doing it this way.
Be quiet!
// implementation 1:
wmOperatorType *WM_operatortype_find(const char *idname, bool quiet){ // this function is called many times
if (!quiet) { CLOG_VERBOSE(WM_LOG_OPERATORS, 2, "search for unknown operator '%s', '%s'", idname_bl, idname); }
}
// implementation 2:
wmOperatorType *WM_operatortype_find(const char *idname, CLG_LogRef *log){ // log can be NULL
if (log) { CLOG_VERBOSE(log, 2, "search for unknown operator '%s', '%s'", idname_bl, idname); }
}
I would avoid any kind of vebose/quiet
argument, but I am open for discussion.
Assert or log fatal?
CLOG_ERROR(...); BLI_assert(0); // fails only in debug builds or
CLOG_FATAL(...); // automatically crashes and creates coredump
Logs from third party libraries
Third party library with custom log system (like ffmpeg)
Well built 3rd party lib will have logging callback. We can easily connect such to bledner log system:
static void ffmpeg_log_callback(void *ptr, int level, const char *format, va_list arg){
// convert as needed and end with CLOG_*
}
But UI for such libraries needs to be developed on one by one basis, currently we do for example:
void IMB_ffmpeg_init(void) {
if (G.debug & G_DEBUG_FFMPEG) {
av_log_set_level(AV_LOG_DEBUG);
}
}
Library with glog
log system
Currently blender integrates 2 libs that use glog
, what causes strange code to avoid double initialization of glog:
#ifdef WITH_LIBMV
libmv_initLogging(argv[0]); // inits glog and sets some settings
#elif defined(WITH_CYCLES_LOGGING)
CCL_init_logging(argv[0]); // inits glog and sets some (different) settings
// later it configures glog: SetCommandLineOption("stderrthreshold", severity_fatal);
#endif
Setting options for glog
is quite strange, because it uses another lib gflags
for managing its settings.
Good news is that we can set callback for glog
(glog
calls it LogSink
) to integrate it with blender.
Bad news is that glog
is pretty complex thus hard to integrate (at least too hard for me for now).
Questions
Logging in cpp files
Does current logging setup fit with cpp style of coding?
Small big questions about style
Not so important questions, but I am going to write a lot of logs.
- put quotes on
%s
? single or double? Helps especially on empty strings
- include pointer to variable?
-
variable=value
or variable: value
- can we accomplish some of it with macros? (or should we?)
-
Capitalize
or not to capitalize
?
CLOG_INFO(&LOG, 0, "writing userprefs: %s ok'", filepath);
CLOG_INFO(&LOG, 0, "Writing userprefs ok filepath='%s'(%p)", filepath, filepath);