Log: Add a new logging infrastructure

Replace the existing 4 logging facilities by a signle one.
Support for log levels (compile and run time) and log modules.
See doc/logging.md for a writeup.

Signed-off-by: Andreas Fuchs <andreas.fuchs@sit.fraunhofer.de>
diff --git a/bootstrap b/bootstrap
index ded9463..23c8652 100755
--- a/bootstrap
+++ b/bootstrap
@@ -22,10 +22,6 @@
   src_listvar "sysapi/sysapi_util" "*.c" "SYSAPIUTIL_C"
   printf "SYSAPI_SRC = \$(SYSAPI_H) \$(SYSAPI_C) \$(SYSAPIUTIL_C)\n"
 
-  src_listvar "common" "*.c" "COMMON_C"
-  src_listvar "common" "*.h" "COMMON_H"
-  printf "COMMON_SRC = \$(COMMON_C) \$(COMMON_H)\n"
-
   src_listvar "marshal" "*.c" "MARSHAL_C"
   src_listvar "marshal" "*.h" "MARSHAL_H"
   printf "MARSHAL_SRC = \$(MARSHAL_C) \$(MARSHAL_H)"
diff --git a/configure.ac b/configure.ac
index 79ca595..3d12e27 100644
--- a/configure.ac
+++ b/configure.ac
@@ -54,6 +54,13 @@
 
 AX_ADD_PREPROC_FLAG([-D_DEFAULT_SOURCE])
 
