alog/alog.c

251 lines
6.3 KiB
C
Raw Permalink Normal View History

/* ALOG: A Logger
* Copyright (C) 2022 Aidan Hahn
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see https://www.gnu.org/licenses/.
*/
#include "alog.h"
#include <stdlib.h>
// ^^^ TODO: build a memory management library
#include <stdarg.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#define TEST_ALOG_STATE() if (!ALOG_LOGGING_STATE) { _init_alog(); }
#define MAX_TIMESTAMP 30
struct _global_logging_state *ALOG_LOGGING_STATE;
int _alog_num_out_fds;
int *_alog_out_fds;
int _alog_num_err_fds;
int *_alog_err_fds;
void _init_alog() {
if (!ALOG_LOGGING_STATE) {
ALOG_LOGGING_STATE = malloc(sizeof(struct _global_logging_state));
ALOG_LOGGING_STATE->broadcast_to_all_fds = 0;
ALOG_LOGGING_STATE->log_debug_messages = 0;
}
_alog_num_out_fds = 1;
_alog_out_fds = malloc(sizeof(int) * 1);
// stdout
_alog_out_fds[0] = 1;
_alog_num_err_fds = 1;
_alog_err_fds = malloc(sizeof(int) * 1);
// stderr
_alog_err_fds[0] = 2;
}
void init_alog() {
TEST_ALOG_STATE();
}
// TODO: should I dont close stdin and stdout?
void deinit_alog() {
TEST_ALOG_STATE();
free(ALOG_LOGGING_STATE);
for (int i = 0; i < _alog_num_out_fds; i++) {
close(_alog_out_fds[i]);
}
for (int i = 0; i < _alog_num_err_fds; i++) {
close(_alog_err_fds[i]);
}
free(_alog_out_fds);
free(_alog_err_fds);
}
void alog_add_target(int fd, char is_err) {
TEST_ALOG_STATE();
int **fd_list = is_err ? &_alog_err_fds : &_alog_out_fds;
int *size = is_err ? &_alog_num_err_fds : &_alog_num_out_fds;
int *fds = malloc(sizeof(int) * (*size + 1));
for (int i = 0; i < *size; i++) {
fds[i] = (*fd_list)[i];
}
fds[*size] = fd;
free(*fd_list);
(*fd_list) = fds;
(*size)++;
}
// inefficient time wise, but avoids extra allocs
// TODO: revisit this and see how to do it less tediously
void alog_remove_target(int fd) {
TEST_ALOG_STATE();
int out_fds = 0;
for (int i = 0; i < _alog_num_out_fds; i++) {
if (_alog_out_fds[i] != fd) {
out_fds++;
}
}
if (!out_fds) {
_alog_num_out_fds = 0;
free(_alog_out_fds);
_alog_out_fds = NULL;
} else {
int *tmp_outs = malloc(sizeof(int) * out_fds);
int tmp_iter_idx = 0;
for (int i = 0; i < _alog_num_out_fds; i++) {
if (_alog_out_fds[i] != fd) {
tmp_outs[tmp_iter_idx] = _alog_out_fds[i];
tmp_iter_idx++;
}
}
free(_alog_out_fds);
_alog_out_fds = tmp_outs;
_alog_num_out_fds = out_fds;
}
int err_fds = 0;
for (int i = 0; i < _alog_num_err_fds; i++) {
if (_alog_err_fds[i] != fd) {
err_fds++;
}
}
if (!err_fds) {
_alog_num_err_fds = 0;
free(_alog_err_fds);
_alog_err_fds = NULL;
} else {
int *tmp_errs = malloc(sizeof(int) * err_fds);
int tmp_iter_idx = 0;
for (int i = 0; i < _alog_num_err_fds; i++) {
if (_alog_err_fds[i] != fd) {
tmp_errs[tmp_iter_idx] = _alog_err_fds[i];
tmp_iter_idx++;
}
}
free(_alog_err_fds);
_alog_err_fds = tmp_errs;
_alog_num_err_fds = err_fds;
}
}
// TODO: use preprocessor directives to gate off the posix timestamp stuff
// unless the right variable is passed in at compile time
int _alog (
alog_sev severity,
const char * message,
va_list fmt
) {
if (severity == DEBUG &&
!ALOG_LOGGING_STATE->log_debug_messages) {
// nop :)
return -1;
}
va_list fmt_list;
va_copy(fmt_list, fmt);
char *buffer;
int size;
if (severity != PRINT) {
// GET TIMESTAMP
// TODO: try to get this all into 1 call to sprintf
time_t t = time(NULL);
struct tm * p = localtime(&t);
int timestamp_len = strftime(NULL, MAX_TIMESTAMP, "[%c]", p) + 1;
char *timestamp = calloc(timestamp_len , sizeof(char));
strftime(timestamp, timestamp_len+1, "[%c]", p);
size = (timestamp_len + strlen(message) + 1);
char *msg_and_timestamp = calloc(size, sizeof(char));
sprintf(msg_and_timestamp, "%s %s\n", timestamp, message);
free(timestamp);
// TODO: Why even use msg_and_timestamp if I am going to write it wholesale into buffer?
size = vsnprintf(NULL, 0, msg_and_timestamp, fmt_list);
va_end(fmt_list);
va_copy(fmt_list, fmt);
buffer = malloc(size + 1);
vsprintf(buffer, msg_and_timestamp, fmt_list);
va_end(fmt_list);
free(msg_and_timestamp);
// if severity is PRINT we avoid timestamp
} else {
size = vsnprintf(NULL, 0, message, fmt_list);
va_end(fmt_list);
va_copy(fmt_list, fmt);
buffer = malloc(size + 1);
vsprintf(buffer, message, fmt_list);
va_end(fmt_list);
}
int written = 0;
for (int i = 0; i < _alog_num_out_fds; i++) {
int num = write(_alog_out_fds[i], buffer, size);
written += num;
if (num < size) {
// TODO: how to handle? probably cant log another message lmao
// perhaps it should be yanked from the list? maybe not?
;;
}
fsync(_alog_out_fds[i]);
}
if (severity == ERROR) {
for (int i = 0; i < _alog_num_err_fds; i++) {
int num = write(_alog_err_fds[i], buffer, size);
written += num;
if (num < size) {
// TODO: see above
;;
}
fsync(_alog_err_fds[i]);
}
}
free(buffer);
return written;
}
int alog (
alog_sev severity,
const char * message,
...
) {
TEST_ALOG_STATE();
va_list fmt_list;
va_start(fmt_list, message);
int res = _alog(severity, message, fmt_list);
va_end(fmt_list);
return res;
}
#ifdef ALOG_HIJACK_PRINTF
int printf(const char *format, ...) {
TEST_ALOG_STATE();
va_list fmt_list;
va_start(fmt_list, format);
int i = _alog(PRINT, format, fmt_list);
va_end(fmt_list);
return i;
/* potentially accept a mem leak at end of program for alog state.
* That is, if the program using this is not already using alog correctly
* or if ALOG has been LD_PRELOAD'ed into a program to override its printf */
}
#endif