4#include <ascii-chat/tooling/panic/instrument_log.h>
5#include <ascii-chat/common.h>
6#include <ascii-chat/platform/util.h>
7#include <ascii-chat/platform/mutex.h>
8#include <ascii-chat/platform/system.h>
9#include <ascii-chat/platform/thread.h>
10#include <ascii-chat/util/path.h>
11#include <ascii-chat/util/time.h>
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
111#if ASCII_INSTR_SOURCE_PRINT_HAVE_REGEX
127static tls_key_t g_runtime_key;
128static mutex_t g_runtime_mutex;
129static bool g_runtime_initialized =
false;
130static bool g_runtime_mutex_initialized =
false;
131static char g_output_dir[PATH_MAX];
132static bool g_output_dir_set =
false;
133static bool g_disable_write =
false;
134static uint64_t g_start_ns = 0;
135static bool g_ticks_initialized =
false;
136static bool g_coverage_enabled =
false;
137static bool g_echo_to_stderr =
false;
138static bool g_echo_to_stderr_initialized =
false;
140static void asciichat_instr_runtime_init_once(
void);
141static void asciichat_instr_runtime_tls_destructor(
void *ptr);
145 uint32_t line_number,
const char *function_name);
146static int asciichat_instr_write_full(
int fd,
const char *buffer,
size_t len);
147static bool asciichat_instr_env_is_enabled(
const char *value);
148static bool asciichat_instr_parse_positive_uint32(
const char *value, uint32_t *out_value);
149#if ASCII_INSTR_SOURCE_PRINT_HAVE_REGEX
150static bool asciichat_instr_compile_regex(regex_t *regex,
const char *pattern);
154 const char *module,
const char *pattern);
155static void asciichat_instr_trim(
char *value);
158 const char *function_name);
159static bool asciichat_instr_match_glob(
const char *pattern,
const char *value);
160static const char *asciichat_instr_basename(
const char *path);
161static bool asciichat_instr_path_contains_module(
const char *
file_path,
const char *module_name);
163static _Thread_local
bool g_logging_reentry_guard =
false;
164static bool g_instrumentation_enabled =
false;
165static bool g_instrumentation_enabled_checked =
false;
168 if (g_disable_write) {
173 if (!g_runtime_initialized) {
175 if (!g_runtime_mutex_initialized) {
177 g_runtime_mutex_initialized =
true;
180 mutex_lock(&g_runtime_mutex);
181 if (!g_runtime_initialized) {
182 asciichat_instr_runtime_init_once();
184 mutex_unlock(&g_runtime_mutex);
188 if (runtime != NULL) {
193 if (runtime == NULL) {
202 runtime->
filter_include = SAFE_GETENV(
"ASCII_INSTR_SOURCE_PRINT_INCLUDE");
203 runtime->
filter_exclude = SAFE_GETENV(
"ASCII_INSTR_SOURCE_PRINT_EXCLUDE");
204 runtime->
filter_thread = SAFE_GETENV(
"ASCII_INSTR_SOURCE_PRINT_THREAD");
211 asciichat_instr_runtime_configure(runtime);
222 if (runtime == NULL) {
226 if (runtime->
fd >= 0) {
231#if ASCII_INSTR_SOURCE_PRINT_HAVE_REGEX
255 if (g_runtime_mutex_initialized) {
256 mutex_lock(&g_runtime_mutex);
259 if (g_runtime_initialized) {
260 g_disable_write =
true;
262 g_runtime_initialized =
false;
263 g_ticks_initialized =
false;
265 g_coverage_enabled =
false;
266 g_output_dir_set =
false;
267 g_output_dir[0] =
'\0';
268 g_instrumentation_enabled_checked =
false;
269 g_instrumentation_enabled =
false;
270 g_echo_to_stderr_initialized =
false;
271 g_echo_to_stderr =
false;
275 g_disable_write =
false;
277 if (g_runtime_mutex_initialized) {
278 mutex_unlock(&g_runtime_mutex);
280 g_runtime_mutex_initialized =
false;
285 const char *snippet, uint8_t is_macro_expansion) {
288 if (!g_instrumentation_enabled_checked) {
289 const char *enable_env = SAFE_GETENV(
"ASCII_INSTR_SOURCE_PRINT_ENABLE");
291 if (enable_env != NULL && enable_env[0] !=
'\0') {
292 g_instrumentation_enabled = asciichat_instr_env_is_enabled(enable_env);
294 g_instrumentation_enabled =
true;
296 g_instrumentation_enabled_checked =
true;
299 if (!g_instrumentation_enabled) {
303 if (g_disable_write) {
307 if (g_logging_reentry_guard) {
311 g_logging_reentry_guard =
true;
314 if (runtime == NULL) {
318 if (!asciichat_instr_should_log(runtime,
file_path, line_number, function_name)) {
325 if (((counter - 1U) % runtime->
rate) != 0U) {
330 if (runtime->
fd < 0) {
331 if (asciichat_instr_open_log_file(runtime) != 0) {
341 time_t sec = (time_t)(realtime_ns / NS_PER_SEC_INT);
342 long nsec = (long)(realtime_ns % NS_PER_SEC_INT);
344 memset(&tm_now, 0,
sizeof(tm_now));
345 if (platform_gtime(&sec, &tm_now) != ASCIICHAT_OK) {
346 memset(&tm_now, 0,
sizeof(tm_now));
350 size_t ts_len = strftime(timestamp,
sizeof(timestamp),
"%Y-%m-%dT%H:%M:%S", &tm_now);
352 SAFE_STRNCPY(timestamp,
"1970-01-01T00:00:00",
sizeof(timestamp));
353 ts_len = strlen(timestamp);
356 char elapsed_buf[32];
357 elapsed_buf[0] =
'\0';
358 if (g_ticks_initialized) {
362 elapsed_buf[0] =
'\0';
368 const char *elapsed_field = (elapsed_buf[0] !=
'\0') ? elapsed_buf :
"-";
372 "pid=%d tid=%llu seq=%llu ts=%.*s.%09ldZ elapsed=%s file=%s line=%u func=%s macro=%u snippet=",
374 (
int)ts_len, timestamp, nsec, elapsed_field, safe_file_path, line_number,
375 function_name ? function_name :
"<unknown>", (unsigned)is_macro_expansion);
377 if (snippet != NULL) {
379 for (
size_t i = 0; i < snippet_len && pos <
sizeof(buffer) - 2; ++i) {
380 const char ch = snippet[i];
383 buffer[pos++] =
'\\';
387 buffer[pos++] =
'\\';
391 buffer[pos++] =
'\\';
401 if (pos >=
sizeof(buffer) - 1) {
402 pos =
sizeof(buffer) - 2;
405 buffer[pos++] =
'\n';
408 asciichat_instr_write_full(fd, buffer, pos);
411 if (!g_echo_to_stderr_initialized) {
412 const char *echo_env = SAFE_GETENV(
"ASCII_CHAT_DEBUG_SELF_SOURCE_CODE_LOG_STDERR");
413 g_echo_to_stderr = asciichat_instr_env_is_enabled(echo_env);
414 g_echo_to_stderr_initialized =
true;
421#pragma clang diagnostic push
422#pragma clang diagnostic ignored "-Wdeprecated-declarations"
426#pragma clang diagnostic pop
431 g_logging_reentry_guard =
false;
435 if (g_disable_write) {
440 if (!g_runtime_initialized) {
441 if (!g_runtime_mutex_initialized) {
443 g_runtime_mutex_initialized =
true;
446 mutex_lock(&g_runtime_mutex);
447 if (!g_runtime_initialized) {
448 asciichat_instr_runtime_init_once();
450 mutex_unlock(&g_runtime_mutex);
453 return g_coverage_enabled;
462 safe_snprintf(snippet,
sizeof(snippet),
"pc=0x%zx", (
size_t)program_counter);
466static void asciichat_instr_runtime_init_once(
void) {
469 if (!g_runtime_initialized) {
471 const char *output_dir_env = SAFE_GETENV(
"ASCII_INSTR_SOURCE_PRINT_OUTPUT_DIR");
472 if (output_dir_env != NULL && output_dir_env[0] !=
'\0') {
473 char *normalized_output_dir = NULL;
474 asciichat_error_t validation_result =
476 if (validation_result == ASCIICHAT_OK && normalized_output_dir != NULL) {
477 SAFE_STRNCPY(g_output_dir, normalized_output_dir,
sizeof(g_output_dir));
478 g_output_dir[
sizeof(g_output_dir) - 1] =
'\0';
479 g_output_dir_set =
true;
481 log_warn(
"Ignoring invalid ASCII_INSTR_SOURCE_PRINT_OUTPUT_DIR path: %s", output_dir_env);
483 SAFE_FREE(normalized_output_dir);
485 const char *coverage_env = SAFE_GETENV(
"ASCII_INSTR_SOURCE_PRINT_ENABLE_COVERAGE");
486 g_coverage_enabled = asciichat_instr_env_is_enabled(coverage_env);
488 g_ticks_initialized =
true;
489 g_runtime_initialized =
true;
493static void asciichat_instr_runtime_tls_destructor(
void *ptr) {
499 const char *custom_log_file = SAFE_GETENV(
"ASCII_CHAT_DEBUG_SELF_SOURCE_CODE_LOG_FILE");
500 bool is_custom_path = (custom_log_file != NULL && custom_log_file[0] !=
'\0');
502 if (is_custom_path) {
503 const char *debug_env = SAFE_GETENV(
"ASCII_INSTR_SOURCE_PRINT_ECHO_STDERR");
504 if (debug_env && debug_env[0] ==
'1') {
505 fprintf(stderr,
"ASCII_INSTR: Using custom log path: %s\n", custom_log_file);
512 log_warn(
"Failed to expand ASCII_CHAT_DEBUG_SELF_SOURCE_CODE_LOG_FILE path: %s", custom_log_file);
517 char absolute_buf[PATH_MAX];
519 char cwd_buf[PATH_MAX];
525 if (strlen(expanded) > 0 && expanded[0] == PATH_DELIM) {
526 safe_snprintf(absolute_buf,
sizeof(absolute_buf),
"%s%s", cwd_buf, expanded);
528 safe_snprintf(absolute_buf,
sizeof(absolute_buf),
"%s%c%s", cwd_buf, PATH_DELIM, expanded);
541 if (debug_env && debug_env[0] ==
'1') {
542 fprintf(stderr,
"ASCII_INSTR: Resolved custom log path: %s\n", runtime->
log_path);
549 char output_dir_buf[PATH_MAX];
550 if (g_output_dir_set) {
552 SAFE_STRNCPY(output_dir_buf, g_output_dir,
sizeof(output_dir_buf));
555 const char *fallback = SAFE_GETENV(
"TMPDIR");
556 if (fallback == NULL) {
557 fallback = SAFE_GETENV(
"TEMP");
559 if (fallback == NULL) {
560 fallback = SAFE_GETENV(
"TMP");
562 if (fallback == NULL) {
565 SAFE_STRNCPY(output_dir_buf, fallback,
sizeof(output_dir_buf));
569 if (g_output_dir_set) {
586 if (!is_custom_path) {
587 char *validated_log_path = NULL;
588 asciichat_error_t validate_result =
590 if (validate_result != ASCIICHAT_OK || validated_log_path == NULL) {
591 SAFE_FREE(validated_log_path);
592 log_warn(
"Failed to validate instrumentation log path: %s", runtime->
log_path);
595 SAFE_STRNCPY(runtime->
log_path, validated_log_path,
sizeof(runtime->
log_path));
597 SAFE_FREE(validated_log_path);
601 const char *last_sep = strrchr(runtime->
log_path, PATH_DELIM);
602 if (last_sep != NULL && last_sep != runtime->
log_path) {
603 const size_t dir_path_len = (size_t)(last_sep - runtime->
log_path);
604 char dir_path[PATH_MAX];
605 memcpy(dir_path, runtime->
log_path, dir_path_len);
606 dir_path[dir_path_len] =
'\0';
607 if (mkdir(dir_path, DIR_PERM_PRIVATE) != 0) {
608 if (errno != EEXIST) {
618 if (!asciichat_instr_build_log_path(runtime)) {
619 const char *debug_env = SAFE_GETENV(
"ASCII_INSTR_SOURCE_PRINT_ECHO_STDERR");
620 if (debug_env && debug_env[0] ==
'1') {
621 fprintf(stderr,
"ASCII_INSTR: Failed to build log path\n");
627 const char *custom_log_file = SAFE_GETENV(
"ASCII_CHAT_DEBUG_SELF_SOURCE_CODE_LOG_FILE");
628 const bool is_custom_file = (custom_log_file != NULL && custom_log_file[0] !=
'\0');
633 if (is_custom_file) {
634 flags = PLATFORM_O_WRONLY | PLATFORM_O_CREAT | PLATFORM_O_APPEND | PLATFORM_O_BINARY;
636 flags = PLATFORM_O_WRONLY | PLATFORM_O_CREAT | PLATFORM_O_EXCL | PLATFORM_O_APPEND | PLATFORM_O_BINARY;
639 const char *debug_env = SAFE_GETENV(
"ASCII_INSTR_SOURCE_PRINT_ECHO_STDERR");
640 if (debug_env && debug_env[0] ==
'1') {
641 fprintf(stderr,
"ASCII_INSTR: Opening log file: %s (custom=%d)\n", runtime->
log_path, is_custom_file);
644 const mode_t mode = S_IRUSR | S_IWUSR;
647 if (debug_env && debug_env[0] ==
'1') {
648 fprintf(stderr,
"ASCII_INSTR: Failed to open log file: %s (errno=%d)\n", runtime->
log_path, errno);
653 if (debug_env && debug_env[0] ==
'1') {
654 fprintf(stderr,
"ASCII_INSTR: Successfully opened log file: %s (fd=%d)\n", runtime->
log_path, fd);
665 const char *only_env = SAFE_GETENV(
"ASCII_INSTR_SOURCE_PRINT_ONLY");
666 asciichat_instr_parse_only_filters(runtime, only_env);
668#if ASCII_INSTR_SOURCE_PRINT_HAVE_REGEX
669 const char *include_regex = SAFE_GETENV(
"ASCII_INSTR_SOURCE_PRINT_INCLUDE_REGEX");
670 if (include_regex != NULL && include_regex[0] !=
'\0') {
674 const char *exclude_regex = SAFE_GETENV(
"ASCII_INSTR_SOURCE_PRINT_EXCLUDE_REGEX");
675 if (exclude_regex != NULL && exclude_regex[0] !=
'\0') {
679 const char *function_include_regex = SAFE_GETENV(
"ASCII_INSTR_SOURCE_PRINT_FUNCTION_INCLUDE_REGEX");
680 if (function_include_regex != NULL && function_include_regex[0] !=
'\0') {
685 const char *function_exclude_regex = SAFE_GETENV(
"ASCII_INSTR_SOURCE_PRINT_FUNCTION_EXCLUDE_REGEX");
686 if (function_exclude_regex != NULL && function_exclude_regex[0] !=
'\0') {
692 const char *rate_env = SAFE_GETENV(
"ASCII_INSTR_SOURCE_PRINT_RATE");
693 uint32_t rate_value = 0;
694 if (asciichat_instr_parse_positive_uint32(rate_env, &rate_value) && rate_value > 1U) {
695 runtime->
rate = rate_value;
709static bool asciichat_instr_env_is_enabled(
const char *value) {
714 while (*value !=
'\0' && isspace((
unsigned char)*value) != 0) {
718 if (*value ==
'\0') {
722 if (strcmp(value, STR_ZERO) == 0) {
728 while (value[len] !=
'\0' && len <
sizeof(lowered) - 1) {
729 lowered[len] = (char)tolower((
unsigned char)value[len]);
734 return !(strcmp(lowered, STR_FALSE) == 0 || strcmp(lowered, STR_OFF) == 0 || strcmp(lowered, STR_NO) == 0);
737static bool asciichat_instr_parse_positive_uint32(
const char *value, uint32_t *out_value) {
738 if (value == NULL || out_value == NULL) {
742 while (*value !=
'\0' && isspace((
unsigned char)*value) != 0) {
746 if (*value ==
'\0') {
752 unsigned long long parsed = strtoull(value, &endptr, 10);
753 if (errno != 0 || endptr == value || parsed == 0ULL || parsed > UINT32_MAX) {
757 *out_value = (uint32_t)parsed;
761#if ASCII_INSTR_SOURCE_PRINT_HAVE_REGEX
762static bool asciichat_instr_compile_regex(regex_t *regex,
const char *pattern) {
763 if (regex == NULL || pattern == NULL) {
766 int ret = regcomp(regex, pattern, REG_EXTENDED | REG_NOSUB);
775 if (list->
items != NULL) {
776 for (
size_t i = 0; i < list->
count; ++i) {
779 SAFE_FREE(selector->module);
781 SAFE_FREE(list->
items);
788 const char *module,
const char *pattern) {
795 if (pattern == NULL || pattern[0] ==
'\0') {
801 if (module == NULL || module[0] ==
'\0') {
807 size_t new_capacity = (list->
capacity == 0) ? 4U : list->capacity * 2U;
810 if (new_items == NULL) {
813 list->
items = new_items;
818 memset(selector, 0,
sizeof(*selector));
819 selector->
type = type;
821 if (module != NULL && module[0] !=
'\0') {
822 SAFE_STRDUP(selector->module, module);
824 if (pattern != NULL && pattern[0] !=
'\0') {
825 SAFE_STRDUP(selector->
pattern, pattern);
832static void asciichat_instr_trim(
char *value) {
838 while (*start !=
'\0' && isspace((
unsigned char)*start) != 0) {
842 char *end = start + strlen(start);
843 while (end > start && isspace((
unsigned char)end[-1]) != 0) {
847 const size_t length = (size_t)(end - start);
848 if (start != value && length > 0U) {
849 memmove(value, start, length);
851 value[length] =
'\0';
855 if (runtime == NULL) {
861 if (value == NULL || value[0] ==
'\0') {
865 char *mutable_value = NULL;
866 SAFE_STRDUP(mutable_value, value);
867 if (mutable_value == NULL) {
871 char *cursor = mutable_value;
872 while (cursor != NULL && *cursor !=
'\0') {
873 char *token_start = cursor;
874 while (*cursor !=
'\0' && *cursor !=
',') {
877 if (*cursor ==
',') {
884 asciichat_instr_trim(token_start);
885 if (*token_start ==
'\0') {
889 char *equal_sign = strchr(token_start,
'=');
890 if (equal_sign != NULL) {
892 char *kind = token_start;
893 char *spec = equal_sign + 1;
894 asciichat_instr_trim(kind);
895 asciichat_instr_trim(spec);
900 if (strcmp(kind,
"func") == 0 || strcmp(kind,
"function") == 0) {
903 }
else if (strcmp(kind,
"module") == 0) {
904 char *module_value = spec;
905 char *module_pattern = strchr(module_value,
':');
906 if (module_pattern != NULL) {
907 *module_pattern =
'\0';
909 asciichat_instr_trim(module_pattern);
911 asciichat_instr_trim(module_value);
912 if (*module_value ==
'\0') {
915 const char *pattern_part = (module_pattern != NULL && *module_pattern !=
'\0') ? module_pattern : NULL;
917 module_value, pattern_part);
926 char *colon = strchr(token_start,
':');
929 char *module_name = token_start;
930 char *pattern_part = colon + 1;
931 asciichat_instr_trim(module_name);
932 asciichat_instr_trim(pattern_part);
933 if (*module_name ==
'\0') {
936 const char *pattern_spec = (*pattern_part !=
'\0') ? pattern_part : NULL;
938 module_name, pattern_spec);
946 SAFE_FREE(mutable_value);
950static bool asciichat_instr_match_glob(
const char *pattern,
const char *value) {
951 if (pattern == NULL || value == NULL) {
955 const char *p = pattern;
956 const char *v = value;
957 const char *star = NULL;
958 const char *match = NULL;
964 }
else if (*p ==
'?' || *p == *v) {
967 }
else if (star != NULL) {
983static const char *asciichat_instr_basename(
const char *path) {
989 const char *last_sep = strrchr(path, PATH_DELIM);
990 if (last_sep != NULL && last_sep[1] !=
'\0') {
996static bool asciichat_instr_path_contains_module(
const char *
file_path,
const char *module_name) {
997 if (
file_path == NULL || module_name == NULL || module_name[0] ==
'\0') {
1001 const size_t module_len = strlen(module_name);
1003 while ((cursor = strstr(cursor, module_name)) != NULL) {
1006 const char prev = cursor[-1];
1007 left_ok = (prev == PATH_DELIM);
1010 const char tail = cursor[module_len];
1011 bool right_ok = (tail ==
'\0' || tail == PATH_DELIM);
1013 if (left_ok && right_ok) {
1017 cursor = cursor + 1;
1024 const char *function_name) {
1026 if (list->
count == 0) {
1030 const char *base_name = asciichat_instr_basename(
file_path);
1031 for (
size_t i = 0; i < list->
count; ++i) {
1033 switch (selector->
type) {
1045 if (function_name != NULL && selector->
pattern != NULL &&
1046 asciichat_instr_match_glob(selector->
pattern, function_name)) {
1051 if (
file_path != NULL && selector->module != NULL &&
1052 asciichat_instr_path_contains_module(
file_path, selector->module)) {
1053 if (selector->
pattern == NULL ||
1054 (base_name != NULL && asciichat_instr_match_glob(selector->
pattern, base_name))) {
1068 uint32_t line_number,
const char *function_name) {
1093#if ASCII_INSTR_SOURCE_PRINT_HAVE_REGEX
1119#if ASCII_INSTR_SOURCE_PRINT_HAVE_REGEX
1121 if (function_name == NULL || regexec(&runtime->
function_include_regex, function_name, 0, NULL, 0) != 0) {
1134 if (!asciichat_instr_only_selectors_match(runtime,
file_path, function_name)) {
1143static int asciichat_instr_write_full(
int fd,
const char *buffer,
size_t len) {
1145 while (total < len) {
1146 ssize_t written =
platform_write(fd, buffer + total, len - total);
1148 if (errno == EINTR) {
1153 total += (size_t)written;
void asciichat_instr_runtime_global_destroy(void)
bool asciichat_instr_coverage_enabled(void)
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
void asciichat_instr_runtime_destroy(asciichat_instr_runtime_t *runtime)
struct asciichat_instr_runtime asciichat_instr_runtime_t
Per-thread instrumentation runtime state.
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)
#define ASCII_INSTR_SOURCE_PRINT_MAX_SNIPPET
struct asciichat_instr_only_selector asciichat_instr_only_selector_t
Selector for filtering instrumentation output by file, function, or module.
void asciichat_instr_log_pc(uintptr_t program_counter)
#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
Dynamic array of instrumentation selectors for filtering output.
#define ASCII_INSTR_SOURCE_PRINT_HAVE_REGEX
asciichat_instr_runtime_t * asciichat_instr_runtime_get(void)
char file_path[PLATFORM_MAX_PATH_LENGTH]
bool path_is_absolute(const char *path)
asciichat_error_t path_validate_user_path(const char *input, path_role_t role, char **normalized_out)
char * expand_path(const char *path)
Dynamic array of instrumentation selectors for filtering output.
asciichat_instr_only_selector_t * items
Array of selectors.
size_t capacity
Allocated capacity.
size_t count
Number of active selectors.
Selector for filtering instrumentation output by file, function, or module.
asciichat_instr_selector_type_t type
Type of selector (file substring, glob, function glob, or module)
char * pattern
Pattern to match (file/function glob pattern)
Per-thread instrumentation runtime state.
const char * filter_exclude
File path substring to exclude (from env var)
bool function_exclude_regex_valid
Whether function_exclude_regex was successfully compiled.
asciichat_instr_only_list_t only_selectors
List of "only" selectors for filtering.
const char * filter_include
File path substring to include (from env var)
uint64_t call_counter
Total number of instrumentation calls.
char log_path[PATH_MAX]
Path to log file.
bool rate_enabled
Whether rate limiting is enabled.
regex_t function_exclude_regex
Compiled regex for function name exclusion.
uint64_t sequence
Sequence number for log entries.
regex_t exclude_regex
Compiled regex for file path exclusion.
bool function_include_regex_valid
Whether function_include_regex was successfully compiled.
uint32_t rate
Rate limiting: log every Nth call.
const char * filter_function_exclude
Function name substring to exclude (from env var)
const char * filter_function_include
Function name substring to include (from env var)
regex_t include_regex
Compiled regex for file path inclusion.
int fd
Log file descriptor (-1 if not open)
uint64_t thread_id
Thread ID.
regex_t function_include_regex
Compiled regex for function name inclusion.
const char * filter_thread
Thread ID filter (from env var)
bool stderr_fallback
Use stderr if log file can't be opened.
bool exclude_regex_valid
Whether exclude_regex was successfully compiled.
bool filters_enabled
Whether any filters are active.
bool include_regex_valid
Whether include_regex was successfully compiled.
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
int ascii_tls_key_create(tls_key_t *key, void(*destructor)(void *))
int ascii_tls_set(tls_key_t key, void *value)
int mutex_init(mutex_t *mutex)
void * ascii_tls_get(tls_key_t key)
int ascii_tls_key_delete(tls_key_t key)
uint64_t asciichat_thread_current_id(void)
int mutex_destroy(mutex_t *mutex)
uint64_t time_get_ns(void)
uint64_t time_get_realtime_ns(void)
int format_duration_ns(double nanoseconds, char *buffer, size_t buffer_size)
uint64_t time_elapsed_ns(uint64_t start_ns, uint64_t end_ns)
bool platform_get_cwd(char *cwd, size_t path_size)
int platform_close(int fd)
int platform_open(const char *pathname, int flags,...)
ssize_t platform_write(int fd, const void *buf, size_t count)
pid_t platform_get_pid(void)