33#define ASCII_INSTR_SOURCE_PRINT_HAVE_REGEX 1
36#define ASCII_INSTR_SOURCE_PRINT_HAVE_REGEX 0
44#define mkdir(path, mode) _mkdir(path)
47#define posix_write _write
49#define STDERR_FILENO 2
52#define posix_write write
55#ifndef ASCII_INSTR_SOURCE_PRINT_DEFAULT_BASENAME
56#define ASCII_INSTR_SOURCE_PRINT_DEFAULT_BASENAME "ascii-instr"
59#ifndef ASCII_INSTR_SOURCE_PRINT_MAX_LINE
60#define ASCII_INSTR_SOURCE_PRINT_MAX_LINE 4096
63#ifndef ASCII_INSTR_SOURCE_PRINT_MAX_SNIPPET
64#define ASCII_INSTR_SOURCE_PRINT_MAX_SNIPPET 2048
99#if ASCII_INSTR_SOURCE_PRINT_HAVE_REGEX
117static bool g_runtime_initialized =
false;
118static bool g_runtime_mutex_initialized =
false;
119static char g_output_dir[PATH_MAX];
120static bool g_output_dir_set =
false;
121static bool g_disable_write =
false;
123static bool g_ticks_initialized =
false;
124static bool g_coverage_enabled =
false;
125static bool g_echo_to_stderr =
false;
126static bool g_echo_to_stderr_initialized =
false;
128static void asciichat_instr_runtime_init_once(
void);
129static void asciichat_instr_runtime_tls_destructor(
void *ptr);
133 uint32_t line_number,
const char *function_name);
134static int asciichat_instr_write_full(
int fd,
const char *buffer,
size_t len);
135static bool asciichat_instr_env_is_enabled(
const char *value);
136static bool asciichat_instr_parse_positive_uint32(
const char *value,
uint32_t *out_value);
137#if ASCII_INSTR_SOURCE_PRINT_HAVE_REGEX
138static bool asciichat_instr_compile_regex(regex_t *regex,
const char *pattern);
142 const char *module,
const char *pattern);
143static void asciichat_instr_trim(
char *value);
146 const char *function_name);
147static bool asciichat_instr_match_glob(
const char *pattern,
const char *value);
148static const char *asciichat_instr_basename(
const char *path);
149static bool asciichat_instr_path_contains_module(
const char *
file_path,
const char *module_name);
151static _Thread_local
bool g_logging_reentry_guard =
false;
152static bool g_instrumentation_enabled =
false;
153static bool g_instrumentation_enabled_checked =
false;
156 if (g_disable_write) {
161 if (!g_runtime_initialized) {
163 if (!g_runtime_mutex_initialized) {
165 g_runtime_mutex_initialized =
true;
169 if (!g_runtime_initialized) {
170 asciichat_instr_runtime_init_once();
176 if (runtime != NULL) {
181 if (runtime == NULL) {
199 asciichat_instr_runtime_configure(runtime);
210 if (runtime == NULL) {
214 if (runtime->
fd >= 0) {
219#if ASCII_INSTR_SOURCE_PRINT_HAVE_REGEX
243 if (g_runtime_mutex_initialized) {
247 if (g_runtime_initialized) {
248 g_disable_write =
true;
250 g_runtime_initialized =
false;
251 g_ticks_initialized =
false;
253 g_coverage_enabled =
false;
254 g_output_dir_set =
false;
255 g_output_dir[0] =
'\0';
256 g_instrumentation_enabled_checked =
false;
257 g_instrumentation_enabled =
false;
258 g_echo_to_stderr_initialized =
false;
259 g_echo_to_stderr =
false;
263 g_disable_write =
false;
265 if (g_runtime_mutex_initialized) {
268 g_runtime_mutex_initialized =
false;
273 const char *snippet,
uint8_t is_macro_expansion) {
276 if (!g_instrumentation_enabled_checked) {
277 const char *enable_env =
SAFE_GETENV(
"ASCII_INSTR_SOURCE_PRINT_ENABLE");
279 if (enable_env != NULL && enable_env[0] !=
'\0') {
280 g_instrumentation_enabled = asciichat_instr_env_is_enabled(enable_env);
282 g_instrumentation_enabled =
true;
284 g_instrumentation_enabled_checked =
true;
287 if (!g_instrumentation_enabled) {
291 if (g_disable_write) {
295 if (g_logging_reentry_guard) {
299 g_logging_reentry_guard =
true;
302 if (runtime == NULL) {
306 if (!asciichat_instr_should_log(runtime,
file_path, line_number, function_name)) {
313 if (((counter - 1U) % runtime->
rate) != 0U) {
318 if (runtime->
fd < 0) {
319 if (asciichat_instr_open_log_file(runtime) != 0) {
329 memset(&ts, 0,
sizeof(ts));
330#if defined(CLOCK_REALTIME)
331 (void)clock_gettime(CLOCK_REALTIME, &ts);
333 time_t sec = ts.tv_sec;
335 memset(&tm_now, 0,
sizeof(tm_now));
337 memset(&tm_now, 0,
sizeof(tm_now));
341 size_t ts_len = strftime(timestamp,
sizeof(timestamp),
"%Y-%m-%dT%H:%M:%S", &tm_now);
343 SAFE_STRNCPY(timestamp,
"1970-01-01T00:00:00",
sizeof(timestamp));
344 ts_len = strlen(timestamp);
347 char elapsed_buf[32];
348 elapsed_buf[0] =
'\0';
349 if (g_ticks_initialized) {
351 double elapsed_ns = stm_ns(stm_diff(now_ticks, g_start_ticks));
353 elapsed_buf[0] =
'\0';
359 const char *elapsed_field = (elapsed_buf[0] !=
'\0') ? elapsed_buf :
"-";
362 pos += snprintf(buffer + pos,
sizeof(buffer) - pos,
363 "pid=%d tid=%llu seq=%llu ts=%.*s.%09ldZ elapsed=%s file=%s line=%u func=%s macro=%u snippet=",
365 (
int)ts_len, timestamp, (
long)ts.tv_nsec, elapsed_field, safe_file_path, line_number,
366 function_name ? function_name :
"<unknown>", (unsigned)is_macro_expansion);
368 if (snippet != NULL) {
370 for (
size_t i = 0; i < snippet_len && pos <
sizeof(buffer) - 2; ++i) {
371 const char ch = snippet[i];
374 buffer[pos++] =
'\\';
378 buffer[pos++] =
'\\';
382 buffer[pos++] =
'\\';
392 if (pos >=
sizeof(buffer) - 1) {
393 pos =
sizeof(buffer) - 2;
396 buffer[pos++] =
'\n';
399 asciichat_instr_write_full(fd, buffer, pos);
402 if (!g_echo_to_stderr_initialized) {
403 const char *echo_env =
SAFE_GETENV(
"ASCII_CHAT_DEBUG_SELF_SOURCE_CODE_LOG_STDERR");
404 g_echo_to_stderr = asciichat_instr_env_is_enabled(echo_env);
405 g_echo_to_stderr_initialized =
true;
412#pragma clang diagnostic push
413#pragma clang diagnostic ignored "-Wdeprecated-declarations"
417#pragma clang diagnostic pop
422 g_logging_reentry_guard =
false;
426 if (g_disable_write) {
431 if (!g_runtime_initialized) {
432 if (!g_runtime_mutex_initialized) {
434 g_runtime_mutex_initialized =
true;
438 if (!g_runtime_initialized) {
439 asciichat_instr_runtime_init_once();
444 return g_coverage_enabled;
453 snprintf(snippet,
sizeof(snippet),
"pc=0x%zx", (
size_t)program_counter);
457static void asciichat_instr_runtime_init_once(
void) {
460 if (!g_runtime_initialized) {
462 const char *output_dir_env =
SAFE_GETENV(
"ASCII_INSTR_SOURCE_PRINT_OUTPUT_DIR");
463 if (output_dir_env != NULL && output_dir_env[0] !=
'\0') {
464 char *normalized_output_dir = NULL;
467 if (validation_result ==
ASCIICHAT_OK && normalized_output_dir != NULL) {
468 SAFE_STRNCPY(g_output_dir, normalized_output_dir,
sizeof(g_output_dir));
469 g_output_dir[
sizeof(g_output_dir) - 1] =
'\0';
470 g_output_dir_set =
true;
472 log_warn(
"Ignoring invalid ASCII_INSTR_SOURCE_PRINT_OUTPUT_DIR path: %s", output_dir_env);
476 const char *coverage_env =
SAFE_GETENV(
"ASCII_INSTR_SOURCE_PRINT_ENABLE_COVERAGE");
477 g_coverage_enabled = asciichat_instr_env_is_enabled(coverage_env);
479 g_start_ticks = stm_now();
480 g_ticks_initialized =
true;
481 g_runtime_initialized =
true;
485static void asciichat_instr_runtime_tls_destructor(
void *ptr) {
491 const char *custom_log_file =
SAFE_GETENV(
"ASCII_CHAT_DEBUG_SELF_SOURCE_CODE_LOG_FILE");
492 bool is_custom_path = (custom_log_file != NULL && custom_log_file[0] !=
'\0');
494 if (is_custom_path) {
495 const char *debug_env =
SAFE_GETENV(
"ASCII_INSTR_SOURCE_PRINT_ECHO_STDERR");
496 if (debug_env && debug_env[0] ==
'1') {
497 fprintf(stderr,
"ASCII_INSTR: Using custom log path: %s\n", custom_log_file);
504 log_warn(
"Failed to expand ASCII_CHAT_DEBUG_SELF_SOURCE_CODE_LOG_FILE path: %s", custom_log_file);
509 char absolute_buf[PATH_MAX];
511 char cwd_buf[PATH_MAX];
517 if (strlen(expanded) > 0 && expanded[0] ==
PATH_DELIM) {
518 safe_snprintf(absolute_buf,
sizeof(absolute_buf),
"%s%s", cwd_buf, expanded);
533 if (debug_env && debug_env[0] ==
'1') {
534 fprintf(stderr,
"ASCII_INSTR: Resolved custom log path: %s\n", runtime->
log_path);
541 char output_dir_buf[PATH_MAX];
542 if (g_output_dir_set) {
544 SAFE_STRNCPY(output_dir_buf, g_output_dir,
sizeof(output_dir_buf));
548 if (fallback == NULL) {
551 if (fallback == NULL) {
554 if (fallback == NULL) {
557 SAFE_STRNCPY(output_dir_buf, fallback,
sizeof(output_dir_buf));
561 if (g_output_dir_set) {
578 if (!is_custom_path) {
579 char *validated_log_path = NULL;
582 if (validate_result !=
ASCIICHAT_OK || validated_log_path == NULL) {
584 log_warn(
"Failed to validate instrumentation log path: %s", runtime->
log_path);
594 if (last_sep != NULL && last_sep != runtime->
log_path) {
595 const size_t dir_path_len = (size_t)(last_sep - runtime->
log_path);
596 char dir_path[PATH_MAX];
597 memcpy(dir_path, runtime->
log_path, dir_path_len);
598 dir_path[dir_path_len] =
'\0';
600 if (
errno != EEXIST) {
610 if (!asciichat_instr_build_log_path(runtime)) {
611 const char *debug_env =
SAFE_GETENV(
"ASCII_INSTR_SOURCE_PRINT_ECHO_STDERR");
612 if (debug_env && debug_env[0] ==
'1') {
613 fprintf(stderr,
"ASCII_INSTR: Failed to build log path\n");
619 const char *custom_log_file =
SAFE_GETENV(
"ASCII_CHAT_DEBUG_SELF_SOURCE_CODE_LOG_FILE");
620 const bool is_custom_file = (custom_log_file != NULL && custom_log_file[0] !=
'\0');
625 if (is_custom_file) {
631 const char *debug_env =
SAFE_GETENV(
"ASCII_INSTR_SOURCE_PRINT_ECHO_STDERR");
632 if (debug_env && debug_env[0] ==
'1') {
633 fprintf(stderr,
"ASCII_INSTR: Opening log file: %s (custom=%d)\n", runtime->
log_path, is_custom_file);
636 const mode_t mode = S_IRUSR | S_IWUSR;
639 if (debug_env && debug_env[0] ==
'1') {
640 fprintf(stderr,
"ASCII_INSTR: Failed to open log file: %s (errno=%d)\n", runtime->
log_path,
errno);
645 if (debug_env && debug_env[0] ==
'1') {
646 fprintf(stderr,
"ASCII_INSTR: Successfully opened log file: %s (fd=%d)\n", runtime->
log_path, fd);
657 const char *only_env =
SAFE_GETENV(
"ASCII_INSTR_SOURCE_PRINT_ONLY");
658 asciichat_instr_parse_only_filters(runtime, only_env);
660#if ASCII_INSTR_SOURCE_PRINT_HAVE_REGEX
661 const char *include_regex =
SAFE_GETENV(
"ASCII_INSTR_SOURCE_PRINT_INCLUDE_REGEX");
662 if (include_regex != NULL && include_regex[0] !=
'\0') {
666 const char *exclude_regex =
SAFE_GETENV(
"ASCII_INSTR_SOURCE_PRINT_EXCLUDE_REGEX");
667 if (exclude_regex != NULL && exclude_regex[0] !=
'\0') {
671 const char *function_include_regex =
SAFE_GETENV(
"ASCII_INSTR_SOURCE_PRINT_FUNCTION_INCLUDE_REGEX");
672 if (function_include_regex != NULL && function_include_regex[0] !=
'\0') {
677 const char *function_exclude_regex =
SAFE_GETENV(
"ASCII_INSTR_SOURCE_PRINT_FUNCTION_EXCLUDE_REGEX");
678 if (function_exclude_regex != NULL && function_exclude_regex[0] !=
'\0') {
684 const char *rate_env =
SAFE_GETENV(
"ASCII_INSTR_SOURCE_PRINT_RATE");
686 if (asciichat_instr_parse_positive_uint32(rate_env, &rate_value) && rate_value > 1U) {
687 runtime->
rate = rate_value;
701static bool asciichat_instr_env_is_enabled(
const char *value) {
706 while (*value !=
'\0' && isspace((
unsigned char)*value) != 0) {
710 if (*value ==
'\0') {
720 while (value[len] !=
'\0' && len <
sizeof(lowered) - 1) {
721 lowered[len] = (char)tolower((
unsigned char)value[len]);
726 return !(strcmp(lowered,
STR_FALSE) == 0 || strcmp(lowered,
STR_OFF) == 0 || strcmp(lowered,
STR_NO) == 0);
729static bool asciichat_instr_parse_positive_uint32(
const char *value,
uint32_t *out_value) {
730 if (value == NULL || out_value == NULL) {
734 while (*value !=
'\0' && isspace((
unsigned char)*value) != 0) {
738 if (*value ==
'\0') {
744 unsigned long long parsed = strtoull(value, &endptr, 10);
745 if (
errno != 0 || endptr == value || parsed == 0ULL || parsed > UINT32_MAX) {
753#if ASCII_INSTR_SOURCE_PRINT_HAVE_REGEX
754static bool asciichat_instr_compile_regex(regex_t *regex,
const char *pattern) {
755 if (regex == NULL || pattern == NULL) {
758 int ret = regcomp(regex, pattern, REG_EXTENDED | REG_NOSUB);
767 if (list->
items != NULL) {
768 for (
size_t i = 0; i < list->
count; ++i) {
780 const char *module,
const char *pattern) {
787 if (pattern == NULL || pattern[0] ==
'\0') {
793 if (module == NULL || module[0] ==
'\0') {
799 size_t new_capacity = (list->
capacity == 0) ? 4U : list->capacity * 2U;
802 if (new_items == NULL) {
805 list->
items = new_items;
810 memset(selector, 0,
sizeof(*selector));
811 selector->
type = type;
813 if (module != NULL && module[0] !=
'\0') {
816 if (pattern != NULL && pattern[0] !=
'\0') {
824static void asciichat_instr_trim(
char *value) {
830 while (*start !=
'\0' && isspace((
unsigned char)*start) != 0) {
834 char *end = start + strlen(start);
835 while (end > start && isspace((
unsigned char)end[-1]) != 0) {
839 const size_t length = (size_t)(end - start);
840 if (start != value && length > 0U) {
841 memmove(value, start, length);
843 value[length] =
'\0';
847 if (runtime == NULL) {
853 if (value == NULL || value[0] ==
'\0') {
857 char *mutable_value = NULL;
859 if (mutable_value == NULL) {
863 char *cursor = mutable_value;
864 while (cursor != NULL && *cursor !=
'\0') {
865 char *token_start = cursor;
866 while (*cursor !=
'\0' && *cursor !=
',') {
869 if (*cursor ==
',') {
876 asciichat_instr_trim(token_start);
877 if (*token_start ==
'\0') {
881 char *equal_sign = strchr(token_start,
'=');
882 if (equal_sign != NULL) {
884 char *kind = token_start;
885 char *spec = equal_sign + 1;
886 asciichat_instr_trim(kind);
887 asciichat_instr_trim(spec);
892 if (strcmp(kind,
"func") == 0 || strcmp(kind,
"function") == 0) {
895 }
else if (strcmp(kind,
"module") == 0) {
896 char *module_value = spec;
897 char *module_pattern = strchr(module_value,
':');
898 if (module_pattern != NULL) {
899 *module_pattern =
'\0';
901 asciichat_instr_trim(module_pattern);
903 asciichat_instr_trim(module_value);
904 if (*module_value ==
'\0') {
907 const char *pattern_part = (module_pattern != NULL && *module_pattern !=
'\0') ? module_pattern : NULL;
909 module_value, pattern_part);
918 char *colon = strchr(token_start,
':');
921 char *module_name = token_start;
922 char *pattern_part = colon + 1;
923 asciichat_instr_trim(module_name);
924 asciichat_instr_trim(pattern_part);
925 if (*module_name ==
'\0') {
928 const char *pattern_spec = (*pattern_part !=
'\0') ? pattern_part : NULL;
930 module_name, pattern_spec);
942static bool asciichat_instr_match_glob(
const char *pattern,
const char *value) {
943 if (pattern == NULL || value == NULL) {
947 const char *p = pattern;
948 const char *v = value;
949 const char *star = NULL;
950 const char *match = NULL;
956 }
else if (*p ==
'?' || *p == *v) {
959 }
else if (star != NULL) {
975static const char *asciichat_instr_basename(
const char *path) {
981 const char *last_sep = strrchr(path,
PATH_DELIM);
982 if (last_sep != NULL && last_sep[1] !=
'\0') {
988static bool asciichat_instr_path_contains_module(
const char *
file_path,
const char *module_name) {
989 if (
file_path == NULL || module_name == NULL || module_name[0] ==
'\0') {
993 const size_t module_len = strlen(module_name);
995 while ((cursor = strstr(cursor, module_name)) != NULL) {
998 const char prev = cursor[-1];
1002 const char tail = cursor[module_len];
1003 bool right_ok = (tail ==
'\0' || tail ==
PATH_DELIM);
1005 if (left_ok && right_ok) {
1009 cursor = cursor + 1;
1016 const char *function_name) {
1018 if (list->
count == 0) {
1022 const char *base_name = asciichat_instr_basename(
file_path);
1023 for (
size_t i = 0; i < list->
count; ++i) {
1025 switch (selector->
type) {
1037 if (function_name != NULL && selector->
pattern != NULL &&
1038 asciichat_instr_match_glob(selector->
pattern, function_name)) {
1043 if (
file_path != NULL && selector->module != NULL &&
1044 asciichat_instr_path_contains_module(
file_path, selector->module)) {
1045 if (selector->
pattern == NULL ||
1046 (base_name != NULL && asciichat_instr_match_glob(selector->
pattern, base_name))) {
1060 uint32_t line_number,
const char *function_name) {
1067 snprintf(tid_buf,
sizeof(tid_buf),
"%llu", (
unsigned long long)runtime->
thread_id);
1085#if ASCII_INSTR_SOURCE_PRINT_HAVE_REGEX
1111#if ASCII_INSTR_SOURCE_PRINT_HAVE_REGEX
1113 if (function_name == NULL || regexec(&runtime->
function_include_regex, function_name, 0, NULL, 0) != 0) {
1126 if (!asciichat_instr_only_selectors_match(runtime,
file_path, function_name)) {
1135static int asciichat_instr_write_full(
int fd,
const char *buffer,
size_t len) {
1137 while (total < len) {
1138 ssize_t written =
platform_write(fd, buffer + total, len - total);
1145 total += (size_t)written;
#define SAFE_REALLOC(ptr, size, cast)
#define SAFE_STRNCPY(dst, src, size)
#define SAFE_GETENV(name)
#define SAFE_CALLOC(count, size, cast)
unsigned long long uint64_t
#define SAFE_STRDUP(dst, src)
asciichat_error_t
Error and exit codes - unified status values (0-255)
uint64_t asciichat_thread_current_id(void)
#define log_warn(...)
Log a WARN message.
int format_duration_ns(double nanoseconds, char *buffer, size_t buffer_size)
Format nanoseconds as human-readable duration string.
bool asciichat_instr_coverage_enabled(void)
Check if coverage logging is enabled.
void asciichat_instr_runtime_destroy(asciichat_instr_runtime_t *runtime)
Destroy a runtime context and release resources.
void asciichat_instr_runtime_global_shutdown(void)
Global shutdown of the instrumentation system.
void asciichat_instr_log_line(const char *file_path, uint32_t line_number, const char *function_name, const char *snippet, uint8_t is_macro_expansion)
Log a source line execution event.
void asciichat_instr_log_pc(uintptr_t program_counter)
Log a program counter for coverage analysis.
asciichat_instr_runtime_t * asciichat_instr_runtime_get(void)
Get or create the thread-local runtime context.
@ ASCII_INSTR_SOURCE_PRINT_MACRO_NONE
#define STR_ZERO
String literal: "0" (zero)
#define STR_FALSE
String literal: "false".
#define STR_NO
String literal: "no".
#define STR_OFF
String literal: "off".
bool path_is_absolute(const char *path)
Determine whether a path is absolute on the current platform.
asciichat_error_t path_validate_user_path(const char *input, path_role_t role, char **normalized_out)
Validate and canonicalize a user-supplied filesystem path.
char * expand_path(const char *path)
Expand path with tilde (~) support.
asciichat_instr_selector_type
@ ASCII_INSTR_SOURCE_PRINT_SELECTOR_FUNCTION_GLOB
@ ASCII_INSTR_SOURCE_PRINT_SELECTOR_FILE_GLOB
@ ASCII_INSTR_SOURCE_PRINT_SELECTOR_FILE_SUBSTRING
@ ASCII_INSTR_SOURCE_PRINT_SELECTOR_MODULE
struct asciichat_instr_runtime asciichat_instr_runtime_t
#define ASCII_INSTR_SOURCE_PRINT_MAX_SNIPPET
struct asciichat_instr_only_selector asciichat_instr_only_selector_t
#define ASCII_INSTR_SOURCE_PRINT_MAX_LINE
#define ASCII_INSTR_SOURCE_PRINT_DEFAULT_BASENAME
enum asciichat_instr_selector_type asciichat_instr_selector_type_t
struct asciichat_instr_only_list asciichat_instr_only_list_t
#define ASCII_INSTR_SOURCE_PRINT_HAVE_REGEX
๐ Debug instrumentation logging runtime for ascii-chat line tracing
Application limits and constraints.
char file_path[PLATFORM_MAX_PATH_LENGTH]
Cross-platform mutex interface for ascii-chat.
๐ Path Manipulation Utilities
asciichat_instr_only_selector_t * items
asciichat_instr_selector_type_t type
const char * filter_exclude
bool function_exclude_regex_valid
asciichat_instr_only_list_t only_selectors
const char * filter_include
regex_t function_exclude_regex
bool function_include_regex_valid
const char * filter_function_exclude
const char * filter_function_include
regex_t function_include_regex
const char * filter_thread
Cross-platform system functions interface for ascii-chat.
โฑ๏ธ High-precision timing utilities using sokol_time.h and uthash