Browse Source

Now using ZF_LOG

  Errors: 2
  Line 607 and 608
  Invalid conversion from 'const unsigned char*' to 'const char*'
  Warnings: 12
  Format %s expects argument of type 'char*', but argument 7-8 has type
'std::string' (warned in zf_log.h multiple times)

  But at least we have things looking better at least.
david 4 years ago
parent
commit
37bfd06819
74 changed files with 8963 additions and 107 deletions
  1. 5 4
      CMakeLists.txt
  2. 37 0
      dbuild.sh
  3. 218 102
      main.cpp
  4. 1 1
      run.sh
  5. 49 0
      zf_log/.appveyor.yml
  6. 3 0
      zf_log/.gitignore
  7. 45 0
      zf_log/.travis.yml
  8. 16 0
      zf_log/CMakeFiles/CMakeDirectoryInformation.cmake
  9. 19 0
      zf_log/CMakeFiles/Export/lib/cmake/zf_log/zf_log-debug.cmake
  10. 94 0
      zf_log/CMakeFiles/Export/lib/cmake/zf_log/zf_log.cmake
  11. 1 0
      zf_log/CMakeFiles/progress.marks
  12. 49 0
      zf_log/CMakeLists.txt
  13. 7 0
      zf_log/CTestTestfile.cmake
  14. 21 0
      zf_log/LICENSE
  15. 196 0
      zf_log/Makefile
  16. 324 0
      zf_log/README.md
  17. 38 0
      zf_log/TODO.md
  18. 68 0
      zf_log/cmake_install.cmake
  19. 26 0
      zf_log/examples/CMakeLists.txt
  20. 41 0
      zf_log/examples/args_eval.c
  21. 75 0
      zf_log/examples/custom_output.c
  22. 43 0
      zf_log/examples/file_output.c
  23. 22 0
      zf_log/examples/hello.c
  24. 180 0
      zf_log/tests/CMakeLists.txt
  25. 36 0
      zf_log/tests/filesize_check.c
  26. 345 0
      zf_log/tests/perf/CMakeLists.txt
  27. 395 0
      zf_log/tests/perf/run_tests.py
  28. 20 0
      zf_log/tests/perf/test_executable_size.cpp
  29. 162 0
      zf_log/tests/perf/test_speed.cpp
  30. 256 0
      zf_log/tests/perf/test_switch.h
  31. 26 0
      zf_log/tests/perf/time_it.py
  32. 21 0
      zf_log/tests/test_aux_spec.c
  33. 17 0
      zf_log/tests/test_builtin_output_facilities.c
  34. 34 0
      zf_log/tests/test_call_site_size_censoring.c
  35. 40 0
      zf_log/tests/test_call_site_size_conditional.c
  36. 533 0
      zf_log/tests/test_call_site_size_fmt_args.c
  37. 526 0
      zf_log/tests/test_call_site_size_msg_only.c
  38. 74 0
      zf_log/tests/test_censoring.c
  39. 16 0
      zf_log/tests/test_compilation_cpp.cpp
  40. 60 0
      zf_log/tests/test_conditional.c
  41. 41 0
      zf_log/tests/test_decoration.main.c
  42. 29 0
      zf_log/tests/test_decoration.module.c
  43. 48 0
      zf_log/tests/test_externally_defined_state.c
  44. 25 0
      zf_log/tests/test_externally_defined_state_cpp.cpp
  45. 45 0
      zf_log/tests/test_log_level_override.c
  46. 218 0
      zf_log/tests/test_log_level_switches.c
  47. 291 0
      zf_log/tests/test_log_message_content.c
  48. 51 0
      zf_log/tests/test_private_parts.c
  49. 60 0
      zf_log/tests/test_source_location.c
  50. 937 0
      zf_log/tests/zf_log.h.master
  51. 85 0
      zf_log/tests/zf_test.h
  52. 1 0
      zf_log/zf_log-config.cmake
  53. 1 0
      zf_log/zf_log-config.cmake.in
  54. 63 0
      zf_log/zf_log.cmake
  55. 16 0
      zf_log/zf_log/CMakeFiles/CMakeDirectoryInformation.cmake
  56. 1 0
      zf_log/zf_log/CMakeFiles/progress.marks
  57. 58 0
      zf_log/zf_log/CMakeFiles/zf_log.dir/C.includecache
  58. 21 0
      zf_log/zf_log/CMakeFiles/zf_log.dir/DependInfo.cmake
  59. 114 0
      zf_log/zf_log/CMakeFiles/zf_log.dir/build.make
  60. 10 0
      zf_log/zf_log/CMakeFiles/zf_log.dir/cmake_clean.cmake
  61. 3 0
      zf_log/zf_log/CMakeFiles/zf_log.dir/cmake_clean_target.cmake
  62. 6 0
      zf_log/zf_log/CMakeFiles/zf_log.dir/depend.internal
  63. 6 0
      zf_log/zf_log/CMakeFiles/zf_log.dir/depend.make
  64. 10 0
      zf_log/zf_log/CMakeFiles/zf_log.dir/flags.make
  65. 2 0
      zf_log/zf_log/CMakeFiles/zf_log.dir/link.txt
  66. 3 0
      zf_log/zf_log/CMakeFiles/zf_log.dir/progress.make
  67. BIN
      zf_log/zf_log/CMakeFiles/zf_log.dir/zf_log.c.o
  68. 54 0
      zf_log/zf_log/CMakeLists.txt
  69. 6 0
      zf_log/zf_log/CTestTestfile.cmake
  70. 242 0
      zf_log/zf_log/Makefile
  71. 47 0
      zf_log/zf_log/cmake_install.cmake
  72. BIN
      zf_log/zf_log/libzf_log.a
  73. 1389 0
      zf_log/zf_log/zf_log.c
  74. 941 0
      zf_log/zf_log/zf_log.h

+ 5 - 4
CMakeLists.txt

@@ -1,13 +1,14 @@
 
 cmake_minimum_required(VERSION 3.0)
-project(testdoors)
+project(spaceconstruct)
 
 set (CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
 find_package(SQLite3 REQUIRED)
 
 add_subdirectory(opendoors)
+add_subdirectory(zf_log)
 include_directories(${SQLITE3_INCLUDE_DIRS})
 
-add_executable(main main.cpp)
-target_link_libraries(main opendoors ${SQLITE3_LIBRARIES})
-
+add_executable(sc main.cpp)
+target_link_libraries(sc opendoors zf_log ${SQLITE3_LIBRARIES})
+target_compile_definitions(sc PUBLIC ZF_LOG_DEF_LEVEL=ZF_LOG_VERBOSE)

+ 37 - 0
dbuild.sh

@@ -0,0 +1,37 @@
+#!/bin/bash
+
+# if image doesn't exist, build it.
+
+BUILD=bbs-build:0.1
+
+if [ -z $(docker images -q $BUILD) ]; then
+echo "Creating bbs-build image..."
+
+DOCKER_BUILDKIT=1 docker build -t $BUILD - << DOCKERFILE
+FROM debian:stretch-slim 
+# Install utilities essential to building BBS DOORS
+RUN apt-get update && apt-get -y install wget build-essential unzip git cmake ninja-build libsqlite3-dev
+DOCKERFILE
+
+fi
+
+echo "Cleaning dbuild directory"
+rm -rf dbuild
+mkdir dbuild
+
+echo "Building using docker bbs-build image..."
+USERID=`id -u`
+GROUPID=`id -g`
+# docker run -it --rm -u $USERID:$GROUPID -v $(pwd):/build -w /build/dbuild $BUILD bash -c "cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug ..; ninja"
+docker run -it --rm -u $USERID:$GROUPID -v $(pwd):/build -w /build/dbuild $BUILD bash -c "cmake -G Ninja -DCMAKE_BUILD_TYPE=Release ..; ninja"
+
+# docker run -it --rm -u 1000:1000 -v $(pwd):/build -w /build/dbuild bbs-build bash -c "cmake -DCMAKE_BUILD_TYPE=Release ..; make"
+# -DCMAKE_BUILD_TYPE=Release
+
+echo "Build results:"
+ls -la dbuild
+
+# echo "to build:"
+# echo "rm -rf dbuild; mkdir dbuild; cd dbuild; cmake ..; make"
+# docker run -it --rm -u 1000:1000 -v $(pwd):/build -w /build bbs-build
+

+ 218 - 102
main.cpp

@@ -39,7 +39,7 @@
 #include <unistd.h> // stat
 #include <string>
 
-std::string log_path = "logs";
+// sc.log ODoors has logging so why not use it!
 int inuse = 0; // Are any other copies of us running? (We are a single user door!)
 int debug = 0; // Are we in debug mode?
 
@@ -47,6 +47,36 @@ int debug = 0; // Are we in debug mode?
 int allowDev = 1; // Allow "Beanzilla" to not need the password.
 std::string sysop_pass = "spaceISbig"; // Case sensitive!
 
+// LOGGING with file output
+
+#include "zf_log.h"
+
+FILE *g_log_file;
+
+static void file_output_callback(const zf_log_message *msg, void *arg) {
+  (void)arg;
+  *msg->p = '\n';
+  fwrite(msg->buf, msg->p - msg->buf + 1, 1, g_log_file);
+  fflush(g_log_file);
+}
+
+static void file_output_close(void) { fclose(g_log_file); }
+
+static int file_output_open(const char *const log_path) {
+  g_log_file = fopen(log_path, "a");
+  if (!g_log_file) {
+    ZF_LOGW("Failed to open log file %s", log_path);
+    return 0;
+  }
+  atexit(file_output_close);
+  zf_log_set_output_v(ZF_LOG_PUT_STD, 0, file_output_callback);
+  return 1;
+}
+
+void log_flush(void) { fflush(g_log_file); }
+
+// END LOGGING
+
 // Randrange
 int randrange(int min, int max){
    return min + rand() / (RAND_MAX / (max - min + 1) + 1);
@@ -319,38 +349,6 @@ typedef struct dateTamp {
   int age;   // Taking down to aproximate day count.
 } dT; // YYYYMMDD it in int form
 
-void dolog(char *fmt, ...) {
-  // Low end Logging
-  char buffer[PATH_MAX];
-  struct tm *time_now;
-  time_t timen;
-  FILE *logfptr;
-
-  timen = time(NULL);
-
-  time_now = localtime(&timen);
-  if (log_path != "") {
-    snprintf(buffer, PATH_MAX, "%s%s%04d%02d%02d.log", log_path, PATH_SEP,
-             time_now->tm_year + 1900, time_now->tm_mon + 1, time_now->tm_mday);
-  } else {
-    snprintf(buffer, PATH_MAX, "%04d%02d%02d.log", time_now->tm_year + 1900,
-             time_now->tm_mon + 1, time_now->tm_mday);
-  }
-  logfptr = fopen(buffer, "a");
-  if (!logfptr) {
-    return;
-  }
-  va_list ap;
-  va_start(ap, fmt);
-  vsnprintf(buffer, 512, fmt, ap);
-  va_end(ap);
-
-  fprintf(logfptr, "%02d:%02d:%02d %s\n", time_now->tm_hour, time_now->tm_min,
-          time_now->tm_sec, buffer);
-
-  fclose(logfptr);
-}
-
 int dateStamp() {
   // YYYYMMDD \o/ In a INT so we can store it and compare against it!
   struct tm *time_now;
@@ -400,7 +398,7 @@ int db_test() {
   int sizeofdb = 0;
   int rc = sqlite3_open("spaceconstruct.db3", &db);
   if(rc) { // Did we do a successful open?
-    dolog("E: failed opening database %s", sqlite3_errmsg(db));
+    ZF_LOGF("Failed opening database %s (%d)", sqlite3_errmsg(db), rc);
     sqlite3_close(db);
     od_exit(-1, FALSE);
   }
@@ -409,7 +407,7 @@ int db_test() {
   strcpy(sqlbuffer, "SELECT * FROM user;");
   sqlite3_prepare_v2(db, sqlbuffer, strlen(sqlbuffer), &stmt, NULL);
   while(sqlite3_step(stmt) == SQLITE_ROW) {
-    od_printf("`white`%s=%d %s=%s %s=%s %s=%d %s=%d %s=%d %s=%d %s=%d %s=%d %s=%d %s=%d %s=%d %s=%d %s=%d\r\n",
+    ZF_LOGV("`white`%s=%d %s=%s %s=%s %s=%d %s=%d %s=%d %s=%d %s=%d %s=%d %s=%d %s=%d %s=%d %s=%d %s=%d\r\n",
       sqlite3_column_name(stmt, 0), // int,  uid
       sqlite3_column_int(stmt, 0),
       sqlite3_column_name(stmt, 1), // text, nick
@@ -441,6 +439,7 @@ int db_test() {
     );
     sizeofdb += 1;
   } // Clean up database
+  ZF_LOGV("There are %d users", sizeofdb);
   sqlite3_finalize(stmt);
   sqlite3_close(db);
   return sizeofdb;
@@ -463,15 +462,16 @@ int grab_lock() {
   if (valid == 0) {
     fhandle = fopen("lock.flg", "w");
     if(!fhandle) {
-      dolog("E: Unable to make lock.flg!");
-      dolog("E: Something went wrong perhaps we don't have permissions?");
+      ZF_LOGE("Unable to make lock.flg!");
+      ZF_LOGE("Something went wrong perhaps we don't have permissions?");
       return -1;
     }
     fprintf(fhandle, "I am in use already!\n");
     fclose(fhandle);
+    ZF_LOGV("Lock Established");
     return 1;
   } else {
-    dolog("W: Lock already established!");
+    ZF_LOGW("Lock already established!");
     return 0;
   }
 }
@@ -481,17 +481,17 @@ void rel_lock() {
   int valid = check_lock();
   if (valid == 1) {
     if (unlink("lock.flg") != 0) {
-      dolog("C: Unable to release lock.flg!");
-      dolog("C: Something went wrong! Players might not be able to play now!");
+      ZF_LOGE("Unable to release lock.flg!");
+      ZF_LOGE("Something went wrong! Players might not be able to play now!");
     }
   } else {
-    dolog("W: Lock already released!");
+    ZF_LOGW("Lock already released!");
   }
 }
 
 void log_drop() {
   // Spits out info from Drop File:
-  od_printf("`white`Name=%s Alias=%s TimeLeft=%d SecLevel=%d Location=%s Node=%d Sysop=%s\r\n",
+  ZF_LOGV("`white`Name=%s Alias=%s TimeLeft=%d SecLevel=%d Location=%s Node=%d Sysop=%s\r\n",
     od_control.user_name,
     od_control.user_handle,
     od_control.user_timelimit,
@@ -505,7 +505,7 @@ void log_drop() {
 void paws() {
   // Aaah, DRY
   od_printf("`white`Press any key to continue...");
-  od_get_key(TRUE);
+  od_get_key(true);
   od_printf("\r\n");
 }
 
@@ -534,7 +534,7 @@ int locate_player(char name[]) {
   int result = 0;
   int rc = sqlite3_open("spaceconstruct.db3", &db);
   if(rc) { // Did we do a successful open?
-    dolog("E: failed opening database %s", sqlite3_errmsg(db));
+    ZF_LOGF("Failed opening database %s (%d)", sqlite3_errmsg(db), rc);
     sqlite3_close(db);
     od_exit(-1, FALSE);
   }
@@ -551,7 +551,7 @@ int locate_player(char name[]) {
     result = sqlite3_column_int(stmt, 0);
   } else {
     // User not found
-    dolog("W: Unable to locate user=%s", name);
+    ZF_LOGW("Unable to locate user %s", name);
     result = 0;
   } // Clean Up, return results
   sqlite3_finalize(stmt);
@@ -566,7 +566,7 @@ void check_database() {
   char sqlbuffer[256];
   int rc = sqlite3_open("spaceconstruct.db3", &db);
   if(rc) {
-    dolog("C: Failed opening database %s (%d)", sqlite3_errmsg(db), rc);
+    ZF_LOGF("Failed opening database %s (%d)", sqlite3_errmsg(db), rc);
     sqlite3_close(db);
     od_exit(-1, FALSE);
   }
@@ -580,7 +580,7 @@ void check_database() {
     // Bad, Create table
     char *errmsg;
     rc = sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS \"user\" (`uid`	INTEGER PRIMARY KEY AUTOINCREMENT,`nick`	TEXT,`real`	TEXT,`experience`	INTEGER,`metal`	INTEGER,`fuel`	INTEGER,`gun`	INTEGER,`armor`	INTEGER,`shield`	INTEGER,`armorpoints`	INTEGER,`shieldpoints`	INTEGER,`hitpoints`	INTEGER,`shieldsup`	INTEGER,`laston`	INTEGER);", NULL, NULL, &errmsg);
-    dolog("W: Users table did not exist created with %d", rc);
+    ZF_LOGW("Users table did not exist created");
   }
   sqlite3_finalize(stmt);
   sqlite3_close(db);
@@ -594,7 +594,7 @@ user_info* load_player(int uuid) {
   user_info *result;
   int rc = sqlite3_open("spaceconstruct.db3", &db);
   if(rc) { // Did we do a successful open?
-    dolog("E: failed opening database %s", sqlite3_errmsg(db));
+    ZF_LOGF("Failed opening database %s (%d)", sqlite3_errmsg(db), rc);
     sqlite3_close(db);
     od_exit(-1, FALSE);
   }
@@ -604,24 +604,10 @@ user_info* load_player(int uuid) {
   sqlite3_bind_int(stmt, 1, uuid);
   rc = sqlite3_step(stmt);
   if (rc == SQLITE_ROW) {
-    /*
-    result.uid = sqlite3_column_int(stmt, 0);
-    strcpy(result.nick, sqlite3_column_text(stmt, 1));
-    strcpy(result.real, sqlite3_column_text(stmt, 2));
-    result.experience = sqlite3_column_int(stmt, 3);
-    result.metal = sqlite3_column_int(stmt, 4);
-    result.fuel = sqlite3_column_int(stmt, 5);
-    result.gun = sqlite3_column_int(stmt, 6);
-    result.armor = sqlite3_column_int(stmt, 7);
-    result.shield = sqlite3_column_int(stmt, 8);
-    result.armorpoints = sqlite3_column_int(stmt, 9);
-    result.shieldpoints = sqlite3_column_int(stmt, 10);
-    result.hitpoints = sqlite3_column_int(stmt, 11);
-    result.shieldsup = sqlite3_column_int(stmt, 12);
-    result.laston = sqlite3_column_int(stmt, 13); */
-    result->set_up(sqlite3_column_int(stmt, 0), sqlite3_column_text(stmt, 1), sqlite3_column_text(stmt, 2), sqlite3_column_int(stmt, 3), sqlite3_column_int(stmt, 13), sqlite3_column_int(stmt, 7), sqlite3_column_int(stmt, 9), sqlite3_column_int(stmt, 8), sqlite3_column_int(stmt, 10), sqlite3_column_int(stmt, 12), sqlite3_column_int(stmt, 11), sqlite3_column_int(stmt, 6), sqlite3_column_int(stmt, 4), sqlite3_column_int(stmt, 5));
+    std::string val1(sqlite3_column_text(stmt, 1));
+    std::string val2(sqlite3_column_text(stmt, 2));
+    result->set_up(sqlite3_column_int(stmt, 0), val1, val2, sqlite3_column_int(stmt, 3), sqlite3_column_int(stmt, 13), sqlite3_column_int(stmt, 7), sqlite3_column_int(stmt, 9), sqlite3_column_int(stmt, 8), sqlite3_column_int(stmt, 10), sqlite3_column_int(stmt, 12), sqlite3_column_int(stmt, 11), sqlite3_column_int(stmt, 6), sqlite3_column_int(stmt, 4), sqlite3_column_int(stmt, 5));
   } else {
-    //dolog("E: Unable to locate user with id=%d got %s (%d)", uuid, sqlite3_errmsg(db), rc);
     sqlite3_finalize(stmt);
     sqlite3_close(db);
     result->set_uid(0);
@@ -662,7 +648,7 @@ void update_player(user_info *data) {
   char sqlbuffer[1024];
   int rc = sqlite3_open("spaceconstruct.db3", &db);
   if(rc) { // Did we do a successful open?
-    dolog("E: failed opening database %s", sqlite3_errmsg(db));
+    ZF_LOGF("Failed opening database %s (%d)", sqlite3_errmsg(db), rc);
     sqlite3_close(db);
     od_exit(-1, FALSE);
   }
@@ -673,7 +659,7 @@ void update_player(user_info *data) {
   strcpy(sqlbuffer, "UPDATE user SET nick=?, experience=?, metal=?, fuel=?, gun=?, armor=?, shield=?, armorpoints=?, shieldpoints=?, hitpoints=?, shieldsup=?, laston=? WHERE uid=?;");
   sqlite3_prepare_v2(db, sqlbuffer, strlen(sqlbuffer) + 1, &stmt, NULL);
   // Bind All data values
-  sqlite3_bind_text(stmt, 1, data->get_nick(), data->get_nick().length(), SQLITE_STATIC);
+  sqlite3_bind_text(stmt, 1, data->get_nick().c_str(), data->get_nick().length(), SQLITE_STATIC);
   sqlite3_bind_int(stmt, 2, data->get_experience());
   sqlite3_bind_int(stmt, 3, data->get_metal());
   sqlite3_bind_int(stmt, 4, data->get_fuel());
@@ -689,7 +675,7 @@ void update_player(user_info *data) {
   // Execute
   rc = sqlite3_step(stmt);
   if(rc != SQLITE_DONE) {
-    dolog("E: failed updating player=%d got error %s (%d)", data->get_uid(), sqlite3_errmsg(db), rc);
+    ZF_LOGF("Failed updating player %s (%d)", sqlite3_errmsg(db), rc);
     sqlite3_finalize(stmt);
     sqlite3_close(db);
     od_exit(-1, FALSE);
@@ -704,15 +690,15 @@ int create_player(user_info *data) {
   char sqlbuffer[1024];
   int rc = sqlite3_open("spaceconstruct.db3", &db);
   if(rc) { // Did we do a successful open?
-    dolog("E: failed opening database %s", sqlite3_errmsg(db));
+    ZF_LOGF("Failed opening database %s (%d)", sqlite3_errmsg(db), rc);
     sqlite3_close(db);
     od_exit(-1, FALSE);
   }
   sqlite3_busy_timeout(db, 5000);
   strcpy(sqlbuffer, "INSERT INTO user (nick, real, experience, metal, fuel, gun, armor, shield, armorpoints, shieldpoints, hitpoints, shieldsup, laston) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);");
   sqlite3_prepare_v2(db, sqlbuffer, strlen(sqlbuffer) + 1, &stmt, NULL);
-  sqlite3_bind_text(stmt, 1, data->get_nick(), data->get_nick().length(), SQLITE_STATIC);
-  sqlite3_bind_text(stmt, 2, data->get_real(), data->get_real().length(), SQLITE_STATIC);
+  sqlite3_bind_text(stmt, 1, data->get_nick().c_str(), data->get_nick().length(), SQLITE_STATIC);
+  sqlite3_bind_text(stmt, 2, data->get_real().c_str(), data->get_real().length(), SQLITE_STATIC);
   sqlite3_bind_int(stmt, 3, data->get_experience());
   sqlite3_bind_int(stmt, 4, data->get_metal());
   sqlite3_bind_int(stmt, 5, data->get_fuel());
@@ -726,7 +712,7 @@ int create_player(user_info *data) {
   sqlite3_bind_int(stmt, 13, data->get_laston());
   rc = sqlite3_step(stmt);
   if(rc != SQLITE_DONE) {
-    dolog("E: failed inserting player=%d got error %s (%d)", data->get_nick(), sqlite3_errmsg(db), rc);
+    ZF_LOGF("Failed inserting player %s (%d)", sqlite3_errmsg(db), rc);
     sqlite3_finalize(stmt);
     sqlite3_close(db);
     od_exit(-1, FALSE);
@@ -736,14 +722,13 @@ int create_player(user_info *data) {
   return 1; // Good
 }
 
-// Removed due to fact it throws off the internal id system, won't be able to list players
-/*void delete_player(int uuid) {
+void delete_player(int uuid) {
   sqlite3 *db;
   sqlite3_stmt *stmt;
   char sqlbuffer[256];
   int rc = sqlite3_open("spaceconstruct.db3", &db);
   if(rc) { // Did we do a successful open?
-    dolog("E: failed opening database %s", sqlite3_errmsg(db));
+    ZF_LOGF("Failed opening database %s (%d)", sqlite3_errmsg(db), rc);
     sqlite3_close(db);
     od_exit(-1, FALSE);
   }
@@ -753,14 +738,14 @@ int create_player(user_info *data) {
   sqlite3_bind_int(stmt, 1, uuid);
   rc = sqlite3_step(stmt);
   if (rc != SQLITE_DONE){
-    dolog("E: Unable to locate user with id=%d got %s (%d)", uuid, sqlite3_errmsg(db), rc);
+    ZF_LOGF("Unable to locate user with id=%d got %s (%d)", uuid, sqlite3_errmsg(db), rc);
     sqlite3_finalize(stmt);
     sqlite3_close(db);
     od_exit(-1, FALSE);
   }
   sqlite3_finalize(stmt);
   sqlite3_close(db);
-}*/
+}
 
 void delete_players() {
   sqlite3 *db;
@@ -768,7 +753,7 @@ void delete_players() {
   char sqlbuffer[256];
   int rc = sqlite3_open("spaceconstruct.db3", &db);
   if(rc) { // Did we do a successful open?
-    dolog("E: failed opening database %s", sqlite3_errmsg(db));
+    ZF_LOGF("Failed opening database %s (%d)", sqlite3_errmsg(db), rc);
     sqlite3_close(db);
     od_exit(-1, FALSE);
   }
@@ -777,7 +762,7 @@ void delete_players() {
   sqlite3_prepare_v2(db, sqlbuffer, strlen(sqlbuffer) + 1, &stmt, NULL);
   rc = sqlite3_step(stmt);
   if (rc != SQLITE_DONE){
-    dolog("E: Delete_players 1/2, Unexpected error got %s (%d)", sqlite3_errmsg(db), rc);
+    ZF_LOGF("Delete_players 1/2, Unexpected error %s (%d)", sqlite3_errmsg(db), rc);
     sqlite3_finalize(stmt);
     sqlite3_close(db);
     od_exit(-1, FALSE);
@@ -786,7 +771,7 @@ void delete_players() {
   sqlite3_prepare_v2(db, sqlbuffer, strlen(sqlbuffer) + 1, &stmt, NULL);
   rc = sqlite3_step(stmt);
   if (rc != SQLITE_DONE){
-    dolog("E: Delete_players 2/2, Unexpected error got %s (%d)", sqlite3_errmsg(db), rc);
+    ZF_LOGF("Delete_players 2/2, Unexpected error %s (%d)", sqlite3_errmsg(db), rc);
     sqlite3_finalize(stmt);
     sqlite3_close(db);
     od_exit(-1, FALSE);
@@ -801,7 +786,7 @@ int display_all_players() {
   char sqlbuffer[1024];
   int rc = sqlite3_open("spaceconstruct.db3", &db);
   if(rc) { // Did we do a successful open?
-    dolog("E: failed opening database %s", sqlite3_errmsg(db));
+    ZF_LOGF("Failed opening database %s (%d)", sqlite3_errmsg(db), rc);
     sqlite3_close(db);
     od_exit(-1, FALSE);
   }
@@ -832,7 +817,7 @@ int display_all_opponents(int us) {
   char sqlbuffer[1024];
   int rc = sqlite3_open("spaceconstruct.db3", &db);
   if(rc) { // Did we do a successful open?
-    dolog("E: failed opening database %s", sqlite3_errmsg(db));
+    ZF_LOGF("Failed opening database %s (%d)", sqlite3_errmsg(db), rc);
     sqlite3_close(db);
     od_exit(-1, FALSE);
   }
@@ -1042,6 +1027,7 @@ void pvp_menu(user_info *my, user_info *targ) {
   if(playerWon) {
     switch(playerWon) {
       case 1: // Online won over offline
+        ZF_LOGV("%s beat %s in pvp", my->get_nick(), targ->get_nick());
         if(targ->get_experience()) {
           my->addEXP((targ->get_experience() / 2));
           targ->rmEXP((targ->get_experience() / 2));
@@ -1057,6 +1043,7 @@ void pvp_menu(user_info *my, user_info *targ) {
         od_printf("`bright green`Congrats on defeating %s!\r\n`white`", targ->get_nick());
         break;
       case 2: // Offline won over online!
+        ZF_LOGV("%s beat %s in pvp", targ->get_nick(), my->get_nick());
         if(my->get_experience()) {
           targ->addEXP((my->get_experience() / 2));
           my->rmEXP((my->get_experience() / 2));
@@ -1079,6 +1066,7 @@ void pvp_menu(user_info *my, user_info *targ) {
         my->set_gun(1);
         break;
       case 3:
+        ZF_LOGV("%s ran away from %s in pvp", my->get_nick(), targ->get_nick());
         my->rmFuel(4);
         if(my->get_fuel() < 0) {
           my->set_fuel(0);
@@ -1208,6 +1196,22 @@ void combat_menu(user_info *my, int targ_hp, int targ_dmg, int asteroid) {
     // Winning move?
     if(playerWon) {
       if(playerWon == 1) {
+        std::string who;
+        switch(asteroid) {
+          case 0:
+            //Pirate
+            who = "Pirate";
+            break;
+          case 1:
+            // Asteroid
+            who = "Asteroid";
+            break;
+          case 2:
+            // Unknown One
+            who = "Unkown One";
+            break;
+        }
+        ZF_LOGV("%s beat %s", my->get_nick(), who);
         // Player won
         my->addEXP((temp_hp + targ_dmg));
         my->addMetal(temp_hp);
@@ -1225,6 +1229,22 @@ void combat_menu(user_info *my, int targ_hp, int targ_dmg, int asteroid) {
         }
         od_printf("`bright green`Congrats on your victory!\r\n`white`");
       } else if(playerWon == 2) {
+        std::string who;
+        switch(asteroid) {
+          case 0:
+            //Pirate
+            who = "Pirate";
+            break;
+          case 1:
+            // Asteroid
+            who = "Asteroid";
+            break;
+          case 2:
+            // Unknown One
+            who = "Unkown One";
+            break;
+        }
+        ZF_LOGV("%s beat %s", who, my->get_nick());
         // Target won
         my->rmEXP((temp_hp + targ_dmg));
         my->rmMetal(temp_hp);
@@ -1247,6 +1267,22 @@ void combat_menu(user_info *my, int targ_hp, int targ_dmg, int asteroid) {
         my->set_shield(0);
         my->set_gun(1);
       } else if(playerWon == 3) {
+        std::string who;
+        switch(asteroid) {
+          case 0:
+            //Pirate
+            who = "Pirate";
+            break;
+          case 1:
+            // Asteroid
+            who = "Asteroid";
+            break;
+          case 2:
+            // Unknown One
+            who = "Unkown One";
+            break;
+        }
+        ZF_LOGV("%s ran away from %s", my->get_nick(), who);
         // Chicken!
         my->rmFuel(1);
       }
@@ -1319,6 +1355,7 @@ void play_game() {
     // Process player inactivity
     if(age.age >= 30) { // 30 days
       od_printf("`bright green`Since you haven't played a while we have reset your account!\r\n");
+      ZF_LOGW("%s was reset for expiring", od_control.user_name);
       me = 0;
       reset = 1;
     }
@@ -1329,10 +1366,8 @@ void play_game() {
   }
   if(me != 0) {
     od_printf("`bright white`Welcome back `bright green`%s\r\n", myself->get_nick());
+    ZF_LOGV("%s is back again", od_control.user_name);
     dT age = fromDate(compareDate(myself->get_laston()));
-    dolog("%s is now playing (G=%d A=%d S=%d F=%d M=%d AP=%d SP=%d (%d) HP=%d LO=%d or %d days)",
-      myself->get_nick(), myself->get_gun(), myself->get_armor(), myself->get_shield(), myself->get_fuel(), myself->get_metal(), myself->get_armorpoints(), 
-      myself->get_shieldpoints(), myself->get_shieldsup(), myself->get_hitpoints(), myself->get_laston(), age.age);
     myself->set_laston(dateStamp());
     if(age.day != 0 || age.month != 0 || age.year != 0) {
       od_printf("`bright yellow`Haven't seen you for");
@@ -1374,6 +1409,7 @@ void play_game() {
     paws();
   } else {
     od_printf("`bright white`You look new here. (Hit Enter to abort)\r\n");
+    ZF_LOGV("%s is a new player", od_control.user_name);
     while(done == 0) {
       od_printf("`bright yellow`What's your name: ");
       od_input_str(ch1, 26, 32, 126);
@@ -1590,7 +1626,7 @@ void sysop_menu() {
           paws();
           break;
         }
-        dolog("%s issued reset", od_control.user_handle);
+        ZF_LOGW("%s issued reset", od_control.user_name);
         delete_players();
         paws();
         break;
@@ -1611,7 +1647,7 @@ void sysop_menu() {
         targ_ply = 1;
         targ_max = 0;
         done1 = 0;
-        dolog("%s issued new day", od_control.user_handle);
+        ZF_LOGW("%s issued new day", od_control.user_name);
         paws();
         break;
     }
@@ -1657,13 +1693,14 @@ void main_menu() {
         if(test->get_experience() >= 8000) { // If the player has the minimum of 3000 exp to fight the Unkown One and wins earning 5000 thats 8000 minimum
           if(test2.age >= 3) { // Auto reset the game or not allow the player to play!
             delete_players();
-            dolog("Auto-Reset Occured!");
+            ZF_LOGW("Auto-Reset Occured");
             od_printf("`bright green`Game has been reset!\r\n");
             paws();
           } else { // And the lucky winner is!
             int resetage = 3 - test2.age;
             od_printf("`bright green`%s has won the game!\r\n", test->get_nick());
             od_printf("`bright green`The Game will reset in %d days!\r\n", resetage);
+            ZF_LOGV("Auto-Reset will occur in %d days", resetage);
             paws();
             break;
           }
@@ -1693,6 +1730,7 @@ void main_menu() {
         auth = 0;
         if(allowDev && strcmp(od_control.user_handle, "Beanzilla") == 0) {
           od_printf("`bright green`Access Granted\r\n");
+          ZF_LOGV("Dev accessed sysop menu");
           auth = 1;
         } else {
           od_printf("`bright red`Sysop Password: ");
@@ -1701,6 +1739,7 @@ void main_menu() {
           od_clr_scr(); // Add a bit of protecton
           if(sys == sysop_pass) {
             od_printf("`bright green`Access Granted\r\n");
+            ZF_LOGV("%s accessed sysop menu", od_control.user_name);
             auth = 1;
           }
         }
@@ -1710,6 +1749,7 @@ void main_menu() {
           sysop_menu();
         } else {
           od_printf("`bright red`Access Denied\r\n");
+          ZF_LOGW("%s tried to access sysop menu", od_control.user_name);
           paws();
         }
         break;
@@ -1722,47 +1762,123 @@ int main(int argc, char *argv[]) {
   // Initiate Door
   od_init();
   od_clr_scr();
-  check_database(); // Ensure the users table exists.
 
   // Debug System
   if (debug) {
-    dolog("=== Debug ===");
-    od_printf("`white`=== Debug ===\r\n");
-    od_printf("DropFile...\r\n");
+    ZF_LOGV("=== Debug ===");
+    ZF_LOGV("DropFile...\r\n");
     log_drop();
     int test = check_lock();
     if (test == 0) {
-      od_printf("Lock is: Avalible\r\n");
-      od_printf("Database...\r\n");
+      ZF_LOGV("Lock is: Avalible\r\n");
+      ZF_LOGV("Database...\r\n");
       db_test();
     } else {
-      od_printf("Lock is: Taken\r\n");
-      od_printf("CANCELED Database Dump\r\n");
+      ZF_LOGV("Lock is: Taken\r\n");
+      ZF_LOGV("CANCELED Database Dump\r\n");
     }
-    paws();
-    od_clr_scr();
   } else {
-    dolog("--- Debug ---");
+    ZF_LOGV("--- Debug ---");
   }
 
-  /* Normal Operations
+  // Normal Operations
   inuse = check_lock();
   if(!inuse) {
     int lock = grab_lock();
     if(!lock) {
       od_printf("`white`Something went wrong getting the lock.\r\n");
+      ZF_LOGF("Lock issue");
       paws();
       od_exit(0, FALSE);
     } else {
       atexit(cleanMe);
+      check_database(); // Ensure the users table exists.
       main_menu();
     }
   } else {
     od_send_file("ansis/sc_inuse.ans");
+    ZF_LOGI("In use");
     paws();
     od_exit(0, FALSE);
-  } */
+  } 
 
   // Odoors testing
-  
+  // Screensaving
+  /*char screen[4096]; // 4MB buffer
+
+  // Draw box
+  if(od_draw_box(5, 1, 20, 15) == true) {
+    od_set_cursor(3, 7);
+    od_printf("`white`Box is `bright green`GOOD");
+  } else {
+    od_set_cursor(3, 7);
+    od_printf("`white`Box is `bright red`BAD");
+  }
+  od_set_cursor(25, 1);
+  paws();
+  od_save_screen(screen);
+  od_clr_scr();
+  od_set_cursor(1, 1);
+
+  // Popup
+  int run = 1;
+  while(run)
+  {
+    switch(od_popup_menu("Main Menu",
+      "^Files|^Electronic Mail|^News|E^xit",
+      20, 5, 0, MENU_NORMAL | MENU_KEEP))
+    {
+      case 1:
+        od_set_cursor(25, 1);
+        od_printf("Files");
+        od_popup_menu("Files Menu",
+          "^Search For Files|^Download|^Upload",
+          30, 7, 1, MENU_NORMAL | MENU_ALLOW_CANCEL | MENU_KEEP);
+        break;
+      case 2:
+        od_set_cursor(25, 1);
+        od_printf("Email");
+        od_popup_menu("EMail Menu",
+          "Get ^New Mail|^Send Mail|Send ^Fax",
+          30, 8, 2, MENU_NORMAL | MENU_ALLOW_CANCEL | MENU_KEEP);
+        break;
+      case 3:
+        od_set_cursor(25, 1);
+        od_printf("News");
+        od_popup_menu("News Menu",
+          "Choose News^Group|^Read News|^Post News",
+          30, 9, 3, MENU_NORMAL | MENU_ALLOW_CANCEL | MENU_KEEP);
+        break;
+      case 4:
+        od_set_cursor(25, 1);
+        od_printf("Exit");
+        od_popup_menu(NULL, NULL, 0, 0, 0, MENU_DESTROY);
+        run = 0;
+        break;
+      case POPUP_ERROR:
+        od_set_cursor(25, 1);
+        od_printf("ERROR");
+        break;
+      case POPUP_ESCAPE:
+        od_set_cursor(25, 1);
+        od_printf("ESCAPE");
+        break;
+      case POPUP_LEFT:
+        od_set_cursor(25, 1);
+        od_printf("LEFT");
+        break;
+      case POPUP_RIGHT:
+        od_set_cursor(25, 1);
+        od_printf("RIGHT");
+        break;
+      default:
+        od_set_cursor(25, 1);
+        od_printf("DEFAULT");
+        break;
+    }
+  }
+
+  paws();
+  od_restore_screen(screen); */
+  od_exit(0, false);
 }

+ 1 - 1
run.sh

@@ -2,5 +2,5 @@
 
 cd ~/SpaceConstruct/
 #./build/main -local
-./build/main -D DOOR.SYS
+./build/sc -D DOOR.SYS
 

+ 49 - 0
zf_log/.appveyor.yml

@@ -0,0 +1,49 @@
+version: 0.3.1-{build}
+
+environment:
+  matrix:
+  - GENERATOR: "Visual Studio 12"
+    CONFIG: Release
+    OPTIONS: -DZF_LOG_EXAMPLES:bool=ON -DZF_LOG_TESTS:bool=ON
+  - GENERATOR: "Visual Studio 12"
+    CONFIG: Debug
+    OPTIONS: -DZF_LOG_EXAMPLES:bool=ON -DZF_LOG_TESTS:bool=ON
+
+  - GENERATOR: "Visual Studio 12 Win64"
+    CONFIG: Release
+    OPTIONS: -DZF_LOG_EXAMPLES:bool=ON -DZF_LOG_TESTS:bool=ON
+  - GENERATOR: "Visual Studio 12 Win64"
+    CONFIG: Debug
+    OPTIONS: -DZF_LOG_EXAMPLES:bool=ON -DZF_LOG_TESTS:bool=ON
+
+  - GENERATOR: "Visual Studio 14"
+    CONFIG: Release
+    OPTIONS: -DZF_LOG_EXAMPLES:bool=ON -DZF_LOG_TESTS:bool=ON
+  - GENERATOR: "Visual Studio 14"
+    CONFIG: Debug
+    OPTIONS: -DZF_LOG_EXAMPLES:bool=ON -DZF_LOG_TESTS:bool=ON
+
+  - GENERATOR: "Visual Studio 14 Win64"
+    CONFIG: Release
+    OPTIONS: -DZF_LOG_EXAMPLES:bool=ON -DZF_LOG_TESTS:bool=ON
+  - GENERATOR: "Visual Studio 14 Win64"
+    CONFIG: Debug
+    OPTIONS: -DZF_LOG_EXAMPLES:bool=ON -DZF_LOG_TESTS:bool=ON
+
+  - GENERATOR: "MinGW Makefiles"
+    CONFIG: Debug
+    OPTIONS: -DZF_LOG_EXAMPLES:bool=ON -DZF_LOG_TESTS:bool=ON
+  - GENERATOR: "MinGW Makefiles"
+    CONFIG: Release
+    OPTIONS: -DZF_LOG_EXAMPLES:bool=ON -DZF_LOG_TESTS:bool=ON
+
+build_script:
+  - if "MinGW Makefiles"=="%GENERATOR%" set PATH=%PATH:C:\Program Files\Git\usr\bin;=%
+  - if "MinGW Makefiles"=="%GENERATOR%" set PATH=C:\MinGW\bin;%PATH%
+  - ps: mkdir build.msvc
+  - ps: cd build.msvc
+  - cmake "-G%GENERATOR%" %OPTIONS% ..
+  - cmake --build . --config "%CONFIG%"
+
+test_script:
+  - ctest --output-on-failure -C "%CONFIG%"

+ 3 - 0
zf_log/.gitignore

@@ -0,0 +1,3 @@
+/build.*
+/CMakeLists.txt.user
+/tags

+ 45 - 0
zf_log/.travis.yml

@@ -0,0 +1,45 @@
+language: c++
+
+compiler:
+- clang
+- gcc
+
+os:
+- osx
+
+env:
+  - OPTIONS="-DZF_LOG_EXAMPLES:bool=ON -DZF_LOG_TESTS:bool=ON -DCMAKE_BUILD_TYPE:string=Debug"
+  - OPTIONS="-DZF_LOG_EXAMPLES:bool=ON -DZF_LOG_TESTS:bool=ON -DCMAKE_BUILD_TYPE:string=Release"
+
+matrix:
+  include:
+  - os: linux
+    dist: trusty
+    sudo: required
+    compiler: clang
+    env:
+    - OPTIONS="-DZF_LOG_EXAMPLES:bool=ON -DZF_LOG_TESTS:bool=ON -DCMAKE_BUILD_TYPE:string=Debug"
+  - os: linux
+    dist: trusty
+    sudo: required
+    compiler: clang
+    env:
+    - OPTIONS="-DZF_LOG_EXAMPLES:bool=ON -DZF_LOG_TESTS:bool=ON -DCMAKE_BUILD_TYPE:string=Release"
+  - os: linux
+    dist: trusty
+    sudo: required
+    compiler: gcc
+    env:
+    - OPTIONS="-DZF_LOG_EXAMPLES:bool=ON -DZF_LOG_TESTS:bool=ON -DCMAKE_BUILD_TYPE:string=Debug"
+  - os: linux
+    dist: trusty
+    sudo: required
+    compiler: gcc
+    env:
+    - OPTIONS="-DZF_LOG_EXAMPLES:bool=ON -DZF_LOG_TESTS:bool=ON -DCMAKE_BUILD_TYPE:string=Release"
+
+install: true
+
+script:
+  - find . -name "CMakeLists.txt" -exec sed -i .bak -e "s/VERSION 3.2/VERSION 2.8/g" {} \;
+  - mkdir build.make && cd build.make && cmake ${OPTIONS} .. && cmake --build . && ctest --output-on-failure

+ 16 - 0
zf_log/CMakeFiles/CMakeDirectoryInformation.cmake

@@ -0,0 +1,16 @@
+# CMAKE generated file: DO NOT EDIT!
+# Generated by "Unix Makefiles" Generator, CMake Version 3.10
+
+# Relative path conversion top directories.
+set(CMAKE_RELATIVE_PATH_TOP_SOURCE "/home/beanzilla/horrible-harry")
+set(CMAKE_RELATIVE_PATH_TOP_BINARY "/home/beanzilla/horrible-harry")
+
+# Force unix paths in dependencies.
+set(CMAKE_FORCE_UNIX_PATHS 1)
+
+
+# The C and CXX include file regular expressions for this directory.
+set(CMAKE_C_INCLUDE_REGEX_SCAN "^.*$")
+set(CMAKE_C_INCLUDE_REGEX_COMPLAIN "^$")
+set(CMAKE_CXX_INCLUDE_REGEX_SCAN ${CMAKE_C_INCLUDE_REGEX_SCAN})
+set(CMAKE_CXX_INCLUDE_REGEX_COMPLAIN ${CMAKE_C_INCLUDE_REGEX_COMPLAIN})

+ 19 - 0
zf_log/CMakeFiles/Export/lib/cmake/zf_log/zf_log-debug.cmake

@@ -0,0 +1,19 @@
+#----------------------------------------------------------------
+# Generated CMake target import file for configuration "Debug".
+#----------------------------------------------------------------
+
+# Commands may need to know the format version.
+set(CMAKE_IMPORT_FILE_VERSION 1)
+
+# Import target "zf_log" for configuration "Debug"
+set_property(TARGET zf_log APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
+set_target_properties(zf_log PROPERTIES
+  IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "C"
+  IMPORTED_LOCATION_DEBUG "${_IMPORT_PREFIX}/lib/libzf_log.a"
+  )
+
+list(APPEND _IMPORT_CHECK_TARGETS zf_log )
+list(APPEND _IMPORT_CHECK_FILES_FOR_zf_log "${_IMPORT_PREFIX}/lib/libzf_log.a" )
+
+# Commands beyond this point should not need to know the version.
+set(CMAKE_IMPORT_FILE_VERSION)

+ 94 - 0
zf_log/CMakeFiles/Export/lib/cmake/zf_log/zf_log.cmake

@@ -0,0 +1,94 @@
+# Generated by CMake
+
+if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" LESS 2.5)
+   message(FATAL_ERROR "CMake >= 2.6.0 required")
+endif()
+cmake_policy(PUSH)
+cmake_policy(VERSION 2.6)
+#----------------------------------------------------------------
+# Generated CMake target import file.
+#----------------------------------------------------------------
+
+# Commands may need to know the format version.
+set(CMAKE_IMPORT_FILE_VERSION 1)
+
+# Protect against multiple inclusion, which would fail when already imported targets are added once more.
+set(_targetsDefined)
+set(_targetsNotDefined)
+set(_expectedTargets)
+foreach(_expectedTarget zf_log)
+  list(APPEND _expectedTargets ${_expectedTarget})
+  if(NOT TARGET ${_expectedTarget})
+    list(APPEND _targetsNotDefined ${_expectedTarget})
+  endif()
+  if(TARGET ${_expectedTarget})
+    list(APPEND _targetsDefined ${_expectedTarget})
+  endif()
+endforeach()
+if("${_targetsDefined}" STREQUAL "${_expectedTargets}")
+  unset(_targetsDefined)
+  unset(_targetsNotDefined)
+  unset(_expectedTargets)
+  set(CMAKE_IMPORT_FILE_VERSION)
+  cmake_policy(POP)
+  return()
+endif()
+if(NOT "${_targetsDefined}" STREQUAL "")
+  message(FATAL_ERROR "Some (but not all) targets in this export set were already defined.\nTargets Defined: ${_targetsDefined}\nTargets not yet defined: ${_targetsNotDefined}\n")
+endif()
+unset(_targetsDefined)
+unset(_targetsNotDefined)
+unset(_expectedTargets)
+
+
+# Compute the installation prefix relative to this file.
+get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_FILE}" PATH)
+get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
+get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
+get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
+if(_IMPORT_PREFIX STREQUAL "/")
+  set(_IMPORT_PREFIX "")
+endif()
+
+# Create imported target zf_log
+add_library(zf_log STATIC IMPORTED)
+
+set_target_properties(zf_log PROPERTIES
+  INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include"
+)
+
+# Load information for each installed configuration.
+get_filename_component(_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
+file(GLOB CONFIG_FILES "${_DIR}/zf_log-*.cmake")
+foreach(f ${CONFIG_FILES})
+  include(${f})
+endforeach()
+
+# Cleanup temporary variables.
+set(_IMPORT_PREFIX)
+
+# Loop over all imported files and verify that they actually exist
+foreach(target ${_IMPORT_CHECK_TARGETS} )
+  foreach(file ${_IMPORT_CHECK_FILES_FOR_${target}} )
+    if(NOT EXISTS "${file}" )
+      message(FATAL_ERROR "The imported target \"${target}\" references the file
+   \"${file}\"
+but this file does not exist.  Possible reasons include:
+* The file was deleted, renamed, or moved to another location.
+* An install or uninstall procedure did not complete successfully.
+* The installation package was faulty and contained
+   \"${CMAKE_CURRENT_LIST_FILE}\"
+but not all the files it references.
+")
+    endif()
+  endforeach()
+  unset(_IMPORT_CHECK_FILES_FOR_${target})
+endforeach()
+unset(_IMPORT_CHECK_TARGETS)
+
+# This file does not depend on other imported targets which have
+# been exported from the same project but in a separate export set.
+
+# Commands beyond this point should not need to know the version.
+set(CMAKE_IMPORT_FILE_VERSION)
+cmake_policy(POP)

+ 1 - 0
zf_log/CMakeFiles/progress.marks

@@ -0,0 +1 @@
+2

+ 49 - 0
zf_log/CMakeLists.txt

@@ -0,0 +1,49 @@
+cmake_minimum_required(VERSION 3.2)
+
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake)
+if(NOT DEFINED CMAKE_BUILD_TYPE)
+	set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type")
+endif()
+
+project(zf_log)
+
+set(INSTALL_INCLUDE_DIR include CACHE PATH
+	"Installation directory for header files")
+set(INSTALL_LIB_DIR lib CACHE PATH
+	"Installation directory for libraries")
+set(INSTALL_CMAKE_DIR lib/cmake/zf_log CACHE PATH
+	"Installation directory for CMake files")
+set(ZF_LOG_LIBRARY_PREFIX CACHE STRING
+	"Prefix for all linker symbols exported by the library")
+option(ZF_LOG_CONFIGURE_INSTALL "Generate install target" ON)
+option(ZF_LOG_EXAMPLES "Build examples" OFF)
+option(ZF_LOG_TESTS "Build tests" OFF)
+option(ZF_LOG_PERF_TESTS "Build performance tests (requires Python)" OFF)
+option(ZF_LOG_USE_ANDROID_LOG "Use Android log by defaul when available" OFF)
+option(ZF_LOG_USE_NSLOG "Use NSLog (Apple System Log) by default when available" OFF)
+option(ZF_LOG_USE_DEBUGSTRING "Use OutputDebugString (Windows) by default when available" OFF)
+option(ZF_LOG_USE_CONFIG_HEADER "Include zf_log_config.h header file in zf_log compilation untis" OFF)
+option(ZF_LOG_OPTIMIZE_SIZE "Optimize for size (prefer size over speed)" OFF)
+
+add_subdirectory(zf_log)
+
+if(ZF_LOG_EXAMPLES)
+	add_subdirectory(examples)
+endif()
+if(ZF_LOG_TESTS)
+	enable_testing()
+	add_subdirectory(tests)
+endif()
+if(ZF_LOG_PERF_TESTS)
+	enable_testing()
+	add_subdirectory(tests/perf)
+endif()
+
+if(ZF_LOG_CONFIGURE_INSTALL)
+	export(EXPORT zf_log)
+	install(EXPORT zf_log
+		DESTINATION ${INSTALL_CMAKE_DIR})
+	configure_file(zf_log-config.cmake.in zf_log-config.cmake @ONLY)
+	install(FILES ${CMAKE_CURRENT_BINARY_DIR}/zf_log-config.cmake
+		DESTINATION ${INSTALL_CMAKE_DIR})
+endif()

+ 7 - 0
zf_log/CTestTestfile.cmake

@@ -0,0 +1,7 @@
+# CMake generated Testfile for 
+# Source directory: /home/beanzilla/horrible-harry/zf_log
+# Build directory: /home/beanzilla/horrible-harry/zf_log
+# 
+# This file includes the relevant testing commands required for 
+# testing this directory and lists subdirectories to be tested as well.
+subdirs("zf_log")

+ 21 - 0
zf_log/LICENSE

@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2017 wonder-mice
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 196 - 0
zf_log/Makefile

@@ -0,0 +1,196 @@
+# CMAKE generated file: DO NOT EDIT!
+# Generated by "Unix Makefiles" Generator, CMake Version 3.10
+
+# Default target executed when no arguments are given to make.
+default_target: all
+
+.PHONY : default_target
+
+# Allow only one "make -f Makefile2" at a time, but pass parallelism.
+.NOTPARALLEL:
+
+
+#=============================================================================
+# Special targets provided by cmake.
+
+# Disable implicit rules so canonical targets will work.
+.SUFFIXES:
+
+
+# Remove some rules from gmake that .SUFFIXES does not remove.
+SUFFIXES =
+
+.SUFFIXES: .hpux_make_needs_suffix_list
+
+
+# Suppress display of executed commands.
+$(VERBOSE).SILENT:
+
+
+# A target that is always out of date.
+cmake_force:
+
+.PHONY : cmake_force
+
+#=============================================================================
+# Set environment variables for the build.
+
+# The shell in which to execute make rules.
+SHELL = /bin/sh
+
+# The CMake executable.
+CMAKE_COMMAND = /usr/bin/cmake
+
+# The command to remove a file.
+RM = /usr/bin/cmake -E remove -f
+
+# Escaping for special characters.
+EQUALS = =
+
+# The top-level source directory on which CMake was run.
+CMAKE_SOURCE_DIR = /home/beanzilla/horrible-harry
+
+# The top-level build directory on which CMake was run.
+CMAKE_BINARY_DIR = /home/beanzilla/horrible-harry
+
+#=============================================================================
+# Targets provided globally by CMake.
+
+# Special rule for the target install/strip
+install/strip: preinstall
+	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Installing the project stripped..."
+	/usr/bin/cmake -DCMAKE_INSTALL_DO_STRIP=1 -P cmake_install.cmake
+.PHONY : install/strip
+
+# Special rule for the target install/strip
+install/strip/fast: preinstall/fast
+	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Installing the project stripped..."
+	/usr/bin/cmake -DCMAKE_INSTALL_DO_STRIP=1 -P cmake_install.cmake
+.PHONY : install/strip/fast
+
+# Special rule for the target edit_cache
+edit_cache:
+	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "No interactive CMake dialog available..."
+	/usr/bin/cmake -E echo No\ interactive\ CMake\ dialog\ available.
+.PHONY : edit_cache
+
+# Special rule for the target edit_cache
+edit_cache/fast: edit_cache
+
+.PHONY : edit_cache/fast
+
+# Special rule for the target list_install_components
+list_install_components:
+	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Available install components are: \"Unspecified\""
+.PHONY : list_install_components
+
+# Special rule for the target list_install_components
+list_install_components/fast: list_install_components
+
+.PHONY : list_install_components/fast
+
+# Special rule for the target test
+test:
+	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Running tests..."
+	/usr/bin/ctest --force-new-ctest-process $(ARGS)
+.PHONY : test
+
+# Special rule for the target test
+test/fast: test
+
+.PHONY : test/fast
+
+# Special rule for the target install/local
+install/local: preinstall
+	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Installing only the local directory..."
+	/usr/bin/cmake -DCMAKE_INSTALL_LOCAL_ONLY=1 -P cmake_install.cmake
+.PHONY : install/local
+
+# Special rule for the target install/local
+install/local/fast: preinstall/fast
+	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Installing only the local directory..."
+	/usr/bin/cmake -DCMAKE_INSTALL_LOCAL_ONLY=1 -P cmake_install.cmake
+.PHONY : install/local/fast
+
+# Special rule for the target rebuild_cache
+rebuild_cache:
+	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Running CMake to regenerate build system..."
+	/usr/bin/cmake -H$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR)
+.PHONY : rebuild_cache
+
+# Special rule for the target rebuild_cache
+rebuild_cache/fast: rebuild_cache
+
+.PHONY : rebuild_cache/fast
+
+# Special rule for the target install
+install: preinstall
+	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Install the project..."
+	/usr/bin/cmake -P cmake_install.cmake
+.PHONY : install
+
+# Special rule for the target install
+install/fast: preinstall/fast
+	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Install the project..."
+	/usr/bin/cmake -P cmake_install.cmake
+.PHONY : install/fast
+
+# The main all target
+all: cmake_check_build_system
+	cd /home/beanzilla/horrible-harry && $(CMAKE_COMMAND) -E cmake_progress_start /home/beanzilla/horrible-harry/CMakeFiles /home/beanzilla/horrible-harry/zf_log/CMakeFiles/progress.marks
+	cd /home/beanzilla/horrible-harry && $(MAKE) -f CMakeFiles/Makefile2 zf_log/all
+	$(CMAKE_COMMAND) -E cmake_progress_start /home/beanzilla/horrible-harry/CMakeFiles 0
+.PHONY : all
+
+# The main clean target
+clean:
+	cd /home/beanzilla/horrible-harry && $(MAKE) -f CMakeFiles/Makefile2 zf_log/clean
+.PHONY : clean
+
+# The main clean target
+clean/fast: clean
+
+.PHONY : clean/fast
+
+# Prepare targets for installation.
+preinstall: all
+	cd /home/beanzilla/horrible-harry && $(MAKE) -f CMakeFiles/Makefile2 zf_log/preinstall
+.PHONY : preinstall
+
+# Prepare targets for installation.
+preinstall/fast:
+	cd /home/beanzilla/horrible-harry && $(MAKE) -f CMakeFiles/Makefile2 zf_log/preinstall
+.PHONY : preinstall/fast
+
+# clear depends
+depend:
+	cd /home/beanzilla/horrible-harry && $(CMAKE_COMMAND) -H$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 1
+.PHONY : depend
+
+# Help Target
+help:
+	@echo "The following are some of the valid targets for this Makefile:"
+	@echo "... all (the default if no target is provided)"
+	@echo "... clean"
+	@echo "... depend"
+	@echo "... install/strip"
+	@echo "... edit_cache"
+	@echo "... list_install_components"
+	@echo "... test"
+	@echo "... install/local"
+	@echo "... rebuild_cache"
+	@echo "... install"
+.PHONY : help
+
+
+
+#=============================================================================
+# Special targets to cleanup operation of make.
+
+# Special rule to run CMake to check the build system integrity.
+# No rule that depends on this can have commands that come from listfiles
+# because they might be regenerated.
+cmake_check_build_system:
+	cd /home/beanzilla/horrible-harry && $(CMAKE_COMMAND) -H$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 0
+.PHONY : cmake_check_build_system
+

+ 324 - 0
zf_log/README.md

@@ -0,0 +1,324 @@
+[![Build Status](https://travis-ci.org/wonder-mice/zf_log.svg?branch=master)](https://travis-ci.org/wonder-mice/zf_log)
+[![Build Status](https://ci.appveyor.com/api/projects/status/u9rmuaw147q578w0/branch/master?svg=true)](https://ci.appveyor.com/project/wonder-mice/zf-log/branch/master)
+[![Gitter Chat](https://badges.gitter.im/wonder-mice/zf_log.svg)](https://gitter.im/wonder-mice/zf_log?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+
+zf_log
+========
+
+### Core logging library for C, Objective-C and C++
+
+Following the [Unix way](https://en.wikipedia.org/wiki/Unix_philosophy), this
+library provides the logging core which can be used directly or extended. In
+essence, it's a thin wrapper around snprintf() function. By implementing less
+than 20% of functionality found in more sophisticated and feature reach
+libraries, it covers more than 80% of common use cases. Found to be
+particularly useful in cross-platform applications and on mobile/embedded
+platforms. Focus is made on simplicity, ease of use and performance (to be
+more precise - low overhead).
+
+Features:
+
+* Debug logging is reduced to no-op in release builds:
+
+  ```c
+  /* no runtime overhead whatsoever if verbose log is disabled */
+  ZF_LOGV("entering foobar(), args: %i, %s", arg0, arg1);
+  ```
+
+* No "unused" warning for variables used in log statements only:
+
+  ```c
+  /* no warning about err being unused even if verbose log is disabled */
+  int err = close(fd);
+  ZF_LOGV("close status %i", err);
+  ```
+
+* Arguments are not evaluated when the message is not logged:
+
+  ```c
+  /* to_utf8() will not be called if debug log is turned off or disabled */
+  ZF_LOGD("Login: %s", to_utf8(loginUtf16));
+  ```
+
+* Log a memory region as HEX and ASCII:
+
+  ```c
+  /* will print HEX and ASCII view of received network packet */
+  ZF_LOGD_MEM(pkg_ptr, pkg_sz, "Received network packet (%u bytes):", pkg_sz);
+  ```
+
+* Compiler warnings when format string and arguments don't match:
+
+  ```c
+  /* warning: format specifies type 'char *' but the argument has type 'int' */
+  ZF_LOGI("This is int %s", 42);
+  ```
+
+* Conditional logging of sensitive information (also known as Personally
+  Identifiable Information or PII):
+
+  ```c
+  /* will be logged only when logging of sensitive information is enabled */
+  ZF_LOG_SECRET(ZF_LOGI("Credit card number: %s", credit_card));
+  ```
+
+* Custom output functions
+* Custom log message formats
+* Compile time configuration of logging level
+* Run time configuration of logging level
+* Optional built-in support for Android log and Apple system log (iOS, OS X)
+* Reasonably cross-platform (OS X, iOS, Linux, Android, other Unix flavors,
+  POSIX platforms and Windows)
+* No external dependencies
+* Compact call site (smaller executables)
+* Thread safe
+* Library size is under 10Kb (when compiled for x86_64)
+* Can be used privatly in libraries
+
+Examples
+--------
+
+Library provides a set of `ZF_LOGX` macros where `X` is an abbreviated log
+level (e.g. `I` for `INFO`). This code will log an `INFO` message:
+
+```c
+ZF_LOGI("Number of arguments: %i", argc);
+```
+
+And will produce the following log line if `NDEBUG` is defined (aka release
+build):
+
+```
++- month           +- process id
+|  +- day          |      +- thread id      +- message
+|  |               |      |                 |
+04-29 22:43:20.244 40059  1299 I hello.MAIN Number of arguments: 1
+      |                        | |     |
+      +- time                  | |     +- tag (optional)
+                               | +- tag prefix (optional)
+                               +- level
+```
+
+And if `NDEBUG` is NOT defined (aka debug build):
+
+```
+04-29 22:43:20.244 40059  1299 I hello.MAIN [email protected]:9 Number of arguments: 1
+                                            |    |       |
+                                            |    |       +- line number
+                                            |    +- source file name
+                                            +- function name
+```
+
+It's also possible to dump a memory region. For example:
+
+```c
+const char data[] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
+ZF_LOGI_MEM(data, sizeof(data), "Lorem ipsum at %p", data);
+```
+
+Will produce the following output:
+
+```
+05-06 00:54:33.825 35864  1299 I hello.MAIN Lorem ipsum at 0x10fbc0f20:
+05-06 00:54:33.825 35864  1299 I hello.MAIN 4c6f72656d20697073756d20646f6c6f  Lorem ipsum dolo
+05-06 00:54:33.825 35864  1299 I hello.MAIN 722073697420616d65742c20636f6e73  r sit amet, cons
+05-06 00:54:33.825 35864  1299 I hello.MAIN 65637465747572206164697069736369  ectetur adipisci
+05-06 00:54:33.825 35864  1299 I hello.MAIN 6e6720656c69742e00                ng elit.?
+```
+
+More examples available in [examples](examples) folder. For more details see
+comments in [zf_log/zf_log.h](zf_log/zf_log.h) file.
+
+Usage
+--------
+
+### Embedding
+
+The simplest way of using this library is to embed its sources into existing
+project. For that, copy the following files to your source tree:
+
+* [zf_log.h](zf_log/zf_log.h)
+* [zf_log.c](zf_log/zf_log.c)
+
+See comments in those files for configuration macros. One particularly useful
+option when embedding into a library project is `ZF_LOG_LIBRARY_PREFIX`. It
+could be used to decorate zf_log exported symbols to avoid linker conflicts
+(when that library project is used in other project that is also uses zf_log).
+
+### Embedding with CMake
+
+Another options is avaibale for projects that are using CMake. Copy
+[zf_log](zf_log) folder to you source tree and add it with `add_subdirectory()`
+call in one of your CMakeLists.txt files. Also see
+[zf_log/CMakeLists.txt](zf_log/CMakeLists.txt) for available `ZF_LOG_`
+configuration options. For example:
+
+```
+set(ZF_LOG_USE_ANDROID_LOG ON)
+add_subdirectory(zf_log)
+```
+
+This will add `zf_log` library target. For each target that uses zf_log in
+corresponding CMakeLists.txt file add:
+
+```cmake
+target_link_libraries(my_target zf_log)
+```
+
+### Installation
+
+Another option is to build and install the library:
+
+```bash
+git clone https://github.com/wonder-mice/zf_log.git zf_log.git
+mkdir zf_log.build && cd zf_log.build
+cmake ../zf_log.git -DCMAKE_INSTALL_PREFIX=/usr/local
+make
+sudo make install
+```
+
+This will also install
+`${CMAKE_INSTALL_PREFIX}/lib/cmake/zf_log/zf_log.cmake`
+and
+`${CMAKE_INSTALL_PREFIX}/lib/cmake/zf_log/zf_log-config.cmake`.
+The first one is for direct `include` from CMakeLists.txt files.
+The second can be located by CMake with:
+
+```cmake
+find_package(zf_log)
+```
+
+Both will add `zf_log` imported library target.
+For each target that uses zf_log in corresponding CMakeLists.txt file add:
+
+```cmake
+target_link_libraries(my_target zf_log)
+```
+
+To build as a shared library set CMake variable `BUILD_SHARED_LIBS`:
+
+```bash
+cmake ../zf_log.git -DBUILD_SHARED_LIBS:BOOL=ON
+```
+
+Performance
+--------
+
+Log statements that are below *current log level* (compile time check) have
+**no overhead** - they are compiled out and their log arguments will **not** be
+evaluated. Consider:
+
+```c
+#include <signal.h>
+#include <unistd.h>
+#define ZF_LOG_LEVEL ZF_LOG_INFO
+#include <zf_log.h>
+
+int main(int argc, char *argv[])
+{
+	ZF_LOGV("Argument of this VERBOSE message will not be evaluated: %i",
+			kill(getpid(), SIGKILL));
+	ZF_LOGI("So you will see that INFO message");
+	return 0;
+}
+```
+
+Log statements that are below *output log level* (run time check)
+have a **small overhead** of compare operation and conditional jump. Arguments
+will **not** be evaluated and no function call will be performed. Consider:
+
+```c
+#include <stdlib.h>
+#define ZF_LOG_LEVEL ZF_LOG_INFO
+#include <zf_log.h>
+
+int main(int argc, char *argv[])
+{
+	zf_log_set_output_level(ZF_LOG_WARN);
+	int count = 0;
+	for (int i = 2; 0 < i--;)
+	{
+		ZF_LOGI("Argument of this INFO message will be evaluated only once: %i",
+				++count);
+		zf_log_set_output_level(ZF_LOG_INFO);
+	}
+	if (1 != count)
+	{
+		abort();
+	}
+	ZF_LOGI("And you will see that INFO message");
+	return 0;
+}
+```
+
+Log statements that are on or above current log level and output log level will
+go into log output (and arguments will be evaluated). In that case it's hard to
+talk about performance because string formatting routines will be called and IO
+will be performed.
+
+To conclude, it is OK to have log statements for debug and development purposes,
+even in performance critical parts. But make sure to set correct current log
+level (to compile them out) or output log level (to suppress them) in release
+builds.
+
+That said, in some rare cases it could be useful to provide a custom output
+function that will use memory buffer for the log output.
+
+Output
+--------
+
+By default log messages are written to the `stderr`, but it is also possible to
+set custom output function. Library has an optional built-in support for the
+following output facilities (see [zf_log/zf_log.c] for details):
+
+* Android Log (via android/log.h)
+* Apple System Log (iOS, OS X via asl.h)
+
+See [examples/custom_output.c] for an example of custom output function.
+
+[zf_log/zf_log.c]: zf_log/zf_log.c
+[examples/custom_output.c]: examples/custom_output.c
+
+Comparison
+--------
+
+This table is work in progress. See [tests/perf](tests/perf) folder for how
+this table was generated (fully automated).
+
+|                                        |  Easylogging++  |     g3log     |     glog    |     spdlog    |      zf_log     |
+| -------------------------------------- | ---------------:| -------------:| -----------:| -------------:| ---------------:|
+|  Call site size: string                |          304 B  |        360 B  |      160 B  |        352 B  |         **48 B**|
+|  Call site size: 3 integers            |          856 B  |        384 B  |      320 B  |        312 B  |         **72 B**|
+|  Executable size: 1 module             |      208.84 KB  |    183.73 KB  |  137.37 KB  |    133.69 KB  |     **18.33 KB**|
+|  Module compile time                   |      3.182 sec  |    0.511 sec  |  0.374 sec  |    2.163 sec  |    **0.024 sec**|
+|  Executable link time                  |      0.025 sec  |    0.022 sec  |  0.026 sec  |  **0.020 sec**|    **0.017 sec**|
+|  Speed: 1 thread, string               |        378,722  |    1,257,168  |    385,098  |    1,909,253  |    **5,296,201**|
+|  Speed: 1 thread, 3 integers           |        311,690  |    1,023,108  |    321,507  |    1,470,271  |    **3,173,097**|
+|  Speed: 1 thread, string, off          |      4,199,431  |   55,297,247  |    411,960  |   78,675,820  |  **482,713,041**|
+|  Speed: 1 thread, slow function, off   |            743  |   54,877,221  |        732  |          750  |  **423,658,146**|
+|  Speed: 4 threads, string              |        132,836  |    2,353,169  |    196,950  |      196,930  |    **9,690,216**|
+|  Speed: 4 threads, 3 integers          |        116,696  |    1,896,178  |    235,141  |      172,880  |    **5,545,075**|
+|  Speed: 4 threads, string, off         |        244,448  |  106,600,866  |    186,233  |  164,582,184  |**1,229,398,467**|
+|  Speed: 4 threads, slow function, off  |            733  |  111,685,033  |      2,887  |        2,976  |**1,106,166,551**|
+|  Speed: 8 threads, string              |        131,691  |    2,447,058  |    172,824  |      183,277  |    **9,731,434**|
+|  Speed: 8 threads, 3 integers          |        115,991  |    1,936,270  |    176,594  |      177,950  |    **5,596,045**|
+|  Speed: 8 threads, string, off         |        240,538  |  104,424,749  |    182,465  |  164,632,729  |**1,228,058,986**|
+|  Speed: 8 threads, slow function, off  |            774  |  116,354,224  |      5,815  |        5,976  |**1,127,173,552**|
+
+Details:
+
+* Call site size - amount of code generated for each LOG() statement when
+  logging a string, format string with 4 integers or format string with 4
+  integers where the function is used to get an integer.
+* Compile time - time to compile a source file that includes public API
+  header from the logging library.
+* Speed - log lines per second. Thread(s) call LOG() N times in a
+  tight loop for T seconds. N/T is log lines per second.
+* When it says "off", it means that log level was turned off. Expecting
+  very low overhead in this mode.
+
+Why zf?
+--------
+
+It stands for Ze Foundation. "Ze" is like "The" but with french or german accent.
+Mostly because zf_anything looks better than tf_anything.

+ 38 - 0
zf_log/TODO.md

@@ -0,0 +1,38 @@
+Things to do
+------------
+
+* Add callback arg to output callback
+* Update README, it sucks now
+* Print start address for each line of memory output?
+* Introduce private format_callback which probably will replace put_msg
+  This will provide more modular structure and will point out where to
+  insert custom code when different format function must be used.
+  As an exercise, add optional support for CFStringCreateWithFormat().
+* Use Cmake GNUInstallDirs for install locations.
+
+Things probably not to do
+-------------------------
+
+* Number of parameters in _zf_log_write_xxx() functions could be reduced
+  by having a separate function for each log level. One way to implement
+  that is to have a static array of logging functions. Log level will be
+  an index in that array. This technique reduces the size of code
+  generated per log statement (e.g. 5 bytes less on x64). But it also
+  increases the size of the library itself, because all those new
+  28 functions (7 levels x 2 mem/msg x 2 debug/ndebug) must be defined.
+  And it also makes implementation more complex. Currently considered
+  not worth the effort.
+
+* Output some memory even when buffer is too small. Will significantly
+  increase complexity of output_mem() function, while providing not
+  much benefits. Memory output line is pretty much limited in length,
+  so problem could be solved easily by choosing right ZF_LOG_BUF_SZ.
+
+* Debug functions (_zf_log_write.*_d) could also receive the length of
+  string passed in as a "file" parameter. That will allow to search for
+  a slash from the end of the string. While it will be up to 30 times
+  faster, overall performance gain will be too small to notice, because
+  the biggest offenders right now are localtime() and file io. Also
+  this will require to push additional argument which will increase the
+  size of call site which could be very noticable - size of the binary
+  will go up.

+ 68 - 0
zf_log/cmake_install.cmake

@@ -0,0 +1,68 @@
+# Install script for directory: /home/beanzilla/horrible-harry/zf_log
+
+# Set the install prefix
+if(NOT DEFINED CMAKE_INSTALL_PREFIX)
+  set(CMAKE_INSTALL_PREFIX "/usr/local")
+endif()
+string(REGEX REPLACE "/$" "" CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}")
+
+# Set the install configuration name.
+if(NOT DEFINED CMAKE_INSTALL_CONFIG_NAME)
+  if(BUILD_TYPE)
+    string(REGEX REPLACE "^[^A-Za-z0-9_]+" ""
+           CMAKE_INSTALL_CONFIG_NAME "${BUILD_TYPE}")
+  else()
+    set(CMAKE_INSTALL_CONFIG_NAME "Debug")
+  endif()
+  message(STATUS "Install configuration: \"${CMAKE_INSTALL_CONFIG_NAME}\"")
+endif()
+
+# Set the component getting installed.
+if(NOT CMAKE_INSTALL_COMPONENT)
+  if(COMPONENT)
+    message(STATUS "Install component: \"${COMPONENT}\"")
+    set(CMAKE_INSTALL_COMPONENT "${COMPONENT}")
+  else()
+    set(CMAKE_INSTALL_COMPONENT)
+  endif()
+endif()
+
+# Install shared libraries without execute permission?
+if(NOT DEFINED CMAKE_INSTALL_SO_NO_EXE)
+  set(CMAKE_INSTALL_SO_NO_EXE "1")
+endif()
+
+# Is this installation the result of a crosscompile?
+if(NOT DEFINED CMAKE_CROSSCOMPILING)
+  set(CMAKE_CROSSCOMPILING "FALSE")
+endif()
+
+if("x${CMAKE_INSTALL_COMPONENT}x" STREQUAL "xUnspecifiedx" OR NOT CMAKE_INSTALL_COMPONENT)
+  if(EXISTS "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/lib/cmake/zf_log/zf_log.cmake")
+    file(DIFFERENT EXPORT_FILE_CHANGED FILES
+         "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/lib/cmake/zf_log/zf_log.cmake"
+         "/home/beanzilla/horrible-harry/zf_log/CMakeFiles/Export/lib/cmake/zf_log/zf_log.cmake")
+    if(EXPORT_FILE_CHANGED)
+      file(GLOB OLD_CONFIG_FILES "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/lib/cmake/zf_log/zf_log-*.cmake")
+      if(OLD_CONFIG_FILES)
+        message(STATUS "Old export file \"$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/lib/cmake/zf_log/zf_log.cmake\" will be replaced.  Removing files [${OLD_CONFIG_FILES}].")
+        file(REMOVE ${OLD_CONFIG_FILES})
+      endif()
+    endif()
+  endif()
+  file(INSTALL DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/cmake/zf_log" TYPE FILE FILES "/home/beanzilla/horrible-harry/zf_log/CMakeFiles/Export/lib/cmake/zf_log/zf_log.cmake")
+  if("${CMAKE_INSTALL_CONFIG_NAME}" MATCHES "^([Dd][Ee][Bb][Uu][Gg])$")
+    file(INSTALL DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/cmake/zf_log" TYPE FILE FILES "/home/beanzilla/horrible-harry/zf_log/CMakeFiles/Export/lib/cmake/zf_log/zf_log-debug.cmake")
+  endif()
+endif()
+
+if("x${CMAKE_INSTALL_COMPONENT}x" STREQUAL "xUnspecifiedx" OR NOT CMAKE_INSTALL_COMPONENT)
+  file(INSTALL DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/cmake/zf_log" TYPE FILE FILES "/home/beanzilla/horrible-harry/zf_log/zf_log-config.cmake")
+endif()
+
+if(NOT CMAKE_INSTALL_LOCAL_ONLY)
+  # Include the install script for each subdirectory.
+  include("/home/beanzilla/horrible-harry/zf_log/zf_log/cmake_install.cmake")
+
+endif()
+

+ 26 - 0
zf_log/examples/CMakeLists.txt

@@ -0,0 +1,26 @@
+cmake_minimum_required(VERSION 3.2)
+
+set(CMAKE_C_STANDARD 99)
+set(CMAKE_C_STANDARD_REQUIRED ON)
+set(CMAKE_C_EXTENSIONS OFF)
+if(MSVC)
+	set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /W4 /WX")
+else()
+	set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror -pedantic-errors")
+endif()
+
+if(ZF_LOG_LIBRARY_PREFIX)
+	add_definitions("-DZF_LOG_LIBRARY_PREFIX=${ZF_LOG_LIBRARY_PREFIX}")
+endif()
+
+add_executable(hello_c hello.c)
+target_link_libraries(hello_c zf_log)
+
+add_executable(custom_output custom_output.c)
+target_link_libraries(custom_output zf_log)
+
+add_executable(file_output file_output.c)
+target_link_libraries(file_output zf_log)
+
+add_executable(args_eval args_eval.c)
+target_link_libraries(args_eval zf_log)

+ 41 - 0
zf_log/examples/args_eval.c

@@ -0,0 +1,41 @@
+#include <signal.h>
+#include <stdlib.h>
+#define ZF_LOG_LEVEL ZF_LOG_INFO
+#include <zf_log.h>
+
+static int call_exit()
+{
+	exit(1);
+}
+
+int main(int argc, char *argv[])
+{
+	(void)argc; (void)argv;
+
+	/* Current log level is set to ZF_LOG_INFO by defining ZF_LOG_LEVEL
+	 * before zf_log.h include. All log messages below INFO level will be
+	 * compiled out.
+	 */
+	ZF_LOGV("Argument of this VERBOSE message will not be evaluated: %i",
+			call_exit());
+	ZF_LOGI("So you will see that INFO message");
+
+	/* Output log level is set to WARN and then to INFO. Argument of INFO log
+	 * statement will be evaluated only once (after setting output log level to
+	 * INFO).
+	 */
+	zf_log_set_output_level(ZF_LOG_WARN);
+	int count = 0;
+	for (int i = 2; 0 < i--;)
+	{
+		ZF_LOGI("Argument of this INFO message will be evaluated only once: %i",
+				++count);
+		zf_log_set_output_level(ZF_LOG_INFO);
+	}
+	if (1 != count)
+	{
+		abort();
+	}
+	ZF_LOGI("And you will see that INFO message");
+	return 0;
+}

+ 75 - 0
zf_log/examples/custom_output.c

@@ -0,0 +1,75 @@
+#include <assert.h>
+#if defined(_WIN32) || defined(_WIN64)
+	#include <windows.h>
+	#define OUTPUT_DEBUG_STRING
+#else
+	#include <syslog.h>
+	#define OUTPUT_SYSLOG
+#endif
+#include <zf_log.h>
+
+#ifdef OUTPUT_SYSLOG
+static int syslog_level(const int lvl)
+{
+	switch (lvl)
+	{
+	case ZF_LOG_VERBOSE:
+		return LOG_DEBUG;
+	case ZF_LOG_DEBUG:
+		return LOG_DEBUG;
+	case ZF_LOG_INFO:
+		return LOG_INFO;
+	case ZF_LOG_WARN:
+		return LOG_WARNING;
+	case ZF_LOG_ERROR:
+		return LOG_ERR;
+	case ZF_LOG_FATAL:
+		return LOG_EMERG;
+	default:
+		assert(!"can't be");
+		return LOG_EMERG;
+	}
+}
+#endif
+
+static void custom_output_callback(const zf_log_message *msg, void *arg)
+{
+	(void)arg;
+	/* p points to the log message end. By default, message is not terminated
+	 * with 0, but it has some space allocated for EOL area, so there is always
+	 * some place for terminating zero in the end (see ZF_LOG_EOL_SZ define in
+	 * zf_log.c).
+	 */
+	*msg->p = 0;
+#if defined(OUTPUT_DEBUG_STRING)
+	OutputDebugStringA(msg->buf);
+#elif defined(OUTPUT_SYSLOG)
+	syslog(syslog_level(msg->lvl), "%s", msg->tag_b);
+#else
+	#error Unsupported platform
+#endif
+}
+
+int main(int argc, char *argv[])
+{
+#if defined(OUTPUT_SYSLOG)
+	openlog("custom_output", LOG_CONS|LOG_PERROR|LOG_PID, LOG_USER);
+#endif
+
+	const unsigned put_mask =
+#if defined(OUTPUT_SYSLOG)
+			ZF_LOG_PUT_STD & !ZF_LOG_PUT_CTX;
+#else
+			ZF_LOG_PUT_STD;
+#endif
+			;
+	zf_log_set_output_v(put_mask, 0, custom_output_callback);
+
+	ZF_LOGI("Number of arguments goes into custom output: %i", argc);
+	ZF_LOGI_MEM(argv, argc * sizeof(*argv), "and argv pointers as well:");
+
+#if defined(OUTPUT_SYSLOG)
+	closelog();
+#endif
+	return 0;
+}

+ 43 - 0
zf_log/examples/file_output.c

@@ -0,0 +1,43 @@
+#if defined(_WIN32) || defined(_WIN64)
+	#define _CRT_SECURE_NO_WARNINGS
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include "zf_log.h"
+
+FILE *g_log_file;
+
+static void file_output_callback(const zf_log_message *msg, void *arg)
+{
+	(void)arg;
+	*msg->p = '\n';
+	fwrite(msg->buf, msg->p - msg->buf + 1, 1, g_log_file);
+	fflush(g_log_file);
+}
+
+static void file_output_close(void)
+{
+	fclose(g_log_file);
+}
+
+static void file_output_open(const char *const log_path)
+{
+	g_log_file = fopen(log_path, "a");
+	if (!g_log_file)
+	{
+		ZF_LOGW("Failed to open log file %s", log_path);
+		return;
+	}
+	atexit(file_output_close);
+	zf_log_set_output_v(ZF_LOG_PUT_STD, 0, file_output_callback);
+}
+
+int main(int argc, char *argv[])
+{
+	file_output_open("example.log");
+
+	ZF_LOGI("Writing number of arguments to log file: %i", argc);
+	ZF_LOGI_MEM(argv, argc * sizeof(*argv), "argv pointers:");
+
+	return 0;
+}

+ 22 - 0
zf_log/examples/hello.c

@@ -0,0 +1,22 @@
+#define ZF_LOG_LEVEL ZF_LOG_INFO
+#define ZF_LOG_TAG "MAIN"
+#include <zf_log.h>
+
+int main(int argc, char *argv[])
+{
+	zf_log_set_tag_prefix("hello");
+
+	ZF_LOGI("You will see the number of arguments: %i", argc);
+	ZF_LOGD("You will NOT see the first argument: %s", *argv);
+
+	zf_log_set_output_level(ZF_LOG_WARN);
+	ZF_LOGW("You will see this WARNING message");
+	ZF_LOGI("You will NOT see this INFO message");
+
+	const char data[] =
+			"Lorem ipsum dolor sit amet, consectetur adipiscing elit. "
+			"Aliquam pharetra orci id velit porttitor tempus.";
+	ZF_LOGW_MEM(data, sizeof(data), "Lorem ipsum at %p:", (const void *) data);
+
+	return 0;
+}

+ 180 - 0
zf_log/tests/CMakeLists.txt

@@ -0,0 +1,180 @@
+cmake_minimum_required(VERSION 3.2)
+
+include(CMakeParseArguments)
+
+set(CMAKE_C_STANDARD 99)
+set(CMAKE_C_STANDARD_REQUIRED ON)
+set(CMAKE_C_EXTENSIONS OFF)
+if(MSVC)
+	set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /W4 /WX")
+else()
+	set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror -pedantic-errors")
+endif()
+
+# zf_test
+set(HEADERS_DIR ${CMAKE_CURRENT_SOURCE_DIR})
+add_library(zf_test INTERFACE)
+target_include_directories(zf_test INTERFACE $<BUILD_INTERFACE:${HEADERS_DIR}>)
+set(HEADERS zf_test.h)
+add_custom_target(zf_test_headers SOURCES ${HEADERS})
+
+function(add_test_target target)
+	cmake_parse_arguments(arg
+		"COMPILE_ONLY"
+		""
+		"SOURCES;CSTD;CXXSTD;DEFINES"
+		${ARGN})
+	if(arg_COMPILE_ONLY)
+		add_library(${target} STATIC ${arg_SOURCES})
+	else()
+		add_executable(${target} ${arg_SOURCES})
+		target_link_libraries(${target} zf_test)
+		add_test(NAME ${target} COMMAND ${target})
+	endif()
+	if(arg_CSTD)
+		set_property(TARGET ${target} PROPERTY C_STANDARD "${arg_CSTD}")
+	endif()
+	if(arg_CXXSTD)
+		set_property(TARGET ${target} PROPERTY CXX_STANDARD "${arg_CXXSTD}")
+	endif()
+	set_property(TARGET ${target} PROPERTY COMPILE_DEFINITIONS "${arg_DEFINES}")
+	target_include_directories(${target} PRIVATE "${PROJECT_SOURCE_DIR}/zf_log")
+endfunction()
+
+function(add_test_target_group target)
+	add_test_target(${target}_c99 ${ARGN} CSTD 99)
+	add_test_target(${target}_c11 ${ARGN} CSTD 11)
+endfunction()
+
+add_test_target_group(test_log_level_switches_verbose SOURCES test_log_level_switches.c DEFINES ZF_LOG_LEVEL=ZF_LOG_VERBOSE)
+add_test_target_group(test_log_level_switches_debug SOURCES test_log_level_switches.c DEFINES ZF_LOG_LEVEL=ZF_LOG_DEBUG)
+add_test_target_group(test_log_level_switches_info SOURCES test_log_level_switches.c DEFINES ZF_LOG_LEVEL=ZF_LOG_INFO)
+add_test_target_group(test_log_level_switches_warn SOURCES test_log_level_switches.c DEFINES ZF_LOG_LEVEL=ZF_LOG_WARN)
+add_test_target_group(test_log_level_switches_error SOURCES test_log_level_switches.c DEFINES ZF_LOG_LEVEL=ZF_LOG_ERROR)
+add_test_target_group(test_log_level_switches_fatal SOURCES test_log_level_switches.c DEFINES ZF_LOG_LEVEL=ZF_LOG_FATAL)
+add_test_target_group(test_log_level_switches_none SOURCES test_log_level_switches.c DEFINES ZF_LOG_LEVEL=ZF_LOG_NONE)
+add_test_target_group(test_log_level_override SOURCES test_log_level_override.c)
+
+add_test_target_group(test_log_message_content SOURCES test_log_message_content.c)
+add_test_target_group(test_log_message_content_Os SOURCES test_log_message_content.c DEFINES ZF_LOG_OPTIMIZE_SIZE=1)
+
+add_test_target_group(test_log_message_content_empty_format SOURCES test_log_message_content.c DEFINES
+	"ZF_LOG_MESSAGE_CTX_FORMAT=()"
+	"ZF_LOG_MESSAGE_TAG_FORMAT=()"
+	"ZF_LOG_MESSAGE_SRC_FORMAT=()")
+add_test_target_group(test_log_message_content_empty_format_Os SOURCES test_log_message_content.c DEFINES
+	"ZF_LOG_OPTIMIZE_SIZE=1"
+	"ZF_LOG_MESSAGE_CTX_FORMAT=()"
+	"ZF_LOG_MESSAGE_TAG_FORMAT=()"
+	"ZF_LOG_MESSAGE_SRC_FORMAT=()")
+
+# Here we use several F_INIT() fields in a row to workaround CMake problem with ";", which acts
+# as a list separator in CMake. If not for CMake, context format specification could look like:
+#   #define ZF_LOG_MESSAGE_CTX_FORMAT \
+#       (YEAR, S("-ctx:"), F_INIT(( struct { int v; } f_box_a; f_box_a.v = 45; )), F_UINT(4, f_box_a.v))
+add_test_target_group(test_log_message_content_custom_fields SOURCES test_log_message_content.c DEFINES
+	"ZF_LOG_MESSAGE_CTX_FORMAT=(YEAR, S(\"-ctx:\"), F_INIT(( struct { int v )), F_INIT(( } f_box_a )), F_INIT(( f_box_a.v = 45 )), F_UINT(4, f_box_a.v))"
+	"ZF_LOG_MESSAGE_TAG_FORMAT=(S(\"-tag:\"), F_INIT(( struct { int v )), F_INIT(( } f_box_b )), F_INIT(( f_box_b.v = 36 )), F_UINT(4, f_box_b.v))"
+	"ZF_LOG_MESSAGE_SRC_FORMAT=(S(\"-src:\"), F_INIT(( struct { int v )), F_INIT(( } f_box_c )), F_INIT(( f_box_c.v = 27 )), F_UINT(4, f_box_c.v))")
+add_test_target_group(test_log_message_content_custom_fields_Os SOURCES test_log_message_content.c DEFINES
+	"ZF_LOG_OPTIMIZE_SIZE=1"
+	"ZF_LOG_MESSAGE_CTX_FORMAT=(YEAR, S(\"ctx:\"), F_INIT(( struct { int v )), F_INIT(( } f_box_a )), F_INIT(( f_box_a.v = 45 )), F_UINT(4, f_box_a.v))"
+	"ZF_LOG_MESSAGE_TAG_FORMAT=(S(\"tag:\"), F_INIT(( struct { int v )), F_INIT(( } f_box_b )), F_INIT(( f_box_b.v = 36 )), F_UINT(4, f_box_b.v))"
+	"ZF_LOG_MESSAGE_SRC_FORMAT=(S(\"src:\"), F_INIT(( struct { int v )), F_INIT(( } f_box_c )), F_INIT(( f_box_c.v = 27 )), F_UINT(4, f_box_c.v))")
+
+add_test_target_group(test_source_location_none SOURCES test_source_location.c
+		DEFINES ZF_LOG_SRCLOC=ZF_LOG_SRCLOC_NONE TEST_SRCLOC=ZF_LOG_SRCLOC_NONE)
+add_test_target_group(test_source_location_short SOURCES test_source_location.c
+		DEFINES ZF_LOG_SRCLOC=ZF_LOG_SRCLOC_SHORT TEST_SRCLOC=ZF_LOG_SRCLOC_SHORT)
+add_test_target_group(test_source_location_long SOURCES test_source_location.c
+		DEFINES ZF_LOG_SRCLOC=ZF_LOG_SRCLOC_LONG TEST_SRCLOC=ZF_LOG_SRCLOC_LONG)
+add_test_target_group(test_source_location_none_Os SOURCES test_source_location.c
+		DEFINES ZF_LOG_SRCLOC=ZF_LOG_SRCLOC_NONE TEST_SRCLOC=ZF_LOG_SRCLOC_NONE ZF_LOG_OPTIMIZE_SIZE=1)
+add_test_target_group(test_source_location_short_Os SOURCES test_source_location.c
+		DEFINES ZF_LOG_SRCLOC=ZF_LOG_SRCLOC_SHORT TEST_SRCLOC=ZF_LOG_SRCLOC_SHORT ZF_LOG_OPTIMIZE_SIZE=1)
+add_test_target_group(test_source_location_long_Os SOURCES test_source_location.c
+		DEFINES ZF_LOG_SRCLOC=ZF_LOG_SRCLOC_LONG TEST_SRCLOC=ZF_LOG_SRCLOC_LONG ZF_LOG_OPTIMIZE_SIZE=1)
+
+add_test_target_group(test_conditional SOURCES test_conditional.c)
+add_test_target_group(test_censoring_off SOURCES test_censoring.c DEFINES ZF_LOG_CENSORING=ZF_LOG_UNCENSORED TEST_LOG_SECRETS=1)
+add_test_target_group(test_censoring_on SOURCES test_censoring.c DEFINES ZF_LOG_CENSORING=ZF_LOG_CENSORED TEST_LOG_SECRETS=0)
+
+add_test_target_group(test_private_parts SOURCES test_private_parts.c)
+add_test_target_group(test_decoration SOURCES test_decoration.module.c test_decoration.main.c)
+add_test_target_group(test_aux_spec SOURCES test_aux_spec.c)
+add_test_target_group(test_builtin_output_facilities SOURCES test_builtin_output_facilities.c COMPILE_ONLY)
+add_test_target_group(test_externally_defined_state SOURCES test_externally_defined_state.c)
+add_test_target(test_externally_defined_state_cpp SOURCES test_externally_defined_state_cpp.cpp CXXSTD 11)
+add_test_target(test_compilation_cpp SOURCES test_compilation_cpp.cpp CXXSTD 11)
+
+# generated code size tests
+add_executable(filesize_check filesize_check.c)
+set(CODE_SIZE_SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/code_size_tests")
+
+function(save_preprocessor_output target)
+	if(MSVC)
+		# MSVC lacks this feature, /P suppresses compilation.
+		#target_compile_options(${target} PRIVATE "/P")
+	else()
+		target_compile_options(${target} PRIVATE "-save-temps")
+	endif()
+endfunction()
+
+function(add_copy_command src dst)
+	add_custom_command(OUTPUT "${dst}"
+		COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${src}" "${dst}"
+		DEPENDS "${src}")
+	set_source_files_properties("${dst}" PROPERTIES GENERATED TRUE)
+endfunction()
+
+# copy reference and current versions of zf_log.h
+add_copy_command("${CMAKE_CURRENT_SOURCE_DIR}/zf_log.h.master" "${CODE_SIZE_SOURCE_DIR}/prev/zf_log.h")
+add_copy_command("${PROJECT_SOURCE_DIR}/zf_log/zf_log.h" "${CODE_SIZE_SOURCE_DIR}/curr/zf_log.h")
+
+function(add_code_size_test target)
+	cmake_parse_arguments(arg
+		""
+		""
+		"SOURCES;DEFINES"
+		${ARGN})
+	set(prev_sources "${CODE_SIZE_SOURCE_DIR}/prev/zf_log.h")
+	set(curr_sources "${CODE_SIZE_SOURCE_DIR}/curr/zf_log.h")
+	foreach(src ${arg_SOURCES})
+		# "-save-temps" will put all ".s" files directly in the "tests" build
+		# directory which is not specific for the target. Since it's useful to
+		# examine those files, test sources will be copied with different
+		# names for *-prev and *-curr targets. That way generated "*.s" files
+		# will have different names too and will not overwrite each other.
+		get_filename_component(src_name "${src}" NAME_WE)
+		get_filename_component(src_ext "${src}" EXT)
+		get_filename_component(src_path "${src}" ABSOLUTE)
+		set(src_copy_prev "${CODE_SIZE_SOURCE_DIR}/${target}-${src_name}-prev${src_ext}")
+		set(src_copy_curr "${CODE_SIZE_SOURCE_DIR}/${target}-${src_name}-curr${src_ext}")
+		add_copy_command("${src_path}" "${src_copy_prev}")
+		add_copy_command("${src_path}" "${src_copy_curr}")
+		list(APPEND prev_sources "${src_copy_prev}")
+		list(APPEND curr_sources "${src_copy_curr}")
+	endforeach()
+
+	# prev library
+	add_library(${target}-prev STATIC ${prev_sources})
+	target_include_directories(${target}-prev PRIVATE "${CODE_SIZE_SOURCE_DIR}/prev")
+	target_compile_definitions(${target}-prev PRIVATE ${arg_DEFINES})
+	save_preprocessor_output(${target}-prev)
+	# curr library
+	add_library(${target}-curr STATIC ${curr_sources})
+	target_include_directories(${target}-curr PRIVATE "${CODE_SIZE_SOURCE_DIR}/curr")
+	target_compile_definitions(${target}-curr PRIVATE ${arg_DEFINES})
+	save_preprocessor_output(${target}-curr)
+	# test case
+	add_dependencies(filesize_check ${target}-prev ${target}-curr)
+	add_test(NAME ${target}_check
+		COMMAND filesize_check "$<TARGET_FILE:${target}-prev>" "$<TARGET_FILE:${target}-curr>")
+endfunction()
+
+add_code_size_test(test_call_site_size_msg_only SOURCES test_call_site_size_msg_only.c)
+add_code_size_test(test_call_site_size_fmt_args SOURCES test_call_site_size_fmt_args.c)
+add_code_size_test(test_call_site_size_conditional_true  SOURCES test_call_site_size_conditional.c DEFINES TEST_CONDITION=1)
+add_code_size_test(test_call_site_size_conditional_false SOURCES test_call_site_size_conditional.c DEFINES TEST_CONDITION=0)
+add_code_size_test(test_call_site_size_censoring_on  SOURCES test_call_site_size_censoring.c DEFINES ZF_LOG_CENSORING=ZF_LOG_CENSORED)
+add_code_size_test(test_call_site_size_censoring_off SOURCES test_call_site_size_censoring.c DEFINES ZF_LOG_CENSORING=ZF_LOG_UNCENSORED)

+ 36 - 0
zf_log/tests/filesize_check.c

@@ -0,0 +1,36 @@
+#if defined(_WIN32) || defined(_WIN64)
+	#define _CRT_SECURE_NO_WARNINGS
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+
+static long filesize(const char *const path)
+{
+	FILE *const f = fopen(path, "rb");
+	if (0 == f)
+	{
+		fprintf(stderr, "Bad file: %s\n", path);
+		exit(1);
+	}
+	fseek(f, 0, SEEK_END);
+	const long sz = (unsigned)ftell(f);
+	fclose(f);
+	return sz;
+}
+
+int main(int argc, const char *argv[])
+{
+	if (2 >= argc)
+	{
+		fprintf(stderr, "Usage: prog f1_path f2_path\n");
+		return 1;
+	}
+	const long f1_sz = filesize(argv[1]);
+	const long f2_sz = filesize(argv[2]);
+	if (f1_sz < f2_sz)
+	{
+		fprintf(stderr, "New size is larger: %li <  %li\n", f1_sz, f2_sz);
+		return 1;
+	}
+	return 0;
+}

+ 345 - 0
zf_log/tests/perf/CMakeLists.txt

@@ -0,0 +1,345 @@
+cmake_minimum_required(VERSION 3.2)
+
+include(ExternalProject)
+include(CMakeParseArguments)
+set(THREADS_PREFER_PTHREAD_FLAG ON)
+find_package(Threads REQUIRED)
+find_package(PythonInterp 2.7 REQUIRED)
+
+set(CMAKE_C_STANDARD 11)
+set(CMAKE_C_STANDARD_REQUIRED ON)
+set(CMAKE_C_EXTENSIONS OFF)
+set(CMAKE_CXX_STANDARD 11)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_CXX_EXTENSIONS OFF)
+if(MSVC)
+	set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /W4 /WX")
+else()
+	set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror -pedantic-errors")
+endif()
+
+option(ZF_LOG_PERF_TEST_SAVE_TEMPS "Save preprocessor and disassembler output" OFF)
+option(ZF_LOG_PERF_TEST_ZF_LOG_OS "Add zf_log built with ZF_LOG_OPTIMIZE_SIZE to performance tests" OFF)
+option(ZF_LOG_PERF_TEST_VERBOSE_3P_BUILD "Enable verbose build output for 3rd party libraries (noisy)" OFF)
+
+# Launch rules are target properties (RULE_LAUNCH_COMPILE, RULE_LAUNCH_LINK)
+# that are used to time compilation and linking. Tests that require this
+# properties will be disabled if current generator doesn't support launch
+# rules. See CMake documentation for up to date list of generators that support
+# launch rules.
+if(CMAKE_GENERATOR MATCHES "Makefiles" OR
+   CMAKE_GENERATOR MATCHES "Ninja")
+	set(LAUNCH_RULES ON)
+else()
+	message(WARNING "Timing compilation and linking is not supported by \"${CMAKE_GENERATOR}\" generator!")
+	set(LAUNCH_RULES OFF)
+endif()
+# Since a lot of 3rd party dependencies envolved, it's only so much we can do here.
+# Non-Unix support is possible, but not in the scope right now.
+if(NOT UNIX)
+	message(WARNING "Performance tests only maintained for Unix platforms!")
+endif()
+# For convenience, ZF_LOG_PERF_TEST_VERBOSE_3P_BUILD is inverse of what is
+# actually needed. SILENT_3P_BUILD variable will be used instead.
+set(SILENT_3P_BUILD ON)
+if(ZF_LOG_PERF_TEST_VERBOSE_3P_BUILD)
+	set(SILENT_3P_BUILD OFF)
+endif()
+
+function(add_target target)
+	cmake_parse_arguments(arg
+		"STATICLIB;EXECUTABLE;NO_THREADS"
+		"TIME_COMPILE;TIME_LINK"
+		"SOURCES;DEFINES;INCLUDES;COMPILE_OPTIONS;LIBRARIES"
+		${ARGN})
+	if(ZF_LOG_PERF_TEST_SAVE_TEMPS)
+		# Clang writes *.s and *.ii files into its current working directory.
+		# Since the same source files are used in multiple targets, need to
+		# copy them, so they will have different names for different targets.
+		set(SOURCES "")
+		foreach(source ${arg_SOURCES})
+			if(IS_ABSOLUTE source)
+				set(src "${source}")
+			else()
+				set(src "${CMAKE_CURRENT_SOURCE_DIR}/${source}")
+			endif()
+			get_filename_component(src_name "${source}" NAME)
+			set(dst "${CMAKE_CURRENT_BINARY_DIR}/sources/${target}-${src_name}")
+			add_custom_command(OUTPUT "${dst}"
+				COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${src}" "${dst}"
+				DEPENDS "${src}")
+			list(APPEND SOURCES "${dst}")
+		endforeach()
+	else()
+		set(SOURCES ${arg_SOURCES})
+	endif()
+	if(arg_STATICLIB)
+		add_library(${target} STATIC ${SOURCES})
+	elseif(arg_EXECUTABLE)
+		add_executable(${target} ${SOURCES})
+		if(NOT NO_THREADS)
+			target_link_libraries(${target} Threads::Threads)
+		endif()
+	else()
+		message(FATAL_ERROR "Test target type is not specified.")
+	endif()
+	if(ZF_LOG_PERF_SAVE_TEMPS)
+		target_include_directories(${target} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
+		target_compile_options(${target} PRIVATE "-save-temps")
+	endif()
+	if(arg_DEFINES)
+		set_property(TARGET ${target} PROPERTY COMPILE_DEFINITIONS "${arg_DEFINES}")
+	endif()
+	if(arg_INCLUDES)
+		target_include_directories(${target} PRIVATE ${arg_INCLUDES})
+	endif()
+	if(arg_COMPILE_OPTIONS)
+		target_compile_options(${target} PRIVATE ${arg_COMPILE_OPTIONS})
+	endif()
+	if(arg_LIBRARIES)
+		target_link_libraries(${target} ${arg_LIBRARIES})
+	endif()
+	if(arg_TIME_COMPILE)
+		set_property(TARGET ${target} PROPERTY RULE_LAUNCH_COMPILE
+			"\"${PYTHON_EXECUTABLE}\" \"${CMAKE_CURRENT_SOURCE_DIR}/time_it.py\" \"${arg_TIME_COMPILE}\"")
+		set_property(TARGET ${target} PROPERTY TIME_COMPILE "${arg_TIME_COMPILE}")
+	endif()
+	if(arg_TIME_LINK)
+		set_property(TARGET ${target} PROPERTY RULE_LAUNCH_LINK
+			"\"${PYTHON_EXECUTABLE}\" \"${CMAKE_CURRENT_SOURCE_DIR}/time_it.py\" \"${arg_TIME_LINK}\"")
+		set_property(TARGET ${target} PROPERTY TIME_LINK "${arg_TIME_LINK}")
+	endif()
+endfunction()
+
+# zf_log
+set(ZF_LOG_DIR "${PROJECT_SOURCE_DIR}/zf_log")
+add_library(zf_log_n STATIC "${ZF_LOG_DIR}/zf_log.h" "${ZF_LOG_DIR}/zf_log.c")
+target_include_directories(zf_log_n PUBLIC "${ZF_LOG_DIR}")
+add_library(zf_log_Os STATIC "${ZF_LOG_DIR}/zf_log.h" "${ZF_LOG_DIR}/zf_log.c")
+target_include_directories(zf_log_Os PUBLIC "${ZF_LOG_DIR}")
+set_property(TARGET zf_log_Os PROPERTY COMPILE_DEFINITIONS "ZF_LOG_OPTIMIZE_SIZE")
+
+# spdlog
+set(SPDLOG_DIR "${CMAKE_CURRENT_BINARY_DIR}/spdlog")
+ExternalProject_Add(spdlog_ep
+	PREFIX "${SPDLOG_DIR}"
+	UPDATE_COMMAND ""
+	GIT_REPOSITORY "https://github.com/gabime/spdlog.git"
+	GIT_TAG "e91e1b80f9c4332bcef8388ff48ee705128e5519"
+	CMAKE_GENERATOR "${CMAKE_GENERATOR}"
+	CMAKE_ARGS
+		"-DCMAKE_TOOLCHAIN_FILE:filepath=${CMAKE_TOOLCHAIN_FILE}"
+		"-DCMAKE_INSTALL_PREFIX:path=<INSTALL_DIR>"
+		"-DCMAKE_BUILD_TYPE:string=${CMAKE_BUILD_TYPE}"
+		"-DCMAKE_OSX_ARCHITECTURES:string=${CMAKE_OSX_ARCHITECTURES}"
+		"-DCMAKE_OSX_DEPLOYMENT_TARGET:string=${CMAKE_OSX_DEPLOYMENT_TARGET}"
+		"-DCMAKE_OSX_SYSROOT:path=${CMAKE_OSX_SYSROOT}"
+	LOG_DOWNLOAD ${SILENT_3P_BUILD}
+	LOG_UPDATE ${SILENT_3P_BUILD}
+	LOG_CONFIGURE ${SILENT_3P_BUILD}
+	LOG_BUILD ${SILENT_3P_BUILD}
+	LOG_TEST ${SILENT_3P_BUILD}
+	LOG_INSTALL ${SILENT_3P_BUILD}
+)
+add_library(spdlog INTERFACE)
+add_dependencies(spdlog spdlog_ep)
+set_target_properties(spdlog PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${SPDLOG_DIR}/include")
+
+# easyloggingpp
+set(EASYLOG_DIR "${CMAKE_CURRENT_BINARY_DIR}/easyloggingpp")
+ExternalProject_Add(easylog_ep
+	PREFIX "${EASYLOG_DIR}"
+	UPDATE_COMMAND ""
+	GIT_REPOSITORY "https://github.com/easylogging/easyloggingpp.git"
+	GIT_TAG "f926802dfbde716d82b64b8ef3c25b7f0fcfec65"
+	CONFIGURE_COMMAND ""
+	BUILD_COMMAND ""
+	INSTALL_COMMAND "${CMAKE_COMMAND}" -E copy_directory
+		"<SOURCE_DIR>/src" "<INSTALL_DIR>/include"
+	LOG_DOWNLOAD ${SILENT_3P_BUILD}
+	LOG_UPDATE ${SILENT_3P_BUILD}
+	LOG_CONFIGURE ${SILENT_3P_BUILD}
+	LOG_BUILD ${SILENT_3P_BUILD}
+	LOG_TEST ${SILENT_3P_BUILD}
+	LOG_INSTALL ${SILENT_3P_BUILD}
+)
+add_library(easylog INTERFACE)
+add_dependencies(easylog easylog_ep)
+set_target_properties(easylog PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${EASYLOG_DIR}/include")
+
+# g3log
+set(G3LOG_DIR "${CMAKE_CURRENT_BINARY_DIR}/g3log")
+set(G3LOG_LIBRARY "${CMAKE_STATIC_LIBRARY_PREFIX}g3logger${CMAKE_STATIC_LIBRARY_SUFFIX}")
+ExternalProject_Add(g3log_ep
+	PREFIX "${G3LOG_DIR}"
+	UPDATE_COMMAND ""
+	GIT_REPOSITORY "https://github.com/KjellKod/g3log.git"
+	GIT_TAG "1c6ede6db4fbb12006b61a913de737df56b9dd32"
+	CMAKE_GENERATOR "${CMAKE_GENERATOR}"
+	CMAKE_ARGS
+		"-Wno-dev"
+		"-DCMAKE_TOOLCHAIN_FILE:filepath=${CMAKE_TOOLCHAIN_FILE}"
+		"-DCMAKE_INSTALL_PREFIX:path=<INSTALL_DIR>"
+		"-DCMAKE_BUILD_TYPE:string=${CMAKE_BUILD_TYPE}"
+		"-DCMAKE_OSX_ARCHITECTURES:string=${CMAKE_OSX_ARCHITECTURES}"
+		"-DCMAKE_OSX_DEPLOYMENT_TARGET:string=${CMAKE_OSX_DEPLOYMENT_TARGET}"
+		"-DCMAKE_OSX_SYSROOT:path=${CMAKE_OSX_SYSROOT}"
+		"-DUSE_DYNAMIC_LOGGING_LEVELS:bool=ON"
+	INSTALL_COMMAND "${CMAKE_COMMAND}" -E copy_directory
+		"<SOURCE_DIR>/src/g3log" "<INSTALL_DIR>/include/g3log"
+		COMMAND "${CMAKE_COMMAND}" -E copy_if_different
+		"<BINARY_DIR>/${G3LOG_LIBRARY}" "<INSTALL_DIR>/lib/${G3LOG_LIBRARY}"
+	LOG_DOWNLOAD ${SILENT_3P_BUILD}
+	LOG_UPDATE ${SILENT_3P_BUILD}
+	LOG_CONFIGURE ${SILENT_3P_BUILD}
+	LOG_BUILD ${SILENT_3P_BUILD}
+	LOG_TEST ${SILENT_3P_BUILD}
+	LOG_INSTALL ${SILENT_3P_BUILD}
+)
+add_library(g3log INTERFACE)
+add_dependencies(g3log g3log_ep)
+set_target_properties(g3log PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${G3LOG_DIR}/include")
+set_target_properties(g3log PROPERTIES INTERFACE_LINK_LIBRARIES "${G3LOG_DIR}/lib/${G3LOG_LIBRARY}")
+
+# glog
+set(GLOG_DIR "${CMAKE_CURRENT_BINARY_DIR}/glog")
+set(GLOG_LIBRARY "${CMAKE_STATIC_LIBRARY_PREFIX}glog${CMAKE_STATIC_LIBRARY_SUFFIX}")
+ExternalProject_Add(glog_ep
+	PREFIX "${GLOG_DIR}"
+	UPDATE_COMMAND ""
+	GIT_REPOSITORY "https://github.com/google/glog.git"
+	GIT_TAG "4d391fe692ae6b9e0105f473945c415a3ce5a401"
+	CMAKE_GENERATOR "${CMAKE_GENERATOR}"
+	CMAKE_ARGS
+		"-Wno-dev"
+		"-DCMAKE_TOOLCHAIN_FILE:filepath=${CMAKE_TOOLCHAIN_FILE}"
+		"-DCMAKE_INSTALL_PREFIX:path=<INSTALL_DIR>"
+		"-DCMAKE_BUILD_TYPE:string=${CMAKE_BUILD_TYPE}"
+		"-DCMAKE_OSX_ARCHITECTURES:string=${CMAKE_OSX_ARCHITECTURES}"
+		"-DCMAKE_OSX_DEPLOYMENT_TARGET:string=${CMAKE_OSX_DEPLOYMENT_TARGET}"
+		"-DCMAKE_OSX_SYSROOT:path=${CMAKE_OSX_SYSROOT}"
+	LOG_DOWNLOAD ${SILENT_3P_BUILD}
+	LOG_UPDATE ${SILENT_3P_BUILD}
+	LOG_CONFIGURE ${SILENT_3P_BUILD}
+	LOG_BUILD ${SILENT_3P_BUILD}
+	LOG_TEST ${SILENT_3P_BUILD}
+	LOG_INSTALL ${SILENT_3P_BUILD}
+)
+add_library(glog INTERFACE)
+add_dependencies(glog glog_ep)
+set_target_properties(glog PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${GLOG_DIR}/include")
+set_target_properties(glog PROPERTIES INTERFACE_LINK_LIBRARIES "${GLOG_DIR}/lib/${GLOG_LIBRARY}")
+
+function(get_test_library lib var)
+	if((lib STREQUAL "zf_log_n") OR (lib STREQUAL "zf_log_Os"))
+		set(lib "zf_log")
+	endif()
+	set(${var} "${lib}" PARENT_SCOPE)
+endfunction()
+
+function(get_test_compile_options lib var)
+	set(compile_options)
+	if(lib STREQUAL "g3log")
+		# g3log public headers generate "braced-scalar-init" warnings
+		set(compile_options "-Wno-braced-scalar-init")
+	endif()
+	set(${var} "${compile_options}" PARENT_SCOPE)
+endfunction()
+
+function(add_executable_size_test lib)
+	get_test_library("${lib}" test_library)
+	get_test_compile_options("${lib}" compile_options)
+	add_target(test_call_site_size.str.${lib}.1 STATICLIB LIBRARIES "${lib}"
+		COMPILE_OPTIONS ${compile_options}
+		TIME_COMPILE "${CMAKE_CURRENT_BINARY_DIR}/compile_time.${lib}.json"
+		SOURCES test_executable_size.cpp
+		DEFINES "TEST_LIBRARY=${test_library}"
+			"TEST_SEVERAL_STATEMENTS")
+	add_target(test_call_site_size.str.${lib}.2 STATICLIB LIBRARIES "${lib}"
+		COMPILE_OPTIONS ${compile_options}
+		SOURCES test_executable_size.cpp
+		DEFINES "TEST_LIBRARY=${test_library}"
+			"TEST_SEVERAL_STATEMENTS" "TEST_EXTRA_STATEMENT")
+	add_target(test_call_site_size.fmti.${lib}.1 STATICLIB LIBRARIES "${lib}"
+		COMPILE_OPTIONS ${compile_options}
+		SOURCES test_executable_size.cpp
+		DEFINES "TEST_LIBRARY=${test_library}"
+			"TEST_FORMAT_INTS" "TEST_SEVERAL_STATEMENTS")
+	add_target(test_call_site_size.fmti.${lib}.2 STATICLIB LIBRARIES "${lib}"
+		COMPILE_OPTIONS ${compile_options}
+		SOURCES test_executable_size.cpp
+		DEFINES "TEST_LIBRARY=${test_library}"
+			"TEST_FORMAT_INTS" "TEST_SEVERAL_STATEMENTS" "TEST_EXTRA_STATEMENT")
+	add_target(test_executable_size.m1.${lib} EXECUTABLE LIBRARIES "${lib}" NO_THREADS
+		COMPILE_OPTIONS ${compile_options}
+		TIME_LINK "${CMAKE_CURRENT_BINARY_DIR}/link_time.${lib}.json"
+		SOURCES test_executable_size.cpp
+		DEFINES "TEST_LIBRARY=${test_library}")
+	list(APPEND PARAMETERS "-p" "call_site_size:str:${lib}:1:$<TARGET_FILE:test_call_site_size.str.${lib}.1>")
+	list(APPEND PARAMETERS "-p" "call_site_size:str:${lib}:2:$<TARGET_FILE:test_call_site_size.str.${lib}.2>")
+	list(APPEND PARAMETERS "-p" "call_site_size:fmti:${lib}:1:$<TARGET_FILE:test_call_site_size.fmti.${lib}.1>")
+	list(APPEND PARAMETERS "-p" "call_site_size:fmti:${lib}:2:$<TARGET_FILE:test_call_site_size.fmti.${lib}.2>")
+	list(APPEND PARAMETERS "-p" "executable_size:m1:${lib}:$<TARGET_FILE:test_executable_size.m1.${lib}>")
+	if(LAUNCH_RULES)
+		list(APPEND PARAMETERS "-p" "compile_time:${lib}:$<TARGET_PROPERTY:test_call_site_size.str.${lib}.1,TIME_COMPILE>")
+		list(APPEND PARAMETERS "-p" "link_time:${lib}:$<TARGET_PROPERTY:test_executable_size.m1.${lib},TIME_LINK>")
+	endif()
+	set(PARAMETERS "${PARAMETERS}" PARENT_SCOPE)
+endfunction()
+
+if(ZF_LOG_PERF_TEST_ZF_LOG_OS)
+	add_executable_size_test(zf_log_Os)
+endif()
+add_executable_size_test(zf_log_n)
+add_executable_size_test(spdlog)
+add_executable_size_test(easylog)
+add_executable_size_test(g3log)
+add_executable_size_test(glog)
+
+function(add_speed_test lib)
+	get_test_library("${lib}" test_library)
+	get_test_compile_options("${lib}" compile_options)
+	add_target(test_speed.str.${lib} EXECUTABLE
+		COMPILE_OPTIONS ${compile_options}
+		SOURCES test_speed.cpp
+		DEFINES "TEST_LIBRARY=${test_library}" "TEST_NULL_SINK"
+		LIBRARIES "${lib}")
+	add_target(test_speed.fmti.${lib} EXECUTABLE
+		COMPILE_OPTIONS ${compile_options}
+		SOURCES test_speed.cpp
+		DEFINES "TEST_LIBRARY=${test_library}" "TEST_NULL_SINK" "TEST_FORMAT_INTS"
+		LIBRARIES "${lib}")
+	add_target(test_speed.str-off.${lib} EXECUTABLE
+		COMPILE_OPTIONS ${compile_options}
+		SOURCES test_speed.cpp
+		DEFINES "TEST_LIBRARY=${test_library}" "TEST_NULL_SINK" "TEST_LOG_OFF"
+		LIBRARIES "${lib}")
+	add_target(test_speed.slowf-off.${lib} EXECUTABLE
+		COMPILE_OPTIONS ${compile_options}
+		SOURCES test_speed.cpp
+		DEFINES "TEST_LIBRARY=${test_library}" "TEST_NULL_SINK" "TEST_FORMAT_SLOW_FUNC" "TEST_LOG_OFF"
+		LIBRARIES "${lib}")
+	list(APPEND PARAMETERS "-p" "speed:str:${lib}:$<TARGET_FILE:test_speed.str.${lib}>")
+	list(APPEND PARAMETERS "-p" "speed:fmti:${lib}:$<TARGET_FILE:test_speed.fmti.${lib}>")
+	list(APPEND PARAMETERS "-p" "speed:str-off:${lib}:$<TARGET_FILE:test_speed.str-off.${lib}>")
+	list(APPEND PARAMETERS "-p" "speed:slowf-off:${lib}:$<TARGET_FILE:test_speed.slowf-off.${lib}>")
+	set(PARAMETERS "${PARAMETERS}" PARENT_SCOPE)
+endfunction()
+
+if(ZF_LOG_PERF_TEST_ZF_LOG_OS)
+	add_speed_test(zf_log_Os)
+endif()
+add_speed_test(zf_log_n)
+add_speed_test(spdlog)
+add_speed_test(easylog)
+add_speed_test(g3log)
+add_speed_test(glog)
+
+# results
+add_test(NAME perf_tests COMMAND "${PYTHON_EXECUTABLE}"
+	"${CMAKE_CURRENT_SOURCE_DIR}/run_tests.py"
+	-t "${CMAKE_CURRENT_BINARY_DIR}/results.txt"
+	-m "${CMAKE_CURRENT_BINARY_DIR}/results.md"
+	-b "${CMAKE_BUILD_TYPE}"
+	-v
+	${PARAMETERS})

+ 395 - 0
zf_log/tests/perf/run_tests.py

@@ -0,0 +1,395 @@
+#!/usr/bin/python
+
+import sys
+import os
+import argparse
+import subprocess
+import multiprocessing
+import json
+import pprint
+
+def take_first(value):
+	if type(value) is tuple or type(value) is list:
+		return value[0]
+	return value
+
+def take_order(value, order):
+	for i in range(len(order)):
+		if value == order[i]:
+			return i
+	return len(order)
+
+def take_map(value, keys, vals):
+	if len(keys) != len(vals):
+		raise RuntimeError("Length of keys and vals must match")
+	for i in range(len(keys)):
+		if value == keys[i]:
+			return vals[i]
+	return value
+
+def take_best(values, key, compare, reverse=False):
+	if 0 == len(values):
+		return []
+	values = sorted(values, key=key, reverse=reverse)
+	for i in range(1, len(values)):
+		if not compare(values[0], values[i]):
+			return values[0:i]
+	return values
+
+def take_plural(count, base, suffix):
+	if 1 == abs(count):
+		return base
+	return base + suffix
+
+def take_threads_variants():
+	# single thread
+	variants = [1]
+	cpus = multiprocessing.cpu_count()
+	if 1 > cpus:
+		cpus = 1
+	# load all CPUs
+	if 1 < cpus:
+		variants.append(cpus)
+	# overcommit
+	variants.append(2 * cpus)
+	return variants
+
+def _cmp_percentage(p, key, a, b):
+	if key(a) == key(b):
+		return True
+	if p >= abs((key(a) - key(b)) / float(key(a))):
+		return True
+	return False
+
+def cmp_percentage(p, key):
+	return lambda a, b: _cmp_percentage(p, key, a, b)
+
+def translate_test(test):
+	name = take_first(test)
+	if "call_site_size.str" == name:
+		return 1000, "Call site size: string"
+	if "call_site_size.fmti" == name:
+		return 2000, "Call site size: 3 integers"
+	if "executable_size.m1" == name:
+		return 3000, "Executable size: 1 module"
+	if "compile_time" == name:
+		return 4000, "Module compile time"
+	if "link_time" == name:
+		return 5000, "Executable link time"
+	if "speed" == name:
+		threads = test[1]
+		mode = test[2]
+		mode_keys = ["str",    "fmti",       "str-off",        "slowf-off"]
+		mode_vals = ["string", "3 integers", "string, off", "slow function, off"]
+		tr_mode = take_map(mode, mode_keys, mode_vals)
+		order = 10 * threads + take_order(mode, mode_keys)
+		return 6000 + order, "Speed: %i %s, %s" % (threads, take_plural(threads, "thread", "s"), tr_mode)
+	if type(test) is tuple or type(test) is list:
+		return 31416, ", ".join(test)
+	return 27183, test
+
+def translate_subj(subj):
+	if "zf_log_n" == subj:
+		return 31416, "zf_log"
+	if "easylog" == subj:
+		return 31416, "Easylogging++"
+	return 31416, subj
+
+def translation_sort_key(v):
+	return "[%04i] %s" % (v[0], v[1])
+
+def translation_value(v):
+	return v[1]
+
+class data_cell:
+	def __init__(self):
+		self.best = False
+	def set_best(self, best=True):
+		self.best = best
+	def ifbest(self, a, b):
+		if hasattr(self, "best") and self.best:
+			return a
+		return b
+
+class data_str(data_cell):
+	def __init__(self, value):
+		if type(value) is not str:
+			raise RuntimeError("Not a string")
+		self.value = value
+	def __str__(self):
+		return self.value
+	def __repr__(self):
+		return repr(self.value)
+
+class data_bytes(data_cell):
+	def __init__(self, value):
+		if type(value) is not int:
+			raise RuntimeError("Not an int")
+		self.value = value
+	def __str__(self):
+		if self.value < 1024:
+			return "%i B" % (self.value)
+		if self.value < 1024 * 1024:
+			return "%.2f KB" % (self.value / 1024.0)
+		return "%.2f MB" % (self.value / 1024.0 / 1024.0)
+	def __repr__(self):
+		return repr(self.value)
+
+class data_seconds(data_cell):
+	def __init__(self, value):
+		if type(value) is not int and type(value) is not float:
+			raise RuntimeError("Not an int or float")
+		self.value = value
+	def __str__(self):
+		return "%.3f sec" % (self.value)
+	def __repr__(self):
+		return repr(self.value)
+
+class data_freq(data_cell):
+	def __init__(self, count, seconds):
+		if type(count) is not int and type(count) is not float:
+			raise RuntimeError("Not an int or float")
+		if type(seconds) is not int and type(seconds) is not float:
+			raise RuntimeError("Not an int or float")
+		self.count = count
+		self.seconds = seconds
+	def __str__(self):
+		return "{:,}".format(self.count / self.seconds)
+	def __repr__(self):
+		return repr((self.count, self.seconds))
+	def freq(self):
+		return self.count / self.seconds
+
+def get_table_data(result):
+	# collect all tests
+	tests = result.keys()
+	tests = sorted(tests, key=lambda x: translation_sort_key(translate_test(x)))
+	# collect all subjects
+	subjs = set()
+	for test in tests:
+		subjs.update(result[test].keys())
+	subjs = sorted(subjs, key=lambda x: translation_sort_key(translate_subj(x)))
+	# create table
+	rows = len(tests) + 1
+	cols = len(subjs) + 1
+	table = [[None for _ in range(cols)] for _ in range(rows)]
+	# put names and captions
+	for i in range(1, rows):
+		table[i][0] = data_str(translation_value(translate_test(tests[i - 1])))
+	for j in range(1, cols):
+		table[0][j] = data_str(translation_value(translate_subj(subjs[j - 1])))
+	# put data
+	for i in range(1, rows):
+		for j in range(1, cols):
+			test = tests[i - 1]
+			subj = subjs[j - 1]
+			if subj in result[test]:
+				table[i][j] = result[test][subj]
+	# gen cells content
+	for i in range(0, rows):
+		for j in range(0, cols):
+			value = table[i][j]
+			if value is None:
+				value = data_str("")
+			elif not isinstance(value, data_cell):
+				raise RuntimeError("Value \"%s\" is of unsupported type \"%s\"" % (value, type(value)))
+			table[i][j] = value
+	# find cols width
+	widths = [0 for _ in range(cols)]
+	for j in range(0, cols):
+		for i in range(0, rows):
+			s = str(table[i][j])
+			if widths[j] < len(s):
+				widths[j] = len(s)
+	return table, rows, cols, widths
+
+def gen_table_ascii(result):
+	table, rows, cols, widths = get_table_data(result)
+	# apply cell format
+	margin_norm  = (" ", " ")
+	margin_best  = ("*", " ")
+	margins = max(map(len, margin_norm)) + max(map(len, margin_norm))
+	for i in range(1, rows):
+		table[i][0] = str(table[i][0]).ljust(widths[0]).join(margin_norm)
+	for j in range(0, cols):
+		table[0][j] = str(table[0][j]).center(widths[j]).join(margin_norm)
+	for i in range(1, rows):
+		for j in range(1, cols):
+			data = table[i][j]
+			margin = data.ifbest(margin_best, margin_norm)
+			table[i][j] = str(data).rjust(widths[j]).join(margin)
+	# draw chart
+	line = "+" + "-" * (sum(widths) + (margins + 1) * len(widths) - 1) + "+\n"
+	chart = line
+	for row in table:
+		chart += "|"
+		for cell in row:
+			chart += "%s|" % (cell)
+		chart += "\n" + line
+	return chart
+
+def gen_table_markdown(result):
+	table, rows, cols, widths = get_table_data(result)
+	# apply cell format
+	margin_norm  = ("  ", "  ")
+	margin_best  = ("**", "**")
+	margins = max(map(len, margin_norm)) + max(map(len, margin_norm))
+	for i in range(1, rows):
+		table[i][0] = str(table[i][0]).ljust(widths[0]).join(margin_norm)
+	for j in range(0, cols):
+		table[0][j] = str(table[0][j]).center(widths[j]).join(margin_norm)
+	for i in range(1, rows):
+		for j in range(1, cols):
+			data = table[i][j]
+			margin = data.ifbest(margin_best, margin_norm)
+			table[i][j] = str(data).join(margin).rjust(widths[j] + margins)
+	# draw chart
+	chart = ""
+	if 0 == rows:
+		return chart
+	chart += "|"
+	for cell in table[0]:
+		chart += "%s|" % (cell)
+	chart += "\n"
+	chart += "| " + "-" * (margins + widths[0] - 2) + " |"
+	for i in range(1, cols):
+		chart += " " + "-" * (margins + widths[i] - 2) + ":|"
+	chart += "\n"
+	for i in range(1, rows):
+		chart += "|"
+		for j in range(0, cols):
+			chart += "%s|" % (table[i][j])
+		chart += "\n"
+	return chart
+
+def run_call_site_size(params, result):
+	if type(result) is not dict:
+		raise RuntimeError("Not a dictionary")
+	id = "call_site_size"
+	params = params[id]
+	for mode in params:
+		name = "%s.%s" % (id, mode)
+		values = dict()
+		for subj in params[mode]:
+			data = params[mode][subj]
+			sz1 = os.path.getsize(data["1"])
+			sz2 = os.path.getsize(data["2"])
+			data = data_bytes(sz2 - sz1)
+			values[subj] = data
+		result[name] = values
+		for best in take_best(values.values(),
+							  key=lambda x: x.value,
+							  compare=cmp_percentage(0.0, key=lambda x: x.value)):
+			best.set_best()
+
+def run_executable_size(params, result):
+	if type(result) is not dict:
+		raise RuntimeError("Not a dictionary")
+	id = "executable_size"
+	params = params[id]
+	for mode in params:
+		name = "%s.%s" % (id, mode)
+		values = dict()
+		for subj in params[mode]:
+			sz = os.path.getsize(params[mode][subj])
+			values[subj] = data_bytes(sz)
+		result[name] = values
+		for best in take_best(values.values(),
+							  key=lambda x: x.value,
+							  compare=cmp_percentage(0.0, key=lambda x: x.value)):
+			best.set_best()
+
+def run_build_time(params, result, id, optional=False):
+	if type(result) is not dict:
+		raise RuntimeError("Not a dictionary")
+	if optional and id not in params:
+		return
+	params = params[id]
+	values = dict()
+	for subj in params:
+		with open(params[subj], "r") as f:
+			dt = json.load(f)["dt"]
+			values[subj] = data_seconds(dt)
+	result[id] = values
+	for best in take_best(values.values(),
+						  key=lambda x: x.value,
+						  compare=cmp_percentage(0.2, key=lambda x: x.value)):
+		best.set_best()
+
+def run_speed(params, result, threads_variants, seconds=1):
+	if type(result) is not dict:
+		raise RuntimeError("Not a dictionary")
+	id = "speed"
+	params = params[id]
+	for threads in threads_variants:
+		for mode in params:
+			name = (id, threads, mode)
+			values = dict()
+			for subj in params[mode]:
+				path = params[mode][subj]
+				p = subprocess.Popen([path, str(threads), str(seconds)], stdout=subprocess.PIPE)
+				stdout, stderr = p.communicate()
+				values[subj] = data_freq(int(stdout), seconds)
+			result[name] = values
+			for best in take_best(values.values(),
+								  key=lambda x: x.freq(), reverse=True,
+								  compare=cmp_percentage(0.1, key=lambda x: x.freq())):
+				best.set_best()
+
+def run_tests(params):
+	result = dict()
+	run_call_site_size(params, result)
+	run_executable_size(params, result)
+	run_build_time(params, result, "compile_time", optional=True)
+	run_build_time(params, result, "link_time", optional=True)
+	run_speed(params, result, take_threads_variants())
+	return result
+
+def main(argv):
+	parser = argparse.ArgumentParser()
+	parser.add_argument("-t", "--text", metavar="PATH", default=None,
+			help="Text output file path")
+	parser.add_argument("-m", "--markdown", metavar="PATH", default=None,
+			help="Markdown output file path")
+	parser.add_argument("-p", "--parameter", metavar="VALUE", action="append", default=[],
+			help="Input parameter")
+	parser.add_argument("-b", "--build", metavar="TYPE",
+			help="Input parameter")
+	parser.add_argument("-v", "--verbose", action="store_true",
+			help="Verbose output")
+	args = parser.parse_args(argv[1:])
+	# process parameters
+	params = dict()
+	for p in args.parameter:
+		d = params
+		vs = p.split(":")
+		key = None
+		for i in range(len(vs)):
+			if i == len(vs) - 1:
+				d[key] = vs[i]
+				break
+			if key is not None:
+				d = d[key]
+			key = vs[i]
+			if key not in d:
+				d[key] = dict()
+	if args.verbose:
+		sys.stderr.write(pprint.pformat(params, indent=4))
+		sys.stderr.write("\n")
+	# run, run, run!
+	result = run_tests(params)
+	if args.verbose:
+		sys.stderr.write(pprint.pformat(result, indent=4))
+		sys.stderr.write("\n")
+	if args.text is not None:
+		with open(args.text, "w") as f:
+			table = gen_table_ascii(result)
+			f.write(table)
+	if args.markdown is not None:
+		with open(args.markdown, "w") as f:
+			table = gen_table_markdown(result)
+			f.write(table)
+	return 0
+
+if __name__ == "__main__":
+	sys.exit(main(sys.argv))

+ 20 - 0
zf_log/tests/perf/test_executable_size.cpp

@@ -0,0 +1,20 @@
+#include "test_switch.h"
+
+int main(int argc, char *argv[])
+{
+	(void)argc; (void)argv;
+	XLOG_INIT();
+	XLOG_STATEMENT();
+#ifdef TEST_SEVERAL_STATEMENTS
+	XLOG_STATEMENT();
+	XLOG_STATEMENT();
+	XLOG_STATEMENT();
+	XLOG_STATEMENT();
+	XLOG_STATEMENT();
+	XLOG_STATEMENT();
+#endif
+#ifdef TEST_EXTRA_STATEMENT
+	XLOG_STATEMENT();
+#endif
+	return 0;
+}

+ 162 - 0
zf_log/tests/perf/test_speed.cpp

@@ -0,0 +1,162 @@
+#include <cstdint>
+#include <cinttypes>
+#include <atomic>
+#include <functional>
+#include <memory>
+#include <vector>
+#include <string>
+#include <thread>
+#include <condition_variable>
+#include "test_switch.h"
+
+namespace
+{
+	class thread_latch
+	{
+	public:
+		thread_latch(): _go(false), _halt(false) {}
+		void wait() const;
+		void release();
+		bool halted() const;
+		void halt();
+	private:
+		mutable std::mutex _m;
+		mutable std::condition_variable _cv;
+		bool _go;
+		std::atomic<bool> _halt;
+	};
+
+	void thread_latch::wait() const
+	{
+		std::unique_lock<std::mutex> lock(_m);
+		while (!_go)
+		{
+			_cv.wait(lock);
+		}
+	}
+
+	void thread_latch::release()
+	{
+		const std::lock_guard<std::mutex> lock(_m);
+		_go = true;
+		_cv.notify_all();
+	}
+
+	bool thread_latch::halted() const
+	{
+		return _halt;
+	}
+
+	void thread_latch::halt()
+	{
+		_halt = true;
+	}
+
+	class thread_group
+	{
+	public:
+		thread_group(const thread_latch &latch, const unsigned n);
+		void start(const std::function<void(void)> f);
+		uint64_t join();
+	private:
+		void run();
+		const thread_latch &_latch;
+		const unsigned _n;
+		std::atomic<uint64_t> _count;
+		std::function<void(void)> _f;
+		std::vector<std::thread> _ths;
+	};
+
+	thread_group::thread_group(const thread_latch &latch, const unsigned n):
+		_latch(latch), _n(n), _count(0)
+	{
+	}
+
+	void thread_group::start(const std::function<void(void)> f)
+	{
+		_f = std::move(f);
+		for (auto i = _n; 0 < i--;)
+		{
+			_ths.push_back(std::thread(&thread_group::run, this));
+		}
+	}
+
+	uint64_t thread_group::join()
+	{
+		for (auto &th : _ths)
+		{
+			th.join();
+		}
+		_ths.clear();
+		return _count;
+	}
+
+	void thread_group::run()
+	{
+		uint64_t count = 0;
+		while (!_latch.halted())
+		{
+			_f();
+			++count;
+		}
+		_count += count;
+	}
+
+	class bench
+	{
+	public:
+		bench() {}
+		void setup(const std::function<void(void)> f);
+		unsigned run(const unsigned n, const unsigned seconds);
+	private:
+		std::function<void(void)> _f;
+	};
+
+	void bench::setup(const std::function<void(void)> f)
+	{
+		_f = std::move(f);
+	}
+
+	unsigned bench::run(const unsigned n, const unsigned seconds)
+	{
+		thread_latch latch;
+		thread_group tg(latch, n);
+		tg.start(_f);
+		latch.release();
+		std::this_thread::sleep_for(std::chrono::seconds(seconds));
+		latch.halt();
+		return tg.join();
+	}
+}
+
+int main(int argc, char *argv[])
+{
+	XLOG_INIT();
+	unsigned n = 1;
+	if (1 < argc)
+	{
+		n = std::stoi(argv[1]);
+		if (n <= 0 || 99 < n)
+		{
+			fprintf(stderr, "Bad thread count (%u).\n", n);
+			return -1;
+		}
+	}
+	unsigned seconds = 1;
+	if (2 < argc)
+	{
+		seconds = std::stoi(argv[2]);
+		if (seconds <= 0 || 60*60 < seconds)
+		{
+			fprintf(stderr, "Bad duration (%u).\n", seconds);
+			return -1;
+		}
+	}
+	bench b;
+	b.setup([](){
+		XLOG_STATEMENT();
+	});
+	const uint64_t k = b.run(n, seconds);
+	fprintf(stdout, "%" PRIu64 "\n", static_cast<uint64_t>(k));
+	return 0;
+}

+ 256 - 0
zf_log/tests/perf/test_switch.h

@@ -0,0 +1,256 @@
+#pragma once
+
+#define TEST_LIBRARY_ID_zf_log 1
+#define TEST_LIBRARY_ID_spdlog 2
+#define TEST_LIBRARY_ID_easylog 3
+#define TEST_LIBRARY_ID_g3log 4
+#define TEST_LIBRARY_ID_glog 5
+
+#define _CONCAT(a, b) a##b
+#define CONCAT(a, b) _CONCAT(a, b)
+
+#if TEST_LIBRARY_ID_zf_log == CONCAT(TEST_LIBRARY_ID_, TEST_LIBRARY)
+	#define TEST_LIBRARY_ZF_LOG
+#elif TEST_LIBRARY_ID_spdlog == CONCAT(TEST_LIBRARY_ID_, TEST_LIBRARY)
+	#define TEST_LIBRARY_SPDLOG
+#elif TEST_LIBRARY_ID_easylog == CONCAT(TEST_LIBRARY_ID_, TEST_LIBRARY)
+	#define TEST_LIBRARY_EASYLOG
+#elif TEST_LIBRARY_ID_g3log == CONCAT(TEST_LIBRARY_ID_, TEST_LIBRARY)
+	#define TEST_LIBRARY_G3LOG
+#elif TEST_LIBRARY_ID_glog == CONCAT(TEST_LIBRARY_ID_, TEST_LIBRARY)
+	#define TEST_LIBRARY_GLOG
+#else
+	#error Unknown test library name
+#endif
+
+#define XLOG_STRING_LITERAL "A random string"
+#define XLOG_INT_LITERAL 42
+/* It's important that values are not const. Otherwise compilers will be able
+ * to optimize out things that we care about.
+ */
+extern const char *XLOG_STRING_VALUE;
+extern int XLOG_INT_VALUE;
+#ifndef TEST_SWITCH_MODULE
+	const char *XLOG_STRING_VALUE = XLOG_STRING_LITERAL;
+	int XLOG_INT_VALUE = XLOG_INT_LITERAL;
+#endif
+#ifdef TEST_FORMAT_SLOW_FUNC
+	#include <chrono>
+	#include <thread>
+	static int XLOG_SLOW_FUNC()
+	{
+		std::this_thread::sleep_for(std::chrono::milliseconds(1));
+		return (int)std::hash<std::thread::id>()(std::this_thread::get_id());
+	}
+#endif
+
+#define XLOG_MESSAGE_STR_LITERAL_PRINTF XLOG_STRING_LITERAL
+#define XLOG_MESSAGE_STR_LITERAL_CPPFMT XLOG_STRING_LITERAL
+#define XLOG_MESSAGE_STR_LITERAL_STREAM XLOG_STRING_LITERAL
+
+#define XLOG_MESSAGE_3INT_VALUES_PRINTF \
+		"vA: %i, vB: %i, vC: %i", \
+		XLOG_INT_VALUE, XLOG_INT_VALUE, XLOG_INT_VALUE
+#define XLOG_MESSAGE_3INT_VALUES_CPPFMT \
+		"vA: {}, vB: {}, vC: {}", \
+		XLOG_INT_VALUE, XLOG_INT_VALUE, XLOG_INT_VALUE
+#define XLOG_MESSAGE_3INT_VALUES_STREAM \
+		"vA: " << XLOG_INT_VALUE << ", vB: " << XLOG_INT_VALUE << \
+		", vC: " << XLOG_INT_VALUE
+
+#define XLOG_MESSAGE_SLOW_FUNC_PRINTF "%i", XLOG_SLOW_FUNC()
+#define XLOG_MESSAGE_SLOW_FUNC_CPPFMT "{}", XLOG_SLOW_FUNC()
+#define XLOG_MESSAGE_SLOW_FUNC_STREAM XLOG_SLOW_FUNC()
+
+#ifdef TEST_LIBRARY_ZF_LOG
+	#include <zf_log.h>
+	#ifdef TEST_NULL_SINK
+		#define _XLOG_INIT_SINK() \
+			zf_log_set_output_v(ZF_LOG_PUT_STD, 0, \
+								[](const zf_log_message *, void *){})
+	#else
+		#define _XLOG_INIT_SINK()
+	#endif
+	#ifdef TEST_LOG_OFF
+		#define _XLOG_INIT_LEVEL() \
+			zf_log_set_output_level(ZF_LOG_ERROR)
+	#else
+		#define _XLOG_INIT_LEVEL()
+	#endif
+	static void XLOG_INIT()
+	{
+		_XLOG_INIT_SINK();
+		_XLOG_INIT_LEVEL();
+	}
+
+	#if defined(TEST_FORMAT_INTS)
+		#define XLOG_STATEMENT() ZF_LOGI(XLOG_MESSAGE_3INT_VALUES_PRINTF)
+	#elif defined(TEST_FORMAT_SLOW_FUNC)
+		#define XLOG_STATEMENT() ZF_LOGI(XLOG_MESSAGE_SLOW_FUNC_PRINTF)
+	#else
+		#define XLOG_STATEMENT() ZF_LOGI(XLOG_MESSAGE_STR_LITERAL_PRINTF)
+	#endif
+#endif
+
+#ifdef TEST_LIBRARY_SPDLOG
+	#include <spdlog/spdlog.h>
+	extern const std::shared_ptr<spdlog::logger> g_logger;
+	#ifdef TEST_NULL_SINK
+		class null_sink: public spdlog::sinks::sink
+		{
+		public:
+			void log(const spdlog::details::log_msg &) override {}
+			void flush() override {}
+		};
+		#ifndef TEST_SWITCH_MODULE
+			const std::shared_ptr<spdlog::logger> g_logger = spdlog::create<null_sink>("null");
+		#endif
+	#else
+		#ifndef TEST_SWITCH_MODULE
+			const std::shared_ptr<spdlog::logger> g_logger = spdlog::stderr_logger_st("stderr");
+		#endif
+	#endif
+	#ifdef TEST_LOG_OFF
+		#define _XLOG_INIT_LEVEL() spdlog::set_level(spdlog::level::err)
+	#else
+		#define _XLOG_INIT_LEVEL()
+	#endif
+	static void XLOG_INIT()
+	{
+		_XLOG_INIT_LEVEL();
+	}
+
+	#if defined(TEST_FORMAT_INTS)
+		#define XLOG_STATEMENT() g_logger->info(XLOG_MESSAGE_3INT_VALUES_CPPFMT)
+	#elif defined(TEST_FORMAT_SLOW_FUNC)
+		#define XLOG_STATEMENT() g_logger->info(XLOG_MESSAGE_SLOW_FUNC_CPPFMT)
+	#else
+		#define XLOG_STATEMENT() g_logger->info(XLOG_MESSAGE_STR_LITERAL_CPPFMT)
+	#endif
+#endif
+
+#ifdef TEST_LIBRARY_EASYLOG
+	#ifdef TEST_NULL_SINK
+		class null_stream {
+		public:
+			template<typename T>
+			null_stream &operator<<(T) { return *this; }
+			null_stream &operator<<(std::ostream& (*)(std::ostream&)) { return *this; }
+		};
+		extern null_stream g_null;
+		#ifndef TEST_SWITCH_MODULE
+			null_stream g_null;
+		#endif
+		#define ELPP_CUSTOM_COUT g_null
+		#define _XLOG_INIT_SINK() \
+			el::Loggers::reconfigureAllLoggers(el::ConfigurationType::ToFile, "false")
+	#else
+		#define _XLOG_INIT_SINK()
+	#endif
+	#ifdef TEST_LOG_OFF
+		#define _XLOG_INIT_LEVEL() \
+			el::Loggers::reconfigureAllLoggers(el::ConfigurationType::Enabled, "false")
+	#else
+		#define _XLOG_INIT_LEVEL()
+	#endif
+	#define ELPP_THREAD_SAFE
+	#include <easylogging++.h>
+	#ifndef TEST_SWITCH_MODULE
+		INITIALIZE_EASYLOGGINGPP
+	#endif
+	static void XLOG_INIT()
+	{
+		_XLOG_INIT_SINK();
+		_XLOG_INIT_LEVEL();
+	}
+
+
+	#if defined(TEST_FORMAT_INTS)
+		#define XLOG_STATEMENT() LOG(INFO) << XLOG_MESSAGE_3INT_VALUES_STREAM
+	#elif defined(TEST_FORMAT_SLOW_FUNC)
+		#define XLOG_STATEMENT() LOG(INFO) << XLOG_MESSAGE_SLOW_FUNC_STREAM
+	#else
+		#define XLOG_STATEMENT() LOG(INFO) << XLOG_MESSAGE_STR_LITERAL_STREAM
+	#endif
+#endif
+
+#ifdef TEST_LIBRARY_G3LOG
+	#include <g3log/g3log.hpp>
+	#include <g3log/logworker.hpp>
+	#ifdef TEST_NULL_SINK
+		class null_sink
+		{
+		public:
+			void log(const std::string) {}
+		};
+		#define _XLOG_INIT_SINK() \
+			auto worker = g3::LogWorker::createLogWorker(); \
+			g3::initializeLogging(worker.get()); \
+			worker->addSink(std::unique_ptr<null_sink>(new null_sink), &null_sink::log);
+	#else
+		#define _XLOG_INIT_SINK() \
+			auto worker = g3::LogWorker::createLogWorker(); \
+			g3::initializeLogging(worker.get()); \
+			worker->addDefaultLogger("g3log", "g3log.log");
+	#endif
+	#ifdef TEST_LOG_OFF
+		#ifndef G3_DYNAMIC_LOGGING
+			#error g3log must be built with G3_DYNAMIC_LOGGING defined
+		#endif
+		#define _XLOG_INIT_LEVEL() \
+			g3::only_change_at_initialization::setLogLevel(INFO, false)
+	#else
+		#define _XLOG_INIT_LEVEL()
+	#endif
+	static void XLOG_INIT()
+	{
+		_XLOG_INIT_SINK();
+		_XLOG_INIT_LEVEL();
+	}
+
+	#if defined(TEST_FORMAT_INTS)
+		#define XLOG_STATEMENT() LOGF(INFO, XLOG_MESSAGE_3INT_VALUES_PRINTF)
+	#elif defined(TEST_FORMAT_SLOW_FUNC)
+		#define XLOG_STATEMENT() LOGF(INFO, XLOG_MESSAGE_SLOW_FUNC_PRINTF)
+	#else
+		#define XLOG_STATEMENT() LOGF(INFO, XLOG_MESSAGE_STR_LITERAL_PRINTF)
+	#endif
+#endif
+
+#ifdef TEST_LIBRARY_GLOG
+	#include <glog/logging.h>
+	#ifdef TEST_NULL_SINK
+		class null_sink: public google::LogSink
+		{
+		public:
+			void send(google::LogSeverity, const char *, const char *, int,
+					  const struct ::tm *, const char *, size_t) override {}
+			void WaitTillSent() override {}
+		};
+		extern null_sink g_sink;
+		#ifndef TEST_SWITCH_MODULE
+			null_sink g_sink;
+		#endif
+		#define _XLOG_LOG(lvl) LOG_TO_SINK_BUT_NOT_TO_LOGFILE(&g_sink, lvl)
+	#else
+		#define _XLOG_LOG(lvl) LOG(lvl)
+	#endif
+	#ifdef TEST_LOG_OFF
+		#define _XLOG_INIT_LEVEL() FLAGS_minloglevel = google::ERROR
+	#else
+		#define _XLOG_INIT_LEVEL()
+	#endif
+	static void XLOG_INIT()
+	{
+		google::InitGoogleLogging("glog");
+		_XLOG_INIT_LEVEL();
+	}
+
+	#if defined(TEST_FORMAT_INTS)
+		#define XLOG_STATEMENT() _XLOG_LOG(INFO) << XLOG_MESSAGE_3INT_VALUES_STREAM
+	#elif defined(TEST_FORMAT_SLOW_FUNC)
+		#define XLOG_STATEMENT() _XLOG_LOG(INFO) << XLOG_MESSAGE_SLOW_FUNC_STREAM
+	#else
+		#define XLOG_STATEMENT() _XLOG_LOG(INFO) << XLOG_MESSAGE_STR_LITERAL_STREAM
+	#endif
+#endif

+ 26 - 0
zf_log/tests/perf/time_it.py

@@ -0,0 +1,26 @@
+#!/usr/bin/python
+
+import sys
+import time
+import subprocess
+import json
+
+def usage(f, st):
+	f.write("Usage:\n")
+	f.write("    time_it.py file utility [argument ...]\n\n")
+	f.write("Writes running time of utility into file.\n")
+	return st
+
+def main(argv):
+	if 3 > len(argv):
+		return usage(sys.stderr, -1)
+	t = time.time()
+	ret = subprocess.call(argv[2:])
+	t = time.time() - t
+	if 0 == ret:
+		with open(argv[1], "w") as f:
+			json.dump({"dt":t}, f)
+	return ret
+
+if __name__ == "__main__":
+	sys.exit(main(sys.argv))

+ 21 - 0
zf_log/tests/test_aux_spec.c

@@ -0,0 +1,21 @@
+#include <zf_log.c>
+#include <zf_test.h>
+
+static const zf_log_spec g_spec =
+{
+	ZF_LOG_GLOBAL_FORMAT,
+	ZF_LOG_GLOBAL_OUTPUT
+};
+
+static void mock_output_callback(const zf_log_message *msg, void *arg)
+{
+	(void)msg; (void)arg;
+}
+
+int main(int argc, char *argv[])
+{
+	zf_log_set_output_v(ZF_LOG_PUT_STD, 0, mock_output_callback);
+	ZF_LOGI_AUX(&g_spec, "aux log, argc=%i", argc);
+	ZF_LOGI_MEM_AUX(&g_spec, argv, argc * sizeof(*argv), "aux log, argv pointers:");
+	return 0;
+}

+ 17 - 0
zf_log/tests/test_builtin_output_facilities.c

@@ -0,0 +1,17 @@
+#if defined(_WIN32) || defined(_WIN64)
+	#define ZF_LOG_USE_DEBUGSTRING
+#endif
+#if defined(__APPLE__) && defined(__MACH__)
+	#define ZF_LOG_USE_NSLOG
+#endif
+#if defined(__ANDROID__)
+	#define ZF_LOG_USE_ANDROID_LOG
+#endif
+#include <zf_log.c>
+
+int main(int argc, char *argv[])
+{
+	(void)argc; (void)argv;
+	/* Testing compilation only for now. */
+	return 0;
+}

+ 34 - 0
zf_log/tests/test_call_site_size_censoring.c

@@ -0,0 +1,34 @@
+#define ZF_LOG_LEVEL ZF_LOG_INFO
+#include <zf_log.h>
+#include <time.h>
+
+#define LOG_SOME_ONCE \
+	ZF_LOG_SECRET(ZF_LOGI("Lorem ipsum dolor sit amet")); \
+	time(0); \
+
+static void log_some()
+{
+	LOG_SOME_ONCE
+	LOG_SOME_ONCE
+	LOG_SOME_ONCE
+	LOG_SOME_ONCE
+	LOG_SOME_ONCE
+	LOG_SOME_ONCE
+	LOG_SOME_ONCE
+	LOG_SOME_ONCE
+	LOG_SOME_ONCE
+	LOG_SOME_ONCE
+	LOG_SOME_ONCE
+	LOG_SOME_ONCE
+	LOG_SOME_ONCE
+	LOG_SOME_ONCE
+	LOG_SOME_ONCE
+	LOG_SOME_ONCE
+}
+
+int main(int argc, char *argv[])
+{
+	(void)argc; (void)argv;
+	log_some();
+	return 0;
+}

+ 40 - 0
zf_log/tests/test_call_site_size_conditional.c

@@ -0,0 +1,40 @@
+#define ZF_LOG_LEVEL ZF_LOG_INFO
+#include <zf_log.h>
+#include <time.h>
+
+#if TEST_CONDITION
+	#define CONDITION 1 < 2
+#else
+	#define CONDITION 1 > 2
+#endif
+
+#define LOG_SOME_ONCE \
+	ZF_LOG_IF(CONDITION, ZF_LOGI("Lorem ipsum dolor sit amet")); \
+	time(0); \
+
+static void log_some()
+{
+	LOG_SOME_ONCE
+	LOG_SOME_ONCE
+	LOG_SOME_ONCE
+	LOG_SOME_ONCE
+	LOG_SOME_ONCE
+	LOG_SOME_ONCE
+	LOG_SOME_ONCE
+	LOG_SOME_ONCE
+	LOG_SOME_ONCE
+	LOG_SOME_ONCE
+	LOG_SOME_ONCE
+	LOG_SOME_ONCE
+	LOG_SOME_ONCE
+	LOG_SOME_ONCE
+	LOG_SOME_ONCE
+	LOG_SOME_ONCE
+}
+
+int main(int argc, char *argv[])
+{
+	(void)argc; (void)argv;
+	log_some();
+	return 0;
+}

+ 533 - 0
zf_log/tests/test_call_site_size_fmt_args.c

@@ -0,0 +1,533 @@
+#define ZF_LOG_LEVEL ZF_LOG_INFO
+#include <zf_log.h>
+#include <time.h>
+
+int g_int1;
+int g_int2;
+char *g_str1;
+char *g_str2;
+void *g_p1;
+void *g_p2;
+
+static void log_some()
+{
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+	ZF_LOGI("%i-%i/%s-%s/%p-%p", g_int1, g_int2, g_str1, g_str2, g_p1, g_p2);
+	time(0);
+}
+
+int main(int argc, char *argv[])
+{
+	(void)argc; (void)argv;
+	log_some();
+	return 0;
+}

+ 526 - 0
zf_log/tests/test_call_site_size_msg_only.c

@@ -0,0 +1,526 @@
+#define ZF_LOG_LEVEL ZF_LOG_INFO
+#include <zf_log.h>
+#include <time.h>
+
+static void log_some()
+{
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+	ZF_LOGI("Lorem ipsum dolor sit amet");
+	time(0);
+}
+
+int main(int argc, char *argv[])
+{
+	(void)argc; (void)argv;
+	log_some();
+	return 0;
+}

+ 74 - 0
zf_log/tests/test_censoring.c

@@ -0,0 +1,74 @@
+#include <zf_log.c>
+#include <zf_test.h>
+
+static unsigned g_logged = 0;
+
+static void mock_output_callback(const zf_log_message *msg, void *arg)
+{
+	(void)msg; (void)arg;
+	++g_logged;
+}
+
+static unsigned was_logged()
+{
+	const unsigned logged = g_logged;
+	g_logged = 0;
+	return logged;
+}
+
+static void some_function_0()
+{
+	++g_logged;
+}
+
+static void some_function_1(const unsigned d)
+{
+	assert(0 < d);
+	g_logged += d;
+}
+
+#if TEST_LOG_SECRETS
+	#define EXPECTED_LINES(n) TEST_VERIFY_EQUAL(was_logged(), (n))
+#else
+	#define EXPECTED_LINES(n) TEST_VERIFY_EQUAL(was_logged(), 0)
+#endif
+
+static void test_censoring()
+{
+	const char name[] = "Orion";
+	const char address[] = "Space";
+	const char cipher[] = "Secret";
+	const zf_log_spec spec = {ZF_LOG_GLOBAL_FORMAT, ZF_LOG_GLOBAL_OUTPUT};
+
+	#if ZF_LOG_SECRETS
+		ZF_LOGI("Customer name: %s", name);
+		ZF_LOGI("Customer address: %s", address);
+	#endif
+	EXPECTED_LINES(2);
+
+	ZF_LOG_SECRET(ZF_LOGI("Customer name: %s", name));
+	EXPECTED_LINES(1);
+	ZF_LOG_SECRET(ZF_LOGI_MEM(cipher, sizeof(cipher), "Customer cipher:"));
+	EXPECTED_LINES(2);
+	ZF_LOG_SECRET(ZF_LOGI_AUX(&spec, "Customer address: %s", address));
+	EXPECTED_LINES(1);
+	ZF_LOG_SECRET(ZF_LOGI_MEM_AUX(&spec, cipher, sizeof(cipher), "Customer cipher:"));
+	EXPECTED_LINES(2);
+
+	ZF_LOG_SECRET(some_function_0());
+	EXPECTED_LINES(1);
+	ZF_LOG_SECRET(some_function_1(42));
+	EXPECTED_LINES(42);
+
+	ZF_LOGI("Must always log this");
+	TEST_VERIFY_EQUAL(was_logged(), 1);
+}
+
+int main(int argc, char *argv[])
+{
+	zf_log_set_output_v(ZF_LOG_PUT_STD, 0, mock_output_callback);
+
+	TEST_RUNNER_CREATE(argc, argv);
+	TEST_EXECUTE(test_censoring());
+	return TEST_RUNNER_EXIT_CODE();
+}

+ 16 - 0
zf_log/tests/test_compilation_cpp.cpp

@@ -0,0 +1,16 @@
+#include <zf_log.c>
+
+namespace
+{
+	void mock_output_callback(const zf_log_message *, void *)
+	{
+	}
+}
+
+int main(int argc, char *argv[])
+{
+	zf_log_set_output_v(ZF_LOG_PUT_STD, 0, mock_output_callback);
+	ZF_LOGI("log from cpp, argc=%i", argc);
+	ZF_LOGI_MEM(argv, argc * sizeof(*argv), "log from cpp, argv pointers:");
+	return 0;
+}

+ 60 - 0
zf_log/tests/test_conditional.c

@@ -0,0 +1,60 @@
+#include <zf_log.c>
+#include <zf_test.h>
+
+static unsigned g_logged = 0;
+/* Keep it extern (non-static), so compiler has less chances to optimize access
+ * to this variable.
+ */
+unsigned g_forty_two = 42;
+
+static void mock_output_callback(const zf_log_message *msg, void *arg)
+{
+	(void)msg; (void)arg;
+	++g_logged;
+}
+
+static unsigned was_logged()
+{
+	const unsigned logged = g_logged;
+	g_logged = 0;
+	return logged;
+}
+
+static unsigned forty_two()
+{
+	return g_forty_two;
+}
+
+#define EXPECTED_LINES(n) TEST_VERIFY_EQUAL(was_logged(), (n))
+
+static void test_conditional()
+{
+	ZF_LOG_IF(1 < 2, ZF_LOGI("True"));
+	EXPECTED_LINES(1);
+	ZF_LOG_IF(2 < 1, ZF_LOGI("False"));
+	EXPECTED_LINES(0);
+
+	ZF_LOG_IF(g_forty_two == 42, ZF_LOGI("True"));
+	EXPECTED_LINES(1);
+	ZF_LOG_IF(g_forty_two != 42, ZF_LOGI("False"));
+	EXPECTED_LINES(0);
+
+	ZF_LOG_IF(g_forty_two == 42, ZF_LOGI("True"));
+	EXPECTED_LINES(1);
+	ZF_LOG_IF(g_forty_two != 42, ZF_LOGI("False"));
+	EXPECTED_LINES(0);
+
+	ZF_LOG_IF(forty_two() == 42, ZF_LOGI("True"));
+	EXPECTED_LINES(1);
+	ZF_LOG_IF(forty_two() != 42, ZF_LOGI("False"));
+	EXPECTED_LINES(0);
+}
+
+int main(int argc, char *argv[])
+{
+	zf_log_set_output_v(ZF_LOG_PUT_STD, 0, mock_output_callback);
+
+	TEST_RUNNER_CREATE(argc, argv);
+	TEST_EXECUTE(test_conditional());
+	return TEST_RUNNER_EXIT_CODE();
+}

+ 41 - 0
zf_log/tests/test_decoration.main.c

@@ -0,0 +1,41 @@
+#define ZF_LOG_LEVEL ZF_LOG_INFO
+#include <zf_log.c>
+#include <stdlib.h>
+#include <stdio.h>
+
+static int main_called;
+
+static void main_output_callback(const zf_log_message *msg, void *arg)
+{
+	(void)arg;
+	if (strncmp("main", msg->msg_b, (size_t)(msg->p - msg->msg_b)))
+	{
+		fprintf(stderr, "incorrect message in main\n");
+		exit(1);
+	}
+	++main_called;
+}
+
+void test_module();
+
+void test_main()
+{
+	zf_log_set_output_v(ZF_LOG_PUT_STD, 0, main_output_callback);
+	ZF_LOGI("main");
+	if (!main_called)
+	{
+		fprintf(stderr, "main callback was not called\n");
+		exit(1);
+	}
+}
+
+int main(int argc, char *argv[])
+{
+	(void)argc; (void)argv;
+	zf_log_set_output_v(ZF_LOG_PUT_STD, 0, main_output_callback);
+
+	test_module();
+	test_main();
+
+	return 0;
+}

+ 29 - 0
zf_log/tests/test_decoration.module.c

@@ -0,0 +1,29 @@
+#define ZF_LOG_LEVEL ZF_LOG_INFO
+#define ZF_LOG_LIBRARY_PREFIX module
+#include <zf_log.c>
+#include <stdlib.h>
+#include <stdio.h>
+
+static int module_called;
+
+static void module_output_callback(const zf_log_message *msg, void *arg)
+{
+	(void)arg;
+	if (strncmp("module", msg->msg_b, (size_t)(msg->p - msg->msg_b)))
+	{
+		fprintf(stderr, "incorrect message in module\n");
+		exit(1);
+	}
+	++module_called;
+}
+
+void test_module()
+{
+	zf_log_set_output_v(ZF_LOG_PUT_STD, 0, module_output_callback);
+	ZF_LOGI("module");
+	if (!module_called)
+	{
+		fprintf(stderr, "module callback was not called\n");
+		exit(1);
+	}
+}

+ 48 - 0
zf_log/tests/test_externally_defined_state.c

@@ -0,0 +1,48 @@
+#define ZF_LOG_EXTERN_TAG_PREFIX
+#define ZF_LOG_EXTERN_GLOBAL_FORMAT
+#define ZF_LOG_EXTERN_GLOBAL_OUTPUT
+#define ZF_LOG_EXTERN_GLOBAL_OUTPUT_LEVEL
+#include <zf_log.c>
+#include "zf_test.h"
+
+enum { MEM_WIDTH = 8 };
+static void mock_output_callback(const zf_log_message *msg, void *arg);
+
+ZF_LOG_DEFINE_TAG_PREFIX = "MOCK_PREFIX";
+ZF_LOG_DEFINE_GLOBAL_FORMAT =
+{
+	0xc0defade
+};
+ZF_LOG_DEFINE_GLOBAL_OUTPUT =
+{
+	0xcafebabe,
+	(void *)(ptrdiff_t)0xfafacaca,
+	mock_output_callback
+};
+ZF_LOG_DEFINE_GLOBAL_OUTPUT_LEVEL = 0xdeadbeef;
+
+static void mock_output_callback(const zf_log_message *msg, void *arg)
+{
+	(void)msg; (void)arg;
+}
+
+static void test_static_initialization()
+{
+	TEST_VERIFY_TRUE(0 == strcmp(_zf_log_tag_prefix, "MOCK_PREFIX"));
+	TEST_VERIFY_EQUAL(_zf_log_global_format.mem_width, 0xc0defade);
+	TEST_VERIFY_EQUAL(_zf_log_global_output.mask, 0xcafebabe);
+	TEST_VERIFY_EQUAL(_zf_log_global_output.arg, (void *)(ptrdiff_t)0xfafacaca);
+	TEST_VERIFY_EQUAL(_zf_log_global_output.callback, mock_output_callback);
+	TEST_VERIFY_EQUAL(_zf_log_global_output_lvl, (int)0xdeadbeef);
+	TEST_VERIFY_EQUAL(ZF_LOG_GLOBAL_FORMAT, &_zf_log_global_format);
+	TEST_VERIFY_EQUAL(ZF_LOG_GLOBAL_OUTPUT, &_zf_log_global_output);
+}
+
+int main(int argc, char *argv[])
+{
+	TEST_RUNNER_CREATE(argc, argv);
+
+	TEST_EXECUTE(test_static_initialization());
+
+	return TEST_RUNNER_EXIT_CODE();
+}

+ 25 - 0
zf_log/tests/test_externally_defined_state_cpp.cpp

@@ -0,0 +1,25 @@
+#define ZF_LOG_EXTERN_TAG_PREFIX
+#define ZF_LOG_EXTERN_GLOBAL_FORMAT
+#define ZF_LOG_EXTERN_GLOBAL_OUTPUT
+#define ZF_LOG_EXTERN_GLOBAL_OUTPUT_LEVEL
+#include <zf_log.c>
+
+ZF_LOG_DEFINE_TAG_PREFIX = "MAIN";
+ZF_LOG_DEFINE_GLOBAL_FORMAT = {32};
+ZF_LOG_DEFINE_GLOBAL_OUTPUT = {ZF_LOG_OUT_STDERR};
+ZF_LOG_DEFINE_GLOBAL_OUTPUT_LEVEL = ZF_LOG_INFO;
+
+namespace
+{
+	void mock_output_callback(const zf_log_message *, void *)
+	{
+	}
+}
+
+int main(int argc, char *argv[])
+{
+	zf_log_set_output_v(ZF_LOG_PUT_STD, 0, mock_output_callback);
+	ZF_LOGI("log from cpp, argc=%i", argc);
+	ZF_LOGI_MEM(argv, argc * sizeof(*argv), "log from cpp, argv pointers:");
+	return 0;
+}

+ 45 - 0
zf_log/tests/test_log_level_override.c

@@ -0,0 +1,45 @@
+#define ZF_LOG_LEVEL 0
+#define ZF_LOG_OUTPUT_LEVEL g_output_level
+#include <zf_log.c>
+#include <zf_test.h>
+
+static const int c_levels[] =
+{
+	ZF_LOG_VERBOSE,
+	ZF_LOG_DEBUG,
+	ZF_LOG_INFO,
+	ZF_LOG_WARN,
+	ZF_LOG_ERROR,
+	ZF_LOG_FATAL,
+	ZF_LOG_NONE,
+};
+
+static int g_output_level = 0;
+
+static void test_level_checks()
+{
+	for (unsigned i = 0; _countof(c_levels) > i; ++i)
+	{
+		/* must not effect anything */
+		zf_log_set_output_level(c_levels[i]);
+		for (unsigned j = 0; _countof(c_levels) > j; ++j)
+		{
+			g_output_level = c_levels[j];
+			TEST_VERIFY_EQUAL(!!ZF_LOG_ON_VERBOSE, g_output_level <= ZF_LOG_VERBOSE);
+			TEST_VERIFY_EQUAL(!!ZF_LOG_ON_DEBUG, g_output_level <= ZF_LOG_DEBUG);
+			TEST_VERIFY_EQUAL(!!ZF_LOG_ON_INFO, g_output_level <= ZF_LOG_INFO);
+			TEST_VERIFY_EQUAL(!!ZF_LOG_ON_WARN, g_output_level <= ZF_LOG_WARN);
+			TEST_VERIFY_EQUAL(!!ZF_LOG_ON_ERROR, g_output_level <= ZF_LOG_ERROR);
+			TEST_VERIFY_EQUAL(!!ZF_LOG_ON_FATAL, g_output_level <= ZF_LOG_FATAL);
+		}
+	}
+}
+
+int main(int argc, char *argv[])
+{
+	TEST_RUNNER_CREATE(argc, argv);
+
+	TEST_EXECUTE(test_level_checks());
+
+	return TEST_RUNNER_EXIT_CODE();
+}

+ 218 - 0
zf_log/tests/test_log_level_switches.c

@@ -0,0 +1,218 @@
+#ifndef ZF_LOG_LEVEL
+	#error ZF_LOG_LEVEL must be defined for this test
+#endif
+#include <zf_log.c>
+#include <zf_test.h>
+#include <string.h>
+#include <stdbool.h>
+
+static int g_output_lvl_used;
+static unsigned g_output_called;
+static unsigned g_arg_called;
+static char g_msg[1024];
+static unsigned g_msg_len;
+
+static const int c_levels[] =
+{
+	ZF_LOG_VERBOSE,
+	ZF_LOG_DEBUG,
+	ZF_LOG_INFO,
+	ZF_LOG_WARN,
+	ZF_LOG_ERROR,
+	ZF_LOG_FATAL,
+	ZF_LOG_NONE,
+};
+
+static void reset()
+{
+	g_output_called = 0;
+	g_arg_called = 0;
+	zf_log_set_output_level(0);
+}
+
+static void mock_output_callback(const zf_log_message *msg, void *arg)
+{
+	(void)arg;
+	g_output_lvl_used = msg->lvl;
+	g_msg_len = (unsigned)(msg->p - msg->buf);
+	memcpy(g_msg, msg->buf, g_msg_len);
+	++g_output_called;
+}
+
+static int get_arg()
+{
+	++g_arg_called;
+	return 0;
+}
+
+static void test_current_level()
+{
+	reset();
+	ZF_LOGV("verbose log");
+	TEST_VERIFY_EQUAL(1 == g_output_called, ZF_LOG_LEVEL <= ZF_LOG_VERBOSE);
+	TEST_VERIFY_TRUE(0 == g_output_called || ZF_LOG_VERBOSE == g_output_lvl_used);
+	reset();
+	ZF_LOGD("debug log");
+	TEST_VERIFY_EQUAL(1 == g_output_called, ZF_LOG_LEVEL <= ZF_LOG_DEBUG);
+	TEST_VERIFY_TRUE(0 == g_output_called || ZF_LOG_DEBUG == g_output_lvl_used);
+	reset();
+	ZF_LOGI("info log");
+	TEST_VERIFY_EQUAL(1 == g_output_called, ZF_LOG_LEVEL <= ZF_LOG_INFO);
+	TEST_VERIFY_TRUE(0 == g_output_called || ZF_LOG_INFO == g_output_lvl_used);
+	reset();
+	ZF_LOGW("warning log");
+	TEST_VERIFY_EQUAL(1 == g_output_called, ZF_LOG_LEVEL <= ZF_LOG_WARN);
+	TEST_VERIFY_TRUE(0 == g_output_called || ZF_LOG_WARN == g_output_lvl_used);
+	reset();
+	ZF_LOGE("error log");
+	TEST_VERIFY_EQUAL(1 == g_output_called, ZF_LOG_LEVEL <= ZF_LOG_ERROR);
+	TEST_VERIFY_TRUE(0 == g_output_called || ZF_LOG_ERROR == g_output_lvl_used);
+	reset();
+	ZF_LOGF("fatal log");
+	TEST_VERIFY_EQUAL(1 == g_output_called, ZF_LOG_LEVEL <= ZF_LOG_FATAL);
+	TEST_VERIFY_TRUE(0 == g_output_called || ZF_LOG_FATAL == g_output_lvl_used);
+}
+
+static void test_output_level()
+{
+	for (unsigned i = 0; _countof(c_levels) > i; ++i)
+	{
+		const int lvl = c_levels[i];
+		reset();
+		zf_log_set_output_level(lvl);
+		ZF_LOGV("verbose log");
+		TEST_VERIFY_EQUAL(1 == g_output_called,
+						  ZF_LOG_LEVEL <= ZF_LOG_VERBOSE && lvl <= ZF_LOG_VERBOSE);
+		TEST_VERIFY_TRUE(0 == g_output_called || ZF_LOG_VERBOSE == g_output_lvl_used);
+		reset();
+		zf_log_set_output_level(lvl);
+		ZF_LOGD("debug log");
+		TEST_VERIFY_EQUAL(1 == g_output_called,
+						  ZF_LOG_LEVEL <= ZF_LOG_DEBUG && lvl <= ZF_LOG_DEBUG);
+		TEST_VERIFY_TRUE(0 == g_output_called || ZF_LOG_DEBUG == g_output_lvl_used);
+		reset();
+		zf_log_set_output_level(lvl);
+		ZF_LOGI("info log");
+		TEST_VERIFY_EQUAL(1 == g_output_called,
+						  ZF_LOG_LEVEL <= ZF_LOG_INFO && lvl <= ZF_LOG_INFO);
+		TEST_VERIFY_TRUE(0 == g_output_called || ZF_LOG_INFO == g_output_lvl_used);
+		reset();
+		zf_log_set_output_level(lvl);
+		ZF_LOGW("warn log");
+		TEST_VERIFY_EQUAL(1 == g_output_called,
+						  ZF_LOG_LEVEL <= ZF_LOG_WARN && lvl <= ZF_LOG_WARN);
+		TEST_VERIFY_TRUE(0 == g_output_called || ZF_LOG_WARN == g_output_lvl_used);
+		reset();
+		zf_log_set_output_level(lvl);
+		ZF_LOGE("error log");
+		TEST_VERIFY_EQUAL(1 == g_output_called,
+						  ZF_LOG_LEVEL <= ZF_LOG_ERROR && lvl <= ZF_LOG_ERROR);
+		TEST_VERIFY_TRUE(0 == g_output_called || ZF_LOG_ERROR == g_output_lvl_used);
+		reset();
+		zf_log_set_output_level(lvl);
+		ZF_LOGF("fatal log");
+		TEST_VERIFY_EQUAL(1 == g_output_called,
+						  ZF_LOG_LEVEL <= ZF_LOG_FATAL && lvl <= ZF_LOG_FATAL);
+		TEST_VERIFY_TRUE(0 == g_output_called || ZF_LOG_FATAL == g_output_lvl_used);
+	}
+}
+
+static void test_args_evaluation()
+{
+	reset();
+	ZF_LOGV("verbose log: %i", get_arg());
+	TEST_VERIFY_EQUAL(1 == g_arg_called, ZF_LOG_LEVEL <= ZF_LOG_VERBOSE);
+	reset();
+	ZF_LOGD("debug log: %i", get_arg());
+	TEST_VERIFY_EQUAL(1 == g_arg_called, ZF_LOG_LEVEL <= ZF_LOG_DEBUG);
+	reset();
+	ZF_LOGI("info log: %i", get_arg());
+	TEST_VERIFY_EQUAL(1 == g_arg_called, ZF_LOG_LEVEL <= ZF_LOG_INFO);
+	reset();
+	ZF_LOGW("warning log: %i", get_arg());
+	TEST_VERIFY_EQUAL(1 == g_arg_called, ZF_LOG_LEVEL <= ZF_LOG_WARN);
+	reset();
+	ZF_LOGE("error log: %i", get_arg());
+	TEST_VERIFY_EQUAL(1 == g_arg_called, ZF_LOG_LEVEL <= ZF_LOG_ERROR);
+	reset();
+	ZF_LOGF("fatal log: %i", get_arg());
+	TEST_VERIFY_EQUAL(1 == g_arg_called, ZF_LOG_LEVEL <= ZF_LOG_FATAL);
+
+	for (unsigned i = 0; _countof(c_levels) > i; ++i)
+	{
+		const int lvl = c_levels[i];
+		reset();
+		zf_log_set_output_level(lvl);
+		ZF_LOGV("verbose log: %i", get_arg());
+		TEST_VERIFY_EQUAL(1 == g_arg_called,
+						  ZF_LOG_LEVEL <= ZF_LOG_VERBOSE && lvl <= ZF_LOG_VERBOSE);
+		reset();
+		zf_log_set_output_level(lvl);
+		ZF_LOGD("debug log: %i", get_arg());
+		TEST_VERIFY_EQUAL(1 == g_arg_called,
+						  ZF_LOG_LEVEL <= ZF_LOG_DEBUG && lvl <= ZF_LOG_DEBUG);
+		reset();
+		zf_log_set_output_level(lvl);
+		ZF_LOGI("info log: %i", get_arg());
+		TEST_VERIFY_EQUAL(1 == g_arg_called,
+						  ZF_LOG_LEVEL <= ZF_LOG_INFO && lvl <= ZF_LOG_INFO);
+		reset();
+		zf_log_set_output_level(lvl);
+		ZF_LOGW("warn log: %i", get_arg());
+		TEST_VERIFY_EQUAL(1 == g_arg_called,
+						  ZF_LOG_LEVEL <= ZF_LOG_WARN && lvl <= ZF_LOG_WARN);
+		reset();
+		zf_log_set_output_level(lvl);
+		ZF_LOGE("error log: %i", get_arg());
+		TEST_VERIFY_EQUAL(1 == g_arg_called,
+						  ZF_LOG_LEVEL <= ZF_LOG_ERROR && lvl <= ZF_LOG_ERROR);
+		reset();
+		zf_log_set_output_level(lvl);
+		ZF_LOGF("fatal log: %i", get_arg());
+		TEST_VERIFY_EQUAL(1 == g_arg_called,
+						  ZF_LOG_LEVEL <= ZF_LOG_FATAL && lvl <= ZF_LOG_FATAL);
+	}
+}
+
+static void test_level_checks()
+{
+	reset();
+	TEST_VERIFY_EQUAL(!!ZF_LOG_ENABLED_VERBOSE, ZF_LOG_LEVEL <= ZF_LOG_VERBOSE);
+	TEST_VERIFY_EQUAL(!!ZF_LOG_ENABLED_DEBUG, ZF_LOG_LEVEL <= ZF_LOG_DEBUG);
+	TEST_VERIFY_EQUAL(!!ZF_LOG_ENABLED_INFO, ZF_LOG_LEVEL <= ZF_LOG_INFO);
+	TEST_VERIFY_EQUAL(!!ZF_LOG_ENABLED_WARN, ZF_LOG_LEVEL <= ZF_LOG_WARN);
+	TEST_VERIFY_EQUAL(!!ZF_LOG_ENABLED_ERROR, ZF_LOG_LEVEL <= ZF_LOG_ERROR);
+	TEST_VERIFY_EQUAL(!!ZF_LOG_ENABLED_FATAL, ZF_LOG_LEVEL <= ZF_LOG_FATAL);
+
+	for (unsigned i = 0; _countof(c_levels) > i; ++i)
+	{
+		const int lvl = c_levels[i];
+		reset();
+		zf_log_set_output_level(lvl);
+		TEST_VERIFY_EQUAL(!!ZF_LOG_ON_VERBOSE,
+						  ZF_LOG_LEVEL <= ZF_LOG_VERBOSE && lvl <= ZF_LOG_VERBOSE);
+		TEST_VERIFY_EQUAL(!!ZF_LOG_ON_DEBUG,
+						  ZF_LOG_LEVEL <= ZF_LOG_DEBUG && lvl <= ZF_LOG_DEBUG);
+		TEST_VERIFY_EQUAL(!!ZF_LOG_ON_INFO,
+						  ZF_LOG_LEVEL <= ZF_LOG_INFO && lvl <= ZF_LOG_INFO);
+		TEST_VERIFY_EQUAL(!!ZF_LOG_ON_WARN,
+						  ZF_LOG_LEVEL <= ZF_LOG_WARN && lvl <= ZF_LOG_WARN);
+		TEST_VERIFY_EQUAL(!!ZF_LOG_ON_ERROR,
+						  ZF_LOG_LEVEL <= ZF_LOG_ERROR && lvl <= ZF_LOG_ERROR);
+		TEST_VERIFY_EQUAL(!!ZF_LOG_ON_FATAL,
+						  ZF_LOG_LEVEL <= ZF_LOG_FATAL && lvl <= ZF_LOG_FATAL);
+	}
+}
+
+int main(int argc, char *argv[])
+{
+	zf_log_set_output_v(ZF_LOG_PUT_STD, 0, mock_output_callback);
+	TEST_RUNNER_CREATE(argc, argv);
+
+	TEST_EXECUTE(test_current_level());
+	TEST_EXECUTE(test_output_level());
+	TEST_EXECUTE(test_args_evaluation());
+	TEST_EXECUTE(test_level_checks());
+
+	return TEST_RUNNER_EXIT_CODE();
+}

+ 291 - 0
zf_log/tests/test_log_message_content.c

@@ -0,0 +1,291 @@
+#if defined(_WIN32) || defined(_WIN64)
+	#define _CRT_NONSTDC_NO_WARNINGS
+#endif
+#define ZF_LOG_ANDROID_LOG 0
+#define ZF_LOG_BUF_SZ 128
+#define ZF_LOG_MEM_WIDTH 16
+#define ZF_LOG_INSTRUMENTED 1
+#define ZF_LOG_LEVEL ZF_LOG_INFO
+#define ZF_LOG_TAG "TAG"
+#include <zf_log.c>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+#if defined(_MSC_VER) && !defined(__INTEL_COMPILER) && !defined(snprintf)
+    #define snprintf(buf, len, ...) _snprintf_s(buf, len, _TRUNCATE, __VA_ARGS__)
+#endif
+
+#define MESSAGE_EXPECTED_PRINTF_FMT__             ""
+#define MESSAGE_EXPECTED_PRINTF_FMT__YEAR         "2016"
+#define MESSAGE_EXPECTED_PRINTF_FMT__MONTH        "12"
+#define MESSAGE_EXPECTED_PRINTF_FMT__DAY          "23"
+#define MESSAGE_EXPECTED_PRINTF_FMT__HOUR         "12"
+#define MESSAGE_EXPECTED_PRINTF_FMT__MINUTE       "34"
+#define MESSAGE_EXPECTED_PRINTF_FMT__SECOND       "56"
+#define MESSAGE_EXPECTED_PRINTF_FMT__MILLISECOND  "789"
+#define MESSAGE_EXPECTED_PRINTF_FMT__PID          " 9876"
+#define MESSAGE_EXPECTED_PRINTF_FMT__TID          " 5432"
+#define MESSAGE_EXPECTED_PRINTF_FMT__LEVEL        "I"
+#define MESSAGE_EXPECTED_PRINTF_FMT__TAG(ps, ts)  "prefix" ps "TAG" ts
+#define MESSAGE_EXPECTED_PRINTF_FMT__FUNCTION     "function"
+#define MESSAGE_EXPECTED_PRINTF_FMT__FILENAME     "file"
+#define MESSAGE_EXPECTED_PRINTF_FMT__FILELINE     "42"
+#define MESSAGE_EXPECTED_PRINTF_FMT__S(s)         s
+#define MESSAGE_EXPECTED_PRINTF_FMT__F_INIT(expr) ""
+#define MESSAGE_EXPECTED_PRINTF_FMT__F_UINT(w, v) "%" #w "u"
+#define MESSAGE_EXPECTED_PRINTF_FMT(field) \
+	_PP_CONCAT_3(MESSAGE_EXPECTED_PRINTF_FMT_, _, field)
+
+#define MESSAGE_EXPECTED_PRINTF_VAL__
+#define MESSAGE_EXPECTED_PRINTF_VAL__YEAR
+#define MESSAGE_EXPECTED_PRINTF_VAL__MONTH
+#define MESSAGE_EXPECTED_PRINTF_VAL__DAY
+#define MESSAGE_EXPECTED_PRINTF_VAL__HOUR
+#define MESSAGE_EXPECTED_PRINTF_VAL__MINUTE
+#define MESSAGE_EXPECTED_PRINTF_VAL__SECOND
+#define MESSAGE_EXPECTED_PRINTF_VAL__MILLISECOND
+#define MESSAGE_EXPECTED_PRINTF_VAL__PID
+#define MESSAGE_EXPECTED_PRINTF_VAL__TID
+#define MESSAGE_EXPECTED_PRINTF_VAL__LEVEL
+#define MESSAGE_EXPECTED_PRINTF_VAL__TAG(ps, ts)
+#define MESSAGE_EXPECTED_PRINTF_VAL__FUNCTION
+#define MESSAGE_EXPECTED_PRINTF_VAL__FILENAME
+#define MESSAGE_EXPECTED_PRINTF_VAL__FILELINE
+#define MESSAGE_EXPECTED_PRINTF_VAL__S(s)
+#define MESSAGE_EXPECTED_PRINTF_VAL__F_INIT(expr)
+#define MESSAGE_EXPECTED_PRINTF_VAL__F_UINT(w, v) ,v
+#define MESSAGE_EXPECTED_PRINTF_VAL(field) \
+	_PP_CONCAT_3(MESSAGE_EXPECTED_PRINTF_VAL_, _, field)
+
+#define MESSAGE_EXPECTED_PRINTF_FMTS \
+	_PP_MAP(MESSAGE_EXPECTED_PRINTF_FMT, ZF_LOG_MESSAGE_CTX_FORMAT) \
+	_PP_MAP(MESSAGE_EXPECTED_PRINTF_FMT, ZF_LOG_MESSAGE_TAG_FORMAT) \
+	_PP_MAP(MESSAGE_EXPECTED_PRINTF_FMT, ZF_LOG_MESSAGE_SRC_FORMAT)
+
+#define MESSAGE_EXPECTED_PRINTF_VALS \
+	_PP_MAP(MESSAGE_EXPECTED_PRINTF_VAL, ZF_LOG_MESSAGE_CTX_FORMAT) \
+	_PP_MAP(MESSAGE_EXPECTED_PRINTF_VAL, ZF_LOG_MESSAGE_TAG_FORMAT) \
+	_PP_MAP(MESSAGE_EXPECTED_PRINTF_VAL, ZF_LOG_MESSAGE_SRC_FORMAT)
+
+static const char c_test_fmt[] =
+	"Lorem ipsum dolor sit amet.";
+static const char c_test_mem[] =
+	"Here's to the crazy ones.";
+
+static const char *c_msg_expected_lines[1];
+static const char *c_mem_expected_lines[3];
+
+#define MAX_LINES 4
+static char g_lines[MAX_LINES][ZF_LOG_BUF_SZ];
+static size_t g_len[MAX_LINES];
+static size_t g_null_pos[MAX_LINES];
+static size_t g_line;
+
+static size_t memchk(const void *const b, const int c, const size_t sz)
+{
+	const unsigned char v = (unsigned char)c;
+	const unsigned char *const s = (const unsigned char *)b;
+	const unsigned char *const e = s + sz;
+	const unsigned char *p = s;
+	for (;p != e && v == *p; ++p) {}
+	return (size_t)(p - s);
+}
+
+static size_t common_prefix(const char *const s1, const size_t s1_len,
+							const char *const s2, const size_t s2_len)
+{
+	const char *const e1 = s1 + s1_len;
+	const char *const e2 = s2 + s2_len;
+	const char *c1 = s1, *c2 = s2;
+	for (;e1 != c1 && e2 != c2 && *c1 == *c2; ++c1, ++c2) {}
+	return (size_t)(c1 - s1);
+}
+
+static void reset()
+{
+	g_buf_sz = ZF_LOG_BUF_SZ;
+	for (size_t i = 0; MAX_LINES > i; ++i)
+	{
+		memset(g_lines[i], -1, ZF_LOG_BUF_SZ);
+		g_len[i] = 0;
+		g_null_pos[i] = 0;
+	}
+	g_line = 0;
+}
+
+static void mock_time_callback(struct tm *const tm, unsigned *const msec)
+{
+	memset(tm, 0, sizeof(*tm));
+	tm->tm_sec = 56;
+	tm->tm_min = 34;
+	tm->tm_hour = 12;
+	tm->tm_mday = 23;
+	tm->tm_mon = 11;
+	tm->tm_year = 2016 - 1900;
+	*msec = 789;
+}
+
+static void mock_pid_callback(int *const pid, int *const tid)
+{
+	*pid = 9876;
+	*tid = 5432;
+}
+
+static void mock_buffer_callback(zf_log_message *msg, char *buf)
+{
+	memset(buf, -1, ZF_LOG_BUF_SZ);
+	buffer_callback(msg, buf);
+}
+
+static void mock_output_callback(const zf_log_message *msg, void *arg)
+{
+	(void)arg;
+	const size_t i = g_line++;
+	if (MAX_LINES <= i)
+	{
+		fprintf(stderr, "too many lines produced\n");
+		exit(1);
+	}
+	char *const line = g_lines[i];
+	memcpy(line, msg->buf, ZF_LOG_BUF_SZ);
+	const size_t len = (size_t)(msg->p - msg->buf);
+	size_t null_pos;
+	for	(null_pos = 0; len > null_pos && 0 != line[null_pos]; ++null_pos) {}
+	g_len[i] = len;
+	g_null_pos[i] = null_pos;
+}
+
+static void verify_log_output(const size_t buf_sz,
+							  const char *const expected[],
+							  const size_t expected_n)
+{
+	const size_t modifiable = buf_sz + 1;
+	const size_t unmodifiable = ZF_LOG_BUF_SZ - modifiable;
+	if (g_line > expected_n)
+	{
+		fprintf(stderr, "Lines produced: actual=%u, expected=<%u\n",
+				(unsigned)g_line, (unsigned)expected_n);
+		exit(1);
+	}
+	size_t complete_lines = 0;
+	for (size_t i = 0; g_line > i; ++i)
+	{
+		const char *const line = g_lines[i];
+		const size_t line_len = strlen(expected[i]);
+		const size_t untouched = memchk(line + modifiable, -1, unmodifiable);
+		const size_t match = common_prefix(expected[i], line_len,
+										   line, g_len[i]);
+		if (untouched != unmodifiable)
+		{
+			fprintf(stderr, "Untouched bytes: actual=%u, expected=%u\n",
+					(unsigned)untouched, (unsigned)unmodifiable);
+			exit(1);
+		}
+		if (g_null_pos[i] != g_len[i])
+		{
+			fprintf(stderr, "Null position: actual=%u, expected=%u\n",
+					(unsigned)g_null_pos[i], (unsigned)g_len[i]);
+			exit(1);
+		}
+		if (match < g_len[i])
+		{
+			fprintf(stderr, "Line partial match: actual=%u, expected=>%u\n",
+					(unsigned)match, (unsigned)g_len[i]);
+			exit(1);
+		}
+		if (line_len <= buf_sz)
+		{
+			++complete_lines;
+			if (line_len <= buf_sz && match != g_len[i])
+			{
+				fprintf(stderr, "Line complete match: actual=%u, expected=%u\n",
+						(unsigned)match, (unsigned)g_len[i]);
+				exit(1);
+			}
+		}
+	}
+	if (expected_n == complete_lines && g_line != expected_n)
+	{
+		fprintf(stderr, "Complete lines produced: actual=%u, expected=<%u\n",
+				(unsigned)g_line, (unsigned)expected_n);
+		exit(1);
+	}
+}
+
+static void test_msg_output()
+{
+	for (unsigned buf_sz = 0; ZF_LOG_BUF_SZ - ZF_LOG_EOL_SZ >= buf_sz; ++buf_sz)
+	{
+		reset();
+		g_buf_sz = buf_sz;
+		_zf_log_write_d("function", "file", 42, ZF_LOG_INFO, ZF_LOG_TAG,
+						c_test_fmt);
+		verify_log_output(buf_sz,
+						  c_msg_expected_lines, _countof(c_msg_expected_lines));
+	}
+}
+
+static void test_mem_output()
+{
+	for (unsigned buf_sz = 0; ZF_LOG_BUF_SZ - ZF_LOG_EOL_SZ >= buf_sz; ++buf_sz)
+	{
+		reset();
+		g_buf_sz = buf_sz;
+		_zf_log_write_mem_d("function", "file", 42, ZF_LOG_INFO, ZF_LOG_TAG,
+							c_test_mem, sizeof(c_test_mem),
+							c_test_fmt);
+		verify_log_output(buf_sz,
+						  c_mem_expected_lines, _countof(c_mem_expected_lines));
+	}
+}
+
+static void init_expected_lines()
+{
+	char expected_header[256];
+	char line[512];
+
+	_PP_MAP(_ZF_LOG_MESSAGE_FORMAT_INIT, ZF_LOG_MESSAGE_CTX_FORMAT)
+	_PP_MAP(_ZF_LOG_MESSAGE_FORMAT_INIT, ZF_LOG_MESSAGE_TAG_FORMAT)
+	_PP_MAP(_ZF_LOG_MESSAGE_FORMAT_INIT, ZF_LOG_MESSAGE_SRC_FORMAT)
+#if _ZF_LOG_MESSAGE_FORMAT_FIELDS(ZF_LOG_MESSAGE_CTX_FORMAT) || \
+	_ZF_LOG_MESSAGE_FORMAT_FIELDS(ZF_LOG_MESSAGE_TAG_FORMAT) || \
+	_ZF_LOG_MESSAGE_FORMAT_FIELDS(ZF_LOG_MESSAGE_SRC_FORMAT)
+	snprintf(expected_header, sizeof(expected_header),
+			MESSAGE_EXPECTED_PRINTF_FMTS
+			MESSAGE_EXPECTED_PRINTF_VALS);
+#else
+	*expected_header ='\0';
+#endif
+
+	snprintf(line, sizeof(line), "%s%s", expected_header,
+			 "Lorem ipsum dolor sit amet.");
+	c_msg_expected_lines[0] = strdup(line);
+
+	snprintf(line, sizeof(line), "%s%s", expected_header,
+			 "Lorem ipsum dolor sit amet.");
+	c_mem_expected_lines[0] = strdup(line);
+	snprintf(line, sizeof(line), "%s%s", expected_header,
+			 "48657265277320746f20746865206372  Here's to the cr");
+	c_mem_expected_lines[1] = strdup(line);
+	snprintf(line, sizeof(line), "%s%s", expected_header,
+			 "617a79206f6e65732e00              azy ones.?");
+	c_mem_expected_lines[2] = strdup(line);
+}
+
+int main(int argc, char *argv[])
+{
+	(void)argc; (void)argv;
+	g_time_cb = mock_time_callback;
+	g_pid_cb = mock_pid_callback;
+	g_buffer_cb = mock_buffer_callback;
+	zf_log_set_output_v(ZF_LOG_PUT_STD, 0, mock_output_callback);
+	zf_log_set_tag_prefix("prefix");
+
+	init_expected_lines();
+
+	test_msg_output();
+	test_mem_output();
+
+	return 0;
+}

+ 51 - 0
zf_log/tests/test_private_parts.c

@@ -0,0 +1,51 @@
+#include <zf_log.c>
+#include <zf_test.h>
+
+static char *strcopy_r(const char *s, char *e)
+{
+	e -= strlen(s) + 1;
+	for (char *p = e; 0 != (*p++ = *s++);) {}
+	return e;
+}
+
+typedef struct put_padding_r_testcase
+{
+	const char *const s;
+	const unsigned w;
+	const char *const p;
+}
+put_padding_r_testcase;
+
+static const put_padding_r_testcase g_put_padding_r_testcases[] =
+{
+	{"", 0, ""},
+	{"1", 0, "1"},
+	{"123", 0, "123"},
+	{"", 3, "---"},
+	{"1", 3, "--1"},
+	{"123", 3, "123"},
+	{"1234", 3, "1234"},
+};
+
+static void test_put_padding_r()
+{
+	char buf[16];
+	char *const e = buf + _countof(buf) - 1;
+	char *p;
+	for (unsigned i = 0; _countof(g_put_padding_r_testcases) > i; ++i)
+	{
+		const put_padding_r_testcase *const tc = g_put_padding_r_testcases + i;
+		p = strcopy_r(tc->s, e + 1);
+		p = put_padding_r(tc->w, '-', p, e);
+		TEST_VERIFY_TRUE_MSG(0 == strcmp(p, tc->p), "i=%u", i);
+	}
+}
+
+int main(int argc, char *argv[])
+{
+	TEST_RUNNER_CREATE(argc, argv);
+
+	TEST_EXECUTE(test_put_padding_r());
+
+	return TEST_RUNNER_EXIT_CODE();
+}

+ 60 - 0
zf_log/tests/test_source_location.c

@@ -0,0 +1,60 @@
+#include <zf_log.c>
+#include <zf_test.h>
+#include <stdio.h>
+
+#if defined(_MSC_VER) && !defined(__INTEL_COMPILER) && !defined(snprintf)
+	#define snprintf(buf, len, ...) _snprintf_s(buf, len, _TRUNCATE, __VA_ARGS__)
+#endif
+
+const char *const c_filename = "test_source_location.c";
+static char g_srcloc_buf[ZF_LOG_BUF_SZ];
+static const char *g_srcloc;
+
+static char *trim(char *s)
+{
+	char *sb;
+	while (0 != *(sb = s) && ' ' == *s) ++s;
+	char *se;
+	while (0 != *(se = s) && ' ' != *s) ++s;
+	*se = 0;
+	return sb;
+}
+
+static void mock_output_callback(const zf_log_message *msg, void *arg)
+{
+	(void)arg;
+	const size_t len = msg->msg_b - msg->tag_e;
+	memcpy(g_srcloc_buf, msg->tag_e, len);
+	g_srcloc_buf[len] = 0;
+	g_srcloc = trim(g_srcloc_buf);
+}
+
+static void test_function()
+{
+	const unsigned line = __LINE__ + 1;
+	ZF_LOGI("test message");
+
+	char expected[64];
+#if ZF_LOG_SRCLOC_NONE==TEST_SRCLOC
+	(void)line;
+	*expected = 0;
+#endif
+#if ZF_LOG_SRCLOC_SHORT==TEST_SRCLOC
+	snprintf(expected, sizeof(expected), "@%s:%u",
+			 c_filename, line);
+#endif
+#if ZF_LOG_SRCLOC_LONG==TEST_SRCLOC
+	snprintf(expected, sizeof(expected), "%s@%s:%u",
+			 _ZF_LOG_FUNCTION, c_filename, line);
+#endif
+	TEST_VERIFY_EQUAL(strcmp(expected, g_srcloc), 0);
+}
+
+int main(int argc, char *argv[])
+{
+	zf_log_set_output_v(ZF_LOG_PUT_STD, 0, mock_output_callback);
+
+	TEST_RUNNER_CREATE(argc, argv);
+	TEST_EXECUTE(test_function());
+	return TEST_RUNNER_EXIT_CODE();
+}

+ 937 - 0
zf_log/tests/zf_log.h.master

@@ -0,0 +1,937 @@
+#pragma once
+
+#ifndef _ZF_LOG_H_
+#define _ZF_LOG_H_
+
+/* To detect incompatible changes you can define ZF_LOG_VERSION_REQUIRED to be
+ * the current value of ZF_LOG_VERSION before including this file (or via
+ * compiler command line):
+ *
+ *   #define ZF_LOG_VERSION_REQUIRED 4
+ *   #include <zf_log.h>
+ *
+ * Compilation will fail when included file has different version.
+ */
+#define ZF_LOG_VERSION 4
+#if defined(ZF_LOG_VERSION_REQUIRED)
+	#if ZF_LOG_VERSION_REQUIRED != ZF_LOG_VERSION
+		#error different zf_log version required
+	#endif
+#endif
+
+/* Log level guideline:
+ * - ZF_LOG_FATAL - happened something impossible and absolutely unexpected.
+ *   Process can't continue and must be terminated.
+ *   Example: division by zero, unexpected modifications from other thread.
+ * - ZF_LOG_ERROR - happened something possible, but highly unexpected. The
+ *   process is able to recover and continue execution.
+ *   Example: out of memory (could also be FATAL if not handled properly).
+ * - ZF_LOG_WARN - happened something that *usually* should not happen and
+ *   significantly changes application behavior for some period of time.
+ *   Example: configuration file not found, auth error.
+ * - ZF_LOG_INFO - happened significant life cycle event or major state
+ *   transition.
+ *   Example: app started, user logged in.
+ * - ZF_LOG_DEBUG - minimal set of events that could help to reconstruct the
+ *   execution path. Usually disabled in release builds.
+ * - ZF_LOG_VERBOSE - all other events. Usually disabled in release builds.
+ *
+ * *Ideally*, log file of debugged, well tested, production ready application
+ * should be empty or very small. Choosing a right log level is as important as
+ * providing short and self descriptive log message.
+ */
+#define ZF_LOG_VERBOSE 1
+#define ZF_LOG_DEBUG   2
+#define ZF_LOG_INFO    3
+#define ZF_LOG_WARN    4
+#define ZF_LOG_ERROR   5
+#define ZF_LOG_FATAL   6
+#define ZF_LOG_NONE    0xFF
+
+/* "Current" log level is a compile time check and has no runtime overhead. Log
+ * level that is below current log level it said to be "disabled". Otherwise,
+ * it's "enabled". Log messages that are disabled has no runtime overhead - they
+ * are converted to no-op by preprocessor and then eliminated by compiler.
+ * Current log level is configured per compilation module (.c/.cpp/.m file) by
+ * defining ZF_LOG_DEF_LEVEL or ZF_LOG_LEVEL. ZF_LOG_LEVEL has higer priority
+ * and when defined overrides value provided by ZF_LOG_DEF_LEVEL.
+ *
+ * Common practice is to define default current log level with ZF_LOG_DEF_LEVEL
+ * in build script (e.g. Makefile, CMakeLists.txt, gyp, etc.) for the entire
+ * project or target:
+ *
+ *   CC_ARGS := -DZF_LOG_DEF_LEVEL=ZF_LOG_INFO
+ *
+ * And when necessary to override it with ZF_LOG_LEVEL in .c/.cpp/.m files
+ * before including zf_log.h:
+ *
+ *   #define ZF_LOG_LEVEL ZF_LOG_VERBOSE
+ *   #include <zf_log.h>
+ *
+ * If both ZF_LOG_DEF_LEVEL and ZF_LOG_LEVEL are undefined, then ZF_LOG_INFO
+ * will be used for release builds (NDEBUG is defined) and ZF_LOG_DEBUG
+ * otherwise (NDEBUG is not defined).
+ */
+#if defined(ZF_LOG_LEVEL)
+	#define _ZF_LOG_LEVEL ZF_LOG_LEVEL
+#elif defined(ZF_LOG_DEF_LEVEL)
+	#define _ZF_LOG_LEVEL ZF_LOG_DEF_LEVEL
+#else
+	#ifdef NDEBUG
+		#define _ZF_LOG_LEVEL ZF_LOG_INFO
+	#else
+		#define _ZF_LOG_LEVEL ZF_LOG_DEBUG
+	#endif
+#endif
+
+/* "Output" log level is a runtime check. When log level is below output log
+ * level it said to be "turned off" (or just "off" for short). Otherwise it's
+ * "turned on" (or just "on"). Log levels that were "disabled" (see
+ * ZF_LOG_LEVEL and ZF_LOG_DEF_LEVEL) can't be "turned on", but "enabled" log
+ * levels could be "turned off". Only messages with log level which is
+ * "turned on" will reach output facility. All other messages will be ignored
+ * (and their arguments will not be evaluated). Output log level is a global
+ * property and configured per process using zf_log_set_output_level() function
+ * which can be called at any time.
+ *
+ * Though in some cases it could be useful to configure output log level per
+ * compilation module or per library. There are two ways to achieve that:
+ * - Define ZF_LOG_OUTPUT_LEVEL to expresion that evaluates to desired output
+ *   log level.
+ * - Copy zf_log.h and zf_log.c files into your library and build it with
+ *   ZF_LOG_LIBRARY_PREFIX defined to library specific prefix. See
+ *   ZF_LOG_LIBRARY_PREFIX for more details.
+ *
+ * When defined, ZF_LOG_OUTPUT_LEVEL must evaluate to integral value that
+ * corresponds to desired output log level. Use it only when compilation module
+ * is required to have output log level which is different from global output
+ * log level set by zf_log_set_output_level() function. For other cases,
+ * consider defining ZF_LOG_LEVEL or using zf_log_set_output_level() function.
+ *
+ * Example:
+ *
+ *   #define ZF_LOG_OUTPUT_LEVEL g_module_log_level
+ *   #include <zf_log.h>
+ *   static int g_module_log_level = ZF_LOG_INFO;
+ *   static void foo() {
+ *       ZF_LOGI("Will check g_module_log_level for output log level");
+ *   }
+ *   void debug_log(bool on) {
+ *       g_module_log_level = on? ZF_LOG_DEBUG: ZF_LOG_INFO;
+ *   }
+ *
+ * Note on performance. This expression will be evaluated each time message is
+ * logged (except when message log level is "disabled" - see ZF_LOG_LEVEL for
+ * details). Keep this expression as simple as possible, otherwise it will not
+ * only add runtime overhead, but also will increase size of call site (which
+ * will result in larger executable). The prefered way is to use integer
+ * variable (as in example above). If structure must be used, log_level field
+ * must be the first field in this structure:
+ *
+ *   #define ZF_LOG_OUTPUT_LEVEL (g_config.log_level)
+ *   #include <zf_log.h>
+ *   struct config {
+ *       int log_level;
+ *       unsigned other_field;
+ *       [...]
+ *   };
+ *   static config g_config = {ZF_LOG_INFO, 0, ...};
+ *
+ * This allows compiler to generate more compact load instruction (no need to
+ * specify offset since it's zero). Calling a function to get output log level
+ * is generaly a bad idea, since it will increase call site size and runtime
+ * overhead even further.
+ */
+#if defined(ZF_LOG_OUTPUT_LEVEL)
+	#define _ZF_LOG_OUTPUT_LEVEL ZF_LOG_OUTPUT_LEVEL
+#else
+	#define _ZF_LOG_OUTPUT_LEVEL _zf_log_global_output_lvl
+#endif
+
+/* "Tag" is a compound string that could be associated with a log message. It
+ * consists of tag prefix and tag (both are optional).
+ *
+ * Tag prefix is a global property and configured per process using
+ * zf_log_set_tag_prefix() function. Tag prefix identifies context in which
+ * component or module is running (e.g. process name). For example, the same
+ * library could be used in both client and server processes that work on the
+ * same machine. Tag prefix could be used to easily distinguish between them.
+ * For more details about tag prefix see zf_log_set_tag_prefix() function. Tag
+ * prefix
+ *
+ * Tag identifies component or module. It is configured per compilation module
+ * (.c/.cpp/.m file) by defining ZF_LOG_TAG or ZF_LOG_DEF_TAG. ZF_LOG_TAG has
+ * higer priority and when defined overrides value provided by ZF_LOG_DEF_TAG.
+ * When defined, value must evaluate to (const char *), so for strings double
+ * quotes must be used.
+ *
+ * Default tag could be defined with ZF_LOG_DEF_TAG in build script (e.g.
+ * Makefile, CMakeLists.txt, gyp, etc.) for the entire project or target:
+ *
+ *   CC_ARGS := -DZF_LOG_DEF_TAG=\"MISC\"
+ *
+ * And when necessary could be overriden with ZF_LOG_TAG in .c/.cpp/.m files
+ * before including zf_log.h:
+ *
+ *   #define ZF_LOG_TAG "MAIN"
+ *   #include <zf_log.h>
+ *
+ * If both ZF_LOG_DEF_TAG and ZF_LOG_TAG are undefined no tag will be added to
+ * the log message (tag prefix still could be added though).
+ *
+ * Output example:
+ *
+ *   04-29 22:43:20.244 40059  1299 I hello.MAIN Number of arguments: 1
+ *                                    |     |
+ *                                    |     +- tag (e.g. module)
+ *                                    +- tag prefix (e.g. process name)
+ */
+#if defined(ZF_LOG_TAG)
+	#define _ZF_LOG_TAG ZF_LOG_TAG
+#elif defined(ZF_LOG_DEF_TAG)
+	#define _ZF_LOG_TAG ZF_LOG_DEF_TAG
+#else
+	#define _ZF_LOG_TAG 0
+#endif
+
+/* Source location is part of a log line that describes location (function or
+ * method name, file name and line number, e.g. "[email protected]:68") of a
+ * log statement that produced it.
+ * Source location formats are:
+ * - ZF_LOG_SRCLOC_NONE - don't add source location to log line.
+ * - ZF_LOG_SRCLOC_SHORT - add source location in short form (file and line
+ *   number, e.g. "@main.cpp:68").
+ * - ZF_LOG_SRCLOC_LONG - add source location in long form (function or method
+ *   name, file and line number, e.g. "[email protected]:68").
+ */
+#define ZF_LOG_SRCLOC_NONE  0
+#define ZF_LOG_SRCLOC_SHORT 1
+#define ZF_LOG_SRCLOC_LONG  2
+
+/* Source location format is configured per compilation module (.c/.cpp/.m
+ * file) by defining ZF_LOG_DEF_SRCLOC or ZF_LOG_SRCLOC. ZF_LOG_SRCLOC has
+ * higer priority and when defined overrides value provided by
+ * ZF_LOG_DEF_SRCLOC.
+ *
+ * Common practice is to define default format with ZF_LOG_DEF_SRCLOC in
+ * build script (e.g. Makefile, CMakeLists.txt, gyp, etc.) for the entire
+ * project or target:
+ *
+ *   CC_ARGS := -DZF_LOG_DEF_SRCLOC=ZF_LOG_SRCLOC_LONG
+ *
+ * And when necessary to override it with ZF_LOG_SRCLOC in .c/.cpp/.m files
+ * before including zf_log.h:
+ *
+ *   #define ZF_LOG_SRCLOC ZF_LOG_SRCLOC_NONE
+ *   #include <zf_log.h>
+ *
+ * If both ZF_LOG_DEF_SRCLOC and ZF_LOG_SRCLOC are undefined, then
+ * ZF_LOG_SRCLOC_NONE will be used for release builds (NDEBUG is defined) and
+ * ZF_LOG_SRCLOC_LONG otherwise (NDEBUG is not defined).
+ */
+#if defined(ZF_LOG_SRCLOC)
+	#define _ZF_LOG_SRCLOC ZF_LOG_SRCLOC
+#elif defined(ZF_LOG_DEF_SRCLOC)
+	#define _ZF_LOG_SRCLOC ZF_LOG_DEF_SRCLOC
+#else
+	#ifdef NDEBUG
+		#define _ZF_LOG_SRCLOC ZF_LOG_SRCLOC_NONE
+	#else
+		#define _ZF_LOG_SRCLOC ZF_LOG_SRCLOC_LONG
+	#endif
+#endif
+#if ZF_LOG_SRCLOC_LONG == _ZF_LOG_SRCLOC
+	#define _ZF_LOG_SRCLOC_FUNCTION _ZF_LOG_FUNCTION
+#else
+	#define _ZF_LOG_SRCLOC_FUNCTION 0
+#endif
+
+/* Censoring provides conditional logging of secret information, also known as
+ * Personally Identifiable Information (PII) or Sensitive Personal Information
+ * (SPI). Censoring can be either enabled (ZF_LOG_CENSORED) or disabled
+ * (ZF_LOG_UNCENSORED). When censoring is enabled, log statements marked as
+ * "secrets" will be ignored and will have zero overhead (arguments also will
+ * not be evaluated).
+ */
+#define ZF_LOG_CENSORED   1
+#define ZF_LOG_UNCENSORED 0
+
+/* Censoring is configured per compilation module (.c/.cpp/.m file) by defining
+ * ZF_LOG_DEF_CENSORING or ZF_LOG_CENSORING. ZF_LOG_CENSORING has higer priority
+ * and when defined overrides value provided by ZF_LOG_DEF_CENSORING.
+ *
+ * Common practice is to define default censoring with ZF_LOG_DEF_CENSORING in
+ * build script (e.g. Makefile, CMakeLists.txt, gyp, etc.) for the entire
+ * project or target:
+ *
+ *   CC_ARGS := -DZF_LOG_DEF_CENSORING=ZF_LOG_CENSORED
+ *
+ * And when necessary to override it with ZF_LOG_CENSORING in .c/.cpp/.m files
+ * before including zf_log.h (consider doing it only for debug purposes and be
+ * very careful not to push such temporary changes to source control):
+ *
+ *   #define ZF_LOG_CENSORING ZF_LOG_UNCENSORED
+ *   #include <zf_log.h>
+ *
+ * If both ZF_LOG_DEF_CENSORING and ZF_LOG_CENSORING are undefined, then
+ * ZF_LOG_CENSORED will be used for release builds (NDEBUG is defined) and
+ * ZF_LOG_UNCENSORED otherwise (NDEBUG is not defined).
+ */
+#if defined(ZF_LOG_CENSORING)
+	#define _ZF_LOG_CENSORING ZF_LOG_CENSORING
+#elif defined(ZF_LOG_DEF_CENSORING)
+	#define _ZF_LOG_CENSORING ZF_LOG_DEF_CENSORING
+#else
+	#ifdef NDEBUG
+		#define _ZF_LOG_CENSORING ZF_LOG_CENSORED
+	#else
+		#define _ZF_LOG_CENSORING ZF_LOG_UNCENSORED
+	#endif
+#endif
+
+/* Check censoring at compile time. Evaluates to true when censoring is disabled
+ * (i.e. when secrets will be logged). For example:
+ *
+ *   #if ZF_LOG_SECRETS
+ *       char ssn[16];
+ *       getSocialSecurityNumber(ssn);
+ *       ZF_LOGI("Customer ssn: %s", ssn);
+ *   #endif
+ *
+ * See ZF_LOG_SECRET() macro for a more convenient way of guarding single log
+ * statement.
+ */
+#define ZF_LOG_SECRETS (ZF_LOG_UNCENSORED == _ZF_LOG_CENSORING)
+
+/* Static (compile-time) initialization support allows to configure logging
+ * before entering main() function. This mostly useful in C++ where functions
+ * and methods could be called during initialization of global objects. Those
+ * functions and methods could record log messages too and for that reason
+ * static initialization of logging configuration is customizable.
+ *
+ * Macros below allow to specify values to use for initial configuration:
+ * - ZF_LOG_EXTERN_TAG_PREFIX - tag prefix (default: none)
+ * - ZF_LOG_EXTERN_GLOBAL_FORMAT - global format options (default: see
+ *   ZF_LOG_MEM_WIDTH in zf_log.c)
+ * - ZF_LOG_EXTERN_GLOBAL_OUTPUT - global output facility (default: stderr or
+ *   platform specific, see ZF_LOG_USE_XXX macros in zf_log.c)
+ * - ZF_LOG_EXTERN_GLOBAL_OUTPUT_LEVEL - global output log level (default: 0 -
+ *   all levals are "turned on")
+ *
+ * For example, in log_config.c:
+ *
+ *   #include <zf_log.h>
+ *   ZF_LOG_DEFINE_TAG_PREFIX = "MyApp";
+ *   ZF_LOG_DEFINE_GLOBAL_FORMAT = {CUSTOM_MEM_WIDTH};
+ *   ZF_LOG_DEFINE_GLOBAL_OUTPUT = {ZF_LOG_PUT_STD, custom_output_callback, 0};
+ *   ZF_LOG_DEFINE_GLOBAL_OUTPUT_LEVEL = ZF_LOG_INFO;
+ *
+ * However, to use any of those macros zf_log library must be compiled with
+ * following macros defined:
+ * - to use ZF_LOG_DEFINE_TAG_PREFIX define ZF_LOG_EXTERN_TAG_PREFIX
+ * - to use ZF_LOG_DEFINE_GLOBAL_FORMAT define ZF_LOG_EXTERN_GLOBAL_FORMAT
+ * - to use ZF_LOG_DEFINE_GLOBAL_OUTPUT define ZF_LOG_EXTERN_GLOBAL_OUTPUT
+ * - to use ZF_LOG_DEFINE_GLOBAL_OUTPUT_LEVEL define
+ *   ZF_LOG_EXTERN_GLOBAL_OUTPUT_LEVEL
+ *
+ * When zf_log library compiled with one of ZF_LOG_EXTERN_XXX macros defined,
+ * corresponding ZF_LOG_DEFINE_XXX macro MUST be used exactly once somewhere.
+ * Otherwise build will fail with link error (undefined symbol).
+ */
+#define ZF_LOG_DEFINE_TAG_PREFIX const char *_zf_log_tag_prefix
+#define ZF_LOG_DEFINE_GLOBAL_FORMAT zf_log_format _zf_log_global_format
+#define ZF_LOG_DEFINE_GLOBAL_OUTPUT zf_log_output _zf_log_global_output
+#define ZF_LOG_DEFINE_GLOBAL_OUTPUT_LEVEL int _zf_log_global_output_lvl
+
+/* Pointer to global format options. Direct modification is not allowed. Use
+ * zf_log_set_mem_width() instead. Could be used to initialize zf_log_spec
+ * structure:
+ *
+ *   const zf_log_output g_output = {ZF_LOG_PUT_STD, output_callback, 0};
+ *   const zf_log_spec g_spec = {ZF_LOG_GLOBAL_FORMAT, &g_output};
+ *   ZF_LOGI_AUX(&g_spec, "Hello");
+ */
+#define ZF_LOG_GLOBAL_FORMAT ((const zf_log_format *)&_zf_log_global_format)
+
+/* Pointer to global output variable. Direct modification is not allowed. Use
+ * zf_log_set_output_v() or zf_log_set_output_p() instead. Could be used to
+ * initialize zf_log_spec structure:
+ *
+ *   const zf_log_format g_format = {40};
+ *   const zf_log_spec g_spec = {g_format, ZF_LOG_GLOBAL_OUTPUT};
+ *   ZF_LOGI_AUX(&g_spec, "Hello");
+ */
+#define ZF_LOG_GLOBAL_OUTPUT ((const zf_log_output *)&_zf_log_global_output)
+
+/* When defined, all library symbols produced by linker will be prefixed with
+ * provided value. That allows to use zf_log library privately in another
+ * libraries without exposing zf_log symbols in their original form (to avoid
+ * possible conflicts with other libraries / components that also could use
+ * zf_log for logging). Value must be without quotes, for example:
+ *
+ *   CC_ARGS := -DZF_LOG_LIBRARY_PREFIX=my_lib_
+ *
+ * Note, that in this mode ZF_LOG_LIBRARY_PREFIX must be defined when building
+ * zf_log library AND it also must be defined to the same value when building
+ * a library that uses it. For example, consider fictional KittyHttp library
+ * that wants to use zf_log for logging. First approach that could be taken is
+ * to add zf_log.h and zf_log.c to the KittyHttp's source code tree directly.
+ * In that case it will be enough just to define ZF_LOG_LIBRARY_PREFIX in
+ * KittyHttp's build script:
+ *
+ *   // KittyHttp/CMakeLists.txt
+ *   target_compile_definitions(KittyHttp PRIVATE
+ *                              "ZF_LOG_LIBRARY_PREFIX=KittyHttp_")
+ *
+ * If KittyHttp doesn't want to include zf_log source code in its source tree
+ * and wants to build zf_log as a separate library than zf_log library must be
+ * built with ZF_LOG_LIBRARY_PREFIX defined to KittyHttp_ AND KittyHttp library
+ * itself also needs to define ZF_LOG_LIBRARY_PREFIX to KittyHttp_. It can do
+ * so either in its build script, as in example above, or by providing a
+ * wrapper header that KittyHttp library will need to use instead of zf_log.h:
+ *
+ *   // KittyHttpLogging.h
+ *   #define ZF_LOG_LIBRARY_PREFIX KittyHttp_
+ *   #include <zf_log.h>
+ *
+ * Regardless of the method chosen, the end result is that zf_log symbols will
+ * be prefixed with "KittyHttp_", so if a user of KittyHttp (say DogeBrowser)
+ * also uses zf_log for logging, they will not interferer with each other. Both
+ * will have their own log level, output facility, format options etc.
+ */
+#ifdef ZF_LOG_LIBRARY_PREFIX
+	#define _ZF_LOG_DECOR__(prefix, name) prefix ## name
+	#define _ZF_LOG_DECOR_(prefix, name) _ZF_LOG_DECOR__(prefix, name)
+	#define _ZF_LOG_DECOR(name) _ZF_LOG_DECOR_(ZF_LOG_LIBRARY_PREFIX, name)
+
+	#define zf_log_set_tag_prefix _ZF_LOG_DECOR(zf_log_set_tag_prefix)
+	#define zf_log_set_mem_width _ZF_LOG_DECOR(zf_log_set_mem_width)
+	#define zf_log_set_output_level _ZF_LOG_DECOR(zf_log_set_output_level)
+	#define zf_log_set_output_v _ZF_LOG_DECOR(zf_log_set_output_v)
+	#define zf_log_set_output_p _ZF_LOG_DECOR(zf_log_set_output_p)
+	#define zf_log_out_stderr_callback _ZF_LOG_DECOR(zf_log_out_stderr_callback)
+	#define _zf_log_tag_prefix _ZF_LOG_DECOR(_zf_log_tag_prefix)
+	#define _zf_log_global_format _ZF_LOG_DECOR(_zf_log_global_format)
+	#define _zf_log_global_output _ZF_LOG_DECOR(_zf_log_global_output)
+	#define _zf_log_global_output_lvl _ZF_LOG_DECOR(_zf_log_global_output_lvl)
+	#define _zf_log_write_d _ZF_LOG_DECOR(_zf_log_write_d)
+	#define _zf_log_write_aux_d _ZF_LOG_DECOR(_zf_log_write_aux_d)
+	#define _zf_log_write _ZF_LOG_DECOR(_zf_log_write)
+	#define _zf_log_write_aux _ZF_LOG_DECOR(_zf_log_write_aux)
+	#define _zf_log_write_mem_d _ZF_LOG_DECOR(_zf_log_write_mem_d)
+	#define _zf_log_write_mem_aux_d _ZF_LOG_DECOR(_zf_log_write_mem_aux_d)
+	#define _zf_log_write_mem _ZF_LOG_DECOR(_zf_log_write_mem)
+	#define _zf_log_write_mem_aux _ZF_LOG_DECOR(_zf_log_write_mem_aux)
+	#define _zf_log_stderr_spec _ZF_LOG_DECOR(_zf_log_stderr_spec)
+#endif
+
+#if defined(__printflike)
+	#define _ZF_LOG_PRINTFLIKE(a, b) __printflike(a, b)
+#else
+	#define _ZF_LOG_PRINTFLIKE(a, b)
+#endif
+
+#if (defined(_WIN32) || defined(_WIN64)) && !defined(__GNUC__)
+	#define _ZF_LOG_FUNCTION __FUNCTION__
+#else
+	#define _ZF_LOG_FUNCTION __func__
+#endif
+
+#if defined(_MSC_VER) && !defined(__INTEL_COMPILER)
+	#define _ZF_LOG_INLINE __inline
+	#define _ZF_LOG_IF(cond) \
+		__pragma(warning(push)) \
+		__pragma(warning(disable:4127)) \
+		if(cond) \
+		__pragma(warning(pop))
+	#define _ZF_LOG_WHILE(cond) \
+		__pragma(warning(push)) \
+		__pragma(warning(disable:4127)) \
+		while(cond) \
+		__pragma(warning(pop))
+#else
+	#define _ZF_LOG_INLINE inline
+	#define _ZF_LOG_IF(cond) if(cond)
+	#define _ZF_LOG_WHILE(cond) while(cond)
+#endif
+#define _ZF_LOG_NEVER _ZF_LOG_IF(0)
+#define _ZF_LOG_ONCE _ZF_LOG_WHILE(0)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Set tag prefix. Prefix will be separated from the tag with dot ('.').
+ * Use 0 or empty string to disable (default). Common use is to set it to
+ * the process (or build target) name (e.g. to separate client and server
+ * processes). Function will NOT copy provided prefix string, but will store the
+ * pointer. Hence specified prefix string must remain valid. See
+ * ZF_LOG_DEFINE_TAG_PREFIX for a way to set it before entering main() function.
+ * See ZF_LOG_TAG for more information about tag and tag prefix.
+ */
+void zf_log_set_tag_prefix(const char *const prefix);
+
+/* Set number of bytes per log line in memory (ASCII-HEX) output. Example:
+ *
+ *   I hello.MAIN 4c6f72656d20697073756d20646f6c6f  Lorem ipsum dolo
+ *                |<-          w bytes         ->|  |<-  w chars ->|
+ *
+ * See ZF_LOGF_MEM and ZF_LOGF_MEM_AUX for more details.
+ */
+void zf_log_set_mem_width(const unsigned w);
+
+/* Set "output" log level. See ZF_LOG_LEVEL and ZF_LOG_OUTPUT_LEVEL for more
+ * info about log levels.
+ */
+void zf_log_set_output_level(const int lvl);
+
+/* Put mask is a set of flags that define what fields will be added to each
+ * log message. Default value is ZF_LOG_PUT_STD and other flags could be used to
+ * alter its behavior. See zf_log_set_output_v() for more details.
+ *
+ * Note about ZF_LOG_PUT_SRC: it will be added only in debug builds (NDEBUG is
+ * not defined).
+ */
+enum
+{
+	ZF_LOG_PUT_CTX = 1 << 0, /* context (time, pid, tid, log level) */
+	ZF_LOG_PUT_TAG = 1 << 1, /* tag (including tag prefix) */
+	ZF_LOG_PUT_SRC = 1 << 2, /* source location (file, line, function) */
+	ZF_LOG_PUT_MSG = 1 << 3, /* message text (formatted string) */
+	ZF_LOG_PUT_STD = 0xffff, /* everything (default) */
+};
+
+typedef struct zf_log_message
+{
+	int lvl; /* Log level of the message */
+	const char *tag; /* Associated tag (without tag prefix) */
+	char *buf; /* Buffer start */
+	char *e; /* Buffer end (last position where EOL with 0 could be written) */
+	char *p; /* Buffer content end (append position) */
+	char *tag_b; /* Prefixed tag start */
+	char *tag_e; /* Prefixed tag end (if != tag_b, points to msg separator) */
+	char *msg_b; /* Message start (expanded format string) */
+}
+zf_log_message;
+
+/* Type of output callback function. It will be called for each log line allowed
+ * by both "current" and "output" log levels ("enabled" and "turned on").
+ * Callback function is allowed to modify content of the buffers pointed by the
+ * msg, but it's not allowed to modify any of msg fields. Buffer pointed by msg
+ * is UTF-8 encoded (no BOM mark).
+ */
+typedef void (*zf_log_output_cb)(const zf_log_message *msg, void *arg);
+
+/* Format options. For more details see zf_log_set_mem_width().
+ */
+typedef struct zf_log_format
+{
+	unsigned mem_width; /* Bytes per line in memory (ASCII-HEX) dump */
+}
+zf_log_format;
+
+/* Output facility.
+ */
+typedef struct zf_log_output
+{
+	unsigned mask; /* What to put into log line buffer (see ZF_LOG_PUT_XXX) */
+	void *arg; /* User provided output callback argument */
+	zf_log_output_cb callback; /* Output callback function */
+}
+zf_log_output;
+
+/* Set output callback function.
+ *
+ * Mask allows to control what information will be added to the log line buffer
+ * before callback function is invoked. Default mask value is ZF_LOG_PUT_STD.
+ */
+void zf_log_set_output_v(const unsigned mask, void *const arg,
+						 const zf_log_output_cb callback);
+static _ZF_LOG_INLINE void zf_log_set_output_p(const zf_log_output *const output)
+{
+	zf_log_set_output_v(output->mask, output->arg, output->callback);
+}
+
+/* Used with _AUX macros and allows to override global format and output
+ * facility. Use ZF_LOG_GLOBAL_FORMAT and ZF_LOG_GLOBAL_OUTPUT for values from
+ * global configuration. Example:
+ *
+ *   static const zf_log_output module_output = {
+ *       ZF_LOG_PUT_STD, 0, custom_output_callback
+ *   };
+ *   static const zf_log_spec module_spec = {
+ *       ZF_LOG_GLOBAL_FORMAT, &module_output
+ *   };
+ *   ZF_LOGI_AUX(&module_spec, "Position: %ix%i", x, y);
+ *
+ * See ZF_LOGF_AUX and ZF_LOGF_MEM_AUX for details.
+ */
+typedef struct zf_log_spec
+{
+	const zf_log_format *format;
+	const zf_log_output *output;
+}
+zf_log_spec;
+
+#ifdef __cplusplus
+}
+#endif
+
+/* Execute log statement if condition is true. Example:
+ *
+ *   ZF_LOG_IF(1 < 2, ZF_LOGI("Log this"));
+ *   ZF_LOG_IF(1 > 2, ZF_LOGI("Don't log this"));
+ *
+ * Keep in mind though, that if condition can't be evaluated at compile time,
+ * then it will be evaluated at run time. This will increase exectuable size
+ * and can have noticeable performance overhead. Try to limit conditions to
+ * expressions that can be evaluated at compile time.
+ */
+#define ZF_LOG_IF(cond, f) do { _ZF_LOG_IF((cond)) { f; } } _ZF_LOG_ONCE
+
+/* Mark log statement as "secret". Log statements that are marked as secrets
+ * will NOT be executed when censoring is enabled (see ZF_LOG_CENSORED).
+ * Example:
+ *
+ *   ZF_LOG_SECRET(ZF_LOGI("Credit card: %s", credit_card));
+ *   ZF_LOG_SECRET(ZF_LOGD_MEM(cipher, cipher_sz, "Cipher bytes:"));
+ */
+#define ZF_LOG_SECRET(f) ZF_LOG_IF(ZF_LOG_SECRETS, f)
+
+/* Check "current" log level at compile time (ignoring "output" log level).
+ * Evaluates to true when specified log level is enabled. For example:
+ *
+ *   #if ZF_LOG_ENABLED_DEBUG
+ *       const char *const g_enum_strings[] = {
+ *           "enum_value_0", "enum_value_1", "enum_value_2"
+ *       };
+ *   #endif
+ *   // ...
+ *   #if ZF_LOG_ENABLED_DEBUG
+ *       ZF_LOGD("enum value: %s", g_enum_strings[v]);
+ *   #endif
+ *
+ * See ZF_LOG_LEVEL for details.
+ */
+#define ZF_LOG_ENABLED(lvl)     ((lvl) >= _ZF_LOG_LEVEL)
+#define ZF_LOG_ENABLED_VERBOSE  ZF_LOG_ENABLED(ZF_LOG_VERBOSE)
+#define ZF_LOG_ENABLED_DEBUG    ZF_LOG_ENABLED(ZF_LOG_DEBUG)
+#define ZF_LOG_ENABLED_INFO     ZF_LOG_ENABLED(ZF_LOG_INFO)
+#define ZF_LOG_ENABLED_WARN     ZF_LOG_ENABLED(ZF_LOG_WARN)
+#define ZF_LOG_ENABLED_ERROR    ZF_LOG_ENABLED(ZF_LOG_ERROR)
+#define ZF_LOG_ENABLED_FATAL    ZF_LOG_ENABLED(ZF_LOG_FATAL)
+
+/* Check "output" log level at run time (taking into account "current" log
+ * level as well). Evaluates to true when specified log level is turned on AND
+ * enabled. For example:
+ *
+ *   if (ZF_LOG_ON_DEBUG)
+ *   {
+ *       char hash[65];
+ *       sha256(data_ptr, data_sz, hash);
+ *       ZF_LOGD("data: len=%u, sha256=%s", data_sz, hash);
+ *   }
+ *
+ * See ZF_LOG_OUTPUT_LEVEL for details.
+ */
+#define ZF_LOG_ON(lvl) \
+		(ZF_LOG_ENABLED((lvl)) && (lvl) >= _ZF_LOG_OUTPUT_LEVEL)
+#define ZF_LOG_ON_VERBOSE   ZF_LOG_ON(ZF_LOG_VERBOSE)
+#define ZF_LOG_ON_DEBUG     ZF_LOG_ON(ZF_LOG_DEBUG)
+#define ZF_LOG_ON_INFO      ZF_LOG_ON(ZF_LOG_INFO)
+#define ZF_LOG_ON_WARN      ZF_LOG_ON(ZF_LOG_WARN)
+#define ZF_LOG_ON_ERROR     ZF_LOG_ON(ZF_LOG_ERROR)
+#define ZF_LOG_ON_FATAL     ZF_LOG_ON(ZF_LOG_FATAL)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern const char *_zf_log_tag_prefix;
+extern zf_log_format _zf_log_global_format;
+extern zf_log_output _zf_log_global_output;
+extern int _zf_log_global_output_lvl;
+extern const zf_log_spec _zf_log_stderr_spec;
+
+void _zf_log_write_d(
+		const char *const func, const char *const file, const unsigned line,
+		const int lvl, const char *const tag,
+		const char *const fmt, ...) _ZF_LOG_PRINTFLIKE(6, 7);
+void _zf_log_write_aux_d(
+		const char *const func, const char *const file, const unsigned line,
+		const zf_log_spec *const log, const int lvl, const char *const tag,
+		const char *const fmt, ...) _ZF_LOG_PRINTFLIKE(7, 8);
+void _zf_log_write(
+		const int lvl, const char *const tag,
+		const char *const fmt, ...) _ZF_LOG_PRINTFLIKE(3, 4);
+void _zf_log_write_aux(
+		const zf_log_spec *const log, const int lvl, const char *const tag,
+		const char *const fmt, ...) _ZF_LOG_PRINTFLIKE(4, 5);
+void _zf_log_write_mem_d(
+		const char *const func, const char *const file, const unsigned line,
+		const int lvl, const char *const tag,
+		const void *const d, const unsigned d_sz,
+		const char *const fmt, ...) _ZF_LOG_PRINTFLIKE(8, 9);
+void _zf_log_write_mem_aux_d(
+		const char *const func, const char *const file, const unsigned line,
+		const zf_log_spec *const log, const int lvl, const char *const tag,
+		const void *const d, const unsigned d_sz,
+		const char *const fmt, ...) _ZF_LOG_PRINTFLIKE(9, 10);
+void _zf_log_write_mem(
+		const int lvl, const char *const tag,
+		const void *const d, const unsigned d_sz,
+		const char *const fmt, ...) _ZF_LOG_PRINTFLIKE(5, 6);
+void _zf_log_write_mem_aux(
+		const zf_log_spec *const log, const int lvl, const char *const tag,
+		const void *const d, const unsigned d_sz,
+		const char *const fmt, ...) _ZF_LOG_PRINTFLIKE(6, 7);
+
+#ifdef __cplusplus
+}
+#endif
+
+/* Message logging macros:
+ * - ZF_LOGV("format string", args, ...)
+ * - ZF_LOGD("format string", args, ...)
+ * - ZF_LOGI("format string", args, ...)
+ * - ZF_LOGW("format string", args, ...)
+ * - ZF_LOGE("format string", args, ...)
+ * - ZF_LOGF("format string", args, ...)
+ *
+ * Memory logging macros:
+ * - ZF_LOGV_MEM(data_ptr, data_sz, "format string", args, ...)
+ * - ZF_LOGD_MEM(data_ptr, data_sz, "format string", args, ...)
+ * - ZF_LOGI_MEM(data_ptr, data_sz, "format string", args, ...)
+ * - ZF_LOGW_MEM(data_ptr, data_sz, "format string", args, ...)
+ * - ZF_LOGE_MEM(data_ptr, data_sz, "format string", args, ...)
+ * - ZF_LOGF_MEM(data_ptr, data_sz, "format string", args, ...)
+ *
+ * Auxiliary logging macros:
+ * - ZF_LOGV_AUX(&log_instance, "format string", args, ...)
+ * - ZF_LOGD_AUX(&log_instance, "format string", args, ...)
+ * - ZF_LOGI_AUX(&log_instance, "format string", args, ...)
+ * - ZF_LOGW_AUX(&log_instance, "format string", args, ...)
+ * - ZF_LOGE_AUX(&log_instance, "format string", args, ...)
+ * - ZF_LOGF_AUX(&log_instance, "format string", args, ...)
+ *
+ * Auxiliary memory logging macros:
+ * - ZF_LOGV_MEM_AUX(&log_instance, data_ptr, data_sz, "format string", args, ...)
+ * - ZF_LOGD_MEM_AUX(&log_instance, data_ptr, data_sz, "format string", args, ...)
+ * - ZF_LOGI_MEM_AUX(&log_instance, data_ptr, data_sz, "format string", args, ...)
+ * - ZF_LOGW_MEM_AUX(&log_instance, data_ptr, data_sz, "format string", args, ...)
+ * - ZF_LOGE_MEM_AUX(&log_instance, data_ptr, data_sz, "format string", args, ...)
+ * - ZF_LOGF_MEM_AUX(&log_instance, data_ptr, data_sz, "format string", args, ...)
+ *
+ * Preformatted string logging macros:
+ * - ZF_LOGV_STR("preformatted string");
+ * - ZF_LOGD_STR("preformatted string");
+ * - ZF_LOGI_STR("preformatted string");
+ * - ZF_LOGW_STR("preformatted string");
+ * - ZF_LOGE_STR("preformatted string");
+ * - ZF_LOGF_STR("preformatted string");
+ *
+ * Explicit log level and tag macros:
+ * - ZF_LOG_WRITE(level, tag, "format string", args, ...)
+ * - ZF_LOG_WRITE_MEM(level, tag, data_ptr, data_sz, "format string", args, ...)
+ * - ZF_LOG_WRITE_AUX(&log_instance, level, tag, "format string", args, ...)
+ * - ZF_LOG_WRITE_MEM_AUX(&log_instance, level, tag, data_ptr, data_sz,
+ *                        "format string", args, ...)
+ *
+ * Format string follows printf() conventions. Both data_ptr and data_sz could
+ * be 0. Tag can be 0 as well. Most compilers will verify that type of arguments
+ * match format specifiers in format string.
+ *
+ * Library assuming UTF-8 encoding for all strings (char *), including format
+ * string itself.
+ */
+#if ZF_LOG_SRCLOC_NONE == _ZF_LOG_SRCLOC
+	#define ZF_LOG_WRITE(lvl, tag, ...) \
+			do { \
+				if (ZF_LOG_ON(lvl)) \
+					_zf_log_write(lvl, tag, __VA_ARGS__); \
+			} _ZF_LOG_ONCE
+	#define ZF_LOG_WRITE_MEM(lvl, tag, d, d_sz, ...) \
+			do { \
+				if (ZF_LOG_ON(lvl)) \
+					_zf_log_write_mem(lvl, tag, d, d_sz, __VA_ARGS__); \
+			} _ZF_LOG_ONCE
+	#define ZF_LOG_WRITE_AUX(log, lvl, tag, ...) \
+			do { \
+				if (ZF_LOG_ON(lvl)) \
+					_zf_log_write_aux(log, lvl, tag, __VA_ARGS__); \
+			} _ZF_LOG_ONCE
+	#define ZF_LOG_WRITE_MEM_AUX(log, lvl, tag, d, d_sz, ...) \
+			do { \
+				if (ZF_LOG_ON(lvl)) \
+					_zf_log_write_mem_aux(log, lvl, tag, d, d_sz, __VA_ARGS__); \
+			} _ZF_LOG_ONCE
+#else
+	#define ZF_LOG_WRITE(lvl, tag, ...) \
+			do { \
+				if (ZF_LOG_ON(lvl)) \
+					_zf_log_write_d(_ZF_LOG_SRCLOC_FUNCTION, __FILE__, __LINE__, \
+							lvl, tag, __VA_ARGS__); \
+			} _ZF_LOG_ONCE
+	#define ZF_LOG_WRITE_MEM(lvl, tag, d, d_sz, ...) \
+			do { \
+				if (ZF_LOG_ON(lvl)) \
+					_zf_log_write_mem_d(_ZF_LOG_SRCLOC_FUNCTION, __FILE__, __LINE__, \
+							lvl, tag, d, d_sz, __VA_ARGS__); \
+			} _ZF_LOG_ONCE
+	#define ZF_LOG_WRITE_AUX(log, lvl, tag, ...) \
+			do { \
+				if (ZF_LOG_ON(lvl)) \
+					_zf_log_write_aux_d(_ZF_LOG_SRCLOC_FUNCTION, __FILE__, __LINE__, \
+							log, lvl, tag, __VA_ARGS__); \
+			} _ZF_LOG_ONCE
+	#define ZF_LOG_WRITE_MEM_AUX(log, lvl, tag, d, d_sz, ...) \
+			do { \
+				if (ZF_LOG_ON(lvl)) \
+					_zf_log_write_mem_aux_d(_ZF_LOG_SRCLOC_FUNCTION, __FILE__, __LINE__, \
+							log, lvl, tag, d, d_sz, __VA_ARGS__); \
+			} _ZF_LOG_ONCE
+#endif
+
+static _ZF_LOG_INLINE void _zf_log_unused(const int dummy, ...) {(void)dummy;}
+
+#define _ZF_LOG_UNUSED(...) \
+		do { _ZF_LOG_NEVER _zf_log_unused(0, __VA_ARGS__); } _ZF_LOG_ONCE
+
+#if ZF_LOG_ENABLED_VERBOSE
+	#define ZF_LOGV(...) \
+			ZF_LOG_WRITE(ZF_LOG_VERBOSE, _ZF_LOG_TAG, __VA_ARGS__)
+	#define ZF_LOGV_AUX(log, ...) \
+			ZF_LOG_WRITE_AUX(log, ZF_LOG_VERBOSE, _ZF_LOG_TAG, __VA_ARGS__)
+	#define ZF_LOGV_MEM(d, d_sz, ...) \
+			ZF_LOG_WRITE_MEM(ZF_LOG_VERBOSE, _ZF_LOG_TAG, d, d_sz, __VA_ARGS__)
+	#define ZF_LOGV_MEM_AUX(log, d, d_sz, ...) \
+			ZF_LOG_WRITE_MEM(log, ZF_LOG_VERBOSE, _ZF_LOG_TAG, d, d_sz, __VA_ARGS__)
+#else
+	#define ZF_LOGV(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGV_AUX(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGV_MEM(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGV_MEM_AUX(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+#endif
+
+#if ZF_LOG_ENABLED_DEBUG
+	#define ZF_LOGD(...) \
+			ZF_LOG_WRITE(ZF_LOG_DEBUG, _ZF_LOG_TAG, __VA_ARGS__)
+	#define ZF_LOGD_AUX(log, ...) \
+			ZF_LOG_WRITE_AUX(log, ZF_LOG_DEBUG, _ZF_LOG_TAG, __VA_ARGS__)
+	#define ZF_LOGD_MEM(d, d_sz, ...) \
+			ZF_LOG_WRITE_MEM(ZF_LOG_DEBUG, _ZF_LOG_TAG, d, d_sz, __VA_ARGS__)
+	#define ZF_LOGD_MEM_AUX(log, d, d_sz, ...) \
+			ZF_LOG_WRITE_MEM_AUX(log, ZF_LOG_DEBUG, _ZF_LOG_TAG, d, d_sz, __VA_ARGS__)
+#else
+	#define ZF_LOGD(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGD_AUX(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGD_MEM(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGD_MEM_AUX(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+#endif
+
+#if ZF_LOG_ENABLED_INFO
+	#define ZF_LOGI(...) \
+			ZF_LOG_WRITE(ZF_LOG_INFO, _ZF_LOG_TAG, __VA_ARGS__)
+	#define ZF_LOGI_AUX(log, ...) \
+			ZF_LOG_WRITE_AUX(log, ZF_LOG_INFO, _ZF_LOG_TAG, __VA_ARGS__)
+	#define ZF_LOGI_MEM(d, d_sz, ...) \
+			ZF_LOG_WRITE_MEM(ZF_LOG_INFO, _ZF_LOG_TAG, d, d_sz, __VA_ARGS__)
+	#define ZF_LOGI_MEM_AUX(log, d, d_sz, ...) \
+			ZF_LOG_WRITE_MEM_AUX(log, ZF_LOG_INFO, _ZF_LOG_TAG, d, d_sz, __VA_ARGS__)
+#else
+	#define ZF_LOGI(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGI_AUX(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGI_MEM(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGI_MEM_AUX(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+#endif
+
+#if ZF_LOG_ENABLED_WARN
+	#define ZF_LOGW(...) \
+			ZF_LOG_WRITE(ZF_LOG_WARN, _ZF_LOG_TAG, __VA_ARGS__)
+	#define ZF_LOGW_AUX(log, ...) \
+			ZF_LOG_WRITE_AUX(log, ZF_LOG_WARN, _ZF_LOG_TAG, __VA_ARGS__)
+	#define ZF_LOGW_MEM(d, d_sz, ...) \
+			ZF_LOG_WRITE_MEM(ZF_LOG_WARN, _ZF_LOG_TAG, d, d_sz, __VA_ARGS__)
+	#define ZF_LOGW_MEM_AUX(log, d, d_sz, ...) \
+			ZF_LOG_WRITE_MEM_AUX(log, ZF_LOG_WARN, _ZF_LOG_TAG, d, d_sz, __VA_ARGS__)
+#else
+	#define ZF_LOGW(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGW_AUX(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGW_MEM(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGW_MEM_AUX(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+#endif
+
+#if ZF_LOG_ENABLED_ERROR
+	#define ZF_LOGE(...) \
+			ZF_LOG_WRITE(ZF_LOG_ERROR, _ZF_LOG_TAG, __VA_ARGS__)
+	#define ZF_LOGE_AUX(log, ...) \
+			ZF_LOG_WRITE_AUX(log, ZF_LOG_ERROR, _ZF_LOG_TAG, __VA_ARGS__)
+	#define ZF_LOGE_MEM(d, d_sz, ...) \
+			ZF_LOG_WRITE_MEM(ZF_LOG_ERROR, _ZF_LOG_TAG, d, d_sz, __VA_ARGS__)
+	#define ZF_LOGE_MEM_AUX(log, d, d_sz, ...) \
+			ZF_LOG_WRITE_MEM_AUX(log, ZF_LOG_ERROR, _ZF_LOG_TAG, d, d_sz, __VA_ARGS__)
+#else
+	#define ZF_LOGE(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGE_AUX(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGE_MEM(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGE_MEM_AUX(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+#endif
+
+#if ZF_LOG_ENABLED_FATAL
+	#define ZF_LOGF(...) \
+			ZF_LOG_WRITE(ZF_LOG_FATAL, _ZF_LOG_TAG, __VA_ARGS__)
+	#define ZF_LOGF_AUX(log, ...) \
+			ZF_LOG_WRITE_AUX(log, ZF_LOG_FATAL, _ZF_LOG_TAG, __VA_ARGS__)
+	#define ZF_LOGF_MEM(d, d_sz, ...) \
+			ZF_LOG_WRITE_MEM(ZF_LOG_FATAL, _ZF_LOG_TAG, d, d_sz, __VA_ARGS__)
+	#define ZF_LOGF_MEM_AUX(log, d, d_sz, ...) \
+			ZF_LOG_WRITE_MEM_AUX(log, ZF_LOG_FATAL, _ZF_LOG_TAG, d, d_sz, __VA_ARGS__)
+#else
+	#define ZF_LOGF(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGF_AUX(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGF_MEM(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGF_MEM_AUX(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+#endif
+
+#define ZF_LOGV_STR(s) ZF_LOGV("%s", (s))
+#define ZF_LOGD_STR(s) ZF_LOGD("%s", (s))
+#define ZF_LOGI_STR(s) ZF_LOGI("%s", (s))
+#define ZF_LOGW_STR(s) ZF_LOGW("%s", (s))
+#define ZF_LOGE_STR(s) ZF_LOGE("%s", (s))
+#define ZF_LOGF_STR(s) ZF_LOGF("%s", (s))
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Output to standard error stream. Library uses it by default, though in few
+ * cases it could be necessary to specify it explicitly. For example, when
+ * zf_log library is compiled with ZF_LOG_EXTERN_GLOBAL_OUTPUT, application must
+ * define and initialize global output variable:
+ *
+ *   ZF_LOG_DEFINE_GLOBAL_OUTPUT = {ZF_LOG_OUT_STDERR};
+ *
+ * Another example is when using custom output, stderr could be used as a
+ * fallback when custom output facility failed to initialize:
+ *
+ *   zf_log_set_output_v(ZF_LOG_OUT_STDERR);
+ */
+enum { ZF_LOG_OUT_STDERR_MASK = ZF_LOG_PUT_STD };
+void zf_log_out_stderr_callback(const zf_log_message *const msg, void *arg);
+#define ZF_LOG_OUT_STDERR ZF_LOG_OUT_STDERR_MASK, 0, zf_log_out_stderr_callback
+
+/* Predefined spec for stderr. Uses global format options (ZF_LOG_GLOBAL_FORMAT)
+ * and ZF_LOG_OUT_STDERR. Could be used to force output to stderr for a
+ * particular message. Example:
+ *
+ *   f = fopen("foo.log", "w");
+ *   if (!f)
+ *       ZF_LOGE_AUX(ZF_LOG_STDERR, "Failed to open log file");
+ */
+#define ZF_LOG_STDERR (&_zf_log_stderr_spec)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

+ 85 - 0
zf_log/tests/zf_test.h

@@ -0,0 +1,85 @@
+#pragma once
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#if !defined(_ZF_TEST_STRINGIFY) && !defined(_ZF_TEST__STRINGIFY)
+#define _ZF_TEST__STRINGIFY(x) #x
+#define _ZF_TEST_STRINGIFY(x) _ZF_TEST__STRINGIFY(x)
+#endif
+
+#if defined(_MSC_VER) && !defined(__INTEL_COMPILER)
+	#define _ZF_TEST_INLINE __inline
+#else
+	#define _ZF_TEST_INLINE inline
+#endif
+
+/* Workaround for MSVC warning 4127 about constant expression in if condition.
+ */
+static _ZF_TEST_INLINE bool _zf_test_bool(const bool v)
+{
+	return v;
+}
+
+#define TEST_RUNNER_CREATE(argc, argv) \
+	(void)argc; (void)argv;
+
+#define TEST_SUIT_ARGUMENTS
+
+#define TEST_VERIFY_TRUE(a) \
+	if (!_zf_test_bool(a)) { \
+		fprintf(stderr, "%s:%u: %s:\n", __FILE__, __LINE__, "not true"); \
+		fprintf(stderr, "    false: %s\n", _ZF_TEST_STRINGIFY(a)); \
+		exit(1); \
+	}
+
+#define TEST_VERIFY_TRUE_MSG(a, ...) \
+	if (!_zf_test_bool(a)) { \
+		fprintf(stderr, "%s:%u: %s:\n", __FILE__, __LINE__, "not true"); \
+		fprintf(stderr, "    false: %s\n", _ZF_TEST_STRINGIFY(a)); \
+		fprintf(stderr, "    about: "); \
+		fprintf(stderr, __VA_ARGS__); \
+		fprintf(stderr, "\n"); \
+		exit(1); \
+	}
+
+#define TEST_VERIFY_FALSE(a) \
+	if (_zf_test_bool(a)) { \
+		fprintf(stderr, "%s:%u: %s:\n", __FILE__, __LINE__, "not false"); \
+		fprintf(stderr, "    true: %s\n", _ZF_TEST_STRINGIFY(a)); \
+		exit(1); \
+	}
+
+#define TEST_VERIFY_EQUAL(a, b) \
+	if (!_zf_test_bool((a) == (b))) { \
+		fprintf(stderr, "%s:%u: %s:\n", __FILE__, __LINE__, "not equal"); \
+		fprintf(stderr, "    left:   %s\n", _ZF_TEST_STRINGIFY(a)); \
+		fprintf(stderr, "    reight: %s\n", _ZF_TEST_STRINGIFY(b)); \
+		exit(1); \
+	}
+
+#define TEST_VERIFY_NOT_EQUAL(a, b) \
+	if (!_zf_test_bool((a) != (b))) { \
+		fprintf(stderr, "%s:%u: %s:\n", __FILE__, __LINE__, "equal"); \
+		fprintf(stderr, "    left:   %s\n", _ZF_TEST_STRINGIFY(a)); \
+		fprintf(stderr, "    reight: %s\n", _ZF_TEST_STRINGIFY(b)); \
+		exit(1); \
+	}
+
+#define TEST_VERIFY_GREATER_OR_EQUAL(a, b) \
+	if (!_zf_test_bool((a) >= (b))) { \
+		fprintf(stderr, "%s:%u: %s:\n", __FILE__, __LINE__, "less"); \
+		fprintf(stderr, "    left:   %s\n", _ZF_TEST_STRINGIFY(a)); \
+		fprintf(stderr, "    reight: %s\n", _ZF_TEST_STRINGIFY(b)); \
+		exit(1); \
+	}
+
+#define TEST_EXECUTE(f) \
+	f
+
+#define TEST_EXECUTE_SUITE(s) \
+	s()
+
+#define TEST_RUNNER_EXIT_CODE() \
+	0

+ 1 - 0
zf_log/zf_log-config.cmake

@@ -0,0 +1 @@
+include("${CMAKE_CURRENT_LIST_DIR}/zf_log.cmake")

+ 1 - 0
zf_log/zf_log-config.cmake.in

@@ -0,0 +1 @@
+include("${CMAKE_CURRENT_LIST_DIR}/zf_log.cmake")

+ 63 - 0
zf_log/zf_log.cmake

@@ -0,0 +1,63 @@
+# Generated by CMake
+
+if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" LESS 2.5)
+   message(FATAL_ERROR "CMake >= 2.6.0 required")
+endif()
+cmake_policy(PUSH)
+cmake_policy(VERSION 2.6)
+#----------------------------------------------------------------
+# Generated CMake target import file.
+#----------------------------------------------------------------
+
+# Commands may need to know the format version.
+set(CMAKE_IMPORT_FILE_VERSION 1)
+
+# Protect against multiple inclusion, which would fail when already imported targets are added once more.
+set(_targetsDefined)
+set(_targetsNotDefined)
+set(_expectedTargets)
+foreach(_expectedTarget zf_log)
+  list(APPEND _expectedTargets ${_expectedTarget})
+  if(NOT TARGET ${_expectedTarget})
+    list(APPEND _targetsNotDefined ${_expectedTarget})
+  endif()
+  if(TARGET ${_expectedTarget})
+    list(APPEND _targetsDefined ${_expectedTarget})
+  endif()
+endforeach()
+if("${_targetsDefined}" STREQUAL "${_expectedTargets}")
+  unset(_targetsDefined)
+  unset(_targetsNotDefined)
+  unset(_expectedTargets)
+  set(CMAKE_IMPORT_FILE_VERSION)
+  cmake_policy(POP)
+  return()
+endif()
+if(NOT "${_targetsDefined}" STREQUAL "")
+  message(FATAL_ERROR "Some (but not all) targets in this export set were already defined.\nTargets Defined: ${_targetsDefined}\nTargets not yet defined: ${_targetsNotDefined}\n")
+endif()
+unset(_targetsDefined)
+unset(_targetsNotDefined)
+unset(_expectedTargets)
+
+
+# Create imported target zf_log
+add_library(zf_log STATIC IMPORTED)
+
+set_target_properties(zf_log PROPERTIES
+  INTERFACE_INCLUDE_DIRECTORIES "/home/beanzilla/horrible-harry/zf_log/zf_log"
+)
+
+# Import target "zf_log" for configuration "Debug"
+set_property(TARGET zf_log APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
+set_target_properties(zf_log PROPERTIES
+  IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "C"
+  IMPORTED_LOCATION_DEBUG "/home/beanzilla/horrible-harry/zf_log/zf_log/libzf_log.a"
+  )
+
+# This file does not depend on other imported targets which have
+# been exported from the same project but in a separate export set.
+
+# Commands beyond this point should not need to know the version.
+set(CMAKE_IMPORT_FILE_VERSION)
+cmake_policy(POP)

+ 16 - 0
zf_log/zf_log/CMakeFiles/CMakeDirectoryInformation.cmake

@@ -0,0 +1,16 @@
+# CMAKE generated file: DO NOT EDIT!
+# Generated by "Unix Makefiles" Generator, CMake Version 3.10
+
+# Relative path conversion top directories.
+set(CMAKE_RELATIVE_PATH_TOP_SOURCE "/home/beanzilla/horrible-harry")
+set(CMAKE_RELATIVE_PATH_TOP_BINARY "/home/beanzilla/horrible-harry")
+
+# Force unix paths in dependencies.
+set(CMAKE_FORCE_UNIX_PATHS 1)
+
+
+# The C and CXX include file regular expressions for this directory.
+set(CMAKE_C_INCLUDE_REGEX_SCAN "^.*$")
+set(CMAKE_C_INCLUDE_REGEX_COMPLAIN "^$")
+set(CMAKE_CXX_INCLUDE_REGEX_SCAN ${CMAKE_C_INCLUDE_REGEX_SCAN})
+set(CMAKE_CXX_INCLUDE_REGEX_COMPLAIN ${CMAKE_C_INCLUDE_REGEX_COMPLAIN})

+ 1 - 0
zf_log/zf_log/CMakeFiles/progress.marks

@@ -0,0 +1 @@
+2

+ 58 - 0
zf_log/zf_log/CMakeFiles/zf_log.dir/C.includecache

@@ -0,0 +1,58 @@
+#IncludeRegexLine: ^[ 	]*[#%][ 	]*(include|import)[ 	]*[<"]([^">]+)([">])
+
+#IncludeRegexScan: ^.*$
+
+#IncludeRegexComplain: ^$
+
+#IncludeRegexTransform: 
+
+/home/beanzilla/horrible-harry/zf_log/zf_log/zf_log.c
+zf_log_config.h
+/home/beanzilla/horrible-harry/zf_log/zf_log/zf_log_config.h
+assert.h
+-
+ctype.h
+-
+string.h
+-
+time.h
+-
+stdarg.h
+-
+stddef.h
+-
+stdlib.h
+-
+stdio.h
+-
+zf_log.h
+/home/beanzilla/horrible-harry/zf_log/zf_log/zf_log.h
+windows.h
+-
+unistd.h
+-
+sys/time.h
+-
+linux/limits.h
+-
+limits.h
+-
+sys/syslimits.h
+-
+sys/prctl.h
+-
+sys/types.h
+-
+sys/syscall.h
+-
+pthread.h
+-
+android/log.h
+-
+CoreFoundation/CoreFoundation.h
+-
+windows.h
+-
+
+/home/beanzilla/horrible-harry/zf_log/zf_log/zf_log.h
+

+ 21 - 0
zf_log/zf_log/CMakeFiles/zf_log.dir/DependInfo.cmake

@@ -0,0 +1,21 @@
+# The set of languages for which implicit dependencies are needed:
+set(CMAKE_DEPENDS_LANGUAGES
+  "C"
+  )
+# The set of files for implicit dependencies of each language:
+set(CMAKE_DEPENDS_CHECK_C
+  "/home/beanzilla/horrible-harry/zf_log/zf_log/zf_log.c" "/home/beanzilla/horrible-harry/zf_log/zf_log/CMakeFiles/zf_log.dir/zf_log.c.o"
+  )
+set(CMAKE_C_COMPILER_ID "GNU")
+
+# The include file search paths:
+set(CMAKE_C_TARGET_INCLUDE_PATH
+  "zf_log/zf_log"
+  )
+
+# Targets to which this target links.
+set(CMAKE_TARGET_LINKED_INFO_FILES
+  )
+
+# Fortran module output directory.
+set(CMAKE_Fortran_TARGET_MODULE_DIR "")

+ 114 - 0
zf_log/zf_log/CMakeFiles/zf_log.dir/build.make

@@ -0,0 +1,114 @@
+# CMAKE generated file: DO NOT EDIT!
+# Generated by "Unix Makefiles" Generator, CMake Version 3.10
+
+# Delete rule output on recipe failure.
+.DELETE_ON_ERROR:
+
+
+#=============================================================================
+# Special targets provided by cmake.
+
+# Disable implicit rules so canonical targets will work.
+.SUFFIXES:
+
+
+# Remove some rules from gmake that .SUFFIXES does not remove.
+SUFFIXES =
+
+.SUFFIXES: .hpux_make_needs_suffix_list
+
+
+# Suppress display of executed commands.
+$(VERBOSE).SILENT:
+
+
+# A target that is always out of date.
+cmake_force:
+
+.PHONY : cmake_force
+
+#=============================================================================
+# Set environment variables for the build.
+
+# The shell in which to execute make rules.
+SHELL = /bin/sh
+
+# The CMake executable.
+CMAKE_COMMAND = /usr/bin/cmake
+
+# The command to remove a file.
+RM = /usr/bin/cmake -E remove -f
+
+# Escaping for special characters.
+EQUALS = =
+
+# The top-level source directory on which CMake was run.
+CMAKE_SOURCE_DIR = /home/beanzilla/horrible-harry
+
+# The top-level build directory on which CMake was run.
+CMAKE_BINARY_DIR = /home/beanzilla/horrible-harry
+
+# Include any dependencies generated for this target.
+include zf_log/zf_log/CMakeFiles/zf_log.dir/depend.make
+
+# Include the progress variables for this target.
+include zf_log/zf_log/CMakeFiles/zf_log.dir/progress.make
+
+# Include the compile flags for this target's objects.
+include zf_log/zf_log/CMakeFiles/zf_log.dir/flags.make
+
+zf_log/zf_log/CMakeFiles/zf_log.dir/zf_log.c.o: zf_log/zf_log/CMakeFiles/zf_log.dir/flags.make
+zf_log/zf_log/CMakeFiles/zf_log.dir/zf_log.c.o: zf_log/zf_log/zf_log.c
+	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green --progress-dir=/home/beanzilla/horrible-harry/CMakeFiles --progress-num=$(CMAKE_PROGRESS_1) "Building C object zf_log/zf_log/CMakeFiles/zf_log.dir/zf_log.c.o"
+	cd /home/beanzilla/horrible-harry/zf_log/zf_log && /usr/bin/cc $(C_DEFINES) $(C_INCLUDES) $(C_FLAGS) -o CMakeFiles/zf_log.dir/zf_log.c.o   -c /home/beanzilla/horrible-harry/zf_log/zf_log/zf_log.c
+
+zf_log/zf_log/CMakeFiles/zf_log.dir/zf_log.c.i: cmake_force
+	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green "Preprocessing C source to CMakeFiles/zf_log.dir/zf_log.c.i"
+	cd /home/beanzilla/horrible-harry/zf_log/zf_log && /usr/bin/cc $(C_DEFINES) $(C_INCLUDES) $(C_FLAGS) -E /home/beanzilla/horrible-harry/zf_log/zf_log/zf_log.c > CMakeFiles/zf_log.dir/zf_log.c.i
+
+zf_log/zf_log/CMakeFiles/zf_log.dir/zf_log.c.s: cmake_force
+	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green "Compiling C source to assembly CMakeFiles/zf_log.dir/zf_log.c.s"
+	cd /home/beanzilla/horrible-harry/zf_log/zf_log && /usr/bin/cc $(C_DEFINES) $(C_INCLUDES) $(C_FLAGS) -S /home/beanzilla/horrible-harry/zf_log/zf_log/zf_log.c -o CMakeFiles/zf_log.dir/zf_log.c.s
+
+zf_log/zf_log/CMakeFiles/zf_log.dir/zf_log.c.o.requires:
+
+.PHONY : zf_log/zf_log/CMakeFiles/zf_log.dir/zf_log.c.o.requires
+
+zf_log/zf_log/CMakeFiles/zf_log.dir/zf_log.c.o.provides: zf_log/zf_log/CMakeFiles/zf_log.dir/zf_log.c.o.requires
+	$(MAKE) -f zf_log/zf_log/CMakeFiles/zf_log.dir/build.make zf_log/zf_log/CMakeFiles/zf_log.dir/zf_log.c.o.provides.build
+.PHONY : zf_log/zf_log/CMakeFiles/zf_log.dir/zf_log.c.o.provides
+
+zf_log/zf_log/CMakeFiles/zf_log.dir/zf_log.c.o.provides.build: zf_log/zf_log/CMakeFiles/zf_log.dir/zf_log.c.o
+
+
+# Object files for target zf_log
+zf_log_OBJECTS = \
+"CMakeFiles/zf_log.dir/zf_log.c.o"
+
+# External object files for target zf_log
+zf_log_EXTERNAL_OBJECTS =
+
+zf_log/zf_log/libzf_log.a: zf_log/zf_log/CMakeFiles/zf_log.dir/zf_log.c.o
+zf_log/zf_log/libzf_log.a: zf_log/zf_log/CMakeFiles/zf_log.dir/build.make
+zf_log/zf_log/libzf_log.a: zf_log/zf_log/CMakeFiles/zf_log.dir/link.txt
+	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green --bold --progress-dir=/home/beanzilla/horrible-harry/CMakeFiles --progress-num=$(CMAKE_PROGRESS_2) "Linking C static library libzf_log.a"
+	cd /home/beanzilla/horrible-harry/zf_log/zf_log && $(CMAKE_COMMAND) -P CMakeFiles/zf_log.dir/cmake_clean_target.cmake
+	cd /home/beanzilla/horrible-harry/zf_log/zf_log && $(CMAKE_COMMAND) -E cmake_link_script CMakeFiles/zf_log.dir/link.txt --verbose=$(VERBOSE)
+
+# Rule to build all files generated by this target.
+zf_log/zf_log/CMakeFiles/zf_log.dir/build: zf_log/zf_log/libzf_log.a
+
+.PHONY : zf_log/zf_log/CMakeFiles/zf_log.dir/build
+
+zf_log/zf_log/CMakeFiles/zf_log.dir/requires: zf_log/zf_log/CMakeFiles/zf_log.dir/zf_log.c.o.requires
+
+.PHONY : zf_log/zf_log/CMakeFiles/zf_log.dir/requires
+
+zf_log/zf_log/CMakeFiles/zf_log.dir/clean:
+	cd /home/beanzilla/horrible-harry/zf_log/zf_log && $(CMAKE_COMMAND) -P CMakeFiles/zf_log.dir/cmake_clean.cmake
+.PHONY : zf_log/zf_log/CMakeFiles/zf_log.dir/clean
+
+zf_log/zf_log/CMakeFiles/zf_log.dir/depend:
+	cd /home/beanzilla/horrible-harry && $(CMAKE_COMMAND) -E cmake_depends "Unix Makefiles" /home/beanzilla/horrible-harry /home/beanzilla/horrible-harry/zf_log/zf_log /home/beanzilla/horrible-harry /home/beanzilla/horrible-harry/zf_log/zf_log /home/beanzilla/horrible-harry/zf_log/zf_log/CMakeFiles/zf_log.dir/DependInfo.cmake --color=$(COLOR)
+.PHONY : zf_log/zf_log/CMakeFiles/zf_log.dir/depend
+

+ 10 - 0
zf_log/zf_log/CMakeFiles/zf_log.dir/cmake_clean.cmake

@@ -0,0 +1,10 @@
+file(REMOVE_RECURSE
+  "CMakeFiles/zf_log.dir/zf_log.c.o"
+  "libzf_log.pdb"
+  "libzf_log.a"
+)
+
+# Per-language clean rules from dependency scanning.
+foreach(lang C)
+  include(CMakeFiles/zf_log.dir/cmake_clean_${lang}.cmake OPTIONAL)
+endforeach()

+ 3 - 0
zf_log/zf_log/CMakeFiles/zf_log.dir/cmake_clean_target.cmake

@@ -0,0 +1,3 @@
+file(REMOVE_RECURSE
+  "libzf_log.a"
+)

+ 6 - 0
zf_log/zf_log/CMakeFiles/zf_log.dir/depend.internal

@@ -0,0 +1,6 @@
+# CMAKE generated file: DO NOT EDIT!
+# Generated by "Unix Makefiles" Generator, CMake Version 3.10
+
+zf_log/zf_log/CMakeFiles/zf_log.dir/zf_log.c.o
+ /home/beanzilla/horrible-harry/zf_log/zf_log/zf_log.c
+ /home/beanzilla/horrible-harry/zf_log/zf_log/zf_log.h

+ 6 - 0
zf_log/zf_log/CMakeFiles/zf_log.dir/depend.make

@@ -0,0 +1,6 @@
+# CMAKE generated file: DO NOT EDIT!
+# Generated by "Unix Makefiles" Generator, CMake Version 3.10
+
+zf_log/zf_log/CMakeFiles/zf_log.dir/zf_log.c.o: zf_log/zf_log/zf_log.c
+zf_log/zf_log/CMakeFiles/zf_log.dir/zf_log.c.o: zf_log/zf_log/zf_log.h
+

+ 10 - 0
zf_log/zf_log/CMakeFiles/zf_log.dir/flags.make

@@ -0,0 +1,10 @@
+# CMAKE generated file: DO NOT EDIT!
+# Generated by "Unix Makefiles" Generator, CMake Version 3.10
+
+# compile C with /usr/bin/cc
+C_FLAGS =  -Wall -Wextra -Werror -pedantic-errors -g   -std=c99
+
+C_DEFINES = 
+
+C_INCLUDES = -I/home/beanzilla/horrible-harry/zf_log/zf_log 
+

+ 2 - 0
zf_log/zf_log/CMakeFiles/zf_log.dir/link.txt

@@ -0,0 +1,2 @@
+/usr/bin/ar qc libzf_log.a  CMakeFiles/zf_log.dir/zf_log.c.o
+/usr/bin/ranlib libzf_log.a

+ 3 - 0
zf_log/zf_log/CMakeFiles/zf_log.dir/progress.make

@@ -0,0 +1,3 @@
+CMAKE_PROGRESS_1 = 53
+CMAKE_PROGRESS_2 = 54
+

BIN
zf_log/zf_log/CMakeFiles/zf_log.dir/zf_log.c.o


+ 54 - 0
zf_log/zf_log/CMakeLists.txt

@@ -0,0 +1,54 @@
+cmake_minimum_required(VERSION 3.2)
+
+# zf_log target (required)
+set(HEADERS_DIR ${CMAKE_CURRENT_SOURCE_DIR})
+set(HEADERS zf_log.h)
+set(SOURCES zf_log.c)
+
+set(CMAKE_C_STANDARD 99)
+set(CMAKE_C_STANDARD_REQUIRED ON)
+set(CMAKE_C_EXTENSIONS OFF)
+if(MSVC)
+	set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /W4 /WX")
+else()
+	set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror -pedantic-errors")
+endif()
+
+add_library(zf_log ${HEADERS} ${SOURCES})
+target_include_directories(zf_log PUBLIC $<BUILD_INTERFACE:${HEADERS_DIR}>)
+if(ZF_LOG_LIBRARY_PREFIX)
+	target_compile_definitions(zf_log PRIVATE "ZF_LOG_LIBRARY_PREFIX=${ZF_LOG_LIBRARY_PREFIX}")
+endif()
+if(ZF_LOG_USE_ANDROID_LOG)
+	target_compile_definitions(zf_log PRIVATE "ZF_LOG_USE_ANDROID_LOG")
+	target_link_libraries(zf_log log)
+endif()
+if(ZF_LOG_USE_NSLOG)
+	target_compile_definitions(zf_log PRIVATE "ZF_LOG_USE_NSLOG")
+	target_link_libraries(zf_log "-framework CoreFoundation")
+endif()
+if(ZF_LOG_USE_DEBUGSTRING)
+	target_compile_definitions(zf_log PRIVATE "ZF_LOG_USE_DEBUGSTRING")
+endif()
+if(ZF_LOG_USE_CONFIG_HEADER)
+	target_compile_definitions(zf_log PRIVATE "ZF_LOG_USE_CONFIG_HEADER")
+endif()
+if(ZF_LOG_OPTIMIZE_SIZE)
+	target_compile_definitions(zf_log PRIVATE "ZF_LOG_OPTIMIZE_SIZE")
+endif()
+
+# install (optional)
+if(ZF_LOG_CONFIGURE_INSTALL)
+	if(NOT DEFINED INSTALL_INCLUDE_DIR)
+		set(INSTALL_INCLUDE_DIR include)
+	endif()
+	if(NOT DEFINED INSTALL_LIB_DIR)
+		set(INSTALL_LIB_DIR lib)
+	endif()
+	install(TARGETS zf_log EXPORT zf_log
+		INCLUDES DESTINATION ${INSTALL_INCLUDE_DIR}
+		ARCHIVE DESTINATION ${INSTALL_LIB_DIR})
+	install(DIRECTORY ${HEADERS_DIR}/
+		DESTINATION ${INSTALL_INCLUDE_DIR}
+		FILES_MATCHING PATTERN "zf_*.h*")
+endif()

+ 6 - 0
zf_log/zf_log/CTestTestfile.cmake

@@ -0,0 +1,6 @@
+# CMake generated Testfile for 
+# Source directory: /home/beanzilla/horrible-harry/zf_log/zf_log
+# Build directory: /home/beanzilla/horrible-harry/zf_log/zf_log
+# 
+# This file includes the relevant testing commands required for 
+# testing this directory and lists subdirectories to be tested as well.

+ 242 - 0
zf_log/zf_log/Makefile

@@ -0,0 +1,242 @@
+# CMAKE generated file: DO NOT EDIT!
+# Generated by "Unix Makefiles" Generator, CMake Version 3.10
+
+# Default target executed when no arguments are given to make.
+default_target: all
+
+.PHONY : default_target
+
+# Allow only one "make -f Makefile2" at a time, but pass parallelism.
+.NOTPARALLEL:
+
+
+#=============================================================================
+# Special targets provided by cmake.
+
+# Disable implicit rules so canonical targets will work.
+.SUFFIXES:
+
+
+# Remove some rules from gmake that .SUFFIXES does not remove.
+SUFFIXES =
+
+.SUFFIXES: .hpux_make_needs_suffix_list
+
+
+# Suppress display of executed commands.
+$(VERBOSE).SILENT:
+
+
+# A target that is always out of date.
+cmake_force:
+
+.PHONY : cmake_force
+
+#=============================================================================
+# Set environment variables for the build.
+
+# The shell in which to execute make rules.
+SHELL = /bin/sh
+
+# The CMake executable.
+CMAKE_COMMAND = /usr/bin/cmake
+
+# The command to remove a file.
+RM = /usr/bin/cmake -E remove -f
+
+# Escaping for special characters.
+EQUALS = =
+
+# The top-level source directory on which CMake was run.
+CMAKE_SOURCE_DIR = /home/beanzilla/horrible-harry
+
+# The top-level build directory on which CMake was run.
+CMAKE_BINARY_DIR = /home/beanzilla/horrible-harry
+
+#=============================================================================
+# Targets provided globally by CMake.
+
+# Special rule for the target install/strip
+install/strip: preinstall
+	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Installing the project stripped..."
+	/usr/bin/cmake -DCMAKE_INSTALL_DO_STRIP=1 -P cmake_install.cmake
+.PHONY : install/strip
+
+# Special rule for the target install/strip
+install/strip/fast: preinstall/fast
+	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Installing the project stripped..."
+	/usr/bin/cmake -DCMAKE_INSTALL_DO_STRIP=1 -P cmake_install.cmake
+.PHONY : install/strip/fast
+
+# Special rule for the target install/local
+install/local: preinstall
+	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Installing only the local directory..."
+	/usr/bin/cmake -DCMAKE_INSTALL_LOCAL_ONLY=1 -P cmake_install.cmake
+.PHONY : install/local
+
+# Special rule for the target install/local
+install/local/fast: preinstall/fast
+	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Installing only the local directory..."
+	/usr/bin/cmake -DCMAKE_INSTALL_LOCAL_ONLY=1 -P cmake_install.cmake
+.PHONY : install/local/fast
+
+# Special rule for the target test
+test:
+	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Running tests..."
+	/usr/bin/ctest --force-new-ctest-process $(ARGS)
+.PHONY : test
+
+# Special rule for the target test
+test/fast: test
+
+.PHONY : test/fast
+
+# Special rule for the target list_install_components
+list_install_components:
+	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Available install components are: \"Unspecified\""
+.PHONY : list_install_components
+
+# Special rule for the target list_install_components
+list_install_components/fast: list_install_components
+
+.PHONY : list_install_components/fast
+
+# Special rule for the target edit_cache
+edit_cache:
+	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "No interactive CMake dialog available..."
+	/usr/bin/cmake -E echo No\ interactive\ CMake\ dialog\ available.
+.PHONY : edit_cache
+
+# Special rule for the target edit_cache
+edit_cache/fast: edit_cache
+
+.PHONY : edit_cache/fast
+
+# Special rule for the target rebuild_cache
+rebuild_cache:
+	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Running CMake to regenerate build system..."
+	/usr/bin/cmake -H$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR)
+.PHONY : rebuild_cache
+
+# Special rule for the target rebuild_cache
+rebuild_cache/fast: rebuild_cache
+
+.PHONY : rebuild_cache/fast
+
+# Special rule for the target install
+install: preinstall
+	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Install the project..."
+	/usr/bin/cmake -P cmake_install.cmake
+.PHONY : install
+
+# Special rule for the target install
+install/fast: preinstall/fast
+	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Install the project..."
+	/usr/bin/cmake -P cmake_install.cmake
+.PHONY : install/fast
+
+# The main all target
+all: cmake_check_build_system
+	cd /home/beanzilla/horrible-harry && $(CMAKE_COMMAND) -E cmake_progress_start /home/beanzilla/horrible-harry/CMakeFiles /home/beanzilla/horrible-harry/zf_log/zf_log/CMakeFiles/progress.marks
+	cd /home/beanzilla/horrible-harry && $(MAKE) -f CMakeFiles/Makefile2 zf_log/zf_log/all
+	$(CMAKE_COMMAND) -E cmake_progress_start /home/beanzilla/horrible-harry/CMakeFiles 0
+.PHONY : all
+
+# The main clean target
+clean:
+	cd /home/beanzilla/horrible-harry && $(MAKE) -f CMakeFiles/Makefile2 zf_log/zf_log/clean
+.PHONY : clean
+
+# The main clean target
+clean/fast: clean
+
+.PHONY : clean/fast
+
+# Prepare targets for installation.
+preinstall: all
+	cd /home/beanzilla/horrible-harry && $(MAKE) -f CMakeFiles/Makefile2 zf_log/zf_log/preinstall
+.PHONY : preinstall
+
+# Prepare targets for installation.
+preinstall/fast:
+	cd /home/beanzilla/horrible-harry && $(MAKE) -f CMakeFiles/Makefile2 zf_log/zf_log/preinstall
+.PHONY : preinstall/fast
+
+# clear depends
+depend:
+	cd /home/beanzilla/horrible-harry && $(CMAKE_COMMAND) -H$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 1
+.PHONY : depend
+
+# Convenience name for target.
+zf_log/zf_log/CMakeFiles/zf_log.dir/rule:
+	cd /home/beanzilla/horrible-harry && $(MAKE) -f CMakeFiles/Makefile2 zf_log/zf_log/CMakeFiles/zf_log.dir/rule
+.PHONY : zf_log/zf_log/CMakeFiles/zf_log.dir/rule
+
+# Convenience name for target.
+zf_log: zf_log/zf_log/CMakeFiles/zf_log.dir/rule
+
+.PHONY : zf_log
+
+# fast build rule for target.
+zf_log/fast:
+	cd /home/beanzilla/horrible-harry && $(MAKE) -f zf_log/zf_log/CMakeFiles/zf_log.dir/build.make zf_log/zf_log/CMakeFiles/zf_log.dir/build
+.PHONY : zf_log/fast
+
+zf_log.o: zf_log.c.o
+
+.PHONY : zf_log.o
+
+# target to build an object file
+zf_log.c.o:
+	cd /home/beanzilla/horrible-harry && $(MAKE) -f zf_log/zf_log/CMakeFiles/zf_log.dir/build.make zf_log/zf_log/CMakeFiles/zf_log.dir/zf_log.c.o
+.PHONY : zf_log.c.o
+
+zf_log.i: zf_log.c.i
+
+.PHONY : zf_log.i
+
+# target to preprocess a source file
+zf_log.c.i:
+	cd /home/beanzilla/horrible-harry && $(MAKE) -f zf_log/zf_log/CMakeFiles/zf_log.dir/build.make zf_log/zf_log/CMakeFiles/zf_log.dir/zf_log.c.i
+.PHONY : zf_log.c.i
+
+zf_log.s: zf_log.c.s
+
+.PHONY : zf_log.s
+
+# target to generate assembly for a file
+zf_log.c.s:
+	cd /home/beanzilla/horrible-harry && $(MAKE) -f zf_log/zf_log/CMakeFiles/zf_log.dir/build.make zf_log/zf_log/CMakeFiles/zf_log.dir/zf_log.c.s
+.PHONY : zf_log.c.s
+
+# Help Target
+help:
+	@echo "The following are some of the valid targets for this Makefile:"
+	@echo "... all (the default if no target is provided)"
+	@echo "... clean"
+	@echo "... depend"
+	@echo "... install/strip"
+	@echo "... install/local"
+	@echo "... test"
+	@echo "... list_install_components"
+	@echo "... zf_log"
+	@echo "... edit_cache"
+	@echo "... rebuild_cache"
+	@echo "... install"
+	@echo "... zf_log.o"
+	@echo "... zf_log.i"
+	@echo "... zf_log.s"
+.PHONY : help
+
+
+
+#=============================================================================
+# Special targets to cleanup operation of make.
+
+# Special rule to run CMake to check the build system integrity.
+# No rule that depends on this can have commands that come from listfiles
+# because they might be regenerated.
+cmake_check_build_system:
+	cd /home/beanzilla/horrible-harry && $(CMAKE_COMMAND) -H$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 0
+.PHONY : cmake_check_build_system
+

+ 47 - 0
zf_log/zf_log/cmake_install.cmake

@@ -0,0 +1,47 @@
+# Install script for directory: /home/beanzilla/horrible-harry/zf_log/zf_log
+
+# Set the install prefix
+if(NOT DEFINED CMAKE_INSTALL_PREFIX)
+  set(CMAKE_INSTALL_PREFIX "/usr/local")
+endif()
+string(REGEX REPLACE "/$" "" CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}")
+
+# Set the install configuration name.
+if(NOT DEFINED CMAKE_INSTALL_CONFIG_NAME)
+  if(BUILD_TYPE)
+    string(REGEX REPLACE "^[^A-Za-z0-9_]+" ""
+           CMAKE_INSTALL_CONFIG_NAME "${BUILD_TYPE}")
+  else()
+    set(CMAKE_INSTALL_CONFIG_NAME "Debug")
+  endif()
+  message(STATUS "Install configuration: \"${CMAKE_INSTALL_CONFIG_NAME}\"")
+endif()
+
+# Set the component getting installed.
+if(NOT CMAKE_INSTALL_COMPONENT)
+  if(COMPONENT)
+    message(STATUS "Install component: \"${COMPONENT}\"")
+    set(CMAKE_INSTALL_COMPONENT "${COMPONENT}")
+  else()
+    set(CMAKE_INSTALL_COMPONENT)
+  endif()
+endif()
+
+# Install shared libraries without execute permission?
+if(NOT DEFINED CMAKE_INSTALL_SO_NO_EXE)
+  set(CMAKE_INSTALL_SO_NO_EXE "1")
+endif()
+
+# Is this installation the result of a crosscompile?
+if(NOT DEFINED CMAKE_CROSSCOMPILING)
+  set(CMAKE_CROSSCOMPILING "FALSE")
+endif()
+
+if("x${CMAKE_INSTALL_COMPONENT}x" STREQUAL "xUnspecifiedx" OR NOT CMAKE_INSTALL_COMPONENT)
+  file(INSTALL DESTINATION "${CMAKE_INSTALL_PREFIX}/lib" TYPE STATIC_LIBRARY FILES "/home/beanzilla/horrible-harry/zf_log/zf_log/libzf_log.a")
+endif()
+
+if("x${CMAKE_INSTALL_COMPONENT}x" STREQUAL "xUnspecifiedx" OR NOT CMAKE_INSTALL_COMPONENT)
+  file(INSTALL DESTINATION "${CMAKE_INSTALL_PREFIX}/include" TYPE DIRECTORY FILES "/home/beanzilla/horrible-harry/zf_log/zf_log/" FILES_MATCHING REGEX "/zf\\_[^/]*\\.h[^/]*$")
+endif()
+

BIN
zf_log/zf_log/libzf_log.a


+ 1389 - 0
zf_log/zf_log/zf_log.c

@@ -0,0 +1,1389 @@
+#ifdef ZF_LOG_USE_CONFIG_HEADER
+	#include "zf_log_config.h"
+#endif
+
+/* When defined, Android log (android/log.h) will be used by default instead of
+ * stderr (ignored on non-Android platforms). Date, time, pid and tid (context)
+ * will be provided by Android log. Android log features will be used to output
+ * log level and tag.
+ */
+#ifdef ZF_LOG_USE_ANDROID_LOG
+	#undef ZF_LOG_USE_ANDROID_LOG
+	#if defined(__ANDROID__)
+		#define ZF_LOG_USE_ANDROID_LOG 1
+	#else
+		#define ZF_LOG_USE_ANDROID_LOG 0
+	#endif
+#else
+	#define ZF_LOG_USE_ANDROID_LOG 0
+#endif
+/* When defined, NSLog (uses Apple System Log) will be used instead of stderr
+ * (ignored on non-Apple platforms). Date, time, pid and tid (context) will be
+ * provided by NSLog. Curiously, doesn't use NSLog() directly, but piggybacks on
+ * non-public CFLog() function. Both use Apple System Log internally, but it's
+ * easier to call CFLog() from C than NSLog(). Current implementation doesn't
+ * support "%@" format specifier.
+ */
+#ifdef ZF_LOG_USE_NSLOG
+	#undef ZF_LOG_USE_NSLOG
+	#if defined(__APPLE__) && defined(__MACH__)
+		#define ZF_LOG_USE_NSLOG 1
+	#else
+		#define ZF_LOG_USE_NSLOG 0
+	#endif
+#else
+	#define ZF_LOG_USE_NSLOG 0
+#endif
+/* When defined, OutputDebugString() will be used instead of stderr (ignored on
+ * non-Windows platforms). Uses OutputDebugStringA() variant and feeds it with
+ * UTF-8 data.
+ */
+#ifdef ZF_LOG_USE_DEBUGSTRING
+	#undef ZF_LOG_USE_DEBUGSTRING
+	#if defined(_WIN32) || defined(_WIN64)
+		#define ZF_LOG_USE_DEBUGSTRING 1
+	#else
+		#define ZF_LOG_USE_DEBUGSTRING 0
+	#endif
+#else
+	#define ZF_LOG_USE_DEBUGSTRING 0
+#endif
+/* When defined, zf_log library will not contain definition of tag prefix
+ * variable. In that case it must be defined elsewhere using
+ * ZF_LOG_DEFINE_TAG_PREFIX macro, for example:
+ *
+ *   ZF_LOG_DEFINE_TAG_PREFIX = "ProcessName";
+ *
+ * This allows to specify custom value for static initialization and avoid
+ * overhead of setting this value in runtime.
+ */
+#ifdef ZF_LOG_EXTERN_TAG_PREFIX
+	#undef ZF_LOG_EXTERN_TAG_PREFIX
+	#define ZF_LOG_EXTERN_TAG_PREFIX 1
+#else
+	#define ZF_LOG_EXTERN_TAG_PREFIX 0
+#endif
+/* When defined, zf_log library will not contain definition of global format
+ * variable. In that case it must be defined elsewhere using
+ * ZF_LOG_DEFINE_GLOBAL_FORMAT macro, for example:
+ *
+ *   ZF_LOG_DEFINE_GLOBAL_FORMAT = {MEM_WIDTH};
+ *
+ * This allows to specify custom value for static initialization and avoid
+ * overhead of setting this value in runtime.
+ */
+#ifdef ZF_LOG_EXTERN_GLOBAL_FORMAT
+	#undef ZF_LOG_EXTERN_GLOBAL_FORMAT
+	#define ZF_LOG_EXTERN_GLOBAL_FORMAT 1
+#else
+	#define ZF_LOG_EXTERN_GLOBAL_FORMAT 0
+#endif
+/* When defined, zf_log library will not contain definition of global output
+ * variable. In that case it must be defined elsewhere using
+ * ZF_LOG_DEFINE_GLOBAL_OUTPUT macro, for example:
+ *
+ *   ZF_LOG_DEFINE_GLOBAL_OUTPUT = {ZF_LOG_PUT_STD, custom_output_callback};
+ *
+ * This allows to specify custom value for static initialization and avoid
+ * overhead of setting this value in runtime.
+ */
+#ifdef ZF_LOG_EXTERN_GLOBAL_OUTPUT
+	#undef ZF_LOG_EXTERN_GLOBAL_OUTPUT
+	#define ZF_LOG_EXTERN_GLOBAL_OUTPUT 1
+#else
+	#define ZF_LOG_EXTERN_GLOBAL_OUTPUT 0
+#endif
+/* When defined, zf_log library will not contain definition of global output
+ * level variable. In that case it must be defined elsewhere using
+ * ZF_LOG_DEFINE_GLOBAL_OUTPUT_LEVEL macro, for example:
+ *
+ *   ZF_LOG_DEFINE_GLOBAL_OUTPUT_LEVEL = ZF_LOG_WARN;
+ *
+ * This allows to specify custom value for static initialization and avoid
+ * overhead of setting this value in runtime.
+ */
+#ifdef ZF_LOG_EXTERN_GLOBAL_OUTPUT_LEVEL
+	#undef ZF_LOG_EXTERN_GLOBAL_OUTPUT_LEVEL
+	#define ZF_LOG_EXTERN_GLOBAL_OUTPUT_LEVEL 1
+#else
+	#define ZF_LOG_EXTERN_GLOBAL_OUTPUT_LEVEL 0
+#endif
+/* When defined, implementation will prefer smaller code size over speed.
+ * Very rough estimate is that code will be up to 2x smaller and up to 2x
+ * slower. Disabled by default.
+ */
+#ifdef ZF_LOG_OPTIMIZE_SIZE
+	#undef ZF_LOG_OPTIMIZE_SIZE
+	#define ZF_LOG_OPTIMIZE_SIZE 1
+#else
+	#define ZF_LOG_OPTIMIZE_SIZE 0
+#endif
+/* Size of the log line buffer. The buffer is allocated on stack. It limits
+ * maximum length of a log line.
+ */
+#ifndef ZF_LOG_BUF_SZ
+	#define ZF_LOG_BUF_SZ 512
+#endif
+/* Default number of bytes in one line of memory output. For large values
+ * ZF_LOG_BUF_SZ also must be increased.
+ */
+#ifndef ZF_LOG_MEM_WIDTH
+	#define ZF_LOG_MEM_WIDTH 32
+#endif
+/* String to put in the end of each log line (can be empty). Its value used by
+ * stderr output callback. Its size used as a default value for ZF_LOG_EOL_SZ.
+ */
+#ifndef ZF_LOG_EOL
+	#define ZF_LOG_EOL "\n"
+#endif
+/* Default delimiter that separates parts of log message. Can NOT contain '%'
+ * or '\0'.
+ *
+ * Log message format specifications can override (or ignore) this value. For
+ * more details see ZF_LOG_MESSAGE_CTX_FORMAT, ZF_LOG_MESSAGE_SRC_FORMAT and
+ * ZF_LOG_MESSAGE_TAG_FORMAT.
+ */
+#ifndef ZF_LOG_DEF_DELIMITER
+	#define ZF_LOG_DEF_DELIMITER " "
+#endif
+/* Specifies log message context format. Log message context includes date,
+ * time, process id, thread id and message's log level. Custom information can
+ * be added as well. Supported fields: YEAR, MONTH, DAY, HOUR, MINUTE, SECOND,
+ * MILLISECOND, PID, TID, LEVEL, S(str), F_INIT(statements),
+ * F_UINT(width, value).
+ *
+ * Must be defined as a tuple, for example:
+ *
+ *   #define ZF_LOG_MESSAGE_CTX_FORMAT (YEAR, S("."), MONTH, S("."), DAY, S(" > "))
+ *
+ * In that case, resulting log message will be:
+ *
+ *   2016.12.22 > TAG [email protected]:line Message text
+ *
+ * Note, that tag, source location and message text are not impacted by
+ * this setting. See ZF_LOG_MESSAGE_TAG_FORMAT and ZF_LOG_MESSAGE_SRC_FORMAT.
+ *
+ * If message context must be visually separated from the rest of the message,
+ * it must be reflected in context format (notice trailing S(" > ") in the
+ * example above).
+ *
+ * S(str) adds constant string str. String can NOT contain '%' or '\0'.
+ *
+ * F_INIT(statements) adds initialization statement(s) that will be evaluated
+ * once for each log message. All statements are evaluated in specified order.
+ * Several F_INIT() fields can be used in every log message format
+ * specification. Fields, like F_UINT(width, value), are allowed to use results
+ * of initialization statements. If statement introduces variables (or other
+ * names, like structures) they must be prefixed with "f_". Statements  must be
+ * enclosed into additional "()". Example:
+ *
+ *   #define ZF_LOG_MESSAGE_CTX_FORMAT \
+ *       (F_INIT(( struct rusage f_ru; getrusage(RUSAGE_SELF, &f_ru); )), \
+ *        YEAR, S("."), MONTH, S("."), DAY, S(" "), \
+ *        F_UINT(5, f_ru.ru_nsignals), \
+ *        S(" "))
+ *
+ * F_UINT(width, value) adds unsigned integer value extended with up to width
+ * spaces (for alignment purposes). Value can be any expression that evaluates
+ * to unsigned integer. If expression contains non-standard functions, they
+ * must be declared with F_INIT(). Example:
+ *
+ *   #define ZF_LOG_MESSAGE_CTX_FORMAT \
+ *        (YEAR, S("."), MONTH, S("."), DAY, S(" "), \
+ *        F_INIT(( unsigned tickcount(); )), \
+ *        F_UINT(5, tickcount()), \
+ *        S(" "))
+ *
+ * Other log message format specifications follow same rules, but have a
+ * different set of supported fields.
+ */
+#ifndef ZF_LOG_MESSAGE_CTX_FORMAT
+	#define ZF_LOG_MESSAGE_CTX_FORMAT \
+		(MONTH, S("-"), DAY, S(ZF_LOG_DEF_DELIMITER), \
+		 HOUR, S(":"), MINUTE, S(":"), SECOND, S("."), MILLISECOND, S(ZF_LOG_DEF_DELIMITER), \
+		 PID, S(ZF_LOG_DEF_DELIMITER), TID, S(ZF_LOG_DEF_DELIMITER), \
+		 LEVEL, S(ZF_LOG_DEF_DELIMITER))
+#endif
+/* Specifies log message tag format. It includes tag prefix and tag. Custom
+ * information can be added as well. Supported fields:
+ * TAG(prefix_delimiter, tag_delimiter), S(str), F_INIT(statements),
+ * F_UINT(width, value).
+ *
+ * TAG(prefix_delimiter, tag_delimiter) adds following string to log message:
+ *
+ *   PREFIX<prefix_delimiter>TAG<tag_delimiter>
+ *
+ * Prefix delimiter will be used only when prefix is not empty. Tag delimiter
+ * will be used only when prefixed tag is not empty. Example:
+ *
+ *   #define ZF_LOG_TAG_FORMAT (S("["), TAG(".", ""), S("] "))
+ *
+ * See ZF_LOG_MESSAGE_CTX_FORMAT for details.
+ */
+#ifndef ZF_LOG_MESSAGE_TAG_FORMAT
+	#define ZF_LOG_MESSAGE_TAG_FORMAT \
+		(TAG(".", ZF_LOG_DEF_DELIMITER))
+#endif
+/* Specifies log message source location format. It includes function name,
+ * file name and file line. Custom information can be added as well. Supported
+ * fields: FUNCTION, FILENAME, FILELINE, S(str), F_INIT(statements),
+ * F_UINT(width, value).
+ *
+ * See ZF_LOG_MESSAGE_CTX_FORMAT for details.
+ */
+#ifndef ZF_LOG_MESSAGE_SRC_FORMAT
+	#define ZF_LOG_MESSAGE_SRC_FORMAT \
+		(FUNCTION, S("@"), FILENAME, S(":"), FILELINE, S(ZF_LOG_DEF_DELIMITER))
+#endif
+/* Fields that can be used in log message format specifications (see above).
+ * Mentioning them here explicitly, so we know that nobody else defined them
+ * before us. See ZF_LOG_MESSAGE_CTX_FORMAT for details.
+ */
+#define YEAR YEAR
+#define MONTH MONTH
+#define DAY DAY
+#define MINUTE MINUTE
+#define SECOND SECOND
+#define MILLISECOND MILLISECOND
+#define PID PID
+#define TID TID
+#define LEVEL LEVEL
+#define TAG(prefix_delim, tag_delim) TAG(prefix_delim, tag_delim)
+#define FUNCTION FUNCTION
+#define FILENAME FILENAME
+#define FILELINE FILELINE
+#define S(str) S(str)
+#define F_INIT(statements) F_INIT(statements)
+#define F_UINT(width, value) F_UINT(width, value)
+/* To use custom `vsnprintf()` function, define `ZF_LOG_CUSTOM_VSNPRINTF` to have its name. Example:
+ *   #define ZF_LOG_CUSTOM_VSNPRINTF my_vsnprintf
+ */
+#ifdef ZF_LOG_CUSTOM_VSNPRINTF
+	#define _ZF_LOG_VSNPRINTF ZF_LOG_CUSTOM_VSNPRINTF
+#endif
+/* To use custom `snprintf()` function, define `ZF_LOG_CUSTOM_SNPRINTF` to have its name. Example:
+ *   #define ZF_LOG_CUSTOM_SNPRINTF my_snprintf
+ * Note, that it only will be used when `ZF_LOG_OPTIMIZE_SIZE` is enabled.
+ */
+#ifdef ZF_LOG_CUSTOM_SNPRINTF
+	#define _ZF_LOG_SNPRINTF ZF_LOG_CUSTOM_SNPRINTF
+#endif
+/* Number of bytes to reserve for EOL in the log line buffer (must be >0).
+ * Must be larger than or equal to length of ZF_LOG_EOL with terminating null.
+ */
+#ifndef ZF_LOG_EOL_SZ
+	#define ZF_LOG_EOL_SZ sizeof(ZF_LOG_EOL)
+#endif
+/* Compile instrumented version of the library to facilitate unit testing.
+ */
+#ifndef ZF_LOG_INSTRUMENTED
+	#define ZF_LOG_INSTRUMENTED 0
+#endif
+
+#if defined(__linux__)
+	#if !defined(__ANDROID__) && !defined(_GNU_SOURCE)
+		#define _GNU_SOURCE
+	#endif
+#endif
+#if defined(__MINGW32__)
+	#ifdef __STRICT_ANSI__
+		#undef __STRICT_ANSI__
+	#endif
+#endif
+#include <assert.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include "zf_log.h"
+
+#if defined(_WIN32) || defined(_WIN64)
+	#include <windows.h>
+#else
+	#include <unistd.h>
+	#include <sys/time.h>
+	#if defined(__linux__)
+		#include <linux/limits.h>
+	#elif defined(_AIX) || defined(__CYGWIN__)
+		#include <limits.h>
+	#else
+		#include <sys/syslimits.h>
+	#endif
+#endif
+
+#if defined(__linux__)
+	#include <sys/prctl.h>
+	#include <sys/types.h>
+	#if !defined(__ANDROID__)
+		#include <sys/syscall.h>
+	#endif
+#endif
+#if defined(__MACH__) || defined(_AIX)
+	#include <pthread.h>
+#endif
+
+#define INLINE _ZF_LOG_INLINE
+#define VAR_UNUSED(var) (void)var
+#define RETVAL_UNUSED(expr) do { while(expr) break; } while(0)
+#define STATIC_ASSERT(name, cond) \
+	typedef char assert_##name[(cond)? 1: -1]
+#define ASSERT_UNREACHABLE(why) assert(!sizeof(why))
+#ifndef _countof
+	#define _countof(xs) (sizeof(xs) / sizeof((xs)[0]))
+#endif
+
+#if ZF_LOG_INSTRUMENTED
+	#define INSTRUMENTED_CONST
+#else
+	#define INSTRUMENTED_CONST const
+#endif
+
+#define _PP_PASTE_2(a, b) a ## b
+#define _PP_CONCAT_2(a, b) _PP_PASTE_2(a, b)
+
+#define _PP_PASTE_3(a, b, c) a ## b ## c
+#define _PP_CONCAT_3(a, b, c) _PP_PASTE_3(a, b, c)
+
+/* Microsoft C preprocessor is a piece of shit. This moron treats __VA_ARGS__
+ * as a single token and requires additional expansion to realize that it's
+ * actually a list. If not for it, there would be no need in this extra
+ * expansion.
+ */
+#define _PP_ID(x) x
+#define _PP_NARGS_N(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,...) _24
+#define _PP_NARGS(...) _PP_ID(_PP_NARGS_N(__VA_ARGS__,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0))
+
+/* There is a more efficient way to implement this, but it requires
+ * working C preprocessor. Unfortunately, Microsoft Visual Studio doesn't
+ * have one.
+ */
+#define _PP_HEAD__(x, ...) x
+#define _PP_HEAD_(...) _PP_ID(_PP_HEAD__(__VA_ARGS__, ~))
+#define _PP_HEAD(xs) _PP_HEAD_ xs
+#define _PP_TAIL_(x, ...) (__VA_ARGS__)
+#define _PP_TAIL(xs) _PP_TAIL_ xs
+#define _PP_UNTUPLE_(...) __VA_ARGS__
+#define _PP_UNTUPLE(xs) _PP_UNTUPLE_ xs
+
+/* Apply function macro to each element in tuple. Output is not
+ * enforced to be a tuple.
+ */
+#define _PP_MAP_1(f, xs) f(_PP_HEAD(xs))
+#define _PP_MAP_2(f, xs) f(_PP_HEAD(xs)) _PP_MAP_1(f, _PP_TAIL(xs))
+#define _PP_MAP_3(f, xs) f(_PP_HEAD(xs)) _PP_MAP_2(f, _PP_TAIL(xs))
+#define _PP_MAP_4(f, xs) f(_PP_HEAD(xs)) _PP_MAP_3(f, _PP_TAIL(xs))
+#define _PP_MAP_5(f, xs) f(_PP_HEAD(xs)) _PP_MAP_4(f, _PP_TAIL(xs))
+#define _PP_MAP_6(f, xs) f(_PP_HEAD(xs)) _PP_MAP_5(f, _PP_TAIL(xs))
+#define _PP_MAP_7(f, xs) f(_PP_HEAD(xs)) _PP_MAP_6(f, _PP_TAIL(xs))
+#define _PP_MAP_8(f, xs) f(_PP_HEAD(xs)) _PP_MAP_7(f, _PP_TAIL(xs))
+#define _PP_MAP_9(f, xs) f(_PP_HEAD(xs)) _PP_MAP_8(f, _PP_TAIL(xs))
+#define _PP_MAP_10(f, xs) f(_PP_HEAD(xs)) _PP_MAP_9(f, _PP_TAIL(xs))
+#define _PP_MAP_11(f, xs) f(_PP_HEAD(xs)) _PP_MAP_10(f, _PP_TAIL(xs))
+#define _PP_MAP_12(f, xs) f(_PP_HEAD(xs)) _PP_MAP_11(f, _PP_TAIL(xs))
+#define _PP_MAP_13(f, xs) f(_PP_HEAD(xs)) _PP_MAP_12(f, _PP_TAIL(xs))
+#define _PP_MAP_14(f, xs) f(_PP_HEAD(xs)) _PP_MAP_13(f, _PP_TAIL(xs))
+#define _PP_MAP_15(f, xs) f(_PP_HEAD(xs)) _PP_MAP_14(f, _PP_TAIL(xs))
+#define _PP_MAP_16(f, xs) f(_PP_HEAD(xs)) _PP_MAP_15(f, _PP_TAIL(xs))
+#define _PP_MAP_17(f, xs) f(_PP_HEAD(xs)) _PP_MAP_16(f, _PP_TAIL(xs))
+#define _PP_MAP_18(f, xs) f(_PP_HEAD(xs)) _PP_MAP_17(f, _PP_TAIL(xs))
+#define _PP_MAP_19(f, xs) f(_PP_HEAD(xs)) _PP_MAP_18(f, _PP_TAIL(xs))
+#define _PP_MAP_20(f, xs) f(_PP_HEAD(xs)) _PP_MAP_19(f, _PP_TAIL(xs))
+#define _PP_MAP_21(f, xs) f(_PP_HEAD(xs)) _PP_MAP_20(f, _PP_TAIL(xs))
+#define _PP_MAP_22(f, xs) f(_PP_HEAD(xs)) _PP_MAP_21(f, _PP_TAIL(xs))
+#define _PP_MAP_23(f, xs) f(_PP_HEAD(xs)) _PP_MAP_22(f, _PP_TAIL(xs))
+#define _PP_MAP_24(f, xs) f(_PP_HEAD(xs)) _PP_MAP_23(f, _PP_TAIL(xs))
+#define _PP_MAP(f, xs) _PP_CONCAT_2(_PP_MAP_, _PP_NARGS xs) (f, xs)
+
+/* Apply function macro to each element in tuple in reverse order.
+ * Output is not enforced to be a tuple.
+ */
+#define _PP_RMAP_1(f, xs) f(_PP_HEAD(xs))
+#define _PP_RMAP_2(f, xs) _PP_RMAP_1(f, _PP_TAIL(xs)) f(_PP_HEAD(xs))
+#define _PP_RMAP_3(f, xs) _PP_RMAP_2(f, _PP_TAIL(xs)) f(_PP_HEAD(xs))
+#define _PP_RMAP_4(f, xs) _PP_RMAP_3(f, _PP_TAIL(xs)) f(_PP_HEAD(xs))
+#define _PP_RMAP_5(f, xs) _PP_RMAP_4(f, _PP_TAIL(xs)) f(_PP_HEAD(xs))
+#define _PP_RMAP_6(f, xs) _PP_RMAP_5(f, _PP_TAIL(xs)) f(_PP_HEAD(xs))
+#define _PP_RMAP_7(f, xs) _PP_RMAP_6(f, _PP_TAIL(xs)) f(_PP_HEAD(xs))
+#define _PP_RMAP_8(f, xs) _PP_RMAP_7(f, _PP_TAIL(xs)) f(_PP_HEAD(xs))
+#define _PP_RMAP_9(f, xs) _PP_RMAP_8(f, _PP_TAIL(xs)) f(_PP_HEAD(xs))
+#define _PP_RMAP_10(f, xs) _PP_RMAP_9(f, _PP_TAIL(xs)) f(_PP_HEAD(xs))
+#define _PP_RMAP_11(f, xs) _PP_RMAP_10(f, _PP_TAIL(xs)) f(_PP_HEAD(xs))
+#define _PP_RMAP_12(f, xs) _PP_RMAP_11(f, _PP_TAIL(xs)) f(_PP_HEAD(xs))
+#define _PP_RMAP_13(f, xs) _PP_RMAP_12(f, _PP_TAIL(xs)) f(_PP_HEAD(xs))
+#define _PP_RMAP_14(f, xs) _PP_RMAP_13(f, _PP_TAIL(xs)) f(_PP_HEAD(xs))
+#define _PP_RMAP_15(f, xs) _PP_RMAP_14(f, _PP_TAIL(xs)) f(_PP_HEAD(xs))
+#define _PP_RMAP_16(f, xs) _PP_RMAP_15(f, _PP_TAIL(xs)) f(_PP_HEAD(xs))
+#define _PP_RMAP_17(f, xs) _PP_RMAP_16(f, _PP_TAIL(xs)) f(_PP_HEAD(xs))
+#define _PP_RMAP_18(f, xs) _PP_RMAP_17(f, _PP_TAIL(xs)) f(_PP_HEAD(xs))
+#define _PP_RMAP_19(f, xs) _PP_RMAP_18(f, _PP_TAIL(xs)) f(_PP_HEAD(xs))
+#define _PP_RMAP_20(f, xs) _PP_RMAP_19(f, _PP_TAIL(xs)) f(_PP_HEAD(xs))
+#define _PP_RMAP_21(f, xs) _PP_RMAP_20(f, _PP_TAIL(xs)) f(_PP_HEAD(xs))
+#define _PP_RMAP_22(f, xs) _PP_RMAP_21(f, _PP_TAIL(xs)) f(_PP_HEAD(xs))
+#define _PP_RMAP_23(f, xs) _PP_RMAP_22(f, _PP_TAIL(xs)) f(_PP_HEAD(xs))
+#define _PP_RMAP_24(f, xs) _PP_RMAP_23(f, _PP_TAIL(xs)) f(_PP_HEAD(xs))
+#define _PP_RMAP(f, xs) _PP_CONCAT_2(_PP_RMAP_, _PP_NARGS xs) (f, xs)
+
+/* Used to implement _ZF_LOG_MESSAGE_FORMAT_CONTAINS() macro. All possible
+ * fields must be mentioned here. Not counting F_INIT() here because it's
+ * somewhat special and is handled spearatly (at least for now).
+ */
+#define _ZF_LOG_MESSAGE_FORMAT_MASK__             (0<<0)
+#define _ZF_LOG_MESSAGE_FORMAT_MASK__YEAR         (1<<1)
+#define _ZF_LOG_MESSAGE_FORMAT_MASK__MONTH        (1<<2)
+#define _ZF_LOG_MESSAGE_FORMAT_MASK__DAY          (1<<3)
+#define _ZF_LOG_MESSAGE_FORMAT_MASK__HOUR         (1<<4)
+#define _ZF_LOG_MESSAGE_FORMAT_MASK__MINUTE       (1<<5)
+#define _ZF_LOG_MESSAGE_FORMAT_MASK__SECOND       (1<<6)
+#define _ZF_LOG_MESSAGE_FORMAT_MASK__MILLISECOND  (1<<7)
+#define _ZF_LOG_MESSAGE_FORMAT_MASK__PID          (1<<8)
+#define _ZF_LOG_MESSAGE_FORMAT_MASK__TID          (1<<9)
+#define _ZF_LOG_MESSAGE_FORMAT_MASK__LEVEL        (1<<10)
+#define _ZF_LOG_MESSAGE_FORMAT_MASK__TAG(ps, ts)  (1<<11)
+#define _ZF_LOG_MESSAGE_FORMAT_MASK__FUNCTION     (1<<12)
+#define _ZF_LOG_MESSAGE_FORMAT_MASK__FILENAME     (1<<13)
+#define _ZF_LOG_MESSAGE_FORMAT_MASK__FILELINE     (1<<14)
+#define _ZF_LOG_MESSAGE_FORMAT_MASK__S(s)         (1<<15)
+#define _ZF_LOG_MESSAGE_FORMAT_MASK__F_INIT(expr) (0<<16)
+#define _ZF_LOG_MESSAGE_FORMAT_MASK__F_UINT(w, v) (1<<17)
+#define _ZF_LOG_MESSAGE_FORMAT_MASK(field) \
+	_PP_CONCAT_3(_ZF_LOG_MESSAGE_FORMAT_MASK_, _, field)
+
+/* Logical "or" of masks of fields used in specified format specification.
+ */
+#define _ZF_LOG_MESSAGE_FORMAT_FIELDS(format) \
+	(0 _PP_MAP(| _ZF_LOG_MESSAGE_FORMAT_MASK, format))
+
+/* Expands to expressions that evaluates to true if field is used in
+ * specified format specification. Example:
+ *
+ *   #if _ZF_LOG_MESSAGE_FORMAT_CONTAINS(F_UINT, ZF_LOG_MESSAGE_CTX_FORMAT)
+ *       ...
+ *   #endif
+ */
+#define _ZF_LOG_MESSAGE_FORMAT_CONTAINS(field, format) \
+	(_ZF_LOG_MESSAGE_FORMAT_MASK(field) & _ZF_LOG_MESSAGE_FORMAT_FIELDS(format))
+
+/* Same, but checks all supported format specifications.
+ */
+#define _ZF_LOG_MESSAGE_FORMAT_FIELD_USED(field) \
+	(_ZF_LOG_MESSAGE_FORMAT_CONTAINS(field, ZF_LOG_MESSAGE_CTX_FORMAT) || \
+	 _ZF_LOG_MESSAGE_FORMAT_CONTAINS(field, ZF_LOG_MESSAGE_TAG_FORMAT) || \
+	 _ZF_LOG_MESSAGE_FORMAT_CONTAINS(field, ZF_LOG_MESSAGE_SRC_FORMAT))
+
+#define _ZF_LOG_MESSAGE_FORMAT_DATETIME_USED \
+	(_ZF_LOG_MESSAGE_FORMAT_CONTAINS(YEAR, ZF_LOG_MESSAGE_CTX_FORMAT) || \
+	 _ZF_LOG_MESSAGE_FORMAT_CONTAINS(MONTH, ZF_LOG_MESSAGE_CTX_FORMAT) || \
+	 _ZF_LOG_MESSAGE_FORMAT_CONTAINS(DAY, ZF_LOG_MESSAGE_CTX_FORMAT) || \
+	 _ZF_LOG_MESSAGE_FORMAT_CONTAINS(HOUR, ZF_LOG_MESSAGE_CTX_FORMAT) || \
+	 _ZF_LOG_MESSAGE_FORMAT_CONTAINS(MINUTE, ZF_LOG_MESSAGE_CTX_FORMAT) || \
+	 _ZF_LOG_MESSAGE_FORMAT_CONTAINS(SECOND, ZF_LOG_MESSAGE_CTX_FORMAT) || \
+	 _ZF_LOG_MESSAGE_FORMAT_CONTAINS(MILLISECOND, ZF_LOG_MESSAGE_CTX_FORMAT))
+
+#if defined(_MSC_VER) && !defined(__INTEL_COMPILER)
+	#pragma warning(disable:4204) /* nonstandard extension used: non-constant aggregate initializer */
+	#define memccpy _memccpy
+#endif
+
+#ifndef _ZF_LOG_VSNPRINTF
+	#if (defined(_MSC_VER) && !defined(__INTEL_COMPILER)) || defined(__MINGW64__)
+		static int fake_vsnprintf(char *s, size_t sz, const char *fmt, va_list ap)
+		{
+			const int n = vsnprintf_s(s, sz, _TRUNCATE, fmt, ap);
+			return 0 < n? n: (int)sz + 1; /* no need in _vscprintf() for now */
+		}
+		#define _ZF_LOG_VSNPRINTF fake_vsnprintf
+	#else
+		#define _ZF_LOG_VSNPRINTF vsnprintf
+	#endif
+#endif
+
+#if ZF_LOG_OPTIMIZE_SIZE
+	#ifndef _ZF_LOG_SNPRINTF
+		#if (defined(_MSC_VER) && !defined(__INTEL_COMPILER)) || defined(__MINGW64__)
+			static int fake_snprintf(char *s, size_t sz, const char *fmt, ...)
+			{
+				va_list va;
+				va_start(va, fmt);
+				const int n = _ZF_LOG_VSNPRINTF(s, sz, fmt, va);
+				va_end(va);
+				return n;
+			}
+			#define _ZF_LOG_SNPRINTF fake_snprintf
+		#else
+			#define _ZF_LOG_SNPRINTF snprintf
+		#endif
+	#endif
+#endif
+
+typedef void (*time_cb)(struct tm *const tm, unsigned *const usec);
+typedef void (*pid_cb)(int *const pid, int *const tid);
+typedef void (*buffer_cb)(zf_log_message *msg, char *buf);
+
+typedef struct src_location
+{
+	const char *const func;
+	const char *const file;
+	const unsigned line;
+}
+src_location;
+
+typedef struct mem_block
+{
+	const void *const d;
+	const unsigned d_sz;
+}
+mem_block;
+
+static void time_callback(struct tm *const tm, unsigned *const usec);
+static void pid_callback(int *const pid, int *const tid);
+static void buffer_callback(zf_log_message *msg, char *buf);
+
+STATIC_ASSERT(eol_fits_eol_sz, sizeof(ZF_LOG_EOL) <= ZF_LOG_EOL_SZ);
+STATIC_ASSERT(eol_sz_greater_than_zero, 0 < ZF_LOG_EOL_SZ);
+STATIC_ASSERT(eol_sz_less_than_buf_sz, ZF_LOG_EOL_SZ < ZF_LOG_BUF_SZ);
+#if !defined(_WIN32) && !defined(_WIN64)
+	STATIC_ASSERT(buf_sz_less_than_pipe_buf, ZF_LOG_BUF_SZ <= PIPE_BUF);
+#endif
+static const char c_hex[] = "0123456789abcdef";
+
+static INSTRUMENTED_CONST unsigned g_buf_sz = ZF_LOG_BUF_SZ - ZF_LOG_EOL_SZ;
+static INSTRUMENTED_CONST time_cb g_time_cb = time_callback;
+static INSTRUMENTED_CONST pid_cb g_pid_cb = pid_callback;
+static INSTRUMENTED_CONST buffer_cb g_buffer_cb = buffer_callback;
+
+#if ZF_LOG_USE_ANDROID_LOG
+	#include <android/log.h>
+
+	static INLINE int android_lvl(const int lvl)
+	{
+		switch (lvl)
+		{
+		case ZF_LOG_VERBOSE:
+			return ANDROID_LOG_VERBOSE;
+		case ZF_LOG_DEBUG:
+			return ANDROID_LOG_DEBUG;
+		case ZF_LOG_INFO:
+			return ANDROID_LOG_INFO;
+		case ZF_LOG_WARN:
+			return ANDROID_LOG_WARN;
+		case ZF_LOG_ERROR:
+			return ANDROID_LOG_ERROR;
+		case ZF_LOG_FATAL:
+			return ANDROID_LOG_FATAL;
+		default:
+			ASSERT_UNREACHABLE("Bad log level");
+			return ANDROID_LOG_UNKNOWN;
+		}
+	}
+
+	static void out_android_callback(const zf_log_message *const msg, void *arg)
+	{
+		VAR_UNUSED(arg);
+		*msg->p = 0;
+		const char *tag = msg->p;
+		if (msg->tag_e != msg->tag_b)
+		{
+			tag = msg->tag_b;
+			*msg->tag_e = 0;
+		}
+		__android_log_print(android_lvl(msg->lvl), tag, "%s", msg->msg_b);
+	}
+
+	enum { OUT_ANDROID_MASK = ZF_LOG_PUT_STD & ~ZF_LOG_PUT_CTX };
+	#define OUT_ANDROID OUT_ANDROID_MASK, 0, out_android_callback
+#endif
+
+#if ZF_LOG_USE_NSLOG
+	#include <CoreFoundation/CoreFoundation.h>
+	CF_EXPORT void CFLog(int32_t level, CFStringRef format, ...);
+
+	static INLINE int apple_lvl(const int lvl)
+	{
+		switch (lvl)
+		{
+		case ZF_LOG_VERBOSE:
+			return 7; /* ASL_LEVEL_DEBUG / kCFLogLevelDebug */;
+		case ZF_LOG_DEBUG:
+			return 7; /* ASL_LEVEL_DEBUG / kCFLogLevelDebug */;
+		case ZF_LOG_INFO:
+			return 6; /* ASL_LEVEL_INFO / kCFLogLevelInfo */;
+		case ZF_LOG_WARN:
+			return 4; /* ASL_LEVEL_WARNING / kCFLogLevelWarning */;
+		case ZF_LOG_ERROR:
+			return 3; /* ASL_LEVEL_ERR / kCFLogLevelError */;
+		case ZF_LOG_FATAL:
+			return 0; /* ASL_LEVEL_EMERG / kCFLogLevelEmergency */;
+		default:
+			ASSERT_UNREACHABLE("Bad log level");
+			return 0; /* ASL_LEVEL_EMERG / kCFLogLevelEmergency */;
+		}
+	}
+
+	static void out_nslog_callback(const zf_log_message *const msg, void *arg)
+	{
+		VAR_UNUSED(arg);
+		*msg->p = 0;
+		CFLog(apple_lvl(msg->lvl), CFSTR("%s"), msg->tag_b);
+	}
+
+	enum { OUT_NSLOG_MASK = ZF_LOG_PUT_STD & ~ZF_LOG_PUT_CTX };
+	#define OUT_NSLOG OUT_NSLOG_MASK, 0, out_nslog_callback
+#endif
+
+#if ZF_LOG_USE_DEBUGSTRING
+	#include <windows.h>
+
+	static void out_debugstring_callback(const zf_log_message *const msg, void *arg)
+	{
+		VAR_UNUSED(arg);
+		msg->p[0] = '\n';
+		msg->p[1] = '\0';
+		OutputDebugStringA(msg->buf);
+	}
+
+	enum { OUT_DEBUGSTRING_MASK = ZF_LOG_PUT_STD };
+	#define OUT_DEBUGSTRING OUT_DEBUGSTRING_MASK, 0, out_debugstring_callback
+#endif
+
+void zf_log_out_stderr_callback(const zf_log_message *const msg, void *arg)
+{
+	VAR_UNUSED(arg);
+	const size_t eol_len = sizeof(ZF_LOG_EOL) - 1;
+	memcpy(msg->p, ZF_LOG_EOL, eol_len);
+#if defined(_WIN32) || defined(_WIN64)
+	/* WriteFile() is atomic for local files opened with FILE_APPEND_DATA and
+	   without FILE_WRITE_DATA */
+	DWORD written;
+	WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg->buf,
+			  (DWORD)(msg->p - msg->buf + eol_len), &written, 0);
+#else
+	/* write() is atomic for buffers less than or equal to PIPE_BUF. */
+	RETVAL_UNUSED(write(STDERR_FILENO, msg->buf,
+						(size_t)(msg->p - msg->buf) + eol_len));
+#endif
+}
+
+static const zf_log_output out_stderr = {ZF_LOG_OUT_STDERR};
+
+#if !ZF_LOG_EXTERN_TAG_PREFIX
+	ZF_LOG_DEFINE_TAG_PREFIX = 0;
+#endif
+
+#if !ZF_LOG_EXTERN_GLOBAL_FORMAT
+	ZF_LOG_DEFINE_GLOBAL_FORMAT = {ZF_LOG_MEM_WIDTH};
+#endif
+
+#if !ZF_LOG_EXTERN_GLOBAL_OUTPUT
+	#if ZF_LOG_USE_ANDROID_LOG
+		ZF_LOG_DEFINE_GLOBAL_OUTPUT = {OUT_ANDROID};
+	#elif ZF_LOG_USE_NSLOG
+		ZF_LOG_DEFINE_GLOBAL_OUTPUT = {OUT_NSLOG};
+	#elif ZF_LOG_USE_DEBUGSTRING
+		ZF_LOG_DEFINE_GLOBAL_OUTPUT = {OUT_DEBUGSTRING};
+	#else
+		ZF_LOG_DEFINE_GLOBAL_OUTPUT = {ZF_LOG_OUT_STDERR};
+	#endif
+#endif
+
+#if !ZF_LOG_EXTERN_GLOBAL_OUTPUT_LEVEL
+	ZF_LOG_DEFINE_GLOBAL_OUTPUT_LEVEL = 0;
+#endif
+
+const zf_log_spec _zf_log_stderr_spec =
+{
+	ZF_LOG_GLOBAL_FORMAT,
+	&out_stderr,
+};
+
+static const zf_log_spec global_spec =
+{
+	ZF_LOG_GLOBAL_FORMAT,
+	ZF_LOG_GLOBAL_OUTPUT,
+};
+
+#if _ZF_LOG_MESSAGE_FORMAT_CONTAINS(LEVEL, ZF_LOG_MESSAGE_CTX_FORMAT)
+static char lvl_char(const int lvl)
+{
+	switch (lvl)
+	{
+	case ZF_LOG_VERBOSE:
+		return 'V';
+	case ZF_LOG_DEBUG:
+		return 'D';
+	case ZF_LOG_INFO:
+		return 'I';
+	case ZF_LOG_WARN:
+		return 'W';
+	case ZF_LOG_ERROR:
+		return 'E';
+	case ZF_LOG_FATAL:
+		return 'F';
+	default:
+		ASSERT_UNREACHABLE("Bad log level");
+		return '?';
+	}
+}
+#endif
+
+#define GCCVER_LESS(MAJOR, MINOR, PATCH) \
+	(__GNUC__ < MAJOR || \
+		(__GNUC__ == MAJOR && (__GNUC_MINOR__ < MINOR || \
+			(__GNUC_MINOR__ == MINOR && __GNUC_PATCHLEVEL__ < PATCH))))
+
+#if !defined(__clang__) && defined(__GNUC__) && GCCVER_LESS(4,7,0)
+	#define __atomic_load_n(vp, model) __sync_fetch_and_add(vp, 0)
+	#define __atomic_fetch_add(vp, n, model) __sync_fetch_and_add(vp, n)
+	#define __atomic_sub_fetch(vp, n, model) __sync_sub_and_fetch(vp, n)
+	#define __atomic_or_fetch(vp, n, model) __sync_or_and_fetch(vp, n)
+	#define __atomic_and_fetch(vp, n, model) __sync_and_and_fetch(vp, n)
+	/* Note: will not store old value of *vp in *ep (non-standard behaviour) */
+	#define __atomic_compare_exchange_n(vp, ep, d, weak, smodel, fmodel) \
+		__sync_bool_compare_and_swap(vp, *(ep), d)
+#endif
+
+#if !ZF_LOG_OPTIMIZE_SIZE && !defined(_WIN32) && !defined(_WIN64)
+#define TCACHE
+#define TCACHE_STALE (0x40000000)
+#define TCACHE_FLUID (0x40000000 | 0x80000000)
+static unsigned g_tcache_mode = TCACHE_STALE;
+static struct timeval g_tcache_tv = {0, 0};
+
+static struct tm g_tcache_tm;
+
+static INLINE int tcache_get(const struct timeval *const tv, struct tm *const tm)
+{
+	unsigned mode;
+	mode = __atomic_load_n(&g_tcache_mode, __ATOMIC_RELAXED);
+	if (0 == (mode & TCACHE_FLUID))
+	{
+		mode = __atomic_fetch_add(&g_tcache_mode, 1, __ATOMIC_ACQUIRE);
+		if (0 == (mode & TCACHE_FLUID))
+		{
+			if (g_tcache_tv.tv_sec == tv->tv_sec)
+			{
+				*tm = g_tcache_tm;
+				__atomic_sub_fetch(&g_tcache_mode, 1, __ATOMIC_RELEASE);
+				return !0;
+			}
+			__atomic_or_fetch(&g_tcache_mode, TCACHE_STALE, __ATOMIC_RELAXED);
+		}
+		__atomic_sub_fetch(&g_tcache_mode, 1, __ATOMIC_RELEASE);
+	}
+	return 0;
+}
+
+static INLINE void tcache_set(const struct timeval *const tv, struct tm *const tm)
+{
+	unsigned stale = TCACHE_STALE;
+	if (__atomic_compare_exchange_n(&g_tcache_mode, &stale, TCACHE_FLUID,
+									0, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED))
+	{
+		g_tcache_tv = *tv;
+		g_tcache_tm = *tm;
+		__atomic_and_fetch(&g_tcache_mode, ~TCACHE_FLUID, __ATOMIC_RELEASE);
+	}
+}
+#endif
+
+static void time_callback(struct tm *const tm, unsigned *const msec)
+{
+#if !_ZF_LOG_MESSAGE_FORMAT_DATETIME_USED
+	VAR_UNUSED(tm);
+	VAR_UNUSED(msec);
+#else
+	#if defined(_WIN32) || defined(_WIN64)
+	SYSTEMTIME st;
+	GetLocalTime(&st);
+	tm->tm_year = st.wYear;
+	tm->tm_mon = st.wMonth - 1;
+	tm->tm_mday = st.wDay;
+	tm->tm_wday = st.wDayOfWeek;
+	tm->tm_hour = st.wHour;
+	tm->tm_min = st.wMinute;
+	tm->tm_sec = st.wSecond;
+	*msec = st.wMilliseconds;
+	#else
+	struct timeval tv;
+	gettimeofday(&tv, 0);
+		#ifndef TCACHE
+		localtime_r(&tv.tv_sec, tm);
+		#else
+		if (!tcache_get(&tv, tm))
+		{
+			localtime_r(&tv.tv_sec, tm);
+			tcache_set(&tv, tm);
+		}
+		#endif
+	*msec = (unsigned)tv.tv_usec / 1000;
+	#endif
+#endif
+}
+
+static void pid_callback(int *const pid, int *const tid)
+{
+#if !_ZF_LOG_MESSAGE_FORMAT_CONTAINS(PID, ZF_LOG_MESSAGE_CTX_FORMAT)
+	VAR_UNUSED(pid);
+#else
+	#if defined(_WIN32) || defined(_WIN64)
+	*pid = GetCurrentProcessId();
+	#else
+	*pid = getpid();
+	#endif
+#endif
+
+#if !_ZF_LOG_MESSAGE_FORMAT_CONTAINS(TID, ZF_LOG_MESSAGE_CTX_FORMAT)
+	VAR_UNUSED(tid);
+#else
+	#if defined(_WIN32) || defined(_WIN64)
+	*tid = GetCurrentThreadId();
+	#elif defined(__ANDROID__)
+	*tid = gettid();
+	#elif defined(__linux__)
+	*tid = syscall(SYS_gettid);
+	#elif defined(__MACH__)
+	*tid = (int)pthread_mach_thread_np(pthread_self());
+	#elif defined(_AIX)
+	pthread_t t = pthread_self();
+	struct __pthrdsinfo tinfo;
+	pthread_getthrds_np(&t, PTHRDSINFO_QUERY_TID, &tinfo, sizeof(tinfo), NULL, 0);
+	*tid = (int)tinfo.__pi_tid;
+	#else
+		#define Platform not supported
+	#endif
+#endif
+}
+
+static void buffer_callback(zf_log_message *msg, char *buf)
+{
+	msg->e = (msg->p = msg->buf = buf) + g_buf_sz;
+}
+
+#if _ZF_LOG_MESSAGE_FORMAT_CONTAINS(FUNCTION, ZF_LOG_MESSAGE_SRC_FORMAT)
+static const char *funcname(const char *func)
+{
+	return func? func: "";
+}
+#endif
+
+#if _ZF_LOG_MESSAGE_FORMAT_CONTAINS(FILENAME, ZF_LOG_MESSAGE_SRC_FORMAT)
+static const char *filename(const char *file)
+{
+	const char *f = file;
+	for (const char *p = file; 0 != *p; ++p)
+	{
+		if ('/' == *p || '\\' == *p)
+		{
+			f = p + 1;
+		}
+	}
+	return f;
+}
+#endif
+
+static INLINE size_t nprintf_size(zf_log_message *const msg)
+{
+	// *nprintf() always puts 0 in the end when input buffer is not empty. This
+	// 0 is not desired because its presence sets (ctx->p) to (ctx->e - 1) which
+	// leaves space for one more character. Some put_xxx() functions don't use
+	// *nprintf() and could use that last character. In that case log line will
+	// have multiple (two) half-written parts which is confusing. To workaround
+	// that we allow *nprintf() to write its 0 in the eol area (which is always
+	// not empty).
+	return (size_t)(msg->e - msg->p + 1);
+}
+
+static INLINE void put_nprintf(zf_log_message *const msg, const int n)
+{
+	if (0 < n)
+	{
+		msg->p = n < msg->e - msg->p? msg->p + n: msg->e;
+	}
+}
+
+static INLINE char *put_padding_r(const unsigned w, const char wc,
+								  char *p, char *e)
+{
+	for (char *const b = e - w; b < p; *--p = wc) {}
+	return p;
+}
+
+static char *put_integer_r(unsigned v, const int sign,
+						   const unsigned w, const char wc, char *const e)
+{
+	static const char _signs[] = {'-', '0', '+'};
+	static const char *const signs = _signs + 1;
+	char *p = e;
+	do { *--p = '0' + v % 10; } while (0 != (v /= 10));
+	if (0 == sign) return put_padding_r(w, wc, p, e);
+	if ('0' != wc)
+	{
+		*--p = signs[sign];
+		return put_padding_r(w, wc, p, e);
+	}
+	p = put_padding_r(w, wc, p, e + 1);
+	*--p = signs[sign];
+	return p;
+}
+
+static INLINE char *put_uint_r(const unsigned v, const unsigned w, const char wc,
+							   char *const e)
+{
+	return put_integer_r(v, 0, w, wc, e);
+}
+
+static INLINE char *put_int_r(const int v, const unsigned w, const char wc,
+							  char *const e)
+{
+	return 0 <= v? put_integer_r((unsigned)v, 0, w, wc, e)
+				 : put_integer_r((unsigned)-v, -1, w, wc, e);
+}
+
+static INLINE char *put_stringn(const char *const s_p, const char *const s_e,
+								char *const p, char *const e)
+{
+	const ptrdiff_t m = e - p;
+	ptrdiff_t n = s_e - s_p;
+	if (n > m)
+	{
+		n = m;
+	}
+	memcpy(p, s_p, n);
+	return p + n;
+}
+
+static INLINE char *put_string(const char *s, char *p, char *const e)
+{
+	const ptrdiff_t n = e - p;
+	char *const c = (char *)memccpy(p, s, '\0', n);
+	return 0 != c? c - 1: e;
+}
+
+static INLINE char *put_uint(unsigned v, const unsigned w, const char wc,
+							 char *const p, char *const e)
+{
+	char buf[16];
+	char *const se = buf + _countof(buf);
+	char *sp = put_uint_r(v, w, wc, se);
+	return put_stringn(sp, se, p, e);
+}
+
+#define PUT_CSTR_R(p, STR) \
+	do { \
+		for (unsigned i = sizeof(STR) - 1; 0 < i--;) { \
+			*--(p) = (STR)[i]; \
+		} \
+	} _ZF_LOG_ONCE
+
+#define PUT_CSTR_CHECKED(p, e, STR) \
+	do { \
+		for (unsigned i = 0; (e) > (p) && (sizeof(STR) - 1) > i; ++i) { \
+			*(p)++ = (STR)[i]; \
+		} \
+	} _ZF_LOG_ONCE
+
+/* F_INIT field support.
+ */
+#define _ZF_LOG_MESSAGE_FORMAT_INIT__
+#define _ZF_LOG_MESSAGE_FORMAT_INIT__YEAR
+#define _ZF_LOG_MESSAGE_FORMAT_INIT__MONTH
+#define _ZF_LOG_MESSAGE_FORMAT_INIT__DAY
+#define _ZF_LOG_MESSAGE_FORMAT_INIT__HOUR
+#define _ZF_LOG_MESSAGE_FORMAT_INIT__MINUTE
+#define _ZF_LOG_MESSAGE_FORMAT_INIT__SECOND
+#define _ZF_LOG_MESSAGE_FORMAT_INIT__MILLISECOND
+#define _ZF_LOG_MESSAGE_FORMAT_INIT__PID
+#define _ZF_LOG_MESSAGE_FORMAT_INIT__TID
+#define _ZF_LOG_MESSAGE_FORMAT_INIT__LEVEL
+#define _ZF_LOG_MESSAGE_FORMAT_INIT__TAG(ps, ts)
+#define _ZF_LOG_MESSAGE_FORMAT_INIT__FUNCTION
+#define _ZF_LOG_MESSAGE_FORMAT_INIT__FILENAME
+#define _ZF_LOG_MESSAGE_FORMAT_INIT__FILELINE
+#define _ZF_LOG_MESSAGE_FORMAT_INIT__S(s)
+#define _ZF_LOG_MESSAGE_FORMAT_INIT__F_INIT(expr) _PP_UNTUPLE(expr);
+#define _ZF_LOG_MESSAGE_FORMAT_INIT__F_UINT(w, v)
+#define _ZF_LOG_MESSAGE_FORMAT_INIT(field) \
+	_PP_CONCAT_3(_ZF_LOG_MESSAGE_FORMAT_INIT_, _, field)
+
+/* Implements generation of printf-like format string for log message
+ * format specification.
+ */
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_FMT__             ""
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_FMT__YEAR         "%04u"
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_FMT__MONTH        "%02u"
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_FMT__DAY          "%02u"
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_FMT__HOUR         "%02u"
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_FMT__MINUTE       "%02u"
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_FMT__SECOND       "%02u"
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_FMT__MILLISECOND  "%03u"
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_FMT__PID          "%5i"
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_FMT__TID          "%5i"
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_FMT__LEVEL        "%c"
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_FMT__TAG          UNDEFINED
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_FMT__FUNCTION     "%s"
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_FMT__FILENAME     "%s"
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_FMT__FILELINE     "%u"
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_FMT__S(s)         s
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_FMT__F_INIT(expr) ""
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_FMT__F_UINT(w, v) "%" #w "u"
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_FMT(field) \
+	_PP_CONCAT_3(_ZF_LOG_MESSAGE_FORMAT_PRINTF_FMT_, _, field)
+
+/* Implements generation of printf-like format parameters for log message
+ * format specification.
+ */
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_VAL__
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_VAL__YEAR         ,(unsigned)(tm.tm_year + 1900)
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_VAL__MONTH        ,(unsigned)(tm.tm_mon + 1)
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_VAL__DAY          ,(unsigned)tm.tm_mday
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_VAL__HOUR         ,(unsigned)tm.tm_hour
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_VAL__MINUTE       ,(unsigned)tm.tm_min
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_VAL__SECOND       ,(unsigned)tm.tm_sec
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_VAL__MILLISECOND  ,(unsigned)msec
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_VAL__PID          ,pid
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_VAL__TID          ,tid
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_VAL__LEVEL        ,(char)lvl_char(msg->lvl)
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_VAL__TAG          UNDEFINED
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_VAL__FUNCTION     ,funcname(src->func)
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_VAL__FILENAME     ,filename(src->file)
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_VAL__FILELINE     ,src->line
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_VAL__S(s)
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_VAL__F_INIT(expr)
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_VAL__F_UINT(w, v) ,v
+#define _ZF_LOG_MESSAGE_FORMAT_PRINTF_VAL(field) \
+	_PP_CONCAT_3(_ZF_LOG_MESSAGE_FORMAT_PRINTF_VAL_, _, field)
+
+/* Implements generation of put_xxx_t statements for log message specification.
+ */
+#define _ZF_LOG_MESSAGE_FORMAT_PUT_R__
+#define _ZF_LOG_MESSAGE_FORMAT_PUT_R__YEAR         p = put_uint_r(tm.tm_year + 1900, 4, '0', p);
+#define _ZF_LOG_MESSAGE_FORMAT_PUT_R__MONTH        p = put_uint_r((unsigned)tm.tm_mon + 1, 2, '0', p);
+#define _ZF_LOG_MESSAGE_FORMAT_PUT_R__DAY          p = put_uint_r((unsigned)tm.tm_mday, 2, '0', p);
+#define _ZF_LOG_MESSAGE_FORMAT_PUT_R__HOUR         p = put_uint_r((unsigned)tm.tm_hour, 2, '0', p);
+#define _ZF_LOG_MESSAGE_FORMAT_PUT_R__MINUTE       p = put_uint_r((unsigned)tm.tm_min, 2, '0', p);
+#define _ZF_LOG_MESSAGE_FORMAT_PUT_R__SECOND       p = put_uint_r((unsigned)tm.tm_sec, 2, '0', p);
+#define _ZF_LOG_MESSAGE_FORMAT_PUT_R__MILLISECOND  p = put_uint_r(msec, 3, '0', p);
+#define _ZF_LOG_MESSAGE_FORMAT_PUT_R__PID          p = put_int_r(pid, 5, ' ', p);
+#define _ZF_LOG_MESSAGE_FORMAT_PUT_R__TID          p = put_int_r(tid, 5, ' ', p);
+#define _ZF_LOG_MESSAGE_FORMAT_PUT_R__LEVEL        *--p = lvl_char(msg->lvl);
+#define _ZF_LOG_MESSAGE_FORMAT_PUT_R__TAG          UNDEFINED
+#define _ZF_LOG_MESSAGE_FORMAT_PUT_R__FUNCTION     UNDEFINED
+#define _ZF_LOG_MESSAGE_FORMAT_PUT_R__FILENAME     UNDEFINED
+#define _ZF_LOG_MESSAGE_FORMAT_PUT_R__FILELINE     UNDEFINED
+#define _ZF_LOG_MESSAGE_FORMAT_PUT_R__S(s)         PUT_CSTR_R(p, s);
+#define _ZF_LOG_MESSAGE_FORMAT_PUT_R__F_INIT(expr)
+#define _ZF_LOG_MESSAGE_FORMAT_PUT_R__F_UINT(w, v) p = put_uint_r(v, w, ' ', p);
+#define _ZF_LOG_MESSAGE_FORMAT_PUT_R(field) \
+	_PP_CONCAT_3(_ZF_LOG_MESSAGE_FORMAT_PUT_R_, _, field)
+
+static void put_ctx(zf_log_message *const msg)
+{
+	_PP_MAP(_ZF_LOG_MESSAGE_FORMAT_INIT, ZF_LOG_MESSAGE_CTX_FORMAT)
+#if !_ZF_LOG_MESSAGE_FORMAT_FIELDS(ZF_LOG_MESSAGE_CTX_FORMAT)
+	VAR_UNUSED(msg);
+#else
+	#if _ZF_LOG_MESSAGE_FORMAT_DATETIME_USED
+	struct tm tm;
+	unsigned msec;
+	g_time_cb(&tm, &msec);
+	#endif
+	#if _ZF_LOG_MESSAGE_FORMAT_CONTAINS(PID, ZF_LOG_MESSAGE_CTX_FORMAT) || \
+		_ZF_LOG_MESSAGE_FORMAT_CONTAINS(TID, ZF_LOG_MESSAGE_CTX_FORMAT)
+	int pid, tid;
+	g_pid_cb(&pid, &tid);
+	#endif
+
+	#if ZF_LOG_OPTIMIZE_SIZE
+	int n;
+	n = _ZF_LOG_SNPRINTF(msg->p, nprintf_size(msg),
+						 _PP_MAP(_ZF_LOG_MESSAGE_FORMAT_PRINTF_FMT, ZF_LOG_MESSAGE_CTX_FORMAT)
+						 _PP_MAP(_ZF_LOG_MESSAGE_FORMAT_PRINTF_VAL, ZF_LOG_MESSAGE_CTX_FORMAT));
+	put_nprintf(msg, n);
+	#else
+	char buf[64];
+	char *const e = buf + sizeof(buf);
+	char *p = e;
+	_PP_RMAP(_ZF_LOG_MESSAGE_FORMAT_PUT_R, ZF_LOG_MESSAGE_CTX_FORMAT)
+	msg->p = put_stringn(p, e, msg->p, msg->e);
+	#endif
+#endif
+}
+
+#define PUT_TAG(msg, tag, prefix_delim, tag_delim) \
+	do { \
+		const char *ch; \
+		msg->tag_b = msg->p; \
+		if (0 != (ch = _zf_log_tag_prefix)) { \
+			for (;msg->e != msg->p && 0 != (*msg->p = *ch); ++msg->p, ++ch) {} \
+		} \
+		if (0 != (ch = tag) && 0 != tag[0]) { \
+			if (msg->tag_b != msg->p) { \
+				PUT_CSTR_CHECKED(msg->p, msg->e, prefix_delim); \
+			} \
+			for (;msg->e != msg->p && 0 != (*msg->p = *ch); ++msg->p, ++ch) {} \
+		} \
+		msg->tag_e = msg->p; \
+		if (msg->tag_b != msg->p) { \
+			PUT_CSTR_CHECKED(msg->p, msg->e, tag_delim); \
+		} \
+	} _ZF_LOG_ONCE
+
+/* Implements simple put statements for log message specification.
+ */
+#define _ZF_LOG_MESSAGE_FORMAT_PUT__
+#define _ZF_LOG_MESSAGE_FORMAT_PUT__YEAR         UNDEFINED
+#define _ZF_LOG_MESSAGE_FORMAT_PUT__MONTH        UNDEFINED
+#define _ZF_LOG_MESSAGE_FORMAT_PUT__DAY          UNDEFINED
+#define _ZF_LOG_MESSAGE_FORMAT_PUT__HOUR         UNDEFINED
+#define _ZF_LOG_MESSAGE_FORMAT_PUT__MINUTE       UNDEFINED
+#define _ZF_LOG_MESSAGE_FORMAT_PUT__SECOND       UNDEFINED
+#define _ZF_LOG_MESSAGE_FORMAT_PUT__MILLISECOND  UNDEFINED
+#define _ZF_LOG_MESSAGE_FORMAT_PUT__PID          UNDEFINED
+#define _ZF_LOG_MESSAGE_FORMAT_PUT__TID          UNDEFINED
+#define _ZF_LOG_MESSAGE_FORMAT_PUT__LEVEL        UNDEFINED
+#define _ZF_LOG_MESSAGE_FORMAT_PUT__TAG(pd, td)  PUT_TAG(msg, tag, pd, td);
+#define _ZF_LOG_MESSAGE_FORMAT_PUT__FUNCTION     msg->p = put_string(funcname(src->func), msg->p, msg->e);
+#define _ZF_LOG_MESSAGE_FORMAT_PUT__FILENAME     msg->p = put_string(filename(src->file), msg->p, msg->e);
+#define _ZF_LOG_MESSAGE_FORMAT_PUT__FILELINE     msg->p = put_uint(src->line, 0, '\0', msg->p, msg->e);
+#define _ZF_LOG_MESSAGE_FORMAT_PUT__S(s)         PUT_CSTR_CHECKED(msg->p, msg->e, s);
+#define _ZF_LOG_MESSAGE_FORMAT_PUT__F_INIT(expr)
+#define _ZF_LOG_MESSAGE_FORMAT_PUT__F_UINT(w, v) msg->p = put_uint(v, w, ' ', msg->p, msg->e);
+#define _ZF_LOG_MESSAGE_FORMAT_PUT(field) \
+	_PP_CONCAT_3(_ZF_LOG_MESSAGE_FORMAT_PUT_, _, field)
+
+static void put_tag(zf_log_message *const msg, const char *const tag)
+{
+	_PP_MAP(_ZF_LOG_MESSAGE_FORMAT_INIT, ZF_LOG_MESSAGE_TAG_FORMAT)
+#if !_ZF_LOG_MESSAGE_FORMAT_CONTAINS(TAG, ZF_LOG_MESSAGE_TAG_FORMAT)
+	VAR_UNUSED(tag);
+#endif
+#if !_ZF_LOG_MESSAGE_FORMAT_FIELDS(ZF_LOG_MESSAGE_TAG_FORMAT)
+	VAR_UNUSED(msg);
+#else
+	_PP_MAP(_ZF_LOG_MESSAGE_FORMAT_PUT, ZF_LOG_MESSAGE_TAG_FORMAT)
+#endif
+}
+
+static void put_src(zf_log_message *const msg, const src_location *const src)
+{
+	_PP_MAP(_ZF_LOG_MESSAGE_FORMAT_INIT, ZF_LOG_MESSAGE_SRC_FORMAT)
+#if !_ZF_LOG_MESSAGE_FORMAT_CONTAINS(FUNCTION, ZF_LOG_MESSAGE_SRC_FORMAT) && \
+	!_ZF_LOG_MESSAGE_FORMAT_CONTAINS(FILENAME, ZF_LOG_MESSAGE_SRC_FORMAT) && \
+	!_ZF_LOG_MESSAGE_FORMAT_CONTAINS(FILELINE, ZF_LOG_MESSAGE_SRC_FORMAT)
+	VAR_UNUSED(src);
+#endif
+#if !_ZF_LOG_MESSAGE_FORMAT_FIELDS(ZF_LOG_MESSAGE_SRC_FORMAT)
+	VAR_UNUSED(msg);
+#else
+	#if ZF_LOG_OPTIMIZE_SIZE
+	int n;
+	n = _ZF_LOG_SNPRINTF(msg->p, nprintf_size(msg),
+						 _PP_MAP(_ZF_LOG_MESSAGE_FORMAT_PRINTF_FMT, ZF_LOG_MESSAGE_SRC_FORMAT)
+						 _PP_MAP(_ZF_LOG_MESSAGE_FORMAT_PRINTF_VAL, ZF_LOG_MESSAGE_SRC_FORMAT));
+	put_nprintf(msg, n);
+	#else
+	_PP_MAP(_ZF_LOG_MESSAGE_FORMAT_PUT, ZF_LOG_MESSAGE_SRC_FORMAT)
+	#endif
+#endif
+}
+
+static void put_msg(zf_log_message *const msg,
+					const char *const fmt, va_list va)
+{
+	int n;
+	msg->msg_b = msg->p;
+	n = _ZF_LOG_VSNPRINTF(msg->p, nprintf_size(msg), fmt, va);
+	put_nprintf(msg, n);
+}
+
+static void output_mem(const zf_log_spec *log, zf_log_message *const msg,
+					   const mem_block *const mem)
+{
+	if (0 == mem->d || 0 == mem->d_sz)
+	{
+		return;
+	}
+	const unsigned char *mem_p = (const unsigned char *)mem->d;
+	const unsigned char *const mem_e = mem_p + mem->d_sz;
+	const unsigned char *mem_cut;
+	const ptrdiff_t mem_width = (ptrdiff_t)log->format->mem_width;
+	char *const hex_b = msg->msg_b;
+	char *const ascii_b = hex_b + 2 * mem_width + 2;
+	char *const ascii_e = ascii_b + mem_width;
+	if (msg->e < ascii_e)
+	{
+		return;
+	}
+	while (mem_p != mem_e)
+	{
+		char *hex = hex_b;
+		char *ascii = ascii_b;
+		for (mem_cut = mem_width < mem_e - mem_p? mem_p + mem_width: mem_e;
+			 mem_cut != mem_p; ++mem_p)
+		{
+			const unsigned char ch = *mem_p;
+			*hex++ = c_hex[(0xf0 & ch) >> 4];
+			*hex++ = c_hex[(0x0f & ch)];
+			*ascii++ = isprint(ch)? (char)ch: '?';
+		}
+		while (hex != ascii_b)
+		{
+			*hex++ = ' ';
+		}
+		msg->p = ascii;
+		log->output->callback(msg, log->output->arg);
+	}
+}
+
+void zf_log_set_tag_prefix(const char *const prefix)
+{
+	_zf_log_tag_prefix = prefix;
+}
+
+void zf_log_set_mem_width(const unsigned w)
+{
+	_zf_log_global_format.mem_width = w;
+}
+
+void zf_log_set_output_level(const int lvl)
+{
+	_zf_log_global_output_lvl = lvl;
+}
+
+void zf_log_set_output_v(const unsigned mask, void *const arg,
+						 const zf_log_output_cb callback)
+{
+	_zf_log_global_output.mask = mask;
+	_zf_log_global_output.arg = arg;
+	_zf_log_global_output.callback = callback;
+}
+
+static void _zf_log_write_imp(
+		const zf_log_spec *log,
+		const src_location *const src, const mem_block *const mem,
+		const int lvl, const char *const tag, const char *const fmt, va_list va)
+{
+	zf_log_message msg;
+	char buf[ZF_LOG_BUF_SZ];
+	const unsigned mask = log->output->mask;
+	msg.lvl = lvl;
+	msg.tag = tag;
+	g_buffer_cb(&msg, buf);
+	if (ZF_LOG_PUT_CTX & mask)
+	{
+		put_ctx(&msg);
+	}
+	if (ZF_LOG_PUT_TAG & mask)
+	{
+		put_tag(&msg, tag);
+	}
+	if (0 != src && ZF_LOG_PUT_SRC & mask)
+	{
+		put_src(&msg, src);
+	}
+	if (ZF_LOG_PUT_MSG & mask)
+	{
+		put_msg(&msg, fmt, va);
+	}
+	log->output->callback(&msg, log->output->arg);
+	if (0 != mem && ZF_LOG_PUT_MSG & mask)
+	{
+		output_mem(log, &msg, mem);
+	}
+}
+
+void _zf_log_write_d(
+		const char *const func, const char *const file, const unsigned line,
+		const int lvl, const char *const tag,
+		const char *const fmt, ...)
+{
+	const src_location src = {func, file, line};
+	va_list va;
+	va_start(va, fmt);
+	_zf_log_write_imp(&global_spec, &src, 0, lvl, tag, fmt, va);
+	va_end(va);
+}
+
+void _zf_log_write_aux_d(
+		const char *const func, const char *const file, const unsigned line,
+		const zf_log_spec *const log, const int lvl, const char *const tag,
+		const char *const fmt, ...)
+{
+	const src_location src = {func, file, line};
+	va_list va;
+	va_start(va, fmt);
+	_zf_log_write_imp(log, &src, 0, lvl, tag, fmt, va);
+	va_end(va);
+}
+
+void _zf_log_write(const int lvl, const char *const tag,
+				   const char *const fmt, ...)
+{
+	va_list va;
+	va_start(va, fmt);
+	_zf_log_write_imp(&global_spec, 0, 0, lvl, tag, fmt, va);
+	va_end(va);
+}
+
+void _zf_log_write_aux(
+		const zf_log_spec *const log, const int lvl, const char *const tag,
+		const char *const fmt, ...)
+{
+	va_list va;
+	va_start(va, fmt);
+	_zf_log_write_imp(log, 0, 0, lvl, tag, fmt, va);
+	va_end(va);
+}
+
+void _zf_log_write_mem_d(
+		const char *const func, const char *const file, const unsigned line,
+		const int lvl, const char *const tag,
+		const void *const d, const unsigned d_sz,
+		const char *const fmt, ...)
+{
+	const src_location src = {func, file, line};
+	const mem_block mem = {d, d_sz};
+	va_list va;
+	va_start(va, fmt);
+	_zf_log_write_imp(&global_spec, &src, &mem, lvl, tag, fmt, va);
+	va_end(va);
+}
+
+void _zf_log_write_mem_aux_d(
+		const char *const func, const char *const file, const unsigned line,
+		const zf_log_spec *const log, const int lvl, const char *const tag,
+		const void *const d, const unsigned d_sz,
+		const char *const fmt, ...)
+{
+	const src_location src = {func, file, line};
+	const mem_block mem = {d, d_sz};
+	va_list va;
+	va_start(va, fmt);
+	_zf_log_write_imp(log, &src, &mem, lvl, tag, fmt, va);
+	va_end(va);
+}
+
+void _zf_log_write_mem(const int lvl, const char *const tag,
+					   const void *const d, const unsigned d_sz,
+					   const char *const fmt, ...)
+{
+	const mem_block mem = {d, d_sz};
+	va_list va;
+	va_start(va, fmt);
+	_zf_log_write_imp(&global_spec, 0, &mem, lvl, tag, fmt, va);
+	va_end(va);
+}
+
+void _zf_log_write_mem_aux(
+		const zf_log_spec *const log, const int lvl, const char *const tag,
+		const void *const d, const unsigned d_sz,
+		const char *const fmt, ...)
+{
+	const mem_block mem = {d, d_sz};
+	va_list va;
+	va_start(va, fmt);
+	_zf_log_write_imp(log, 0, &mem, lvl, tag, fmt, va);
+	va_end(va);
+}

+ 941 - 0
zf_log/zf_log/zf_log.h

@@ -0,0 +1,941 @@
+#pragma once
+
+#ifndef _ZF_LOG_H_
+#define _ZF_LOG_H_
+
+/* To detect incompatible changes you can define ZF_LOG_VERSION_REQUIRED to be
+ * the current value of ZF_LOG_VERSION before including this file (or via
+ * compiler command line):
+ *
+ *   #define ZF_LOG_VERSION_REQUIRED 4
+ *   #include <zf_log.h>
+ *
+ * Compilation will fail when included file has different version.
+ */
+#define ZF_LOG_VERSION 4
+#if defined(ZF_LOG_VERSION_REQUIRED)
+	#if ZF_LOG_VERSION_REQUIRED != ZF_LOG_VERSION
+		#error different zf_log version required
+	#endif
+#endif
+
+/* Log level guideline:
+ * - ZF_LOG_FATAL - happened something impossible and absolutely unexpected.
+ *   Process can't continue and must be terminated.
+ *   Example: division by zero, unexpected modifications from other thread.
+ * - ZF_LOG_ERROR - happened something possible, but highly unexpected. The
+ *   process is able to recover and continue execution.
+ *   Example: out of memory (could also be FATAL if not handled properly).
+ * - ZF_LOG_WARN - happened something that *usually* should not happen and
+ *   significantly changes application behavior for some period of time.
+ *   Example: configuration file not found, auth error.
+ * - ZF_LOG_INFO - happened significant life cycle event or major state
+ *   transition.
+ *   Example: app started, user logged in.
+ * - ZF_LOG_DEBUG - minimal set of events that could help to reconstruct the
+ *   execution path. Usually disabled in release builds.
+ * - ZF_LOG_VERBOSE - all other events. Usually disabled in release builds.
+ *
+ * *Ideally*, log file of debugged, well tested, production ready application
+ * should be empty or very small. Choosing a right log level is as important as
+ * providing short and self descriptive log message.
+ */
+#define ZF_LOG_VERBOSE 1
+#define ZF_LOG_DEBUG   2
+#define ZF_LOG_INFO    3
+#define ZF_LOG_WARN    4
+#define ZF_LOG_ERROR   5
+#define ZF_LOG_FATAL   6
+#define ZF_LOG_NONE    0xFF
+
+/* "Current" log level is a compile time check and has no runtime overhead. Log
+ * level that is below current log level it said to be "disabled". Otherwise,
+ * it's "enabled". Log messages that are disabled has no runtime overhead - they
+ * are converted to no-op by preprocessor and then eliminated by compiler.
+ * Current log level is configured per compilation module (.c/.cpp/.m file) by
+ * defining ZF_LOG_DEF_LEVEL or ZF_LOG_LEVEL. ZF_LOG_LEVEL has higer priority
+ * and when defined overrides value provided by ZF_LOG_DEF_LEVEL.
+ *
+ * Common practice is to define default current log level with ZF_LOG_DEF_LEVEL
+ * in build script (e.g. Makefile, CMakeLists.txt, gyp, etc.) for the entire
+ * project or target:
+ *
+ *   CC_ARGS := -DZF_LOG_DEF_LEVEL=ZF_LOG_INFO
+ *
+ * And when necessary to override it with ZF_LOG_LEVEL in .c/.cpp/.m files
+ * before including zf_log.h:
+ *
+ *   #define ZF_LOG_LEVEL ZF_LOG_VERBOSE
+ *   #include <zf_log.h>
+ *
+ * If both ZF_LOG_DEF_LEVEL and ZF_LOG_LEVEL are undefined, then ZF_LOG_INFO
+ * will be used for release builds (NDEBUG is defined) and ZF_LOG_DEBUG
+ * otherwise (NDEBUG is not defined).
+ */
+#if defined(ZF_LOG_LEVEL)
+	#define _ZF_LOG_LEVEL ZF_LOG_LEVEL
+#elif defined(ZF_LOG_DEF_LEVEL)
+	#define _ZF_LOG_LEVEL ZF_LOG_DEF_LEVEL
+#else
+	#ifdef NDEBUG
+		#define _ZF_LOG_LEVEL ZF_LOG_INFO
+	#else
+		#define _ZF_LOG_LEVEL ZF_LOG_DEBUG
+	#endif
+#endif
+
+/* "Output" log level is a runtime check. When log level is below output log
+ * level it said to be "turned off" (or just "off" for short). Otherwise it's
+ * "turned on" (or just "on"). Log levels that were "disabled" (see
+ * ZF_LOG_LEVEL and ZF_LOG_DEF_LEVEL) can't be "turned on", but "enabled" log
+ * levels could be "turned off". Only messages with log level which is
+ * "turned on" will reach output facility. All other messages will be ignored
+ * (and their arguments will not be evaluated). Output log level is a global
+ * property and configured per process using zf_log_set_output_level() function
+ * which can be called at any time.
+ *
+ * Though in some cases it could be useful to configure output log level per
+ * compilation module or per library. There are two ways to achieve that:
+ * - Define ZF_LOG_OUTPUT_LEVEL to expresion that evaluates to desired output
+ *   log level.
+ * - Copy zf_log.h and zf_log.c files into your library and build it with
+ *   ZF_LOG_LIBRARY_PREFIX defined to library specific prefix. See
+ *   ZF_LOG_LIBRARY_PREFIX for more details.
+ *
+ * When defined, ZF_LOG_OUTPUT_LEVEL must evaluate to integral value that
+ * corresponds to desired output log level. Use it only when compilation module
+ * is required to have output log level which is different from global output
+ * log level set by zf_log_set_output_level() function. For other cases,
+ * consider defining ZF_LOG_LEVEL or using zf_log_set_output_level() function.
+ *
+ * Example:
+ *
+ *   #define ZF_LOG_OUTPUT_LEVEL g_module_log_level
+ *   #include <zf_log.h>
+ *   static int g_module_log_level = ZF_LOG_INFO;
+ *   static void foo() {
+ *       ZF_LOGI("Will check g_module_log_level for output log level");
+ *   }
+ *   void debug_log(bool on) {
+ *       g_module_log_level = on? ZF_LOG_DEBUG: ZF_LOG_INFO;
+ *   }
+ *
+ * Note on performance. This expression will be evaluated each time message is
+ * logged (except when message log level is "disabled" - see ZF_LOG_LEVEL for
+ * details). Keep this expression as simple as possible, otherwise it will not
+ * only add runtime overhead, but also will increase size of call site (which
+ * will result in larger executable). The prefered way is to use integer
+ * variable (as in example above). If structure must be used, log_level field
+ * must be the first field in this structure:
+ *
+ *   #define ZF_LOG_OUTPUT_LEVEL (g_config.log_level)
+ *   #include <zf_log.h>
+ *   struct config {
+ *       int log_level;
+ *       unsigned other_field;
+ *       [...]
+ *   };
+ *   static config g_config = {ZF_LOG_INFO, 0, ...};
+ *
+ * This allows compiler to generate more compact load instruction (no need to
+ * specify offset since it's zero). Calling a function to get output log level
+ * is generaly a bad idea, since it will increase call site size and runtime
+ * overhead even further.
+ */
+#if defined(ZF_LOG_OUTPUT_LEVEL)
+	#define _ZF_LOG_OUTPUT_LEVEL ZF_LOG_OUTPUT_LEVEL
+#else
+	#define _ZF_LOG_OUTPUT_LEVEL _zf_log_global_output_lvl
+#endif
+
+/* "Tag" is a compound string that could be associated with a log message. It
+ * consists of tag prefix and tag (both are optional).
+ *
+ * Tag prefix is a global property and configured per process using
+ * zf_log_set_tag_prefix() function. Tag prefix identifies context in which
+ * component or module is running (e.g. process name). For example, the same
+ * library could be used in both client and server processes that work on the
+ * same machine. Tag prefix could be used to easily distinguish between them.
+ * For more details about tag prefix see zf_log_set_tag_prefix() function. Tag
+ * prefix
+ *
+ * Tag identifies component or module. It is configured per compilation module
+ * (.c/.cpp/.m file) by defining ZF_LOG_TAG or ZF_LOG_DEF_TAG. ZF_LOG_TAG has
+ * higer priority and when defined overrides value provided by ZF_LOG_DEF_TAG.
+ * When defined, value must evaluate to (const char *), so for strings double
+ * quotes must be used.
+ *
+ * Default tag could be defined with ZF_LOG_DEF_TAG in build script (e.g.
+ * Makefile, CMakeLists.txt, gyp, etc.) for the entire project or target:
+ *
+ *   CC_ARGS := -DZF_LOG_DEF_TAG=\"MISC\"
+ *
+ * And when necessary could be overriden with ZF_LOG_TAG in .c/.cpp/.m files
+ * before including zf_log.h:
+ *
+ *   #define ZF_LOG_TAG "MAIN"
+ *   #include <zf_log.h>
+ *
+ * If both ZF_LOG_DEF_TAG and ZF_LOG_TAG are undefined no tag will be added to
+ * the log message (tag prefix still could be added though).
+ *
+ * Output example:
+ *
+ *   04-29 22:43:20.244 40059  1299 I hello.MAIN Number of arguments: 1
+ *                                    |     |
+ *                                    |     +- tag (e.g. module)
+ *                                    +- tag prefix (e.g. process name)
+ */
+#if defined(ZF_LOG_TAG)
+	#define _ZF_LOG_TAG ZF_LOG_TAG
+#elif defined(ZF_LOG_DEF_TAG)
+	#define _ZF_LOG_TAG ZF_LOG_DEF_TAG
+#else
+	#define _ZF_LOG_TAG 0
+#endif
+
+/* Source location is part of a log line that describes location (function or
+ * method name, file name and line number, e.g. "[email protected]:68") of a
+ * log statement that produced it.
+ * Source location formats are:
+ * - ZF_LOG_SRCLOC_NONE - don't add source location to log line.
+ * - ZF_LOG_SRCLOC_SHORT - add source location in short form (file and line
+ *   number, e.g. "@main.cpp:68").
+ * - ZF_LOG_SRCLOC_LONG - add source location in long form (function or method
+ *   name, file and line number, e.g. "[email protected]:68").
+ */
+#define ZF_LOG_SRCLOC_NONE  0
+#define ZF_LOG_SRCLOC_SHORT 1
+#define ZF_LOG_SRCLOC_LONG  2
+
+/* Source location format is configured per compilation module (.c/.cpp/.m
+ * file) by defining ZF_LOG_DEF_SRCLOC or ZF_LOG_SRCLOC. ZF_LOG_SRCLOC has
+ * higer priority and when defined overrides value provided by
+ * ZF_LOG_DEF_SRCLOC.
+ *
+ * Common practice is to define default format with ZF_LOG_DEF_SRCLOC in
+ * build script (e.g. Makefile, CMakeLists.txt, gyp, etc.) for the entire
+ * project or target:
+ *
+ *   CC_ARGS := -DZF_LOG_DEF_SRCLOC=ZF_LOG_SRCLOC_LONG
+ *
+ * And when necessary to override it with ZF_LOG_SRCLOC in .c/.cpp/.m files
+ * before including zf_log.h:
+ *
+ *   #define ZF_LOG_SRCLOC ZF_LOG_SRCLOC_NONE
+ *   #include <zf_log.h>
+ *
+ * If both ZF_LOG_DEF_SRCLOC and ZF_LOG_SRCLOC are undefined, then
+ * ZF_LOG_SRCLOC_NONE will be used for release builds (NDEBUG is defined) and
+ * ZF_LOG_SRCLOC_LONG otherwise (NDEBUG is not defined).
+ */
+#if defined(ZF_LOG_SRCLOC)
+	#define _ZF_LOG_SRCLOC ZF_LOG_SRCLOC
+#elif defined(ZF_LOG_DEF_SRCLOC)
+	#define _ZF_LOG_SRCLOC ZF_LOG_DEF_SRCLOC
+#else
+	#ifdef NDEBUG
+		#define _ZF_LOG_SRCLOC ZF_LOG_SRCLOC_NONE
+	#else
+		#define _ZF_LOG_SRCLOC ZF_LOG_SRCLOC_LONG
+	#endif
+#endif
+#if ZF_LOG_SRCLOC_LONG == _ZF_LOG_SRCLOC
+	#define _ZF_LOG_SRCLOC_FUNCTION _ZF_LOG_FUNCTION
+#else
+	#define _ZF_LOG_SRCLOC_FUNCTION 0
+#endif
+
+/* Censoring provides conditional logging of secret information, also known as
+ * Personally Identifiable Information (PII) or Sensitive Personal Information
+ * (SPI). Censoring can be either enabled (ZF_LOG_CENSORED) or disabled
+ * (ZF_LOG_UNCENSORED). When censoring is enabled, log statements marked as
+ * "secrets" will be ignored and will have zero overhead (arguments also will
+ * not be evaluated).
+ */
+#define ZF_LOG_CENSORED   1
+#define ZF_LOG_UNCENSORED 0
+
+/* Censoring is configured per compilation module (.c/.cpp/.m file) by defining
+ * ZF_LOG_DEF_CENSORING or ZF_LOG_CENSORING. ZF_LOG_CENSORING has higer priority
+ * and when defined overrides value provided by ZF_LOG_DEF_CENSORING.
+ *
+ * Common practice is to define default censoring with ZF_LOG_DEF_CENSORING in
+ * build script (e.g. Makefile, CMakeLists.txt, gyp, etc.) for the entire
+ * project or target:
+ *
+ *   CC_ARGS := -DZF_LOG_DEF_CENSORING=ZF_LOG_CENSORED
+ *
+ * And when necessary to override it with ZF_LOG_CENSORING in .c/.cpp/.m files
+ * before including zf_log.h (consider doing it only for debug purposes and be
+ * very careful not to push such temporary changes to source control):
+ *
+ *   #define ZF_LOG_CENSORING ZF_LOG_UNCENSORED
+ *   #include <zf_log.h>
+ *
+ * If both ZF_LOG_DEF_CENSORING and ZF_LOG_CENSORING are undefined, then
+ * ZF_LOG_CENSORED will be used for release builds (NDEBUG is defined) and
+ * ZF_LOG_UNCENSORED otherwise (NDEBUG is not defined).
+ */
+#if defined(ZF_LOG_CENSORING)
+	#define _ZF_LOG_CENSORING ZF_LOG_CENSORING
+#elif defined(ZF_LOG_DEF_CENSORING)
+	#define _ZF_LOG_CENSORING ZF_LOG_DEF_CENSORING
+#else
+	#ifdef NDEBUG
+		#define _ZF_LOG_CENSORING ZF_LOG_CENSORED
+	#else
+		#define _ZF_LOG_CENSORING ZF_LOG_UNCENSORED
+	#endif
+#endif
+
+/* Check censoring at compile time. Evaluates to true when censoring is disabled
+ * (i.e. when secrets will be logged). For example:
+ *
+ *   #if ZF_LOG_SECRETS
+ *       char ssn[16];
+ *       getSocialSecurityNumber(ssn);
+ *       ZF_LOGI("Customer ssn: %s", ssn);
+ *   #endif
+ *
+ * See ZF_LOG_SECRET() macro for a more convenient way of guarding single log
+ * statement.
+ */
+#define ZF_LOG_SECRETS (ZF_LOG_UNCENSORED == _ZF_LOG_CENSORING)
+
+/* Static (compile-time) initialization support allows to configure logging
+ * before entering main() function. This mostly useful in C++ where functions
+ * and methods could be called during initialization of global objects. Those
+ * functions and methods could record log messages too and for that reason
+ * static initialization of logging configuration is customizable.
+ *
+ * Macros below allow to specify values to use for initial configuration:
+ * - ZF_LOG_EXTERN_TAG_PREFIX - tag prefix (default: none)
+ * - ZF_LOG_EXTERN_GLOBAL_FORMAT - global format options (default: see
+ *   ZF_LOG_MEM_WIDTH in zf_log.c)
+ * - ZF_LOG_EXTERN_GLOBAL_OUTPUT - global output facility (default: stderr or
+ *   platform specific, see ZF_LOG_USE_XXX macros in zf_log.c)
+ * - ZF_LOG_EXTERN_GLOBAL_OUTPUT_LEVEL - global output log level (default: 0 -
+ *   all levals are "turned on")
+ *
+ * For example, in log_config.c:
+ *
+ *   #include <zf_log.h>
+ *   ZF_LOG_DEFINE_TAG_PREFIX = "MyApp";
+ *   ZF_LOG_DEFINE_GLOBAL_FORMAT = {CUSTOM_MEM_WIDTH};
+ *   ZF_LOG_DEFINE_GLOBAL_OUTPUT = {ZF_LOG_PUT_STD, custom_output_callback, 0};
+ *   ZF_LOG_DEFINE_GLOBAL_OUTPUT_LEVEL = ZF_LOG_INFO;
+ *
+ * However, to use any of those macros zf_log library must be compiled with
+ * following macros defined:
+ * - to use ZF_LOG_DEFINE_TAG_PREFIX define ZF_LOG_EXTERN_TAG_PREFIX
+ * - to use ZF_LOG_DEFINE_GLOBAL_FORMAT define ZF_LOG_EXTERN_GLOBAL_FORMAT
+ * - to use ZF_LOG_DEFINE_GLOBAL_OUTPUT define ZF_LOG_EXTERN_GLOBAL_OUTPUT
+ * - to use ZF_LOG_DEFINE_GLOBAL_OUTPUT_LEVEL define
+ *   ZF_LOG_EXTERN_GLOBAL_OUTPUT_LEVEL
+ *
+ * When zf_log library compiled with one of ZF_LOG_EXTERN_XXX macros defined,
+ * corresponding ZF_LOG_DEFINE_XXX macro MUST be used exactly once somewhere.
+ * Otherwise build will fail with link error (undefined symbol).
+ */
+#define ZF_LOG_DEFINE_TAG_PREFIX const char *_zf_log_tag_prefix
+#define ZF_LOG_DEFINE_GLOBAL_FORMAT zf_log_format _zf_log_global_format
+#define ZF_LOG_DEFINE_GLOBAL_OUTPUT zf_log_output _zf_log_global_output
+#define ZF_LOG_DEFINE_GLOBAL_OUTPUT_LEVEL int _zf_log_global_output_lvl
+
+/* Pointer to global format options. Direct modification is not allowed. Use
+ * zf_log_set_mem_width() instead. Could be used to initialize zf_log_spec
+ * structure:
+ *
+ *   const zf_log_output g_output = {ZF_LOG_PUT_STD, output_callback, 0};
+ *   const zf_log_spec g_spec = {ZF_LOG_GLOBAL_FORMAT, &g_output};
+ *   ZF_LOGI_AUX(&g_spec, "Hello");
+ */
+#define ZF_LOG_GLOBAL_FORMAT ((const zf_log_format *)&_zf_log_global_format)
+
+/* Pointer to global output variable. Direct modification is not allowed. Use
+ * zf_log_set_output_v() or zf_log_set_output_p() instead. Could be used to
+ * initialize zf_log_spec structure:
+ *
+ *   const zf_log_format g_format = {40};
+ *   const zf_log_spec g_spec = {g_format, ZF_LOG_GLOBAL_OUTPUT};
+ *   ZF_LOGI_AUX(&g_spec, "Hello");
+ */
+#define ZF_LOG_GLOBAL_OUTPUT ((const zf_log_output *)&_zf_log_global_output)
+
+/* When defined, all library symbols produced by linker will be prefixed with
+ * provided value. That allows to use zf_log library privately in another
+ * libraries without exposing zf_log symbols in their original form (to avoid
+ * possible conflicts with other libraries / components that also could use
+ * zf_log for logging). Value must be without quotes, for example:
+ *
+ *   CC_ARGS := -DZF_LOG_LIBRARY_PREFIX=my_lib_
+ *
+ * Note, that in this mode ZF_LOG_LIBRARY_PREFIX must be defined when building
+ * zf_log library AND it also must be defined to the same value when building
+ * a library that uses it. For example, consider fictional KittyHttp library
+ * that wants to use zf_log for logging. First approach that could be taken is
+ * to add zf_log.h and zf_log.c to the KittyHttp's source code tree directly.
+ * In that case it will be enough just to define ZF_LOG_LIBRARY_PREFIX in
+ * KittyHttp's build script:
+ *
+ *   // KittyHttp/CMakeLists.txt
+ *   target_compile_definitions(KittyHttp PRIVATE
+ *                              "ZF_LOG_LIBRARY_PREFIX=KittyHttp_")
+ *
+ * If KittyHttp doesn't want to include zf_log source code in its source tree
+ * and wants to build zf_log as a separate library than zf_log library must be
+ * built with ZF_LOG_LIBRARY_PREFIX defined to KittyHttp_ AND KittyHttp library
+ * itself also needs to define ZF_LOG_LIBRARY_PREFIX to KittyHttp_. It can do
+ * so either in its build script, as in example above, or by providing a
+ * wrapper header that KittyHttp library will need to use instead of zf_log.h:
+ *
+ *   // KittyHttpLogging.h
+ *   #define ZF_LOG_LIBRARY_PREFIX KittyHttp_
+ *   #include <zf_log.h>
+ *
+ * Regardless of the method chosen, the end result is that zf_log symbols will
+ * be prefixed with "KittyHttp_", so if a user of KittyHttp (say DogeBrowser)
+ * also uses zf_log for logging, they will not interferer with each other. Both
+ * will have their own log level, output facility, format options etc.
+ */
+#ifdef ZF_LOG_LIBRARY_PREFIX
+	#define _ZF_LOG_DECOR__(prefix, name) prefix ## name
+	#define _ZF_LOG_DECOR_(prefix, name) _ZF_LOG_DECOR__(prefix, name)
+	#define _ZF_LOG_DECOR(name) _ZF_LOG_DECOR_(ZF_LOG_LIBRARY_PREFIX, name)
+
+	#define zf_log_set_tag_prefix _ZF_LOG_DECOR(zf_log_set_tag_prefix)
+	#define zf_log_set_mem_width _ZF_LOG_DECOR(zf_log_set_mem_width)
+	#define zf_log_set_output_level _ZF_LOG_DECOR(zf_log_set_output_level)
+	#define zf_log_set_output_v _ZF_LOG_DECOR(zf_log_set_output_v)
+	#define zf_log_set_output_p _ZF_LOG_DECOR(zf_log_set_output_p)
+	#define zf_log_out_stderr_callback _ZF_LOG_DECOR(zf_log_out_stderr_callback)
+	#define _zf_log_tag_prefix _ZF_LOG_DECOR(_zf_log_tag_prefix)
+	#define _zf_log_global_format _ZF_LOG_DECOR(_zf_log_global_format)
+	#define _zf_log_global_output _ZF_LOG_DECOR(_zf_log_global_output)
+	#define _zf_log_global_output_lvl _ZF_LOG_DECOR(_zf_log_global_output_lvl)
+	#define _zf_log_write_d _ZF_LOG_DECOR(_zf_log_write_d)
+	#define _zf_log_write_aux_d _ZF_LOG_DECOR(_zf_log_write_aux_d)
+	#define _zf_log_write _ZF_LOG_DECOR(_zf_log_write)
+	#define _zf_log_write_aux _ZF_LOG_DECOR(_zf_log_write_aux)
+	#define _zf_log_write_mem_d _ZF_LOG_DECOR(_zf_log_write_mem_d)
+	#define _zf_log_write_mem_aux_d _ZF_LOG_DECOR(_zf_log_write_mem_aux_d)
+	#define _zf_log_write_mem _ZF_LOG_DECOR(_zf_log_write_mem)
+	#define _zf_log_write_mem_aux _ZF_LOG_DECOR(_zf_log_write_mem_aux)
+	#define _zf_log_stderr_spec _ZF_LOG_DECOR(_zf_log_stderr_spec)
+#endif
+
+#if defined(__printflike)
+	#define _ZF_LOG_PRINTFLIKE(str_index, first_to_check) \
+		__printflike(str_index, first_to_check)
+#elif defined(__GNUC__)
+	#define _ZF_LOG_PRINTFLIKE(str_index, first_to_check) \
+		__attribute__((format(__printf__, str_index, first_to_check)))
+#else
+	#define _ZF_LOG_PRINTFLIKE(str_index, first_to_check)
+#endif
+
+#if (defined(_WIN32) || defined(_WIN64)) && !defined(__GNUC__)
+	#define _ZF_LOG_FUNCTION __FUNCTION__
+#else
+	#define _ZF_LOG_FUNCTION __func__
+#endif
+
+#if defined(_MSC_VER) && !defined(__INTEL_COMPILER)
+	#define _ZF_LOG_INLINE __inline
+	#define _ZF_LOG_IF(cond) \
+		__pragma(warning(push)) \
+		__pragma(warning(disable:4127)) \
+		if(cond) \
+		__pragma(warning(pop))
+	#define _ZF_LOG_WHILE(cond) \
+		__pragma(warning(push)) \
+		__pragma(warning(disable:4127)) \
+		while(cond) \
+		__pragma(warning(pop))
+#else
+	#define _ZF_LOG_INLINE inline
+	#define _ZF_LOG_IF(cond) if(cond)
+	#define _ZF_LOG_WHILE(cond) while(cond)
+#endif
+#define _ZF_LOG_NEVER _ZF_LOG_IF(0)
+#define _ZF_LOG_ONCE _ZF_LOG_WHILE(0)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Set tag prefix. Prefix will be separated from the tag with dot ('.').
+ * Use 0 or empty string to disable (default). Common use is to set it to
+ * the process (or build target) name (e.g. to separate client and server
+ * processes). Function will NOT copy provided prefix string, but will store the
+ * pointer. Hence specified prefix string must remain valid. See
+ * ZF_LOG_DEFINE_TAG_PREFIX for a way to set it before entering main() function.
+ * See ZF_LOG_TAG for more information about tag and tag prefix.
+ */
+void zf_log_set_tag_prefix(const char *const prefix);
+
+/* Set number of bytes per log line in memory (ASCII-HEX) output. Example:
+ *
+ *   I hello.MAIN 4c6f72656d20697073756d20646f6c6f  Lorem ipsum dolo
+ *                |<-          w bytes         ->|  |<-  w chars ->|
+ *
+ * See ZF_LOGF_MEM and ZF_LOGF_MEM_AUX for more details.
+ */
+void zf_log_set_mem_width(const unsigned w);
+
+/* Set "output" log level. See ZF_LOG_LEVEL and ZF_LOG_OUTPUT_LEVEL for more
+ * info about log levels.
+ */
+void zf_log_set_output_level(const int lvl);
+
+/* Put mask is a set of flags that define what fields will be added to each
+ * log message. Default value is ZF_LOG_PUT_STD and other flags could be used to
+ * alter its behavior. See zf_log_set_output_v() for more details.
+ *
+ * Note about ZF_LOG_PUT_SRC: it will be added only in debug builds (NDEBUG is
+ * not defined).
+ */
+enum
+{
+	ZF_LOG_PUT_CTX = 1 << 0, /* context (time, pid, tid, log level) */
+	ZF_LOG_PUT_TAG = 1 << 1, /* tag (including tag prefix) */
+	ZF_LOG_PUT_SRC = 1 << 2, /* source location (file, line, function) */
+	ZF_LOG_PUT_MSG = 1 << 3, /* message text (formatted string) */
+	ZF_LOG_PUT_STD = 0xffff, /* everything (default) */
+};
+
+typedef struct zf_log_message
+{
+	int lvl; /* Log level of the message */
+	const char *tag; /* Associated tag (without tag prefix) */
+	char *buf; /* Buffer start */
+	char *e; /* Buffer end (last position where EOL with 0 could be written) */
+	char *p; /* Buffer content end (append position) */
+	char *tag_b; /* Prefixed tag start */
+	char *tag_e; /* Prefixed tag end (if != tag_b, points to msg separator) */
+	char *msg_b; /* Message start (expanded format string) */
+}
+zf_log_message;
+
+/* Type of output callback function. It will be called for each log line allowed
+ * by both "current" and "output" log levels ("enabled" and "turned on").
+ * Callback function is allowed to modify content of the buffers pointed by the
+ * msg, but it's not allowed to modify any of msg fields. Buffer pointed by msg
+ * is UTF-8 encoded (no BOM mark).
+ */
+typedef void (*zf_log_output_cb)(const zf_log_message *msg, void *arg);
+
+/* Format options. For more details see zf_log_set_mem_width().
+ */
+typedef struct zf_log_format
+{
+	unsigned mem_width; /* Bytes per line in memory (ASCII-HEX) dump */
+}
+zf_log_format;
+
+/* Output facility.
+ */
+typedef struct zf_log_output
+{
+	unsigned mask; /* What to put into log line buffer (see ZF_LOG_PUT_XXX) */
+	void *arg; /* User provided output callback argument */
+	zf_log_output_cb callback; /* Output callback function */
+}
+zf_log_output;
+
+/* Set output callback function.
+ *
+ * Mask allows to control what information will be added to the log line buffer
+ * before callback function is invoked. Default mask value is ZF_LOG_PUT_STD.
+ */
+void zf_log_set_output_v(const unsigned mask, void *const arg,
+						 const zf_log_output_cb callback);
+static _ZF_LOG_INLINE void zf_log_set_output_p(const zf_log_output *const output)
+{
+	zf_log_set_output_v(output->mask, output->arg, output->callback);
+}
+
+/* Used with _AUX macros and allows to override global format and output
+ * facility. Use ZF_LOG_GLOBAL_FORMAT and ZF_LOG_GLOBAL_OUTPUT for values from
+ * global configuration. Example:
+ *
+ *   static const zf_log_output module_output = {
+ *       ZF_LOG_PUT_STD, 0, custom_output_callback
+ *   };
+ *   static const zf_log_spec module_spec = {
+ *       ZF_LOG_GLOBAL_FORMAT, &module_output
+ *   };
+ *   ZF_LOGI_AUX(&module_spec, "Position: %ix%i", x, y);
+ *
+ * See ZF_LOGF_AUX and ZF_LOGF_MEM_AUX for details.
+ */
+typedef struct zf_log_spec
+{
+	const zf_log_format *format;
+	const zf_log_output *output;
+}
+zf_log_spec;
+
+#ifdef __cplusplus
+}
+#endif
+
+/* Execute log statement if condition is true. Example:
+ *
+ *   ZF_LOG_IF(1 < 2, ZF_LOGI("Log this"));
+ *   ZF_LOG_IF(1 > 2, ZF_LOGI("Don't log this"));
+ *
+ * Keep in mind though, that if condition can't be evaluated at compile time,
+ * then it will be evaluated at run time. This will increase exectuable size
+ * and can have noticeable performance overhead. Try to limit conditions to
+ * expressions that can be evaluated at compile time.
+ */
+#define ZF_LOG_IF(cond, f) do { _ZF_LOG_IF((cond)) { f; } } _ZF_LOG_ONCE
+
+/* Mark log statement as "secret". Log statements that are marked as secrets
+ * will NOT be executed when censoring is enabled (see ZF_LOG_CENSORED).
+ * Example:
+ *
+ *   ZF_LOG_SECRET(ZF_LOGI("Credit card: %s", credit_card));
+ *   ZF_LOG_SECRET(ZF_LOGD_MEM(cipher, cipher_sz, "Cipher bytes:"));
+ */
+#define ZF_LOG_SECRET(f) ZF_LOG_IF(ZF_LOG_SECRETS, f)
+
+/* Check "current" log level at compile time (ignoring "output" log level).
+ * Evaluates to true when specified log level is enabled. For example:
+ *
+ *   #if ZF_LOG_ENABLED_DEBUG
+ *       const char *const g_enum_strings[] = {
+ *           "enum_value_0", "enum_value_1", "enum_value_2"
+ *       };
+ *   #endif
+ *   // ...
+ *   #if ZF_LOG_ENABLED_DEBUG
+ *       ZF_LOGD("enum value: %s", g_enum_strings[v]);
+ *   #endif
+ *
+ * See ZF_LOG_LEVEL for details.
+ */
+#define ZF_LOG_ENABLED(lvl)     ((lvl) >= _ZF_LOG_LEVEL)
+#define ZF_LOG_ENABLED_VERBOSE  ZF_LOG_ENABLED(ZF_LOG_VERBOSE)
+#define ZF_LOG_ENABLED_DEBUG    ZF_LOG_ENABLED(ZF_LOG_DEBUG)
+#define ZF_LOG_ENABLED_INFO     ZF_LOG_ENABLED(ZF_LOG_INFO)
+#define ZF_LOG_ENABLED_WARN     ZF_LOG_ENABLED(ZF_LOG_WARN)
+#define ZF_LOG_ENABLED_ERROR    ZF_LOG_ENABLED(ZF_LOG_ERROR)
+#define ZF_LOG_ENABLED_FATAL    ZF_LOG_ENABLED(ZF_LOG_FATAL)
+
+/* Check "output" log level at run time (taking into account "current" log
+ * level as well). Evaluates to true when specified log level is turned on AND
+ * enabled. For example:
+ *
+ *   if (ZF_LOG_ON_DEBUG)
+ *   {
+ *       char hash[65];
+ *       sha256(data_ptr, data_sz, hash);
+ *       ZF_LOGD("data: len=%u, sha256=%s", data_sz, hash);
+ *   }
+ *
+ * See ZF_LOG_OUTPUT_LEVEL for details.
+ */
+#define ZF_LOG_ON(lvl) \
+		(ZF_LOG_ENABLED((lvl)) && (lvl) >= _ZF_LOG_OUTPUT_LEVEL)
+#define ZF_LOG_ON_VERBOSE   ZF_LOG_ON(ZF_LOG_VERBOSE)
+#define ZF_LOG_ON_DEBUG     ZF_LOG_ON(ZF_LOG_DEBUG)
+#define ZF_LOG_ON_INFO      ZF_LOG_ON(ZF_LOG_INFO)
+#define ZF_LOG_ON_WARN      ZF_LOG_ON(ZF_LOG_WARN)
+#define ZF_LOG_ON_ERROR     ZF_LOG_ON(ZF_LOG_ERROR)
+#define ZF_LOG_ON_FATAL     ZF_LOG_ON(ZF_LOG_FATAL)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern const char *_zf_log_tag_prefix;
+extern zf_log_format _zf_log_global_format;
+extern zf_log_output _zf_log_global_output;
+extern int _zf_log_global_output_lvl;
+extern const zf_log_spec _zf_log_stderr_spec;
+
+void _zf_log_write_d(
+		const char *const func, const char *const file, const unsigned line,
+		const int lvl, const char *const tag,
+		const char *const fmt, ...) _ZF_LOG_PRINTFLIKE(6, 7);
+void _zf_log_write_aux_d(
+		const char *const func, const char *const file, const unsigned line,
+		const zf_log_spec *const log, const int lvl, const char *const tag,
+		const char *const fmt, ...) _ZF_LOG_PRINTFLIKE(7, 8);
+void _zf_log_write(
+		const int lvl, const char *const tag,
+		const char *const fmt, ...) _ZF_LOG_PRINTFLIKE(3, 4);
+void _zf_log_write_aux(
+		const zf_log_spec *const log, const int lvl, const char *const tag,
+		const char *const fmt, ...) _ZF_LOG_PRINTFLIKE(4, 5);
+void _zf_log_write_mem_d(
+		const char *const func, const char *const file, const unsigned line,
+		const int lvl, const char *const tag,
+		const void *const d, const unsigned d_sz,
+		const char *const fmt, ...) _ZF_LOG_PRINTFLIKE(8, 9);
+void _zf_log_write_mem_aux_d(
+		const char *const func, const char *const file, const unsigned line,
+		const zf_log_spec *const log, const int lvl, const char *const tag,
+		const void *const d, const unsigned d_sz,
+		const char *const fmt, ...) _ZF_LOG_PRINTFLIKE(9, 10);
+void _zf_log_write_mem(
+		const int lvl, const char *const tag,
+		const void *const d, const unsigned d_sz,
+		const char *const fmt, ...) _ZF_LOG_PRINTFLIKE(5, 6);
+void _zf_log_write_mem_aux(
+		const zf_log_spec *const log, const int lvl, const char *const tag,
+		const void *const d, const unsigned d_sz,
+		const char *const fmt, ...) _ZF_LOG_PRINTFLIKE(6, 7);
+
+#ifdef __cplusplus
+}
+#endif
+
+/* Message logging macros:
+ * - ZF_LOGV("format string", args, ...)
+ * - ZF_LOGD("format string", args, ...)
+ * - ZF_LOGI("format string", args, ...)
+ * - ZF_LOGW("format string", args, ...)
+ * - ZF_LOGE("format string", args, ...)
+ * - ZF_LOGF("format string", args, ...)
+ *
+ * Memory logging macros:
+ * - ZF_LOGV_MEM(data_ptr, data_sz, "format string", args, ...)
+ * - ZF_LOGD_MEM(data_ptr, data_sz, "format string", args, ...)
+ * - ZF_LOGI_MEM(data_ptr, data_sz, "format string", args, ...)
+ * - ZF_LOGW_MEM(data_ptr, data_sz, "format string", args, ...)
+ * - ZF_LOGE_MEM(data_ptr, data_sz, "format string", args, ...)
+ * - ZF_LOGF_MEM(data_ptr, data_sz, "format string", args, ...)
+ *
+ * Auxiliary logging macros:
+ * - ZF_LOGV_AUX(&log_instance, "format string", args, ...)
+ * - ZF_LOGD_AUX(&log_instance, "format string", args, ...)
+ * - ZF_LOGI_AUX(&log_instance, "format string", args, ...)
+ * - ZF_LOGW_AUX(&log_instance, "format string", args, ...)
+ * - ZF_LOGE_AUX(&log_instance, "format string", args, ...)
+ * - ZF_LOGF_AUX(&log_instance, "format string", args, ...)
+ *
+ * Auxiliary memory logging macros:
+ * - ZF_LOGV_MEM_AUX(&log_instance, data_ptr, data_sz, "format string", args, ...)
+ * - ZF_LOGD_MEM_AUX(&log_instance, data_ptr, data_sz, "format string", args, ...)
+ * - ZF_LOGI_MEM_AUX(&log_instance, data_ptr, data_sz, "format string", args, ...)
+ * - ZF_LOGW_MEM_AUX(&log_instance, data_ptr, data_sz, "format string", args, ...)
+ * - ZF_LOGE_MEM_AUX(&log_instance, data_ptr, data_sz, "format string", args, ...)
+ * - ZF_LOGF_MEM_AUX(&log_instance, data_ptr, data_sz, "format string", args, ...)
+ *
+ * Preformatted string logging macros:
+ * - ZF_LOGV_STR("preformatted string");
+ * - ZF_LOGD_STR("preformatted string");
+ * - ZF_LOGI_STR("preformatted string");
+ * - ZF_LOGW_STR("preformatted string");
+ * - ZF_LOGE_STR("preformatted string");
+ * - ZF_LOGF_STR("preformatted string");
+ *
+ * Explicit log level and tag macros:
+ * - ZF_LOG_WRITE(level, tag, "format string", args, ...)
+ * - ZF_LOG_WRITE_MEM(level, tag, data_ptr, data_sz, "format string", args, ...)
+ * - ZF_LOG_WRITE_AUX(&log_instance, level, tag, "format string", args, ...)
+ * - ZF_LOG_WRITE_MEM_AUX(&log_instance, level, tag, data_ptr, data_sz,
+ *                        "format string", args, ...)
+ *
+ * Format string follows printf() conventions. Both data_ptr and data_sz could
+ * be 0. Tag can be 0 as well. Most compilers will verify that type of arguments
+ * match format specifiers in format string.
+ *
+ * Library assuming UTF-8 encoding for all strings (char *), including format
+ * string itself.
+ */
+#if ZF_LOG_SRCLOC_NONE == _ZF_LOG_SRCLOC
+	#define ZF_LOG_WRITE(lvl, tag, ...) \
+			do { \
+				if (ZF_LOG_ON(lvl)) \
+					_zf_log_write(lvl, tag, __VA_ARGS__); \
+			} _ZF_LOG_ONCE
+	#define ZF_LOG_WRITE_MEM(lvl, tag, d, d_sz, ...) \
+			do { \
+				if (ZF_LOG_ON(lvl)) \
+					_zf_log_write_mem(lvl, tag, d, d_sz, __VA_ARGS__); \
+			} _ZF_LOG_ONCE
+	#define ZF_LOG_WRITE_AUX(log, lvl, tag, ...) \
+			do { \
+				if (ZF_LOG_ON(lvl)) \
+					_zf_log_write_aux(log, lvl, tag, __VA_ARGS__); \
+			} _ZF_LOG_ONCE
+	#define ZF_LOG_WRITE_MEM_AUX(log, lvl, tag, d, d_sz, ...) \
+			do { \
+				if (ZF_LOG_ON(lvl)) \
+					_zf_log_write_mem_aux(log, lvl, tag, d, d_sz, __VA_ARGS__); \
+			} _ZF_LOG_ONCE
+#else
+	#define ZF_LOG_WRITE(lvl, tag, ...) \
+			do { \
+				if (ZF_LOG_ON(lvl)) \
+					_zf_log_write_d(_ZF_LOG_SRCLOC_FUNCTION, __FILE__, __LINE__, \
+							lvl, tag, __VA_ARGS__); \
+			} _ZF_LOG_ONCE
+	#define ZF_LOG_WRITE_MEM(lvl, tag, d, d_sz, ...) \
+			do { \
+				if (ZF_LOG_ON(lvl)) \
+					_zf_log_write_mem_d(_ZF_LOG_SRCLOC_FUNCTION, __FILE__, __LINE__, \
+							lvl, tag, d, d_sz, __VA_ARGS__); \
+			} _ZF_LOG_ONCE
+	#define ZF_LOG_WRITE_AUX(log, lvl, tag, ...) \
+			do { \
+				if (ZF_LOG_ON(lvl)) \
+					_zf_log_write_aux_d(_ZF_LOG_SRCLOC_FUNCTION, __FILE__, __LINE__, \
+							log, lvl, tag, __VA_ARGS__); \
+			} _ZF_LOG_ONCE
+	#define ZF_LOG_WRITE_MEM_AUX(log, lvl, tag, d, d_sz, ...) \
+			do { \
+				if (ZF_LOG_ON(lvl)) \
+					_zf_log_write_mem_aux_d(_ZF_LOG_SRCLOC_FUNCTION, __FILE__, __LINE__, \
+							log, lvl, tag, d, d_sz, __VA_ARGS__); \
+			} _ZF_LOG_ONCE
+#endif
+
+static _ZF_LOG_INLINE void _zf_log_unused(const int dummy, ...) {(void)dummy;}
+
+#define _ZF_LOG_UNUSED(...) \
+		do { _ZF_LOG_NEVER _zf_log_unused(0, __VA_ARGS__); } _ZF_LOG_ONCE
+
+#if ZF_LOG_ENABLED_VERBOSE
+	#define ZF_LOGV(...) \
+			ZF_LOG_WRITE(ZF_LOG_VERBOSE, _ZF_LOG_TAG, __VA_ARGS__)
+	#define ZF_LOGV_AUX(log, ...) \
+			ZF_LOG_WRITE_AUX(log, ZF_LOG_VERBOSE, _ZF_LOG_TAG, __VA_ARGS__)
+	#define ZF_LOGV_MEM(d, d_sz, ...) \
+			ZF_LOG_WRITE_MEM(ZF_LOG_VERBOSE, _ZF_LOG_TAG, d, d_sz, __VA_ARGS__)
+	#define ZF_LOGV_MEM_AUX(log, d, d_sz, ...) \
+			ZF_LOG_WRITE_MEM(log, ZF_LOG_VERBOSE, _ZF_LOG_TAG, d, d_sz, __VA_ARGS__)
+#else
+	#define ZF_LOGV(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGV_AUX(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGV_MEM(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGV_MEM_AUX(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+#endif
+
+#if ZF_LOG_ENABLED_DEBUG
+	#define ZF_LOGD(...) \
+			ZF_LOG_WRITE(ZF_LOG_DEBUG, _ZF_LOG_TAG, __VA_ARGS__)
+	#define ZF_LOGD_AUX(log, ...) \
+			ZF_LOG_WRITE_AUX(log, ZF_LOG_DEBUG, _ZF_LOG_TAG, __VA_ARGS__)
+	#define ZF_LOGD_MEM(d, d_sz, ...) \
+			ZF_LOG_WRITE_MEM(ZF_LOG_DEBUG, _ZF_LOG_TAG, d, d_sz, __VA_ARGS__)
+	#define ZF_LOGD_MEM_AUX(log, d, d_sz, ...) \
+			ZF_LOG_WRITE_MEM_AUX(log, ZF_LOG_DEBUG, _ZF_LOG_TAG, d, d_sz, __VA_ARGS__)
+#else
+	#define ZF_LOGD(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGD_AUX(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGD_MEM(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGD_MEM_AUX(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+#endif
+
+#if ZF_LOG_ENABLED_INFO
+	#define ZF_LOGI(...) \
+			ZF_LOG_WRITE(ZF_LOG_INFO, _ZF_LOG_TAG, __VA_ARGS__)
+	#define ZF_LOGI_AUX(log, ...) \
+			ZF_LOG_WRITE_AUX(log, ZF_LOG_INFO, _ZF_LOG_TAG, __VA_ARGS__)
+	#define ZF_LOGI_MEM(d, d_sz, ...) \
+			ZF_LOG_WRITE_MEM(ZF_LOG_INFO, _ZF_LOG_TAG, d, d_sz, __VA_ARGS__)
+	#define ZF_LOGI_MEM_AUX(log, d, d_sz, ...) \
+			ZF_LOG_WRITE_MEM_AUX(log, ZF_LOG_INFO, _ZF_LOG_TAG, d, d_sz, __VA_ARGS__)
+#else
+	#define ZF_LOGI(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGI_AUX(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGI_MEM(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGI_MEM_AUX(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+#endif
+
+#if ZF_LOG_ENABLED_WARN
+	#define ZF_LOGW(...) \
+			ZF_LOG_WRITE(ZF_LOG_WARN, _ZF_LOG_TAG, __VA_ARGS__)
+	#define ZF_LOGW_AUX(log, ...) \
+			ZF_LOG_WRITE_AUX(log, ZF_LOG_WARN, _ZF_LOG_TAG, __VA_ARGS__)
+	#define ZF_LOGW_MEM(d, d_sz, ...) \
+			ZF_LOG_WRITE_MEM(ZF_LOG_WARN, _ZF_LOG_TAG, d, d_sz, __VA_ARGS__)
+	#define ZF_LOGW_MEM_AUX(log, d, d_sz, ...) \
+			ZF_LOG_WRITE_MEM_AUX(log, ZF_LOG_WARN, _ZF_LOG_TAG, d, d_sz, __VA_ARGS__)
+#else
+	#define ZF_LOGW(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGW_AUX(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGW_MEM(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGW_MEM_AUX(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+#endif
+
+#if ZF_LOG_ENABLED_ERROR
+	#define ZF_LOGE(...) \
+			ZF_LOG_WRITE(ZF_LOG_ERROR, _ZF_LOG_TAG, __VA_ARGS__)
+	#define ZF_LOGE_AUX(log, ...) \
+			ZF_LOG_WRITE_AUX(log, ZF_LOG_ERROR, _ZF_LOG_TAG, __VA_ARGS__)
+	#define ZF_LOGE_MEM(d, d_sz, ...) \
+			ZF_LOG_WRITE_MEM(ZF_LOG_ERROR, _ZF_LOG_TAG, d, d_sz, __VA_ARGS__)
+	#define ZF_LOGE_MEM_AUX(log, d, d_sz, ...) \
+			ZF_LOG_WRITE_MEM_AUX(log, ZF_LOG_ERROR, _ZF_LOG_TAG, d, d_sz, __VA_ARGS__)
+#else
+	#define ZF_LOGE(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGE_AUX(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGE_MEM(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGE_MEM_AUX(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+#endif
+
+#if ZF_LOG_ENABLED_FATAL
+	#define ZF_LOGF(...) \
+			ZF_LOG_WRITE(ZF_LOG_FATAL, _ZF_LOG_TAG, __VA_ARGS__)
+	#define ZF_LOGF_AUX(log, ...) \
+			ZF_LOG_WRITE_AUX(log, ZF_LOG_FATAL, _ZF_LOG_TAG, __VA_ARGS__)
+	#define ZF_LOGF_MEM(d, d_sz, ...) \
+			ZF_LOG_WRITE_MEM(ZF_LOG_FATAL, _ZF_LOG_TAG, d, d_sz, __VA_ARGS__)
+	#define ZF_LOGF_MEM_AUX(log, d, d_sz, ...) \
+			ZF_LOG_WRITE_MEM_AUX(log, ZF_LOG_FATAL, _ZF_LOG_TAG, d, d_sz, __VA_ARGS__)
+#else
+	#define ZF_LOGF(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGF_AUX(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGF_MEM(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+	#define ZF_LOGF_MEM_AUX(...) _ZF_LOG_UNUSED(__VA_ARGS__)
+#endif
+
+#define ZF_LOGV_STR(s) ZF_LOGV("%s", (s))
+#define ZF_LOGD_STR(s) ZF_LOGD("%s", (s))
+#define ZF_LOGI_STR(s) ZF_LOGI("%s", (s))
+#define ZF_LOGW_STR(s) ZF_LOGW("%s", (s))
+#define ZF_LOGE_STR(s) ZF_LOGE("%s", (s))
+#define ZF_LOGF_STR(s) ZF_LOGF("%s", (s))
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Output to standard error stream. Library uses it by default, though in few
+ * cases it could be necessary to specify it explicitly. For example, when
+ * zf_log library is compiled with ZF_LOG_EXTERN_GLOBAL_OUTPUT, application must
+ * define and initialize global output variable:
+ *
+ *   ZF_LOG_DEFINE_GLOBAL_OUTPUT = {ZF_LOG_OUT_STDERR};
+ *
+ * Another example is when using custom output, stderr could be used as a
+ * fallback when custom output facility failed to initialize:
+ *
+ *   zf_log_set_output_v(ZF_LOG_OUT_STDERR);
+ */
+enum { ZF_LOG_OUT_STDERR_MASK = ZF_LOG_PUT_STD };
+void zf_log_out_stderr_callback(const zf_log_message *const msg, void *arg);
+#define ZF_LOG_OUT_STDERR ZF_LOG_OUT_STDERR_MASK, 0, zf_log_out_stderr_callback
+
+/* Predefined spec for stderr. Uses global format options (ZF_LOG_GLOBAL_FORMAT)
+ * and ZF_LOG_OUT_STDERR. Could be used to force output to stderr for a
+ * particular message. Example:
+ *
+ *   f = fopen("foo.log", "w");
+ *   if (!f)
+ *       ZF_LOGE_AUX(ZF_LOG_STDERR, "Failed to open log file");
+ */
+#define ZF_LOG_STDERR (&_zf_log_stderr_spec)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif