I have a function:
log(const char *domain, int log_level, const char *fmt, ...)
I would like first and second arguments optional, so it's like following calls would be possible:
log("SYSTEM-A", 1, "Example %s", "...message");
log(1, "Example %s", "...message");
log("Example %s", "...message");
I've read about neat macro tricks, however they (almost?) all rely on trailing arguments to 'stand out' in a helper macro:
HELPER_SELECT(_1, _2, _3, func, ...) func
I however cannot use this method, because log()
can take arbitrary number of variadic arguments. Is this possible to overcome somehow? With use of _Generics
, maybe?
(1) log("SYSTEM-A", 1, "Example %s", "...message"); (2) log(1, "Example %s", "...message"); (3) log("Example %s", "...message");
From what I understand:
%
in it's first argument.%
in it's argument.You can:
log
macro on number of arguments_log_wrapper(const char *arg, ...)
strchr(arg, '%')
A possible implementation looks like this:
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
void vlog_domain(const char *domain, int log_level, const char *fmt, va_list va) {
printf("domain\n");
}
void vlog_level(int log_level, const char *fmt, va_list va) {
printf("level\n");
}
void vlog_normal(const char *fmt, va_list va) {
printf("normal\n");
}
void _log_wrapper(int type, ...) {
va_list va;
va_start(va, type);
if (type == 1) {
int log_level = va_arg(va, int);
const char *fmt = va_arg(va, const char *);
vlog_level(log_level, fmt, va);
} else {
const char *arg = va_arg(va, const char*);
if (!strchr(arg, '%')) {
const char *domain = arg;
int log_level = va_arg(va, int);
const char *fmt = va_arg(va, const char*);
vlog_domain(domain, log_level, fmt, va);
} else {
const char *fmt = arg;
vlog_normal(fmt, va);
}
}
va_end(va);
}
#define _log_1(_1) vlog_normal(_1) // TODO
#define _log_2(_1, ...) _log_wrapper( \
_Generic((_1), int: 1, char *: 2), _1, ##__VA_ARGS__)
// this implementation supports max ca. 10 arguments
#define _log_N(_9,_8,_7,_6,_5,_4,_3,_2,_1,_0,N,...) _log_##N
#define log(...) _log_N(__VA_ARGS__,2,2,2,2,2,2,2,2,2,2,1)(__VA_ARGS__)
int main() {
log("SYSTEM-A", 1, "Example %s", "...message"); // domain
log(1, "Example %s", "...message"); // level
log("Example %s", "...message"); // normal
}
These are some time spent on writing the interface, that the next developer will most probably anyway not understand and will have to rewrite and refactor the whole code. I suggest instead to be as possible clear and write as possibly easy code to understand and just name your functions:
logd("SYSTEM-A", 1, "Example %s", "...message");
logl(1, "Example %s", "...message");
log("Example %s", "...message");
and be done with it.
Inspect other projects how they solved logging with "domain+loglevel" (which sounds like syslog()
severity and facility....) have a look how other projects solved logging interface. From my mind I enjoyed zephyr project solved logging, and it's open source so see inspect it's sources.