muon/src/log.c
2026-01-12 13:58:58 -05:00

541 lines
9.9 KiB
C

/*
* SPDX-FileCopyrightText: Stone Tickle <lattis@mochiro.moe>
* SPDX-FileCopyrightText: Simon Zeni <simon@bl4ckb0ne.ca>
* SPDX-License-Identifier: GPL-3.0-only
*/
#include "compat.h"
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include "buf_size.h"
#include "formats/ansi.h"
#include "lang/string.h"
#include "lang/workspace.h"
#include "log.h"
#include "platform/assert.h"
#include "platform/filesystem.h"
#include "platform/log.h"
#include "platform/os.h"
#include "platform/term.h"
#include "tracy.h"
const char *log_level_clr[log_level_count] = {
[log_error] = STRINGIZE(c_red),
[log_warn] = STRINGIZE(c_yellow),
[log_note] = STRINGIZE(c_cyan),
[log_info] = "0",
[log_debug] = STRINGIZE(c_cyan),
};
const char *log_level_name[log_level_count] = {
[log_error] = "error",
[log_warn] = "warning",
[log_note] = "note",
[log_info] = "info",
[log_debug] = "debug",
};
const char *log_level_shortname[log_level_count] = {
[log_error] = "error ",
[log_warn] = "warn ",
[log_note] = "note ",
[log_info] = "",
[log_debug] = "dbg ",
};
struct log_progress_lvl {
double pos, end;
};
struct log_progress {
struct log_progress_lvl stack[64];
const char *prev_name;
struct log_progress_style style;
double sum_done;
double sub_val;
double sum_total;
uint32_t len;
uint32_t width;
bool init;
};
static struct {
FILE *file, *debug_file;
enum log_level level;
bool file_is_a_tty;
uint32_t indent;
struct workspace *tstr_wk;
struct tstr *tstr;
struct log_progress progress;
} log_cfg = {
.level = log_info,
};
bool
log_is_progress_bar_enabled(void)
{
struct log_progress *lp = &log_cfg.progress;
return lp->init;
}
void
log_progress_push_state(struct workspace *wk)
{
struct log_progress lp = { 0 };
stack_push(&wk->stack, log_cfg.progress, lp);
}
void
log_progress_pop_state(struct workspace *wk)
{
stack_pop(&wk->stack, log_cfg.progress);
}
void
log_progress_set_style(const struct log_progress_style *style)
{
log_cfg.progress.style = *style;
}
void
log_progress_enable(struct workspace *wk)
{
if (!log_cfg.file_is_a_tty) {
return;
}
struct log_progress *lp = &log_cfg.progress;
*lp = (struct log_progress){
.init = true,
};
int term_fd;
if (fs_fileno(log_cfg.file, &term_fd)) {
uint32_t _h;
term_winsize(wk, term_fd, &_h, &lp->width);
}
if (!lp->width) {
lp->width = 80;
}
}
void
log_progress_disable(void)
{
struct log_progress *lp = &log_cfg.progress;
if (!lp->init) {
return;
}
lp->init = false;
if (log_cfg.file_is_a_tty) {
log_raw("\033[K");
}
}
void
log_progress_push_level(double start, double end)
{
struct log_progress *lp = &log_cfg.progress;
if (!lp->init) {
return;
}
if (lp->len >= ARRAY_LEN(lp->stack)) {
return;
}
lp->stack[lp->len] = (struct log_progress_lvl){
.pos = start,
.end = end,
};
++lp->len;
lp->sum_total += end - start;
}
void
log_progress_pop_level(void)
{
struct log_progress *lp = &log_cfg.progress;
if (!lp->init) {
return;
}
if (lp->len) {
--lp->len;
lp->sum_done += lp->stack[lp->len].end - lp->stack[lp->len].pos;
}
}
void
log_progress_inc(struct workspace *wk)
{
struct log_progress *lp = &log_cfg.progress;
if (!lp->init) {
return;
}
log_progress(wk, lp->stack[lp->len - 1].pos + 1);
}
static void
log_progress_print_bar(const char *name)
{
struct log_progress *lp = &log_cfg.progress;
uint32_t pad = 0;
char info[BUF_SIZE_4k];
snprintf_append(info, &pad, "%s", " ");
if (lp->style.show_count) {
snprintf_append(info, &pad, "%3d/%3d ", (uint32_t)lp->sum_done, (uint32_t)lp->sum_total);
}
char fmt[16];
snprintf(fmt, sizeof(fmt), "%%-%d.%ds", lp->style.name_pad, lp->style.name_pad);
snprintf_append(info, &pad, fmt, name ? name : "");
pad += 2;
const double pct_scale = 1.0 / lp->sum_total * (double)(lp->width - pad);
const double pct_done = lp->sum_done * pct_scale;
const double sub_pct_done = lp->sub_val * pct_scale;
log_raw("[");
uint32_t i;
for (i = 0; i < lp->width - pad; ++i) {
if (i <= pct_done) {
fputc('=', log_cfg.file);
} else if (i < sub_pct_done) {
fputc('-', log_cfg.file);
} else if (i == sub_pct_done) {
fputc('>', log_cfg.file);
} else {
fputc(' ', log_cfg.file);
}
}
log_raw("]%s%s", info, lp->style.decorate ? "" : "\r");
if (lp->style.decorate) {
lp->style.decorate(lp->style.usr_ctx, lp->width);
}
fflush(log_cfg.file);
}
void
log_progress_subval(struct workspace *wk, double val, double sub_val)
{
struct log_progress *lp = &log_cfg.progress;
if (!lp->init) {
return;
}
struct log_progress_lvl *cur = &lp->stack[lp->len - 1];
if (lp->style.rate_limit > 0) {
if (!(cur->pos < val && val <= cur->end)) {
return;
}
}
const double diff = val - cur->pos;
if (diff < lp->style.rate_limit) {
return;
}
cur->pos = val;
lp->sum_done += diff;
if (sub_val == 0) {
lp->sub_val = lp->sum_done;
} else {
lp->sub_val = sub_val;
}
const char *name = lp->style.name;
if (!name && wk->projects.len) {
obj proj_name = current_project(wk)->cfg.name;
if (proj_name) {
name = get_str(wk, proj_name)->s;
}
}
lp->prev_name = name;
log_progress_print_bar(name);
}
void
log_progress(struct workspace *wk, double val)
{
log_progress_subval(wk, val, 0);
}
bool
log_should_print(enum log_level lvl)
{
return lvl <= log_cfg.level;
}
void
log_inc_indent(int32_t n)
{
log_cfg.indent += n;
}
void
log_set_indent(uint32_t n)
{
log_cfg.indent = n;
}
static uint32_t
log_print_prefix(enum log_level lvl, char *buf, uint32_t buf_len)
{
uint32_t len = 0;
for (uint32_t i = 0; i < log_cfg.indent; ++i) {
snprintf_append_(buf, buf_len, &len, " ");
}
if (*log_level_shortname[lvl]) {
snprintf_append_(buf, buf_len, &len, "\033[%sm%s\033[0m", log_level_clr[lvl], log_level_shortname[lvl]);
}
return len;
}
static void
print_buffer(FILE *out, const char *s, uint32_t len, bool tty, bool progress)
{
print_colorized(out, s, len, !tty);
if (progress && s[len - 1] == '\n') {
log_progress_print_bar(log_cfg.progress.prev_name);
}
}
void
log_printn(enum log_level lvl, const char *buf, uint32_t len)
{
// TracyCMessage(buf, len);
if (log_cfg.debug_file) {
print_buffer(log_cfg.debug_file, buf, len, false, false);
}
if (!log_should_print(lvl)) {
return;
}
if (log_cfg.progress.init && lvl == log_error) {
if (!log_cfg.progress.style.dont_disable_on_error) {
log_progress_disable();
}
}
if (log_cfg.tstr) {
tstr_pushn(log_cfg.tstr_wk, log_cfg.tstr, buf, len);
} else if (log_cfg.file) {
print_buffer(log_cfg.file,
buf,
len,
log_cfg.file_is_a_tty,
log_cfg.progress.init && (lvl == log_info || lvl == log_warn));
}
}
void
log_print_middle_truncated(enum log_level lvl, const struct str *buf, const struct str *sep, uint32_t truncate_limit)
{
if (buf->len > truncate_limit) {
const uint32_t truncate_half = truncate_limit / 2;
log_printn(lvl, buf->s, truncate_half);
log_printn(lvl, sep->s, sep->len);
log_printn(lvl, buf->s + (buf->len - truncate_half), truncate_half);
} else {
log_printn(lvl, buf->s, buf->len);
}
}
static char log_buf[(1024 * 16) + 16];
enum log_format_flag {
log_format_flag_newline = 1 << 0,
log_format_flag_prefix = 1 << 1,
};
static uint32_t
log_format(enum log_level lvl, const char *fmt, va_list ap, char *buf, const uint32_t buf_len, enum log_format_flag flags)
{
uint32_t len = buf_len;
if (flags & log_format_flag_prefix) {
uint32_t prefix_len = log_print_prefix(lvl, buf, len);
assert(prefix_len < len);
buf += prefix_len;
len -= prefix_len;
}
const struct str truncate_suffix = STR(" [truncated]");
const uint32_t buf_reserve = truncate_suffix.len + 2;
assert(len > buf_reserve);
uint32_t buf_avail = len - buf_reserve;
int printed_len = vsnprintf(buf, buf_avail, fmt, ap);
// vsnprintf returns negative on error
if (printed_len < 0) {
printed_len = 0;
}
if ((uint32_t)printed_len >= buf_avail) {
buf += buf_avail;
len -= buf_avail;
memcpy(buf, truncate_suffix.s, truncate_suffix.len);
buf += truncate_suffix.len;
len -= truncate_suffix.len;
} else {
buf += printed_len;
len -= printed_len;
}
if (flags & log_format_flag_newline) {
buf[0] = '\n';
++buf;
--len;
}
buf[0] = 0;
return buf_len - len;
}
void
log_printv(enum log_level lvl, const char *fmt, va_list ap)
{
uint32_t len = log_format(lvl, fmt, ap, log_buf, ARRAY_LEN(log_buf), 0);
log_printn(lvl, log_buf, len);
}
void
log_print(bool nl, enum log_level lvl, const char *fmt, ...)
{
uint32_t flags = log_format_flag_prefix;
if (nl) {
flags |= log_format_flag_newline;
}
va_list ap;
va_start(ap, fmt);
uint32_t len = log_format(lvl, fmt, ap, log_buf, ARRAY_LEN(log_buf), flags);
va_end(ap);
if (log_cfg.progress.init) {
log_raw("\033[K");
}
log_printn(lvl, log_buf, len);
}
void
log_plain(enum log_level lvl, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
log_printv(lvl, fmt, ap);
va_end(ap);
}
void
log_rawv(const char *fmt, va_list ap)
{
vfprintf(log_cfg.file, fmt, ap);
}
void
log_raw(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
log_rawv(fmt, ap);
va_end(ap);
}
void
log_flush(void)
{
if (log_cfg.file) {
fflush(log_cfg.file);
}
if (log_cfg.debug_file) {
fflush(log_cfg.debug_file);
}
}
void
log_plain_version_string(enum log_level lvl, const char *version)
{
if (strcmp(version, "undefined") == 0) {
return;
}
log_plain(lvl, " version: %s", version);
}
const char *
bool_to_yn(bool v)
{
return v ? CLR(c_green) "YES" CLR(0) : CLR(c_red) "NO" CLR(0);
}
void
log_set_file(struct workspace *wk, FILE *log_file)
{
log_cfg.file = log_file;
log_cfg.tstr = 0;
log_cfg.tstr_wk = 0;
log_cfg.file_is_a_tty = log_file && fs_is_a_tty(wk, log_file);
}
void
log_set_debug_file(FILE *log_file)
{
log_cfg.debug_file = log_file;
}
void
log_set_buffer(struct workspace *wk, struct tstr *buf)
{
log_set_file(wk, 0);
log_cfg.tstr = buf;
log_cfg.tstr_wk = wk;
}
void
log_set_lvl(enum log_level lvl)
{
if (lvl > log_level_count) {
L("attempted to set log level to invalid value %d (max: %d)", lvl, log_level_count);
return;
}
log_cfg.level = lvl;
}
FILE *
_log_file(void)
{
return log_cfg.file;
}