Created
September 25, 2021 12:48
-
-
Save hbobenicio/553a344ccbd25f469f15b406ae27b172 to your computer and use it in GitHub Desktop.
Simple Logging in C
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <time.h> | |
#include <stdarg.h> | |
// Posix | |
#include <errno> | |
#ifndef LOG_RECORD_MSG_MAX_SIZE | |
#define LOG_RECORD_MSG_MAX_SIZE 1024 | |
#endif | |
enum log_outcome { | |
LOG_OK = 0, | |
LOG_ERR, | |
LOG_ERR_IO, | |
LOG_ERR_OVERFLOW, | |
}; | |
struct log_record { | |
FILE* stream; | |
const char* level; | |
const char* tag; | |
const char* file; | |
int line; | |
const char* caused_by; | |
const char* fmt; | |
}; | |
#define log_io_info(format, ...) \ | |
log_buffered( \ | |
(struct log_record) { \ | |
.stream = stderr, \ | |
.level = "info", \ | |
.tag = "io", \ | |
.file = __FILE__, \ | |
.line = __LINE__, \ | |
.caused_by = NULL, \ | |
.fmt = format \ | |
}, \ | |
__VA_ARGS__ \ | |
) | |
#define log_io_error(format, ...) \ | |
log_buffered( \ | |
(struct log_record) { \ | |
.stream = stderr, \ | |
.level = "error", \ | |
.tag = "io", \ | |
.file = __FILE__, \ | |
.line = __LINE__, \ | |
.caused_by = strerror(errno), \ | |
.fmt = format \ | |
}, \ | |
__VA_ARGS__ \ | |
) | |
/** | |
* Simple log function that just writes multiple times to the stream. | |
* The stream must then be buffered to avoid performance problems with excessive I/O calls. | |
*/ | |
// TODO add color support | |
static enum log_outcome log_unbuffered(struct log_record record, ...) { | |
time_t now = time(NULL); | |
struct tm* t = localtime(&now); | |
// timestamp | |
// TODO improve timestamping by printing milisseconds probably with clock_gettime() | |
int rc = fprintf( | |
record.stream, | |
"[%04d-%02d-%02d %02d:%02d:%02d] [%s:%d] %s: %s: ", | |
t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, | |
record.file, | |
record.line, | |
record.level, | |
record.tag | |
); | |
if (rc < 0) return LOG_ERR_IO; | |
// record msg | |
va_list args; | |
va_start(args, record); | |
if (vfprintf(record.stream, record.fmt, args) < 0) { | |
return LOG_ERR_IO; | |
} | |
va_end(args); | |
// caused by msg | |
if (record.caused_by) { | |
if (fprintf(record.stream, ": %s\n", record.caused_by) < 0) { | |
return LOG_ERR_IO; | |
} | |
} else { | |
if (fprintf(record.stream, "\n") < 0) { | |
return LOG_ERR_IO; | |
} | |
} | |
return LOG_OK; | |
} | |
/** | |
* Simple log function that does buffering writes to a internal stack buffer before flush everything to the stream with just one I/O call. | |
* Works best for unbuffered streams like stderr. | |
* For buffered streams like stdout, please check the log_unbuffered function. | |
*/ | |
// TODO add color support | |
static enum log_outcome log_buffered(struct log_record record, ...) | |
{ | |
int rc, offset = 0; | |
char buf[LOG_RECORD_MSG_MAX_SIZE] = {0}; | |
time_t now = time(NULL); | |
struct tm* t = localtime(&now); | |
// log record timestamp, level, file and line number | |
// TODO improve timestamping by printing milisseconds probably with clock_gettime() | |
if (offset >= LOG_RECORD_MSG_MAX_SIZE) return LOG_ERR_OVERFLOW; | |
rc = snprintf( | |
buf + offset, | |
LOG_RECORD_MSG_MAX_SIZE - offset, | |
"[%04d-%02d-%02d %02d:%02d:%02d] [%s:%d] %s: %s: ", | |
t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, | |
record.file, | |
record.line, | |
record.level, | |
record.tag | |
); | |
if (rc < 0) return LOG_ERR_IO; | |
if (rc >= LOG_RECORD_MSG_MAX_SIZE - offset) return LOG_ERR_OVERFLOW; | |
offset += rc; | |
// log record msg | |
if (offset >= LOG_RECORD_MSG_MAX_SIZE) return LOG_ERR_OVERFLOW; | |
va_list args; | |
va_start(args, record); | |
rc = vsnprintf(buf + offset, LOG_RECORD_MSG_MAX_SIZE - offset, record.fmt, args); | |
va_end(args); | |
if (rc < 0) return LOG_ERR_IO; | |
if (rc >= LOG_RECORD_MSG_MAX_SIZE - offset) return LOG_ERR_OVERFLOW; | |
offset += rc; | |
// caused by msg | |
if (record.caused_by) { | |
if (offset >= LOG_RECORD_MSG_MAX_SIZE) return LOG_ERR_OVERFLOW; | |
rc = snprintf(buf + offset, LOG_RECORD_MSG_MAX_SIZE - offset, ": %s\n", record.caused_by); | |
if (rc < 0) return LOG_ERR_IO; | |
if (rc >= LOG_RECORD_MSG_MAX_SIZE - offset) return LOG_ERR_OVERFLOW; | |
offset += rc; | |
} else { | |
if (offset >= LOG_RECORD_MSG_MAX_SIZE) return LOG_ERR_OVERFLOW; | |
rc = snprintf(buf + offset, LOG_RECORD_MSG_MAX_SIZE - offset, "\n"); | |
if (rc < 0) return LOG_ERR_IO; | |
if (rc >= LOG_RECORD_MSG_MAX_SIZE - offset) return LOG_ERR_OVERFLOW; | |
offset += rc; | |
} | |
// flush to stream | |
if (fputs(buf, record.stream) == EOF) { | |
return LOG_ERR_IO; | |
} | |
return LOG_OK; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment