Skip to content

Instantly share code, notes, and snippets.

@hbobenicio
Created September 25, 2021 12:48
Show Gist options
  • Save hbobenicio/553a344ccbd25f469f15b406ae27b172 to your computer and use it in GitHub Desktop.
Save hbobenicio/553a344ccbd25f469f15b406ae27b172 to your computer and use it in GitHub Desktop.
Simple Logging in C
#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