#include #include #include #include #include /* * 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; }