Add trace_agg_logger/main.c

This commit is contained in:
Mika 2025-12-08 09:42:01 +00:00
parent 514186c8e2
commit 8f1f04e9b5

161
trace_agg_logger/main.c Normal file
View file

@ -0,0 +1,161 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>
/*
* trace_agg_logger - kleines CLI-Tool zur Protokollierung von
* Aggregationsvorgaengen rund um trace_agg.py.
*
* Idee:
* - Wird von trace_agg.py (oder einem Test-Wrapper) als separates
* Kommandozeilenprogramm aufgerufen.
* - Nimmt eine frei formulierbare Lognachricht entgegen
* (z. B. JSON oder Key-Value-Paare) und schreibt diese
* zeilenbasiert in eine Logdatei.
* - Jede Zeile wird um einen einfachen Zeitstempel und ein Level
* ergaenzt.
*
* Nutzung (Beispiele):
* - ./trace_agg_logger "Aggregation started"
* - ./trace_agg_logger --level DEBUG "Filtered 496 clocksource events"
* - LOG_FILE=custom.log ./trace_agg_logger "custom target file"
*
* Rueckgabewerte:
* - 0 bei Erfolg
* - != 0 bei Fehler (z. B. Datei nicht schreibbar, fehlende Message)
*
* Diese Implementierung ist bewusst simpel gehalten, damit sie in
* Unit-/Integration-Tests leicht aufrufbar ist.
*/
#define DEFAULT_LOG_FILE "trace_agg.log"
#define MAX_LEVEL_LEN 16
static void print_usage(const char *prog)
{
fprintf(stderr,
"Usage: %s [--level LEVEL] message...\n"
"\n"
"Options:\n"
" --level LEVEL Log-Level, z.B. INFO, DEBUG, ERROR (Default: INFO)\n"
"\n"
"Die eigentliche Nachricht wird aus allen verbleibenden\n"
"Argumenten nach --level zusammengesetzt. Die Ausgabe erfolgt\n"
"zeilenweise in eine Logdatei. Der Pfad kann per Umgebungs-\n"
"variable LOG_FILE gesetzt werden (Default: %s).\n",
prog, DEFAULT_LOG_FILE);
}
static const char *get_log_file_path(void)
{
const char *path = getenv("LOG_FILE");
if (path == NULL || path[0] == '\0') {
return DEFAULT_LOG_FILE;
}
return path;
}
static void current_timestamp(char *buf, size_t buflen)
{
/* Erzeugt einen einfachen Zeitstempel im Format YYYY-MM-DD HH:MM:SS */
time_t now = time(NULL);
struct tm tm_now;
if (now == (time_t)-1) {
/* Fallback: leerer Zeitstempel bei Fehler */
if (buflen > 0) {
buf[0] = '\0';
}
return;
}
/* Lokale Zeit in tm umwandeln; thread-sichere Variante */
#if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(__APPLE__)
localtime_r(&now, &tm_now);
#else
struct tm *tmp = localtime(&now);
if (!tmp) {
if (buflen > 0) {
buf[0] = '\0';
}
return;
}
tm_now = *tmp;
#endif
strftime(buf, buflen, "%Y-%m-%d %H:%M:%S", &tm_now);
}
int main(int argc, char *argv[])
{
const char *level = "INFO";
int msg_index = 1; /* Index des ersten Message-Arguments */
int i;
if (argc < 2) {
print_usage(argv[0]);
return 1;
}
/* Einfache Argument-Parsing-Logik fuer --level */
if (argc >= 4 && strcmp(argv[1], "--level") == 0) {
level = argv[2];
msg_index = 3;
}
if (msg_index >= argc) {
fprintf(stderr, "Error: missing log message.\n");
print_usage(argv[0]);
return 1;
}
/* Nachricht aus den verbleibenden Argumenten zusammenbauen */
size_t total_len = 0;
for (i = msg_index; i < argc; ++i) {
total_len += strlen(argv[i]);
if (i + 1 < argc) {
total_len += 1; /* Leerzeichen zwischen Tokens */
}
}
char *message = (char *)malloc(total_len + 1);
if (!message) {
fprintf(stderr, "Error: failed to allocate memory for log message.\n");
return 1;
}
message[0] = '\0';
for (i = msg_index; i < argc; ++i) {
strcat(message, argv[i]);
if (i + 1 < argc) {
strcat(message, " ");
}
}
const char *log_path = get_log_file_path();
FILE *fp = fopen(log_path, "a");
if (!fp) {
fprintf(stderr, "Error: cannot open log file '%s': %s\n",
log_path, strerror(errno));
free(message);
return 1;
}
char ts[32];
current_timestamp(ts, sizeof(ts));
/* Ausgabeformat: [TIMESTAMP] [LEVEL] message */
if (fprintf(fp, "[%s] [%s] %s\n", ts, level, message) < 0) {
fprintf(stderr, "Error: failed to write to log file '%s'.\n", log_path);
fclose(fp);
free(message);
return 1;
}
fclose(fp);
free(message);
return 0;
}