The open source OpenXR runtime
at main 540 lines 14 kB view raw
1// Copyright 2019-2025, Collabora, Ltd. 2// Copyright 2025, NVIDIA CORPORATION. 3// SPDX-License-Identifier: BSL-1.0 4/*! 5 * @file 6 * @brief Logging functions. 7 * @author Jakob Bornecrantz <jakob@collabora.com> 8 * @ingroup aux_util 9 */ 10 11#include "util/u_logging.h" 12#include "xrt/xrt_config_os.h" 13#include "xrt/xrt_config_build.h" 14 15#include "util/u_debug.h" 16#include "util/u_pretty_print.h" 17#include "u_json.h" 18#include "util/u_truncate_printf.h" 19 20#include <assert.h> 21#include <stddef.h> 22#include <stdio.h> 23#include <stdarg.h> 24 25 26/* 27 * 28 * Defines. 29 * 30 */ 31 32/* 33 * Avoid 4K stack variables. 34 * https://learn.microsoft.com/en-us/windows/win32/devnotes/-win32-chkstk 35 */ 36#define LOG_BUFFER_SIZE (3 * 1024) 37 38/* 39 * 16MB max binary data 40 */ 41#define LOG_MAX_HEX_DUMP (0x00ffffff) 42 43#define LOG_MAX_HEX_DUMP_HUMAN_READABLE "16MB" 44 45/* 46 * Hex dumps put 16 bytes per line 47 */ 48#define LOG_HEX_BYTES_PER_LINE (16) 49 50/* 51 * This is enough space for the line's bytes to be in both hex and ascii 52 */ 53#define LOG_HEX_LINE_BUF_SIZE (128) 54 55 56#ifndef LOG_ANDROID_TAG_PREFIX 57#define LOG_ANDROID_TAG_PREFIX "monado" 58#endif 59 60/* 61 * 62 * Global log level functions. 63 * 64 */ 65 66DEBUG_GET_ONCE_LOG_OPTION(global_log, "XRT_LOG", U_LOGGING_WARN) 67DEBUG_GET_ONCE_BOOL_OPTION(json_log, "XRT_JSON_LOG", false) 68 69enum u_logging_level 70u_log_get_global_level(void) 71{ 72 return debug_get_log_option_global_log(); 73} 74 75 76/* 77 * 78 * Logging file code. 79 * 80 */ 81 82static FILE *g_log_file = NULL; 83 84void 85u_log_set_output_file(const char *filename) 86{ 87 // While not thread safe, this function is externally synchronized. 88 if (g_log_file != NULL) { 89 FILE *tmp = g_log_file; 90 g_log_file = NULL; 91 92 fflush(tmp); 93 fclose(tmp); 94 tmp = NULL; 95 } 96 97 if (filename == NULL) { 98 return; // Turning off file logging. 99 } 100 101 g_log_file = fopen(filename, "w"); 102 if (g_log_file == NULL) { 103 U_LOG_E("Failed to open '%s'", filename); 104 } 105} 106 107 108/* 109 * 110 * Logging sink. 111 * 112 */ 113 114// Logging sink global data. 115static u_log_sink_func_t g_log_sink_func; 116static void *g_log_sink_data; 117 118void 119u_log_set_sink(u_log_sink_func_t func, void *data) 120{ 121 g_log_sink_func = func; 122 g_log_sink_data = data; 123} 124 125#define DISPATCH_SINK(FILE, LINE, FUNC, LEVEL, FORMAT, ARGS) \ 126 if (g_log_sink_func != NULL) { \ 127 va_list copy; \ 128 va_copy(copy, ARGS); \ 129 g_log_sink_func(FILE, LINE, FUNC, LEVEL, FORMAT, copy, g_log_sink_data); \ 130 va_end(copy); \ 131 } 132 133 134/* 135 * 136 * Hexdump functions. 137 * 138 */ 139 140static void 141u_log_hexdump_line(char *buf, size_t offset, const uint8_t *data, size_t data_size) 142{ 143 char *pos = buf; 144 145 if (data_size > LOG_HEX_BYTES_PER_LINE) { 146 data_size = LOG_HEX_BYTES_PER_LINE; 147 } 148 149 pos += sprintf(pos, "%08x: ", (uint32_t)offset); 150 151 char *ascii = pos + ((ptrdiff_t)LOG_HEX_BYTES_PER_LINE * 3) + 1; 152 size_t i; 153 154 for (i = 0; i < data_size; i++) { 155 pos += sprintf(pos, "%02x ", data[i]); 156 157 if (data[i] >= ' ' && data[i] <= '~') { 158 *ascii++ = data[i]; 159 } else { 160 *ascii++ = '.'; 161 } 162 } 163 164 /* Pad short lines with spaces, and null terminate */ 165 while (i++ < LOG_HEX_BYTES_PER_LINE) { 166 pos += sprintf(pos, " "); 167 } 168 /* Replace the first NULL terminator with a space */ 169 *pos++ = ' '; 170 /* and set it after the ASCII representation */ 171 *ascii++ = '\0'; 172} 173 174void 175u_log_hex(const char *file, 176 int line, 177 const char *func, 178 enum u_logging_level level, 179 const uint8_t *data, 180 const size_t data_size) 181{ 182 size_t offset = 0; 183 184 while (offset < data_size) { 185 char tmp[LOG_HEX_LINE_BUF_SIZE]; 186 u_log_hexdump_line(tmp, offset, data + offset, data_size - offset); 187 u_log(file, line, func, level, "%s", tmp); 188 189 offset += LOG_HEX_BYTES_PER_LINE; 190 /* 191 * Limit the dump length to 16MB, this used to be 4GB which 192 * would on 32bit system always evaltuate to false. So we have 193 * the limit on something more sensible. 194 */ 195 if (offset > LOG_MAX_HEX_DUMP) { 196 u_log(file, line, func, level, "Truncating output over " LOG_MAX_HEX_DUMP_HUMAN_READABLE); 197 break; 198 } 199 } 200} 201 202void 203u_log_xdev_hex(const char *file, 204 int line, 205 const char *func, 206 enum u_logging_level level, 207 struct xrt_device *xdev, 208 const uint8_t *data, 209 const size_t data_size) 210{ 211 size_t offset = 0; 212 213 while (offset < data_size) { 214 char tmp[LOG_HEX_LINE_BUF_SIZE]; 215 u_log_hexdump_line(tmp, offset, data + offset, data_size - offset); 216 u_log_xdev(file, line, func, level, xdev, "%s", tmp); 217 218 offset += LOG_HEX_BYTES_PER_LINE; 219 /* 220 * Limit the dump length to 16MB, this used to be 4GB which 221 * would on 32bit system always evaltuate to false. So we have 222 * the limit on something more sensible. 223 */ 224 if (offset > LOG_MAX_HEX_DUMP) { 225 u_log_xdev(file, line, func, level, xdev, 226 "Truncating output over " LOG_MAX_HEX_DUMP_HUMAN_READABLE); 227 break; 228 } 229 } 230} 231 232 233/* 234 * 235 * Platform specific functions. 236 * 237 */ 238 239#if defined(XRT_OS_ANDROID) 240 241#include <android/log.h> 242 243static android_LogPriority 244u_log_convert_priority(enum u_logging_level level) 245{ 246 switch (level) { 247 case U_LOGGING_TRACE: return ANDROID_LOG_VERBOSE; 248 case U_LOGGING_DEBUG: return ANDROID_LOG_DEBUG; 249 case U_LOGGING_INFO: return ANDROID_LOG_INFO; 250 case U_LOGGING_WARN: return ANDROID_LOG_WARN; 251 case U_LOGGING_ERROR: return ANDROID_LOG_ERROR; 252 case U_LOGGING_RAW: return ANDROID_LOG_INFO; 253 default: break; 254 } 255 return ANDROID_LOG_INFO; 256} 257 258#elif defined(XRT_OS_WINDOWS) 259 260#include <debugapi.h> 261 262#else 263 264#include <unistd.h> 265 266#endif 267 268 269/* 270 * 271 * Helper functions. 272 * 273 */ 274 275#define CHECK_RET_AND_UPDATE_STATE() \ 276 do { \ 277 if (ret < 0) { \ 278 return ret; \ 279 } \ 280 \ 281 printed += ret; /* Is not negative, checked above. */ \ 282 remaining -= ret; \ 283 buf += ret; /* Continue in the buffer from where we left off. */ \ 284 } while (false); 285 286#ifdef XRT_FEATURE_COLOR_LOG 287#define COLOR_TRACE "\033[2m" 288#define COLOR_DEBUG "\033[36m" 289#define COLOR_INFO "\033[32m" 290#define COLOR_WARN "\033[33m" 291#define COLOR_ERROR "\033[31m" 292#define COLOR_RESET "\033[0m" 293 294static int 295print_prefix_color(const char *func, enum u_logging_level level, char *buf, int remaining) 296{ 297 switch (level) { 298 case U_LOGGING_TRACE: return u_truncate_snprintf(buf, remaining, COLOR_TRACE "TRACE " COLOR_RESET); 299 case U_LOGGING_DEBUG: return u_truncate_snprintf(buf, remaining, COLOR_DEBUG "DEBUG " COLOR_RESET); 300 case U_LOGGING_INFO: return u_truncate_snprintf(buf, remaining, COLOR_INFO " INFO " COLOR_RESET); 301 case U_LOGGING_WARN: return u_truncate_snprintf(buf, remaining, COLOR_WARN " WARN " COLOR_RESET); 302 case U_LOGGING_ERROR: return u_truncate_snprintf(buf, remaining, COLOR_ERROR "ERROR " COLOR_RESET); 303 case U_LOGGING_RAW: return 0; 304 default: return 0; 305 } 306} 307#endif 308 309static int 310print_prefix_mono(const char *func, enum u_logging_level level, char *buf, int remaining) 311{ 312 switch (level) { 313 case U_LOGGING_TRACE: return u_truncate_snprintf(buf, remaining, "TRACE "); 314 case U_LOGGING_DEBUG: return u_truncate_snprintf(buf, remaining, "DEBUG "); 315 case U_LOGGING_INFO: return u_truncate_snprintf(buf, remaining, " INFO "); 316 case U_LOGGING_WARN: return u_truncate_snprintf(buf, remaining, " WARN "); 317 case U_LOGGING_ERROR: return u_truncate_snprintf(buf, remaining, "ERROR "); 318 case U_LOGGING_RAW: return 0; 319 default: return 0; 320 } 321} 322 323static int 324print_prefix(const char *func, enum u_logging_level level, char *buf, int remaining) 325{ 326 int printed = 0; 327 int ret = 0; 328 329#ifdef XRT_FEATURE_COLOR_LOG 330 if (g_log_file == NULL && isatty(STDERR_FILENO)) { 331 ret = print_prefix_color(func, level, buf, remaining); 332 } else { 333 ret = print_prefix_mono(func, level, buf, remaining); 334 } 335#else 336 ret = print_prefix_mono(func, level, buf, remaining); 337#endif 338 339 // Does what it says. 340 CHECK_RET_AND_UPDATE_STATE(); 341 342 // Print the function name. 343 if (level != U_LOGGING_RAW && func != NULL) { 344 ret = u_truncate_snprintf(buf, remaining, "[%s] ", func); 345 } 346 347 // Does what it says. 348 CHECK_RET_AND_UPDATE_STATE(); 349 350 // Total printed characters. 351 return printed; 352} 353 354static int 355log_as_json(const char *file, const char *func, enum u_logging_level level, const char *format, va_list args) 356{ 357 cJSON *root = cJSON_CreateObject(); 358 359 char *level_s; 360 switch (level) { 361 case U_LOGGING_TRACE: level_s = "trace"; break; 362 case U_LOGGING_DEBUG: level_s = "debug"; break; 363 case U_LOGGING_INFO: level_s = "info"; break; 364 case U_LOGGING_WARN: level_s = "warn"; break; 365 case U_LOGGING_ERROR: level_s = "error"; break; 366 default: level_s = "raw"; break; 367 } 368 369 // Add metadata. 370 cJSON_AddItemToObject(root, "level", cJSON_CreateString(level_s)); 371 cJSON_AddItemToObject(root, "file", cJSON_CreateString(file)); 372 cJSON_AddItemToObject(root, "func", cJSON_CreateString(func)); 373 374 // Add message. 375 char msg_buf[LOG_BUFFER_SIZE]; 376 u_truncate_vsnprintf(msg_buf, LOG_BUFFER_SIZE, format, args); 377 cJSON_AddItemToObject(root, "message", cJSON_CreateString(msg_buf)); 378 379 // Get string and print to stderr. 380 char *out = cJSON_PrintUnformatted(root); 381 int printed = fprintf(stderr, "%s\n", out); 382 383 // Clean up after us. 384 cJSON_Delete(root); 385 cJSON_free(out); 386 387 return printed; 388} 389 390static int 391do_print(const char *file, int line, const char *func, enum u_logging_level level, const char *format, va_list args) 392{ 393 if (debug_get_bool_option_json_log()) { 394 return log_as_json(file, func, level, format, args); 395 } 396 397 char storage[LOG_BUFFER_SIZE]; 398 399 int remaining = sizeof(storage) - 2; // 2 for \n\0 400 int printed = 0; 401 char *buf = storage; // We update the pointer. 402 int ret = 0; 403 404 // The prefix of the log. 405 ret = print_prefix(func, level, buf, remaining); 406 407 // Does what it says. 408 CHECK_RET_AND_UPDATE_STATE(); 409 410 ret = u_truncate_vsnprintf(buf, remaining, format, args); 411 412 // Does what it says. 413 CHECK_RET_AND_UPDATE_STATE(); 414 415 /* 416 * The variable storage now holds the entire null-terminated message, 417 * but without a new-line character, proceed to output it. 418 */ 419 assert(storage[printed] == '\0'); 420 421 422#ifdef XRT_OS_ANDROID 423 android_LogPriority prio = u_log_convert_priority(level); 424 425 char storage_tag[256]; 426 int tag_ret = u_truncate_snprintf(storage_tag, sizeof(storage_tag), LOG_ANDROID_TAG_PREFIX ".%s", func); 427 if (tag_ret < 0) { 428 __android_log_write(prio, func, storage); 429 } else { 430 __android_log_write(prio, storage_tag, storage); 431 } 432#elif defined XRT_OS_WINDOWS || defined XRT_OS_LINUX 433 434 // We want a newline, so add it, then null-terminate again. 435 storage[printed++] = '\n'; 436 storage[printed] = '\0'; // Don't count zero termination as printed. 437 438#if defined XRT_OS_WINDOWS 439 // Visual Studio output needs the newline char 440 OutputDebugStringA(storage); 441#endif 442 443 FILE *output = g_log_file != NULL ? g_log_file : stderr; 444 fwrite(storage, printed, 1, output); 445 446#if defined(XRT_OS_WINDOWS) 447 /* 448 * Windows like to buffer messages, call flush here to make sure all 449 * logs gets written out in case of sudden exits and crashes. 450 */ 451 fflush(output); 452#endif 453 454#else 455#error "Port needed for logging function" 456#endif 457 458 return printed; 459} 460 461 462/* 463 * 464 * 'Exported' functions. 465 * 466 */ 467 468void 469u_log_print_result(enum u_logging_level cond_level, 470 const char *file, 471 int line, 472 const char *calling_fn, 473 xrt_result_t xret, 474 const char *called_fn) 475{ 476 bool success = xret == XRT_SUCCESS; 477 enum u_logging_level level = success ? U_LOGGING_INFO : U_LOGGING_ERROR; 478 479 // Should we be logging? 480 if (level < cond_level) { 481 return; 482 } 483 484 struct u_pp_sink_stack_only sink; 485 u_pp_delegate_t dg = u_pp_sink_stack_only_init(&sink); 486 487 if (success) { 488 u_pp(dg, "%s: ", called_fn); 489 } else { 490 u_pp(dg, "%s failed: ", called_fn); 491 } 492 493 u_pp_xrt_result(dg, xret); 494 u_pp(dg, " [%s:%i]", file, line); 495 496 u_log(file, line, calling_fn, level, "%s", sink.buffer); 497} 498 499static u_log_filter_func_t g_filter = NULL; 500 501void 502u_log_set_filter(u_log_filter_func_t filter) 503{ 504 g_filter = filter; 505} 506 507void 508u_log(const char *file, int line, const char *func, enum u_logging_level level, const char *format, ...) 509{ 510 va_list args; 511 // Check filter first 512 if (g_filter != NULL && !g_filter(file, line, func, level)) { 513 return; // Skip this message 514 } 515 516 va_start(args, format); 517 DISPATCH_SINK(file, line, func, level, format, args); 518 do_print(file, line, func, level, format, args); 519 va_end(args); 520} 521 522void 523u_log_xdev(const char *file, 524 int line, 525 const char *func, 526 enum u_logging_level level, 527 struct xrt_device *xdev, 528 const char *format, 529 ...) 530{ 531 va_list args; 532 // Check filter first 533 if (g_filter != NULL && !g_filter(file, line, func, level)) { 534 return; // Skip this message 535 } 536 va_start(args, format); 537 DISPATCH_SINK(file, line, func, level, format, args); 538 do_print(file, line, func, level, format, args); 539 va_end(args); 540}