+AC_ARG_WITH([loglevel],
+            [AS_HELP_STRING([--with-loglevel={none,error,warning,info,debug,trace}],
+                            [sets the maximum log level (default is trace)])],
+            [],
+            [with_loglevel=trace])
+AC_DEFINE_UNQUOTED([LOGLEVEL], [$with_loglevel])
+
 AC_ARG_ENABLE([debug],
             [AS_HELP_STRING([--enable-debug],
                             [build with debug info (default is no)])],
diff --git a/doc/logging.md b/doc/logging.md
new file mode 100644
index 0000000..0652265
--- /dev/null
+++ b/doc/logging.md
@@ -0,0 +1,32 @@
+# Compile time log levels
+
+At compile time the maximum log level can be specified. This allows distros to
+disable the overhead code and reduce overall code size.
+
+# Runtime log level
+
+At runtime, the log level is determined by an environment variable. The default
+log level is WARNING. The level can be changed by setting the TSS2_LOG
+environment variable.
+
+Possible levels are: NONE, ERROR, WARNING, INFO, DEBUG, TRACE
+
+The level can be set for all module using the `all` module name or individually
+per module. The environment variable is evaluated left to right.
+
+Example: `TSS2_LOG=all+ERROR,marshal+TRACE,tcti+DEBUG
+
+# Implementation
+
+Each source code file specifies its corresponding module before including log.h.
+```
+#define LOGMODULE tcti
+#include "log.h"
+```
+Optionally, the default log-level for this module can be set:
+```
+#define LOGMODULE tcti
+#define LOGDEFAULT ERROR
+#include "log.h"
+```
+
diff --git a/log/log.c b/log/log.c
index 40fc890..2ce1910 100644
--- a/log/log.c
+++ b/log/log.c
@@ -1,43 +1,112 @@
-#include "log.h"
+#include <string.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
 
-/*
- * This array of structures defines the mapping from the log_level
- * enumeration to their string representation.
- */
-static const struct log_string {
-    log_level   level;
-    const char *string;
-} log_strings [] = {
-    {
-        DEBUG,
-        "DEBUG",
-    },
-    {
-        INFO,
-        "INFO",
-    },
-    {
-        WARNING,
-        "WARNING",
-    },
-    {
-        ERROR,
-        "ERROR",
-    }
-};
-/*
- * This function converts a member of the log_level enumeration to its
- * string representation. It's a simple lookup in the log_strings array
- * defined above.
- */
-const char*
-level_to_str (log_level level)
+#define LOGMODULE log
+#include "log/log.h"
+
+#define likely(x)       __builtin_expect(!!(x), 1)
+#define unlikely(x)     __builtin_expect(!!(x), 0)
+
+log_level
+getLogLevel(const char *module, log_level logdefault);
+
+void
+doLogBlob(log_level loglevel, const char *module, log_level logdefault,
+           log_level *status,
+           const char *file, const char *func, int line,
+           const uint8_t *blob, size_t size, const char *fmt, ...)
 {
-    unsigned int i;
-    for (i = 0; i < sizeof (log_strings) / sizeof (log_strings[0]); ++i) {
-        if (level == log_strings[i].level) {
-            return log_strings[i].string;
+    if (unlikely(*status == LOGLEVEL_UNDEFINED))
+        *status = getLogLevel(module, logdefault);
+
+    if (loglevel > *status)
+        return;
+
+    size_t width = 8;
+    size_t buffer_size = (size * 2) + (size / width) * 2 + 1;
+    char buffer[buffer_size];
+    for (size_t i = 0, off = 0; i < size && off < buffer_size; i++, off+=2) {
+        if (width < buffer_size && i % width == 0) {
+            *(&buffer[0] + off) = '\n';
+            off += 1;
+            *(&buffer[0] + off) = '\t';
+            off += 1;
+        }
+        sprintf(&buffer[0] + off, "%02x", blob[i]);
+    }
+
+    va_list vaargs;
+    va_start(vaargs, fmt);
+    /* TODO: Unfortunately, vsnprintf(NULL, 0, ...) do not behave the same as
+       snprintf(NULL, 0, ...). Until there is an alternative, messages on
+       logblob are restricted to 255 characters
+    int msg_len = vsnprintf(NULL, 0, fmt, vaargs); */
+    int msg_len = 255;
+    char msg[msg_len+1];
+    vsnprintf(msg, sizeof(msg), fmt, vaargs);
+    va_end(vaargs);
+
+    doLog(loglevel, module, logdefault, status, file, func, line,
+          "%s (size=%zi): %s", msg, size, buffer);
+}
+
+void
+doLog(log_level loglevel, const char *module, log_level logdefault,
+           log_level *status,
+           const char *file, const char *func, int line,
+           const char *msg, ...)
+{
+    if (unlikely(*status == LOGLEVEL_UNDEFINED))
+        *status = getLogLevel(module, logdefault);
+
+    if (loglevel > *status)
+        return;
+
+    int size = snprintf(NULL, 0, "%s:%s:%s:%d:%s() %s \n",
+                log_strings[loglevel], module, file, line, func, msg);
+    char fmt[size+1];
+    snprintf(fmt, sizeof(fmt), "%s:%s:%s:%d:%s() %s \n",
+                log_strings[loglevel], module, file, line, func, msg);
+
+    va_list vaargs;
+    va_start(vaargs, msg);
+    vfprintf (stderr, fmt,
+        //log_strings[loglevel], module, file, func, line,
+        vaargs);
+    va_end(vaargs);
+}
+
+log_level
+log_stringlevel(const char *n)
+{
+    for(log_level i = 0; i < sizeof(log_strings)/sizeof(log_strings[0]); i++) {
+        if (strncasecmp(log_strings[i], n, strlen(log_strings[i])) == 0) {
+            return i;
         }
     }
-    return "unknown";
+    return LOGLEVEL_UNDEFINED;
 }
+
+log_level
+getLogLevel(const char *module, log_level logdefault)
+{
+    log_level loglevel = logdefault;
+    char *envlevel = getenv("TSS2_LOG");
+    char *i = envlevel;
+    if (envlevel == NULL)
+        return loglevel;
+    while ((i = index(i, '+')) != NULL) {
+        if ((envlevel <= i - strlen("all") && strncasecmp(i - 3, "all", 3) == 0) ||
+            (envlevel <= i - strlen(module) &&
+             strncasecmp(i - strlen(module), module, strlen(module)) == 0)) {
+            log_level tmp = log_stringlevel(i+1);
+            if (tmp != LOGLEVEL_UNDEFINED)
+                loglevel = tmp;
+        }
+        i = i + 1;
+    }
+    return loglevel;
+}
+
diff --git a/log/log.h b/log/log.h
index c3742a0..d1f0465 100644
--- a/log/log.h
+++ b/log/log.h
@@ -1,14 +1,148 @@
 #ifndef LOG_H
 #define LOG_H
 
+#include <stdint.h>
+#include <stddef.h>
+
+#ifndef LOGMODULE
+#error "LOGMODULE must be set before including log/log.h"
+#endif
+
+#ifndef LOGDEFAULT
+#define LOGDEFAULT LOGLEVEL_WARNING
+#endif
+
 typedef enum {
-    DEBUG,
-    INFO,
-    WARNING,
-    ERROR,
-    OFF
+    LOGLEVEL_NONE     = 0,
+    LOGLEVEL_ERROR    = 1,
+    LOGLEVEL_WARNING  = 2,
+    LOGLEVEL_INFO     = 3,
+    LOGLEVEL_DEBUG    = 4,
+    LOGLEVEL_TRACE    = 5,
+    LOGLEVEL_UNDEFINED    = 0xff
 } log_level;
 
-const char* level_to_str (log_level level);
+static const char *log_strings[] __attribute__((unused)) = {
+    "none",
+    "ERROR",
+    "WARNING",
+    "info",
+    "debug",
+    "trace"
+};
+
+#define xstr(s) str(s)
+#define str(s) #s
+
+static log_level LOGMODULE_status __attribute__((unused)) = LOGLEVEL_UNDEFINED;
+
+#if LOGLEVEL == LOGLEVEL_ERROR || \
+    LOGLEVEL == LOGLEVEL_WARNING || \
+    LOGLEVEL == LOGLEVEL_INFO || \
+    LOGLEVEL == LOGLEVEL_DEBUG || \
+    LOGLEVEL == LOGLEVEL_TRACE
+#define LOG_ERROR(FORMAT, ...) doLog(LOGLEVEL_ERROR, \
+                                     xstr(LOGMODULE), LOGDEFAULT, \
+                                     &LOGMODULE_status, \
+                                     __FILE__, __func__, __LINE__, \
+                                     FORMAT, ## __VA_ARGS__)
+#define LOGBLOB_ERROR(BUFFER, SIZE, FORMAT, ...) doLogBlob(LOGLEVEL_ERROR, \
+                                             xstr(LOGMODULE), LOGDEFAULT, \
+                                             &LOGMODULE_status, \
+                                             __FILE__, __func__, __LINE__, \
+                                             BUFFER, SIZE, \
+                                             FORMAT, ## __VA_ARGS__)
+#else /* LOGLEVEL >= LOGLEVEL_ERROR */
+#define LOG_ERROR(FORMAT, ...) {}
+#define LOGBLOB_ERROR(FORMAT, ...) {}
+#endif /* LOGLEVEL >= LOGLEVEL_ERROR */
+
+#if LOGLEVEL == LOGLEVEL_WARNING || \
+    LOGLEVEL == LOGLEVEL_INFO || \
+    LOGLEVEL == LOGLEVEL_DEBUG || \
+    LOGLEVEL == LOGLEVEL_TRACE
+#define LOG_WARNING(FORMAT, ...) doLog(LOGLEVEL_WARNING, \
+                                     xstr(LOGMODULE), LOGDEFAULT, \
+                                     &LOGMODULE_status, \
+                                     __FILE__, __func__, __LINE__, \
+                                     FORMAT, ## __VA_ARGS__)
+#define LOGBLOB_WARNING(BUFFER, SIZE, FORMAT, ...) doLogBlob(LOGLEVEL_WARNING, \
+                                                 xstr(LOGMODULE), LOGDEFAULT, \
+                                                 &LOGMODULE_status, \
+                                                 __FILE__, __func__, __LINE__, \
+                                                 BUFFER, SIZE, \
+                                                 FORMAT, ## __VA_ARGS__)
+#else /* LOGLEVEL >= LOGLEVEL_WARNING */
+#define LOG_WARNING(FORMAT, ...) {}
+#define LOGBLOB_WARNING(FORMAT, ...) {}
+#endif /* LOGLEVEL >= LOGLEVEL_WARNING */
+
+#if LOGLEVEL == LOGLEVEL_INFO || \
+    LOGLEVEL == LOGLEVEL_DEBUG || \
+    LOGLEVEL == LOGLEVEL_TRACE
+#define LOG_INFO(FORMAT, ...) doLog(LOGLEVEL_INFO, \
+                                     xstr(LOGMODULE), LOGDEFAULT, \
+                                     &LOGMODULE_status, \
+                                     __FILE__, __func__, __LINE__, \
+                                     FORMAT, ## __VA_ARGS__)
+#define LOGBLOB_INFO(BUFFER, SIZE, FORMAT, ...) doLogBlob(LOGLEVEL_INFO, \
+                                     xstr(LOGMODULE), LOGDEFAULT, \
+                                     &LOGMODULE_status, \
+                                     __FILE__, __func__, __LINE__, \
+                                     BUFFER, SIZE, \
+                                     FORMAT, ## __VA_ARGS__)
+#else /* LOGLEVEL >= LOGLEVEL_INFO */
+#define LOG_INFO(FORMAT, ...) {}
+#define LOGBLOB_INFO(FORMAT, ...) {}
+#endif /* LOGLEVEL >= LOGLEVEL_INFO */
+
+#if LOGLEVEL == LOGLEVEL_DEBUG || \
+    LOGLEVEL == LOGLEVEL_TRACE
+#define LOG_DEBUG(FORMAT, ...) doLog(LOGLEVEL_DEBUG, \
+                                     xstr(LOGMODULE), LOGDEFAULT, \
+                                     &LOGMODULE_status, \
+                                     __FILE__, __func__, __LINE__, \
+                                     FORMAT, ## __VA_ARGS__)
+#define LOGBLOB_DEBUG(BUFFER, SIZE, FORMAT, ...) doLogBlob(LOGLEVEL_DEBUG, \
+                                             xstr(LOGMODULE), LOGDEFAULT, \
+                                             &LOGMODULE_status, \
+                                             __FILE__, __func__, __LINE__, \
+                                             BUFFER, SIZE, \
+                                             FORMAT, ## __VA_ARGS__)
+#else /* LOGLEVEL >= LOGLEVEL_DEBUG */
+#define LOG_DEBUG(FORMAT, ...) {}
+#define LOGBLOB_DEBUG(FORMAT, ...) {}
+#endif /* LOGLEVEL >= LOGLEVEL_DEBUG */
+
+#if LOGLEVEL == LOGLEVEL_TRACE
+#define LOG_TRACE(FORMAT, ...) doLog(LOGLEVEL_TRACE, \
+                                     xstr(LOGMODULE), LOGDEFAULT, \
+                                     &LOGMODULE_status, \
+                                     __FILE__, __func__, __LINE__, \
+                                     FORMAT, ## __VA_ARGS__)
+#define LOGBLOB_TRACE(BUFFER, SIZE, FORMAT, ...) doLogBlob(LOGLEVEL_TRACE, \
+                                             xstr(LOGMODULE), LOGDEFAULT, \
+                                             &LOGMODULE_status, \
+                                             __FILE__, __func__, __LINE__, \
+                                             BUFFER, SIZE, \
+                                             FORMAT, ## __VA_ARGS__)
+#else /* LOGLEVEL >= LOGLEVEL_TRACE */
+#define LOG_TRACE(FORMAT, ...) {}
+#define LOGBLOB_TRACE(FORMAT, ...) {}
+#endif /* LOGLEVEL >= LOGLEVEL_TRACE */
+
+void
+doLog(log_level loglevel, const char *module, log_level logdefault,
+       log_level *status,
+       const char *file, const char *func, int line,
+       const char *msg, ...)
+    __attribute__((unused, format (printf, 8, 9)));
+
+void
+doLogBlob(log_level loglevel, const char *module, log_level logdefault,
+          log_level *status,
+          const char *file, const char *func, int line,
+          const uint8_t *buffer, size_t size, const char *msg, ...)
+    __attribute__((unused, format (printf, 10, 11)));
 
 #endif /* LOG_H */
diff --git a/marshal/log.h b/marshal/log.h
index 40d3b5d..747678c 100644
--- a/marshal/log.h
+++ b/marshal/log.h
@@ -1,5 +1,6 @@
 #ifndef TSS2T_LOG_H
 
+#define LOGMODULE marshal
 #include "log/log.h"
 
 #include <stdio.h>
@@ -25,7 +26,7 @@
     if (level >= MARSHAL_LOG_LEVEL) do { \
         fprintf (stderr, \
                  "%s:marshal:%s:%d " fmt "\n", \
-                 level_to_str (level), \
+                 log_strings[level], \
                  __FILE__, \
                  __LINE__, \
                  ##__VA_ARGS__); \