[PATCH 5/8] lib: re-work logging to add in json formatted log output

Keng-Yu Lin kengyu at canonical.com
Wed May 23 06:04:16 UTC 2012


On Wed, May 16, 2012 at 9:20 PM, Colin King <colin.king at canonical.com> wrote:
> From: Colin Ian King <colin.king at canonical.com>
>
> Signed-off-by: Colin Ian King <colin.king at canonical.com>
> ---
>  src/lib/include/fwts_framework.h |    3 +-
>  src/lib/include/fwts_log.h       |   63 +++++++---
>  src/lib/src/Makefile.am          |    2 +
>  src/lib/src/fwts_framework.c     |   94 +++++++++++++--
>  src/lib/src/fwts_log.c           |  239 ++++++++++++++++----------------------
>  src/lib/src/fwts_log_json.c      |  185 +++++++++++++++++++++++++++++
>  src/lib/src/fwts_log_plaintext.c |  195 +++++++++++++++++++++++++++++++
>  src/lib/src/fwts_summary.c       |   16 ++-
>  src/lib/src/fwts_tag.c           |    2 +-
>  9 files changed, 623 insertions(+), 176 deletions(-)
>  create mode 100644 src/lib/src/fwts_log_json.c
>  create mode 100644 src/lib/src/fwts_log_plaintext.c
>
> diff --git a/src/lib/include/fwts_framework.h b/src/lib/include/fwts_framework.h
> index 38879f6..fb74253 100644
> --- a/src/lib/include/fwts_framework.h
> +++ b/src/lib/include/fwts_framework.h
> @@ -26,7 +26,6 @@
>
>  #include "fwts_log.h"
>  #include "fwts_list.h"
> -#include "fwts_framework.h"
>
>  #define FWTS_FRAMEWORK_MAGIC   0x2af61aec
>
> @@ -139,6 +138,8 @@ typedef struct {
>
>        int firmware_type;                      /* Type of firmware */
>        int show_progress;                      /* Show progress while running current test */
> +
> +       fwts_log_type   log_type;               /* Output log type, default is plain text ASCII */
>  } fwts_framework;
>
>  typedef struct {
> diff --git a/src/lib/include/fwts_log.h b/src/lib/include/fwts_log.h
> index ab94029..8903bab 100644
> --- a/src/lib/include/fwts_log.h
> +++ b/src/lib/include/fwts_log.h
> @@ -53,19 +53,43 @@ typedef enum {
>        LOG_LEVEL_INFO      = 0x00000010,
>  } fwts_log_level;
>
> +typedef enum {
> +       LOG_TYPE_NONE       = 0x00000000,
> +       LOG_TYPE_PLAINTEXT  = 0x00000001,
> +       LOG_TYPE_JSON       = 0x00000002,
> +       LOG_TYPE_XML        = 0x00000003,
> +} fwts_log_type;
> +
>  typedef struct log_t {
>        unsigned int magic;
>        FILE *fp;
>        char *owner;
>        int line_width;
>        int line_number;
> +       struct fwts_log_ops_t *ops;
>  } fwts_log;
>
> -fwts_log *fwts_log_open(const char* owner, const char *name, const char *mode);
> +typedef struct fwts_log_ops_t {
> +       int (*vprintf)(fwts_log *log, const fwts_log_field field, const fwts_log_level level, const char *status, const char *label, const char *prefix, const char *fmt, va_list args);
> +       void (*underline)(fwts_log *log, int ch);
> +       void (*newline)(fwts_log *log);
> +       void (*section_begin)(fwts_log *, const char *tag);
> +       void (*section_end)(fwts_log *);
> +       void (*open)(fwts_log *);
> +       void (*close)(fwts_log *);
> +} fwts_log_ops;
> +
> +fwts_log_ops fwts_log_plaintext_ops;
> +fwts_log_ops fwts_log_json_ops;
> +
> +extern fwts_log_field fwts_log_filter;
> +extern const char *fwts_log_format;
> +
> +fwts_log *fwts_log_open(const char* owner, const char *name, const char *mode, fwts_log_type);
>  int       fwts_log_close(fwts_log *log);
> -int       fwts_log_printf(fwts_log *log, const fwts_log_field field, const fwts_log_level level, const char *fmt, ...)
> -       __attribute__((format(printf, 4, 5)));
> -int       fwts_log_vprintf(fwts_log *log, const fwts_log_field field, const fwts_log_level level, const char *fmt, va_list args);
> +int       fwts_log_printf(fwts_log *log, const fwts_log_field field, const fwts_log_level level, const char *status, const char *label, const char *prefix, const char *fmt, ...)
> +       __attribute__((format(printf, 7, 8)));
> +int       fwts_log_vprintf(fwts_log *log, const fwts_log_field field, const fwts_log_level level, const char *status, const char *label, const char *prefix, const char *fmt, va_list args);
>  void      fwts_log_newline(fwts_log *log);
>  void      fwts_log_underline(fwts_log *log, const int ch);
>  void      fwts_log_set_field_filter(const char *str);
> @@ -75,47 +99,52 @@ void      fwts_log_print_fields(void);
>  void      fwts_log_filter_set_field(const fwts_log_field filter);
>  void      fwts_log_filter_unset_field(const fwts_log_field filter);
>  int       fwts_log_str_to_level(const char *str);
> +fwts_log_field fwts_log_str_to_field(const char *text);
>  char     *fwts_log_level_to_str(const fwts_log_level level);
> +char    *fwts_log_field_to_str(const fwts_log_field field);
> +char     *fwts_log_field_to_str_full(const fwts_log_field field);
>  int      fwts_log_line_number(fwts_log *log);
>  void     fwts_log_set_line_width(const int width);
> +void     fwts_log_section_begin(fwts_log *log, const char *name);
> +void     fwts_log_section_end(fwts_log *log);
>
>  #define fwts_log_result(fw, fmt, args...)      \
> -       fwts_log_printf(fw->results, LOG_RESULT, LOG_LEVEL_NONE, fmt, ## args)
> +       fwts_log_printf(fw->results, LOG_RESULT, LOG_LEVEL_NONE, "", "", "", fmt, ## args)
>
>  #define fwts_log_warning(fw, fmt, args...)     \
> -       fwts_log_printf(fw->results, LOG_WARNING, LOG_LEVEL_NONE, fmt, ## args)
> +       fwts_log_printf(fw->results, LOG_WARNING, LOG_LEVEL_NONE, "", "", "", fmt, ## args)
>
>  #define fwts_log_warning_verbatum(fw, fmt, args...)    \
> -       fwts_log_printf(fw->results, LOG_WARNING | LOG_VERBATUM, LOG_LEVEL_NONE, fmt, ## args)
> +       fwts_log_printf(fw->results, LOG_WARNING | LOG_VERBATUM, LOG_LEVEL_NONE, "", "", "", fmt, ## args)
>
>  #define fwts_log_error(fw, fmt, args...)       \
> -       fwts_log_printf(fw->results, LOG_ERROR, LOG_LEVEL_NONE, fmt, ## args)
> +       fwts_log_printf(fw->results, LOG_ERROR, LOG_LEVEL_NONE, "", "", "", fmt, ## args)
>
>  #define fwts_log_error_verbatum(fw, fmt, args...)      \
> -       fwts_log_printf(fw->results, LOG_ERROR | LOG_VERBATUM, LOG_LEVEL_NONE, fmt, ## args)
> +       fwts_log_printf(fw->results, LOG_ERROR | LOG_VERBATUM, LOG_LEVEL_NONE, "", "", "", fmt, ## args)
>
>  #define fwts_log_info(fw, fmt, args...)        \
> -       fwts_log_printf(fw->results, LOG_INFO, LOG_LEVEL_NONE, fmt, ## args)
> +       fwts_log_printf(fw->results, LOG_INFO, LOG_LEVEL_NONE, "", "", "", fmt, ## args)
>
>  #define fwts_log_info_verbatum(fw, fmt, args...)       \
> -       fwts_log_printf(fw->results, LOG_INFO | LOG_VERBATUM, LOG_LEVEL_NONE, fmt, ## args)
> +       fwts_log_printf(fw->results, LOG_INFO | LOG_VERBATUM, LOG_LEVEL_NONE, "", "", "", fmt, ## args)
>
>  #define fwts_log_summary(fw, fmt, args...)     \
> -       fwts_log_printf(fw->results, LOG_SUMMARY, LOG_LEVEL_NONE, fmt, ## args)
> +       fwts_log_printf(fw->results, LOG_SUMMARY, LOG_LEVEL_NONE, "", "", "", fmt, ## args)
>
>  #define fwts_log_summary_verbatum(fw, fmt, args...)    \
> -       fwts_log_printf(fw->results, LOG_SUMMARY | LOG_VERBATUM, LOG_LEVEL_NONE, fmt, ## args)
> +       fwts_log_printf(fw->results, LOG_SUMMARY | LOG_VERBATUM, LOG_LEVEL_NONE, "", "", "", fmt, ## args)
>
>  #define fwts_log_advice(fw, fmt, args...)      \
> -       fwts_log_printf(fw->results, LOG_ADVICE, LOG_LEVEL_NONE, fmt, ## args)
> +       fwts_log_printf(fw->results, LOG_ADVICE, LOG_LEVEL_NONE, "", "", "", fmt, ## args)
>
>  #define fwts_log_heading(fw, fmt, args...)     \
> -       fwts_log_printf(fw->results, LOG_HEADING, LOG_LEVEL_NONE, fmt, ## args)
> +       fwts_log_printf(fw->results, LOG_HEADING, LOG_LEVEL_NONE, "", "", "", fmt, ## args)
>
>  #define fwts_log_tag(fw, fmt, args...) \
> -       fwts_log_printf(fw->results, LOG_TAG | LOG_VERBATUM, LOG_LEVEL_NONE, fmt, ## args)
> +       fwts_log_printf(fw->results, LOG_TAG | LOG_VERBATUM, LOG_LEVEL_NONE, "", "", "", fmt, ## args)
>
>  #define fwts_log_nl(fw) \
> -       fwts_log_printf(fw->results, LOG_NEWLINE, LOG_LEVEL_NONE, "%s", "")
> +       fwts_log_printf(fw->results, LOG_NEWLINE, LOG_LEVEL_NONE, "", "", "", "%s", "")
>
>  #endif
> diff --git a/src/lib/src/Makefile.am b/src/lib/src/Makefile.am
> index 2aac13e..cae1f91 100644
> --- a/src/lib/src/Makefile.am
> +++ b/src/lib/src/Makefile.am
> @@ -37,6 +37,8 @@ libfwts_la_SOURCES = \
>        fwts_klog.c \
>        fwts_list.c \
>        fwts_log.c \
> +       fwts_log_plaintext.c \
> +       fwts_log_json.c \
>        fwts_memorymap.c \
>        fwts_microcode.c \
>        fwts_mmap.c \
> diff --git a/src/lib/src/fwts_framework.c b/src/lib/src/fwts_framework.c
> index da24b71..bc39b6b 100644
> --- a/src/lib/src/fwts_framework.c
> +++ b/src/lib/src/fwts_framework.c
> @@ -76,6 +76,7 @@ static fwts_option fwts_framework_options[] = {
>        { "json-data-path",     "j:", 1, "Specify path to fwts json data files - default is /usr/share/fwts." },
>        { "lp-tags-log",        "",   0, "Output LaunchPad bug tags in results log." },
>        { "disassemble-aml",    "",   0, "Disassemble AML from DSDT and SSDT tables." },
> +       { "log-type",           "",   1, "Specify log type (plaintext or json)." },
>        { NULL, NULL, 0, NULL }
>  };
>
> @@ -111,6 +112,39 @@ static fwts_framework_setting fwts_framework_settings[] = {
>        { ID_NAME(FWTS_FRAMEWORK_INFOONLY),             "INFO", NULL },
>  };
>
> +#if 0
> +static const char *fwts_framework_results_to_str(fwts_framework_results result)
> +{
> +       switch (result) {
> +       case FWTS_FRAMEWORK_PASSED:
> +               return "Passed";
> +       case FWTS_FRAMEWORK_FAILED:
> +               return "Failed";
> +       case FWTS_FRAMEWORK_FAILED_LOW:
> +               return "Failed Low";
> +       case FWTS_FRAMEWORK_FAILED_HIGH:
> +               return "Failed High";
> +       case FWTS_FRAMEWORK_FAILED_MEDIUM:
> +               return "Failed Medium";
> +       case FWTS_FRAMEWORK_FAILED_CRITICAL:
> +               return "Failed Critical";
> +       case FWTS_FRAMEWORK_WARNING:
> +               return "Warning";
> +       case FWTS_FRAMEWORK_ERROR:
> +               return "Error";
> +       case FWTS_FRAMEWORK_ADVICE:
> +               return "Advice";
> +       case FWTS_FRAMEWORK_SKIPPED:
> +               return "Skipped";
> +        case FWTS_FRAMEWORK_ABORTED:
> +               return "Aborted";
> +       case FWTS_FRAMEWORK_INFOONLY:
> +               return "Info";
> +       default:
> +               return "Unknown";
> +}
> +#endif
> +
>  /*
>  *  fwts_framework_compare_priority()
>  *     used to register tests sorted on run priority
> @@ -535,6 +569,7 @@ static int fwts_framework_run_test(fwts_framework *fw, const int num_tests, fwts
>
>        fw->failed_level = 0;
>
> +       fwts_log_section_begin(fw->results, test->name);
>        fwts_log_set_owner(fw->results, test->name);
>
>        fw->current_minor_test_num = 1;
> @@ -591,20 +626,27 @@ static int fwts_framework_run_test(fwts_framework *fw, const int num_tests, fwts
>                goto done;
>        }
>
> +       fwts_log_section_begin(fw->results, "subtests");
>        for (minor_test = test->ops->minor_tests;
>                *minor_test->test_func != NULL;
>                minor_test++, fw->current_minor_test_num++) {
>
> +               fwts_log_section_begin(fw->results, "subtest");
>                fw->current_minor_test_name = minor_test->name;
>
>                fwts_results_zero(&fw->minor_tests);
>
> -               if (minor_test->name != NULL)
> +               if (minor_test->name != NULL) {
> +                       fwts_log_section_begin(fw->results, "subtest_info");
>                        fwts_log_info(fw, "Test %d of %d: %s",
>                                fw->current_minor_test_num,
>                                test->ops->total_tests, minor_test->name);
> +                       fwts_log_section_end(fw->results);
> +               }
>
> +               fwts_log_section_begin(fw->results, "subtest_results");
>                fwts_framework_minor_test_progress(fw, 0, "");
> +
>                ret = (*minor_test->test_func)(fw);
>
>                /* Something went horribly wrong, abort all other tests too */
> @@ -625,23 +667,33 @@ static int fwts_framework_run_test(fwts_framework *fw, const int num_tests, fwts
>                        fprintf(stderr, "  %-55.55s %s\n", namebuf,
>                                *resbuf ? resbuf : "     ");
>                }
> +               fwts_log_section_end(fw->results);
>                fwts_log_nl(fw);
> +               fwts_log_section_end(fw->results);
>        }
> +       fwts_log_section_end(fw->results);
>
>        fwts_framework_summate_results(&fw->total, &fw->current_major_test->results);
>
>        if (test->ops->deinit)
>                test->ops->deinit(fw);
>
> -       if (fw->flags & FWTS_FRAMEWORK_FLAGS_LP_TAGS_LOG)
> +       if (fw->flags & FWTS_FRAMEWORK_FLAGS_LP_TAGS_LOG) {
> +               fwts_log_section_begin(fw->results, "tags");
>                fwts_tag_report(fw, LOG_TAG, &fw->test_taglist);
> +               fwts_log_section_end(fw->results);
> +       }
>
>  done:
>        fwts_list_free_items(&fw->test_taglist, free);
>
> -       if (!(test->flags & FWTS_UTILS))
> +       if (!(test->flags & FWTS_UTILS)) {
> +               fwts_log_section_begin(fw->results, "results");
>                fwts_framework_test_summary(fw);
> +               fwts_log_section_end(fw->results);
> +       }
>
> +       fwts_log_section_end(fw->results);
>        fwts_log_set_owner(fw->results, "fwts");
>
>        return FWTS_OK;
> @@ -694,6 +746,7 @@ void fwts_framework_log(fwts_framework *fw,
>        const char *fmt, ...)
>  {
>        char buffer[4096];
> +       char prefix[256];
>        char *str = fwts_framework_get_env(result);
>
>        if (fmt) {
> @@ -711,21 +764,24 @@ void fwts_framework_log(fwts_framework *fw,
>        switch (result) {
>        case FWTS_FRAMEWORK_ADVICE:
>                fwts_log_nl(fw);
> -               fwts_log_printf(fw->results, LOG_RESULT, level, "%s: %s", str, buffer);
> +               snprintf(prefix, sizeof(prefix), "%s: ", str);
> +               fwts_log_printf(fw->results, LOG_RESULT, level, str, label, prefix, "%s", buffer);
>                fwts_log_nl(fw);
>                break;
>        case FWTS_FRAMEWORK_FAILED:
>                fw->failed_level |= level;
>                fwts_summary_add(fw, fw->current_major_test->name, level, buffer);
> -               fwts_log_printf(fw->results, LOG_RESULT, level, "%s [%s] %s: Test %d, %s",
> -                       str, fwts_log_level_to_str(level), label, fw->current_minor_test_num, buffer);
> +               snprintf(prefix, sizeof(prefix), "%s [%s] %s: Test %d, ",
> +                       str, fwts_log_level_to_str(level), label, fw->current_minor_test_num);
> +               fwts_log_printf(fw->results, LOG_RESULT, level, str, label, prefix, "%s", buffer);
>                break;
>        case FWTS_FRAMEWORK_PASSED:
>        case FWTS_FRAMEWORK_WARNING:
>        case FWTS_FRAMEWORK_SKIPPED:
>        case FWTS_FRAMEWORK_ABORTED:
> -               fwts_log_printf(fw->results, LOG_RESULT, level, "%s: Test %d, %s",
> -                       str, fw->current_minor_test_num, buffer);
> +               snprintf(prefix, sizeof(prefix), "%s: Test %d, ",
> +                       str, fw->current_minor_test_num);
> +               fwts_log_printf(fw->results, LOG_RESULT, level, str, label, prefix, "%s", buffer);
>                break;
>        case FWTS_FRAMEWORK_INFOONLY:
>                break;  /* no-op */
> @@ -979,6 +1035,16 @@ int fwts_framework_options_handler(fwts_framework *fw, int argc, char * const ar
>                case 31: /* --disassemble-aml */
>                        fwts_iasl_disassemble_all_to_file(fw);
>                        return FWTS_COMPLETE;
> +               case 32: /* --log-type */
> +                       if (!strcmp(optarg, "plaintext"))
> +                               fw->log_type = LOG_TYPE_PLAINTEXT;
> +                       else if (!strcmp(optarg, "json"))
> +                               fw->log_type = LOG_TYPE_JSON;
> +                       else {
> +                               fprintf(stderr, "--log-type can be either plaintext or json.\n");
> +                               return FWTS_ERROR;
> +                       }
> +                       break;
>                }
>                break;
>        case 'a': /* --all */
> @@ -1124,8 +1190,9 @@ int fwts_framework_args(const int argc, char **argv)
>
>        /* Results log */
>        if ((fw->results = fwts_log_open("fwts",
> -                       fw->results_logname,
> -                       fw->flags & FWTS_FRAMEWORK_FLAGS_FORCE_CLEAN ? "w" : "a")) == NULL) {
> +                       fw->results_logname,
> +                       fw->flags & FWTS_FRAMEWORK_FLAGS_FORCE_CLEAN ? "w" : "a",
> +                       fw->log_type)) == NULL) {
>                ret = FWTS_ERROR;
>                fprintf(stderr, "%s: Cannot open results log '%s'.\n", argv[0], fw->results_logname);
>                goto tidy_close;
> @@ -1165,10 +1232,16 @@ int fwts_framework_args(const int argc, char **argv)
>                        fwts_list_len(&tests_to_run),
>                        fw->results_logname);
>
> +       fwts_log_section_begin(fw->results, "heading");
>        fwts_framework_heading_info(fw, &tests_to_run);
> +       fwts_log_section_end(fw->results);
> +
> +       fwts_log_section_begin(fw->results, "tests");
>        fwts_framework_tests_run(fw, &tests_to_run);
> +       fwts_log_section_end(fw->results);
>
>        if (fw->print_summary) {
> +               fwts_log_section_begin(fw->results, "summary");
>                fwts_log_set_owner(fw->results, "summary");
>                fwts_log_nl(fw);
>                if (fw->flags & FWTS_FRAMEWORK_FLAGS_LP_TAGS_LOG)
> @@ -1176,6 +1249,7 @@ int fwts_framework_args(const int argc, char **argv)
>                fwts_framework_total_summary(fw);
>                fwts_log_nl(fw);
>                fwts_summary_report(fw, &fwts_framework_test_list);
> +               fwts_log_section_end(fw->results);
>        }
>
>        if (fw->flags & FWTS_FRAMEWORK_FLAGS_LP_TAGS)
> diff --git a/src/lib/src/fwts_log.c b/src/lib/src/fwts_log.c
> index fabfcf5..5331fff 100644
> --- a/src/lib/src/fwts_log.c
> +++ b/src/lib/src/fwts_log.c
> @@ -32,9 +32,9 @@
>
>  static int log_line_width = 0;
>
> -static fwts_log_field fwts_log_filter = ~0;
> +fwts_log_field fwts_log_filter = ~0;
>
> -static char fwts_log_format[256] = "";
> +const char *fwts_log_format = "";
>
>  /*
>  *  fwts_log_set_line_width()
> @@ -59,7 +59,7 @@ int fwts_log_line_number(fwts_log *log)
>  *  fwts_log_field_to_str()
>  *     return string name of log field
>  */
> -static char *fwts_log_field_to_str(const fwts_log_field field)
> +char *fwts_log_field_to_str(const fwts_log_field field)
>  {
>        switch (field & LOG_FIELD_MASK) {
>        case LOG_RESULT:
> @@ -90,6 +90,40 @@ static char *fwts_log_field_to_str(const fwts_log_field field)
>  }
>
>  /*
> + *  fwts_log_field_to_str_full()
> + *     return full string name of log field
> + */
> +char *fwts_log_field_to_str_full(const fwts_log_field field)
> +{
> +       switch (field & LOG_FIELD_MASK) {
> +       case LOG_RESULT:
> +               return "Result";
> +       case LOG_ERROR:
> +               return "Error";
> +       case LOG_WARNING:
> +               return "Warning";
> +       case LOG_DEBUG:
> +               return "Debug";
> +       case LOG_INFO:
> +               return "Info";
> +       case LOG_SUMMARY:
> +               return "Summary";
> +       case LOG_SEPARATOR:
> +               return "Separator";
> +       case LOG_NEWLINE:
> +               return "Newline";
> +       case LOG_ADVICE:
> +               return "Advice";
> +       case LOG_HEADING:
> +               return "Heading";
> +       case LOG_TAG:
> +               return "Tag";
> +       default:
> +               return "Unknown";
> +       }
> +}
> +
> +/*
>  *  fwts_log_str_to_level()
>  *     return log level mapped from the given string
>  */
> @@ -158,7 +192,7 @@ void fwts_log_print_fields(void)
>  *  fwts_log_str_to_field()
>  *     return log field of a given string, 0 if not matching
>  */
> -static fwts_log_field fwts_log_str_to_field(const char *text)
> +fwts_log_field fwts_log_str_to_field(const char *text)
>  {
>        int i;
>
> @@ -238,79 +272,26 @@ void fwts_log_set_field_filter(const char *str)
>  */
>  void fwts_log_set_format(const char *str)
>  {
> -       strncpy(fwts_log_format, str, sizeof(fwts_log_format)-1);
> -       fwts_log_format[sizeof(fwts_log_format)-1]='\0';
> +       fwts_log_format = str;
>  }
>
>  /*
> - *  fwts_log_header()
> - *     format up a tabulated log heading
> - */
> -static int fwts_log_header(fwts_log *log, char *buffer, const int len, const fwts_log_field field, const fwts_log_level level)
> -{
> -       char *ptr;
> -       int n = 0;
> -       struct tm tm;
> -       time_t now;
> -
> -       time(&now);
> -       localtime_r(&now, &tm);
> -
> -       for (ptr = fwts_log_format; *ptr; ) {
> -               if (*ptr == '%') {
> -                       ptr++;
> -                       if (!strncmp(ptr, "line", 4)) {
> -                               n += snprintf(buffer + n, len - n,
> -                                       "%5.5d", log->line_number);
> -                               ptr += 4;
> -                       }
> -                       if (!strncmp(ptr, "date", 4)) {
> -                               n += snprintf(buffer + n, len - n,
> -                                       "%2.2d/%2.2d/%-2.2d",
> -                                       tm.tm_mday, tm.tm_mon + 1, (tm.tm_year+1900) % 100);
> -                               ptr += 4;
> -                       }
> -                       if (!strncmp(ptr, "time", 4)) {
> -                               n += snprintf(buffer + n, len - n,
> -                                       "%2.2d:%2.2d:%2.2d",
> -                                       tm.tm_hour, tm.tm_min, tm.tm_sec);
> -                               ptr += 4;
> -                       }
> -                       if (!strncmp(ptr, "field", 5)) {
> -                               n += snprintf(buffer + n, len - n, "%s",
> -                                       fwts_log_field_to_str(field));
> -                               ptr += 5;
> -                       }
> -                       if (!strncmp(ptr, "level", 5)) {
> -                               n += snprintf(buffer + n, len - n, "%1.1s",
> -                                       fwts_log_level_to_str(level));
> -                               ptr += 5;
> -                       }
> -                       if (!strncmp(ptr,"owner", 5) && log->owner) {
> -                               n += snprintf(buffer + n, len - n, "%-15.15s", log->owner);
> -                               ptr += 5;
> -                       }
> -               }
> -               else {
> -                       n += snprintf(buffer+n, len-n, "%c", *ptr);
> -                       ptr++;
> -               }
> -       }
> -       return n;
> -}
> -
> -
> -/*
>  *  fwts_log_vprintf()
>  *     printf to a log
>  */
> -int fwts_log_printf(fwts_log *log, const fwts_log_field field, const fwts_log_level level, const char *fmt, ...)
> +int fwts_log_printf(fwts_log *log,
> +       const fwts_log_field field,
> +       const fwts_log_level level,
> +       const char *status,
> +       const char *label,
> +       const char *prefix,
> +       const char *fmt, ...)
>  {
>        va_list args;
>        int ret;
>
>        va_start(args, fmt);
> -       ret = fwts_log_vprintf(log, field, level, fmt, args);
> +       ret = fwts_log_vprintf(log, field, level, status, label, prefix, fmt, args);
>        va_end(args);
>
>        return ret;
> @@ -320,53 +301,23 @@ int fwts_log_printf(fwts_log *log, const fwts_log_field field, const fwts_log_le
>  *  fwts_log_vprintf()
>  *     vprintf to a log
>  */
> -int fwts_log_vprintf(fwts_log *log, const fwts_log_field field, const fwts_log_level level, const char *fmt, va_list args)
> +int fwts_log_vprintf(fwts_log *log,
> +       const fwts_log_field field,
> +       const fwts_log_level level,
> +       const char *status,
> +       const char *label,
> +       const char *prefix,
> +       const char *fmt,
> +       va_list args)
>  {
> -       char buffer[4096];
> -       int n = 0;
> -       int len = 0;
> -
> -       fwts_list *lines;
> -       fwts_list_link *item;
> -
> -       if ((!log) || (log && log->magic != LOG_MAGIC))
> -               return 0;
> -
>        if (!((field & LOG_FIELD_MASK) & fwts_log_filter))
>                return 0;
>
> -       /* This is a pain, we neen to find out how big the leading log
> -          message is, so format one up. */
> -       n = fwts_log_header(log, buffer, sizeof(buffer), field, level);
> -
> -       vsnprintf(buffer+n, sizeof(buffer)-n, fmt, args);
> -
> -       /* Break text into multi-lines if necessary */
> -       if (field & LOG_VERBATUM)
> -               lines = fwts_list_from_text(buffer+n);
> +       if (log && log->magic == LOG_MAGIC &&
> +           log->ops && log->ops->underline)
> +               return log->ops->vprintf(log, field, level, status, label, prefix, fmt, args);
>        else
> -               lines = fwts_format_text(buffer+n, log->line_width-n);
> -
> -       len = n;
> -
> -       fwts_list_foreach(item, lines) {
> -               char *text = fwts_text_list_text(item);
> -
> -               if (!(field & LOG_NO_FIELDS)) {
> -                       /* Re-format up a log heading with current line number which
> -                          may increment with multiple line log messages */
> -                       fwts_log_header(log, buffer, sizeof(buffer), field, level);
> -                       fwrite(buffer, 1, n, log->fp);
> -               }
> -               fwrite(text, 1, strlen(text), log->fp);
> -               fwrite("\n", 1, 1, log->fp);
> -               fflush(log->fp);
> -               log->line_number++;
> -               len += strlen(text) + 1;
> -       }
> -       fwts_text_list_free(lines);
> -
> -       return len;
> +               return 0;
>  }
>
>  /*
> @@ -375,33 +326,9 @@ int fwts_log_vprintf(fwts_log *log, const fwts_log_field field, const fwts_log_l
>  */
>  void fwts_log_underline(fwts_log *log, const int ch)
>  {
> -       int n;
> -       char *buffer;
> -       size_t width;
> -
> -       if (!log || (log->magic != LOG_MAGIC))
> -               return;
> -
> -       if (!((LOG_SEPARATOR & LOG_FIELD_MASK) & fwts_log_filter))
> -               return;
> -
> -       width = log->line_width + 1;
> -
> -       buffer = calloc(1, width);
> -       if (!buffer)
> -               return; /* Unlikely, and just abort */
> -
> -       /* Write in leading optional line prefix */
> -       n = fwts_log_header(log, buffer, width, LOG_SEPARATOR, LOG_LEVEL_NONE);
> -
> -       memset(buffer + n, ch, width  - n);
> -       buffer[width - 1] = '\n';
> -
> -       fwrite(buffer, 1, width, log->fp);
> -       fflush(log->fp);
> -       log->line_number++;
> -
> -       free(buffer);
> +       if (log && log->magic == LOG_MAGIC &&
> +           log->ops && log->ops->underline)
> +               log->ops->underline(log, ch);
>  }
>
>  /*
> @@ -410,11 +337,9 @@ void fwts_log_underline(fwts_log *log, const int ch)
>  */
>  void fwts_log_newline(fwts_log *log)
>  {
> -       if (log && (log->magic == LOG_MAGIC)) {
> -               fwrite("\n", 1, 1, log->fp);
> -               fflush(log->fp);
> -               log->line_number++;
> -       }
> +       if (log && log->magic == LOG_MAGIC &&
> +           log->ops && log->ops->underline)
> +               log->ops->newline(log);
>  }
>
>  int fwts_log_set_owner(fwts_log *log, const char *owner)
> @@ -431,12 +356,26 @@ int fwts_log_set_owner(fwts_log *log, const char *owner)
>        return FWTS_ERROR;
>  }
>
> +void fwts_log_section_begin(fwts_log *log, const char *name)
> +{
> +       if (log && log->magic == LOG_MAGIC &&
> +           log->ops && log->ops->section_begin)
> +               log->ops->section_begin(log, name);
> +}
> +
> +void fwts_log_section_end(fwts_log *log)
> +{
> +       if (log && log->magic == LOG_MAGIC &&
> +           log->ops && log->ops->section_end)
> +               log->ops->section_end(log);
> +}
> +
>  /*
>  *  fwts_log_open()
>  *     open a log file. if name is stderr or stdout, then attach log to these
>  *     streams.
>  */
> -fwts_log *fwts_log_open(const char *owner, const char *name, const char *mode)
> +fwts_log *fwts_log_open(const char *owner, const char *name, const char *mode, fwts_log_type type)
>  {
>        fwts_log *newlog;
>
> @@ -444,6 +383,18 @@ fwts_log *fwts_log_open(const char *owner, const char *name, const char *mode)
>                return NULL;
>
>        newlog->magic = LOG_MAGIC;
> +       switch (type) {
> +       case LOG_TYPE_JSON:
> +               newlog->ops = &fwts_log_json_ops;
> +               break;
> +       case LOG_TYPE_PLAINTEXT:
> +               newlog->ops = &fwts_log_plaintext_ops;
> +               break;
> +       case LOG_TYPE_NONE:
> +       default:
> +               newlog->ops = &fwts_log_plaintext_ops;
> +               break;
> +       }
>
>        if (owner) {
>                if ((newlog->owner = calloc(1, strlen(owner)+1)) == NULL) {
> @@ -469,6 +420,9 @@ fwts_log *fwts_log_open(const char *owner, const char *name, const char *mode)
>                newlog->line_width = fwts_tty_width(fileno(newlog->fp), LOG_LINE_WIDTH);
>        }
>
> +       if (newlog->ops && newlog->ops->open)
> +               newlog->ops->open(newlog);
> +
>        return newlog;
>  }
>
> @@ -479,6 +433,9 @@ fwts_log *fwts_log_open(const char *owner, const char *name, const char *mode)
>  int fwts_log_close(fwts_log *log)
>  {
>        if (log && (log->magic == LOG_MAGIC)) {
> +               if (log->ops && log->ops->close)
> +                       log->ops->close(log);
> +
>                if (log->fp && (log->fp != stdout && log->fp != stderr))
>                        fclose(log->fp);
>                if (log->owner)
> diff --git a/src/lib/src/fwts_log_json.c b/src/lib/src/fwts_log_json.c
> new file mode 100644
> index 0000000..208847c
> --- /dev/null
> +++ b/src/lib/src/fwts_log_json.c
> @@ -0,0 +1,185 @@
> +/*
> + * Copyright (C) 2010-2012 Canonical
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * as published by the Free Software Foundation; either version 2
> + * of the License, or (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
> + *
> + */
> +#define LOG_LINE_WIDTH 100
> +
> +#include <stdlib.h>
> +#include <stdio.h>
> +#include <stdarg.h>
> +#include <string.h>
> +#include <unistd.h>
> +#include <sys/ioctl.h>
> +#include <time.h>
> +
> +#include <json/json.h>
> +#include "fwts.h"
> +
> +#define MAX_JSON_STACK (64)
> +
> +typedef struct {
> +       json_object     *obj;
> +       json_object     *log;
> +} fwts_log_json_stack_t;
> +
> +static fwts_log_json_stack_t json_stack[MAX_JSON_STACK];
> +static int json_stack_index = 0;
> +
> +/*
> + *  fwts_log_vprintf_json()
> + *     vprintf to a log
> + */
> +static int fwts_log_vprintf_json(fwts_log *log,
> +       const fwts_log_field field,
> +       const fwts_log_level level,
> +       const char *status,
> +       const char *label,
> +       const char *prefix,
> +       const char *fmt,
> +       va_list args)
> +{
> +       char buffer[4096];
> +       struct tm tm;
> +       time_t now;
> +       json_object *header;
> +       json_object *json_log = (json_object*)json_stack[json_stack_index-1].log;
> +       char *str;
> +
> +       if (!((field & LOG_FIELD_MASK) & fwts_log_filter))
> +               return 0;
> +
> +       if (field & (LOG_NEWLINE | LOG_SEPARATOR | LOG_DEBUG))
> +               return 0;
> +
> +       time(&now);
> +       localtime_r(&now, &tm);
> +
> +       header = json_object_new_object();
> +       json_object_object_add(header, "line_num", json_object_new_int(log->line_number));
> +       snprintf(buffer, sizeof(buffer), "%2.2d/%2.2d/%-2.2d",
> +               tm.tm_mday, tm.tm_mon + 1, (tm.tm_year+1900) % 100);
> +       json_object_object_add(header, "date", json_object_new_string(buffer));
> +       snprintf(buffer, sizeof(buffer), "%2.2d:%2.2d:%2.2d",
> +               tm.tm_hour, tm.tm_min, tm.tm_sec);
> +       json_object_object_add(header, "time", json_object_new_string(buffer));
> +       json_object_object_add(header, "field_type",
> +               json_object_new_string(fwts_log_field_to_str_full(field)));
> +
> +       str = fwts_log_level_to_str(level);
> +       if (!strcmp(str, " "))
> +               str = "None";
> +       json_object_object_add(header, "level",
> +               json_object_new_string(str));
> +
> +       json_object_object_add(header, "status", json_object_new_string(*status ? status : "None"));
> +       json_object_object_add(header, "failure_label", json_object_new_string(label && *label ? label : "None"));
> +
> +       /* Redundant really
> +       json_object_object_add(header, "owner",
> +               json_object_new_string(log->owner));
> +       */
> +       vsnprintf(buffer, sizeof(buffer), fmt, args);
> +       json_object_object_add(header, "log_text", json_object_new_string(buffer));
> +
> +       json_object_array_add(json_log, header);
> +
> +       log->line_number++;
> +
> +       return 0;
> +}
> +
> +/*
> + *  fwts_log_underline_json()
> + *     write an underline across log, using character ch as the underline
> + */
> +static void fwts_log_underline_json(fwts_log *log, const int ch)
> +{
> +       /* No-op for json */
> +}
> +
> +/*
> + *  fwts_log_newline()
> + *     write newline to log
> + */
> +static void fwts_log_newline_json(fwts_log *log)
> +{
> +       /* No-op for json */
> +}
> +
> +static void fwts_log_section_begin_json(fwts_log *log, const char *name)
> +{
> +       json_object *json_obj;
> +       json_object *json_log;
> +
> +       json_obj = json_object_new_object();
> +       json_log = json_object_new_array();
> +       json_object_object_add(json_obj, name, json_log);
> +
> +       json_stack[json_stack_index].obj = json_obj;
> +       json_stack[json_stack_index].log = json_log;
> +
> +       if (json_stack_index > 0)
> +               json_object_array_add(json_stack[json_stack_index-1].log, json_obj);
> +
> +       if (json_stack_index < MAX_JSON_STACK)
> +               json_stack_index++;
> +       else  {
> +               fprintf(stderr, "json log stack overflow pushing section %s.\n", name);
> +               exit(EXIT_FAILURE);
> +       }
> +}
> +
> +static void fwts_log_section_end_json(fwts_log *log)
> +{
> +       if (json_stack_index > 0)
> +               json_stack_index--;
> +       else {
> +               fprintf(stderr, "json log stack underflow.\n");
> +               exit(EXIT_FAILURE);
> +       }
> +}
> +
> +static void fwts_log_open_json(fwts_log *log)
> +{
> +       fwts_log_section_begin_json(log, "fwts");
> +}
> +
> +static void fwts_log_close_json(fwts_log *log)
> +{
> +       const char *str;
> +       size_t len;
> +
> +       fwts_log_section_end_json(log);
> +
> +       str = json_object_to_json_string(json_stack[0].obj);
> +       len = strlen(str);
> +
> +       fwrite(str, 1, len, log->fp);
> +       fwrite("\n", 1, 1, log->fp);
> +       fflush(log->fp);
> +       json_object_put(json_stack[0].obj);
> +}
> +
> +fwts_log_ops fwts_log_json_ops = {
> +       .vprintf =       fwts_log_vprintf_json,
> +       .underline =     fwts_log_underline_json,
> +       .newline =       fwts_log_newline_json,
> +       .section_begin = fwts_log_section_begin_json,
> +       .section_end   = fwts_log_section_end_json,
> +       .open          = fwts_log_open_json,
> +       .close         = fwts_log_close_json
> +};
> diff --git a/src/lib/src/fwts_log_plaintext.c b/src/lib/src/fwts_log_plaintext.c
> new file mode 100644
> index 0000000..44c443f
> --- /dev/null
> +++ b/src/lib/src/fwts_log_plaintext.c
> @@ -0,0 +1,195 @@
> +/*
> + * Copyright (C) 2010-2012 Canonical
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * as published by the Free Software Foundation; either version 2
> + * of the License, or (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
> + *
> + */
> +#define LOG_LINE_WIDTH 100
> +
> +#include <stdlib.h>
> +#include <stdio.h>
> +#include <stdarg.h>
> +#include <string.h>
> +#include <unistd.h>
> +#include <sys/ioctl.h>
> +#include <time.h>
> +
> +#include "fwts.h"
> +
> +/*
> + *  fwts_log_header_plaintext()
> + *     format up a tabulated log heading
> + */
> +static int fwts_log_header_plaintext(fwts_log *log,
> +       char *buffer,
> +       const int len,
> +       const fwts_log_field field,
> +       const fwts_log_level level)
> +{
> +       const char *ptr;
> +       int n = 0;
> +       struct tm tm;
> +       time_t now;
> +
> +       time(&now);
> +       localtime_r(&now, &tm);
> +
> +       for (ptr = fwts_log_format; *ptr; ) {
> +               if (*ptr == '%') {
> +                       ptr++;
> +                       if (!strncmp(ptr, "line", 4)) {
> +                               n += snprintf(buffer + n, len - n,
> +                                       "%5.5d", log->line_number);
> +                               ptr += 4;
> +                       }
> +                       if (!strncmp(ptr, "date", 4)) {
> +                               n += snprintf(buffer + n, len - n,
> +                                       "%2.2d/%2.2d/%-2.2d",
> +                                       tm.tm_mday, tm.tm_mon + 1, (tm.tm_year+1900) % 100);
> +                               ptr += 4;
> +                       }
> +                       if (!strncmp(ptr, "time", 4)) {
> +                               n += snprintf(buffer + n, len - n,
> +                                       "%2.2d:%2.2d:%2.2d",
> +                                       tm.tm_hour, tm.tm_min, tm.tm_sec);
> +                               ptr += 4;
> +                       }
> +                       if (!strncmp(ptr, "field", 5)) {
> +                               n += snprintf(buffer + n, len - n, "%s",
> +                                       fwts_log_field_to_str(field));
> +                               ptr += 5;
> +                       }
> +                       if (!strncmp(ptr, "level", 5)) {
> +                               n += snprintf(buffer + n, len - n, "%1.1s",
> +                                       fwts_log_level_to_str(level));
> +                               ptr += 5;
> +                       }
> +                       if (!strncmp(ptr,"owner", 5) && log->owner) {
> +                               n += snprintf(buffer + n, len - n, "%-15.15s", log->owner);
> +                               ptr += 5;
> +                       }
> +               } else {
> +                       n += snprintf(buffer+n, len-n, "%c", *ptr);
> +                       ptr++;
> +               }
> +       }
> +       return n;
> +}
> +
> +
> +/*
> + *  fwts_log_vprintf()
> + *     vprintf to a log
> + */
> +static int fwts_log_vprintf_plaintext(fwts_log *log,
> +       const fwts_log_field field,
> +       const fwts_log_level level,
> +       const char *status,     /* Ignored */
> +       const char *label,      /* Ignored */
> +       const char *prefix,
> +       const char *fmt,
> +       va_list args)
> +{
> +       char buffer[4096];
> +       int n = 0;
> +       int header_len;
> +       int len = 0;
> +
> +       fwts_list *lines;
> +       fwts_list_link *item;
> +
> +       if (!((field & LOG_FIELD_MASK) & fwts_log_filter))
> +               return 0;
> +
> +       /* This is a pain, we neen to find out how big the leading log
> +          message is, so format one up. */
> +       n = header_len = fwts_log_header_plaintext(log, buffer, sizeof(buffer), field, level);
> +       n += snprintf(buffer + n, sizeof(buffer) - n, "%s", prefix);
> +       n += vsnprintf(buffer + n, sizeof(buffer) - n, fmt, args);
> +
> +       /* Break text into multi-lines if necessary */
> +       if (field & LOG_VERBATUM)
> +               lines = fwts_list_from_text(buffer + header_len);
> +       else
> +               lines = fwts_format_text(buffer + header_len, log->line_width - header_len);
> +
> +       len = n;
> +
> +       fwts_list_foreach(item, lines) {
> +               char *text = fwts_text_list_text(item);
> +
> +               if (!(field & LOG_NO_FIELDS)) {
> +                       /* Re-format up a log heading with current line number which
> +                          may increment with multiple line log messages */
> +                       fwts_log_header_plaintext(log, buffer, sizeof(buffer), field, level);
> +                       fwrite(buffer, 1, header_len, log->fp);
> +               }
> +               fwrite(text, 1, strlen(text), log->fp);
> +               fwrite("\n", 1, 1, log->fp);
> +               fflush(log->fp);
> +               log->line_number++;
> +               len += strlen(text) + 1;
> +       }
> +       fwts_text_list_free(lines);
> +
> +       return len;
> +}
> +
> +/*
> + *  fwts_log_underline_plaintext()
> + *     write an underline across log, using character ch as the underline
> + */
> +static void fwts_log_underline_plaintext(fwts_log *log, const int ch)
> +{
> +       int n;
> +       char *buffer;
> +       size_t width = log->line_width + 1;
> +
> +       if (!((LOG_SEPARATOR & LOG_FIELD_MASK) & fwts_log_filter))
> +               return;
> +
> +       buffer = calloc(1, width);
> +       if (!buffer)
> +               return; /* Unlikely, and just abort */
> +
> +       /* Write in leading optional line prefix */
> +       n = fwts_log_header_plaintext(log, buffer, width, LOG_SEPARATOR, LOG_LEVEL_NONE);
> +
> +       memset(buffer + n, ch, width  - n);
> +       buffer[width - 1] = '\n';
> +
> +       fwrite(buffer, 1, width, log->fp);
> +       fflush(log->fp);
> +       log->line_number++;
> +
> +       free(buffer);
> +}
> +
> +/*
> + *  fwts_log_newline_plaintext()
> + *     write newline to log
> + */
> +static void fwts_log_newline_plaintext(fwts_log *log)
> +{
> +       fwrite("\n", 1, 1, log->fp);
> +       fflush(log->fp);
> +       log->line_number++;
> +}
> +
> +fwts_log_ops fwts_log_plaintext_ops = {
> +       .vprintf =      fwts_log_vprintf_plaintext,
> +       .underline =    fwts_log_underline_plaintext,
> +       .newline =      fwts_log_newline_plaintext
> +};
> diff --git a/src/lib/src/fwts_summary.c b/src/lib/src/fwts_summary.c
> index 192043d..38d6a79 100644
> --- a/src/lib/src/fwts_summary.c
> +++ b/src/lib/src/fwts_summary.c
> @@ -210,34 +210,38 @@ int fwts_summary_report(fwts_framework *fw, fwts_list *test_list)
>        fwts_list_link *item;
>
>        fwts_log_summary(fw, "Test Failure Summary");
> -       fwts_log_summary(fw, "====================");
> +       fwts_log_underline(fw->results, '=');
>        fwts_log_nl(fw);
>
>        for (i=0;i<SUMMARY_MAX;i++) {
> +               fwts_log_section_begin(fw->results, "failure");
> +
>                if (fwts_summaries[i]->len) {
>                        fwts_list_link *item;
>                        fwts_log_summary(fw, "%s failures: %d", summary_names[i], fwts_summaries[i]->len);
>
> +                       fwts_log_section_begin(fw->results, "failures");
>                        fwts_list_foreach(item, fwts_summaries[i]) {
>                                fwts_summary_item *summary_item = fwts_list_data(fwts_summary_item *,item);
>                                char *lines = fwts_summary_lines(&summary_item->log_lines);
> -                               fwts_log_summary(fw, " %s test, at %d log line%s: %s",
> +                               fwts_log_summary(fw, " %s test, at %d log line%s: %s: %s",
>                                        summary_item->test,
>                                        fwts_list_len(&summary_item->log_lines),
>                                        fwts_list_len(&summary_item->log_lines) > 1 ? "s" : "",
> -                                       lines);
> -                               free(lines);
> -                               fwts_log_summary_verbatum(fw, "  \"%s\"",
> +                                       lines,
>                                        summary_item->text);
> +                               free(lines);
>                        }
> +                       fwts_log_section_end(fw->results);
>                }
>                else
>                        fwts_log_summary(fw, "%s failures: NONE", summary_names[i]);
>
> +               fwts_log_section_end(fw->results);
>                fwts_log_nl(fw);
>        }
>
> -       if (fw->total_run > 0) {
> +       if (fw->log_type == LOG_TYPE_PLAINTEXT && fw->total_run > 0) {
>                sorted = fwts_list_new();
>                fwts_list_foreach(item, test_list)
>                        fwts_list_add_ordered(sorted, fwts_list_data(fwts_framework_test *,item), fwts_framework_compare_test_name);
> diff --git a/src/lib/src/fwts_tag.c b/src/lib/src/fwts_tag.c
> index c1ba33d..38d1449 100644
> --- a/src/lib/src/fwts_tag.c
> +++ b/src/lib/src/fwts_tag.c
> @@ -180,7 +180,7 @@ void fwts_tag_report(fwts_framework *fw, const fwts_log_field field, fwts_list *
>        if ((taglist != NULL) && (fwts_list_len(taglist) > 0)) {
>                char *tags = fwts_tag_list_to_str(taglist);
>                if (tags) {
> -                       fwts_log_printf(fw->results, field | LOG_VERBATUM, LOG_LEVEL_NONE, "Tags: %s", tags);
> +                       fwts_log_printf(fw->results, field | LOG_VERBATUM, LOG_LEVEL_NONE, "", "", "", "Tags: %s", tags);
>                        free(tags);
>                }
>        }
> --
> 1.7.10
>
Acked-by: Keng-Yu Lin <kengyu at canonical.com>




More information about the fwts-devel mailing list