diff mbox

[1/3] lib: rework logging and framework to allow for multiple output logs

Message ID 1339408838-31877-2-git-send-email-colin.king@canonical.com
State Accepted
Headers show

Commit Message

Colin Ian King June 11, 2012, 10 a.m. UTC
From: Colin Ian King <colin.king@canonical.com>

Since we now can output many different log types we should also
allow for multiple log types to be written during a run.  This involves
some considerable re-working of the logging engine.

1. The fw->log_type is now a  bit map of log_types
2. We add a list of log types to be written to fwts_log, this is a list
   of fwts_log_file types.
3. We need to re-work the log name handling so that we can open multiple
   log files with different suffixes depending on the log type.
4. To reduce the amount of vsnprintf() of the formatted log output we
   now handle this in the log fwts_log_printf() and pass down the formatted
   output to the different logging handlers rather than keep on re-formatting
   at the lowest logging handler layer.

There are a lot of changing is this patch. I tried to break it down, but
since there are so many interdependant changes I had to resort to one big
patch

Signed-off-by: Colin Ian King <colin.king@canonical.com>
---
 src/lib/include/fwts_log.h       |   66 +++++--
 src/lib/src/fwts_framework.c     |   94 +++++-----
 src/lib/src/fwts_log.c           |  363 +++++++++++++++++++++++++++++---------
 src/lib/src/fwts_log_html.c      |  133 +++++++-------
 src/lib/src/fwts_log_json.c      |   52 +++---
 src/lib/src/fwts_log_plaintext.c |   63 ++++---
 src/lib/src/fwts_log_xml.c       |   72 ++++----
 7 files changed, 527 insertions(+), 316 deletions(-)

Comments

Chris Van Hoof June 11, 2012, 3:53 p.m. UTC | #1
On 06/11/2012 06:00 AM, Colin King wrote:
> From: Colin Ian King <colin.king@canonical.com>
> 
> Since we now can output many different log types we should also
> allow for multiple log types to be written during a run.  This involves
> some considerable re-working of the logging engine.
> 
> 1. The fw->log_type is now a  bit map of log_types
> 2. We add a list of log types to be written to fwts_log, this is a list
>    of fwts_log_file types.
> 3. We need to re-work the log name handling so that we can open multiple
>    log files with different suffixes depending on the log type.
> 4. To reduce the amount of vsnprintf() of the formatted log output we
>    now handle this in the log fwts_log_printf() and pass down the formatted
>    output to the different logging handlers rather than keep on re-formatting
>    at the lowest logging handler layer.
> 
> There are a lot of changing is this patch. I tried to break it down, but
> since there are so many interdependant changes I had to resort to one big
> patch
> 
> Signed-off-by: Colin Ian King <colin.king@canonical.com>
> ---
>  src/lib/include/fwts_log.h       |   66 +++++--
>  src/lib/src/fwts_framework.c     |   94 +++++-----
>  src/lib/src/fwts_log.c           |  363 +++++++++++++++++++++++++++++---------
>  src/lib/src/fwts_log_html.c      |  133 +++++++-------
>  src/lib/src/fwts_log_json.c      |   52 +++---
>  src/lib/src/fwts_log_plaintext.c |   63 ++++---
>  src/lib/src/fwts_log_xml.c       |   72 ++++----
>  7 files changed, 527 insertions(+), 316 deletions(-)
> 
> diff --git a/src/lib/include/fwts_log.h b/src/lib/include/fwts_log.h
> index a659baa..513bf88 100644
> --- a/src/lib/include/fwts_log.h
> +++ b/src/lib/include/fwts_log.h
> @@ -23,7 +23,10 @@
>  #include <stdio.h>
>  #include <stdarg.h>
>  
> -#define LOG_MAGIC	0xfe23ab13
> +#include "fwts_list.h"
> +
> +#define LOG_MAGIC		(0xfe23ab13)
> +#define LOG_MAX_BUF_SIZE	(4096)		/* Max output per log line */
>  
>  typedef enum {
>  	LOG_RESULT	    = 0x00000001,
> @@ -58,31 +61,56 @@ typedef enum {
>  	LOG_LEVEL_INFO      = 0x00000010,
>  } fwts_log_level;
>  
> +/*
> + *  different types of log file
> + */
>  typedef enum {
>  	LOG_TYPE_NONE       = 0x00000000,
>  	LOG_TYPE_PLAINTEXT  = 0x00000001,
>  	LOG_TYPE_JSON       = 0x00000002,
> -	LOG_TYPE_XML        = 0x00000003,
> -	LOG_TYPE_HTML       = 0x00000004,
> +	LOG_TYPE_XML        = 0x00000004,
> +	LOG_TYPE_HTML       = 0x00000008,
>  } fwts_log_type;
>  
> +/*
> + *   different types of output log
> + */
> +typedef enum {
> +	LOG_FILENAME_TYPE_STDOUT = 0x00000001,	/* log output to stdout */
> +	LOG_FILENAME_TYPE_STDERR = 0x00000002,	/* log output to stderr */
> +	LOG_FILENAME_TYPE_FILE   = 0x00000003,	/* log output to a file */
> +} fwts_log_filename_type;
> +
> +/*
> + *  top level log descriptor
> + */
>  typedef struct log_t {
> -	unsigned int magic;
> -	FILE *fp;
> -	char *owner;
> -	int line_width;
> -	int line_number;
> -	struct fwts_log_ops_t *ops;
> +	unsigned int magic;			/* magic ID of the log */
> +	fwts_list log_files;			/* list of fwts_log_file */
> +	int line_number;			/* keeps track of the line numbering */
> +	char *owner;				/* who is writing to this log */
>  } fwts_log;
>  
> +/*
> + *  info for a specific log type
> + */
> +typedef struct {
> +	FILE *fp;				/* file descriptor for log */
> +	fwts_log *log;				/* parent log struct */
> +	fwts_log_type type;			/* log type */
> +	fwts_log_filename_type filename_type;	/* log filename type */
> +	struct fwts_log_ops_t *ops;		/* log operators */
> +	int line_width;				/* width of log in chars */
> +} fwts_log_file;
> +
>  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 *);
> +	int (*print)(fwts_log_file *log_file, const fwts_log_field field, const fwts_log_level level, const char *status, const char *label, const char *prefix, const char *buffer);
> +	void (*underline)(fwts_log_file *log_file, int ch);
> +	void (*newline)(fwts_log_file *log_file);
> +	void (*section_begin)(fwts_log_file *log_file, const char *tag);
> +	void (*section_end)(fwts_log_file *log_file);
> +	void (*open)(fwts_log_file *log_file);
> +	void (*close)(fwts_log_file *log_file);
>  } fwts_log_ops;
>  
>  extern fwts_log_ops fwts_log_plaintext_ops;
> @@ -116,6 +144,12 @@ 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);
> +fwts_log_filename_type fwts_log_get_filename_type(const char *name);
> +
> +static inline int fwts_log_type_count(fwts_log_type type)
> +{
> +	return __builtin_popcount(type);
> +}
>  
>  #define fwts_log_result(fw, fmt, args...)	\
>  	fwts_log_printf(fw->results, LOG_RESULT, LOG_LEVEL_NONE, "", "", "", fmt, ## args)
> diff --git a/src/lib/src/fwts_framework.c b/src/lib/src/fwts_framework.c
> index f808d8e..a6c2ba2 100644
> --- a/src/lib/src/fwts_framework.c
> +++ b/src/lib/src/fwts_framework.c
> @@ -28,7 +28,8 @@
>  
>  #include "fwts.h"
>  
> -#define RESULTS_LOG	"results.log"
> +/* Suffix ".log", ".xml", etc gets automatically appended */
> +#define RESULTS_LOG	"results"
>  
>  #define FWTS_RUN_ALL_FLAGS		\
>  	(FWTS_BATCH |			\
> @@ -96,34 +97,6 @@ static const char *fwts_copyright[] = {
>  };
>  
>  /*
> - *  fwts_framework_log_suffix()
> - *	set the log name suffix
> - */
> -static void fwts_framework_log_suffix(fwts_framework *fw, const char *suffix)
> -{
> -	char *ptr;
> -	char *new;
> -	size_t len;
> -
> -	/* Locate old suffix and kill it */
> -	ptr = rindex(fw->results_logname, '.');
> -	if (ptr != NULL)
> -		*ptr = '\0';
> -
> -	/* Space for old log name sans old suffix + new suffix + '.' + '\0' */
> -	len = strlen(fw->results_logname) + strlen(suffix) + 2;
> -
> -	if ((new = calloc(len, 1)) == NULL) {
> -		fprintf(stderr, "Cannot allocate log name.\n");
> -		exit(EXIT_FAILURE);
> -	}
> -
> -	snprintf(new, len, "%s.%s", fw->results_logname, suffix);
> -	free(fw->results_logname);
> -	fw->results_logname = new;
> -}
> -
> -/*
>   *  fwts_framework_compare_priority()
>   *	used to register tests sorted on run priority
>   */
> @@ -677,7 +650,7 @@ static fwts_framework_test *fwts_framework_test_find(fwts_framework *fw, const c
>   *  fwts_framework_log()
>   *	log a test result
>   */
> -void fwts_framework_log(fwts_framework *fw, 
> +void fwts_framework_log(fwts_framework *fw,
>  	fwts_log_field field,
>  	const char *label,
>  	fwts_log_level level,
> @@ -860,6 +833,39 @@ static int fwts_framework_skip_test_parse(fwts_framework *fw, const char *arg, f
>  	return FWTS_OK;
>  }
>  
> +/*
> + *  fwts_framework_log_type_parse()
> + *	parse optarg of comma separated log types
> + */
> +static int fwts_framework_log_type_parse(fwts_framework *fw, const char *arg)
> +{
> +	char *str;
> +	char *token;
> +	char *saveptr = NULL;
> +
> +	fw->log_type = 0;
> +
> +	for (str = (char*)arg; (token = strtok_r(str, ",", &saveptr)) != NULL; str = NULL) {
> +		if (!strcmp(token, "plaintext"))
> +			fw->log_type |= LOG_TYPE_PLAINTEXT;
> +		else if (!strcmp(token, "json"))
> +			fw->log_type |= LOG_TYPE_JSON;
> +		else if (!strcmp(token, "xml"))
> +			fw->log_type |= LOG_TYPE_XML;
> +		else if (!strcmp(token, "html"))
> +			fw->log_type |= LOG_TYPE_HTML;
> +		else {
> +			fprintf(stderr, "--log-type can be plaintext, xml, html or json.\n");
> +			return FWTS_ERROR;
> +		}
> +	}
> +
> +	if (!fw->log_type)
> +		fw->log_type = LOG_TYPE_PLAINTEXT;
> +
> +	return FWTS_OK;
> +}
> +
>  int fwts_framework_options_handler(fwts_framework *fw, int argc, char * const argv[], int option_char, int long_index)
>  {
>  	switch (option_char) {
> @@ -975,22 +981,8 @@ int fwts_framework_options_handler(fwts_framework *fw, int argc, char * const ar
>  			fwts_iasl_disassemble_all_to_file(fw);
>  			return FWTS_COMPLETE;
>  		case 32: /* --log-type */
> -			if (!strcmp(optarg, "plaintext")) {
> -				fw->log_type = LOG_TYPE_PLAINTEXT;
> -				fwts_framework_log_suffix(fw, "log");
> -			} else if (!strcmp(optarg, "json")) {
> -				fw->log_type = LOG_TYPE_JSON;
> -				fwts_framework_log_suffix(fw, "json");
> -			} else if (!strcmp(optarg, "xml")) {
> -				fw->log_type = LOG_TYPE_XML;
> -				fwts_framework_log_suffix(fw, "xml");
> -			} else if (!strcmp(optarg, "html")) {
> -				fw->log_type = LOG_TYPE_HTML;
> -				fwts_framework_log_suffix(fw, "html");
> -			} else {
> -				fprintf(stderr, "--log-type can be either plaintext, xml, html or json.\n");
> -				return FWTS_ERROR;
> -			}
> +			fwts_framework_log_type_parse(fw, optarg);
> +			/* FIX ME - check return */
>  			break;
>  		}
>  		break;
> @@ -1136,6 +1128,16 @@ int fwts_framework_args(const int argc, char **argv)
>  		goto tidy_close;
>  	}
>  
> +	/* Ensure we have just one log type specified for non-filename logging */
> +	if (fwts_log_type_count(fw->log_type) > 1 &&
> +	    fwts_log_get_filename_type(fw->results_logname) != LOG_FILENAME_TYPE_FILE) {
> +		fprintf(stderr,
> +			"Cannot specify more than one log type when "
> +			"logging to stderr or stdout\n");
> +		ret = FWTS_ERROR;
> +		goto tidy_close;
> +	}
> +
>  	/* Results log */
>  	if ((fw->results = fwts_log_open("fwts",
>  			fw->results_logname,	
> diff --git a/src/lib/src/fwts_log.c b/src/lib/src/fwts_log.c
> index fea7c41..3ab7930 100644
> --- a/src/lib/src/fwts_log.c
> +++ b/src/lib/src/fwts_log.c
> @@ -315,48 +315,125 @@ void fwts_log_set_format(const char *str)
>  }
>  
>  /*
> - *  fwts_log_vprintf()
> - *	printf to a log
> + *  fwts_log_type_filename_suffix()
> + *	return a filename suffix on a given log type
>   */
> -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, ...)
> +static char *fwts_log_type_filename_suffix(fwts_log_type type)
>  {
> -	va_list	args;
> -	int ret;
> +	switch (type) {
> +	case LOG_TYPE_JSON:
> +		return ".json";
> +	case LOG_TYPE_XML:
> +		return ".xml";
> +	case LOG_TYPE_HTML:
> +		return ".html";
> +	case LOG_TYPE_NONE:
> +	case LOG_TYPE_PLAINTEXT:
> +	default:
> +		return ".log";
> +	}
> +}
> +
> +/*
> + *  fwts_log_filename_new_suffix()
> + *	return the log name with suffix based on log type
> + */
> +static char *fwts_log_filename(const char *filename, fwts_log_type type)
> +{
> +	char *ptr;
> +	char *new_name;
> +	char *suffix;
> +	size_t suffix_len;
> +	size_t trunc_len;
> +	size_t filename_len;
> +
> +	suffix = fwts_log_type_filename_suffix(type);
> +	suffix_len = strlen(suffix);
> +
> +	/*
> +	 * Locate an existing suffix, if it is one we recognise
> +	 * then remove it and append the appropriate one
> +	 */
> +	ptr = rindex(filename, '.');
> +	if (ptr &&
> +		(!strcmp(ptr, ".log") ||
> +		 !strcmp(ptr, ".json") ||
> +		 !strcmp(ptr, ".xml") ||
> +		 !strcmp(ptr, ".html"))) {
> +
> +		trunc_len = ptr - filename;
> +		if ((new_name = calloc(trunc_len + suffix_len + 1, 1)) == NULL) {
> +			fprintf(stderr, "Cannot allocate log name.\n");
> +			return NULL;
> +		}
> +		strncpy(new_name, filename, trunc_len);
> +		strcat(new_name, suffix); /* strcat OK because calloc zero'd all of new_name */
> +		return new_name;
> +	}
>  
> -	va_start(args, fmt);
> -	ret = fwts_log_vprintf(log, field, level, status, label, prefix, fmt, args);
> -	va_end(args);
> +	/*
> +	 * We didn't find a suffix or a known suffix, so append
> +	 * the appropriate one to the given log filename
> +	 */
> +	filename_len = strlen(filename);
> +	if ((new_name = calloc(filename_len + suffix_len + 1, 1)) == NULL) {
> +		fprintf(stderr, "Cannot allocate log name.\n");
> +		return NULL;
> +	}
>  
> -	return ret;
> +	strcpy(new_name, filename);
> +	strcat(new_name, suffix);
> +
> +	return new_name;
>  }
>  
>  /*
>   *  fwts_log_vprintf()
> - *	vprintf to a log
> + *	printf to a log
>   */
> -int fwts_log_vprintf(fwts_log *log,
> +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)
> +	const char *fmt, ...)
>  {
> +	va_list	args;
> +	int ret = 0;
> +
> +	char buffer[LOG_MAX_BUF_SIZE];
> +
>  	if (!((field & LOG_FIELD_MASK) & fwts_log_filter))
> -		return 0;
> +		return ret;
> +
> +	if (log && log->magic == LOG_MAGIC) {
> +		fwts_list_link *item;
> +
> +		/*
> +		 * With the possibility of having multiple logs being written
> +		 * to per call of fwts_log_printf() it is more efficient to
> +		 * vsnprintf() here and then pass the formatted output down to
> +		 * each log handler rather than re-formatting each time in each
> +		 * handler
> +		 */
> +		va_start(args, fmt);
> +		ret = vsnprintf(buffer, sizeof(buffer), fmt, args);
> +		if (ret < 0)
> +			return ret;
> +
> +		fwts_list_foreach(item, &log->log_files) {
> +			fwts_log_file *log_file = fwts_list_data(fwts_log_file *, item);
> +
> +			if (log_file->ops && log_file->ops->print)
> +				log_file->ops->print(log_file, field, level,
> +					status, label, prefix, buffer);
> +		}
> +		log->line_number++;
>  
> -	if (log && log->magic == LOG_MAGIC &&
> -	    log->ops && log->ops->underline)
> -		return log->ops->vprintf(log, field, level, status, label, prefix, fmt, args);
> -	else
> -		return 0;
> +		va_end(args);
> +	}
> +	return ret;
>  }
>  
>  /*
> @@ -365,9 +442,16 @@ int fwts_log_vprintf(fwts_log *log,
>   */
>  void fwts_log_underline(fwts_log *log, const int ch)
>  {
> -	if (log && log->magic == LOG_MAGIC &&
> -	    log->ops && log->ops->underline)
> -		log->ops->underline(log, ch);
> +	if (log && log->magic == LOG_MAGIC) {
> +		fwts_list_link *item;
> +
> +		fwts_list_foreach(item, &log->log_files) {
> +			fwts_log_file *log_file = fwts_list_data(fwts_log_file *, item);
> +
> +			if (log_file->ops && log_file->ops->underline)
> +				log_file->ops->underline(log_file, ch);
> +		}
> +	}
>  }
>  
>  /*
> @@ -376,9 +460,17 @@ void fwts_log_underline(fwts_log *log, const int ch)
>   */
>  void fwts_log_newline(fwts_log *log)
>  {
> -	if (log && log->magic == LOG_MAGIC &&
> -	    log->ops && log->ops->underline)
> -		log->ops->newline(log);
> +	if (log && log->magic == LOG_MAGIC) {
> +		fwts_list_link *item;
> +
> +		fwts_list_foreach(item, &log->log_files) {
> +			fwts_log_file *log_file = fwts_list_data(fwts_log_file *, item);
> +
> +			if (log_file->ops && log_file->ops->newline)
> +				log_file->ops->newline(log_file);
> +		}
> +		log->line_number++;
> +	}
>  }
>  
>  int fwts_log_set_owner(fwts_log *log, const char *owner)
> @@ -395,96 +487,199 @@ int fwts_log_set_owner(fwts_log *log, const char *owner)
>  	return FWTS_ERROR;
>  }
>  
> +
> +/*
> + *  fwts_log_section_begin()
> + *	mark a start of a named section.  For structured logging
> + *	such as XML and JSON this pushes a new named tagged section
> + */
>  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);
> +	if (log && log->magic == LOG_MAGIC) {
> +		fwts_list_link *item;
> +
> +		fwts_list_foreach(item, &log->log_files) {
> +			fwts_log_file *log_file = fwts_list_data(fwts_log_file *, item);
> +
> +			if (log_file->ops && log_file->ops->section_begin)
> +				log_file->ops->section_begin(log_file, name);
> +		}
> +	}
>  }
>  
> +/*
> + *  fwts_log_section_end()
> + *	mark end of a named section.  For structured logging
> + *	such as XML and JSON this pops the end of a tagged section
> + */
>  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);
> +	if (log && log->magic == LOG_MAGIC) {
> +		fwts_list_link *item;
> +
> +		fwts_list_foreach(item, &log->log_files) {
> +			fwts_log_file *log_file = fwts_list_data(fwts_log_file *, item);
> +
> +			if (log_file->ops && log_file->ops->section_end)
> +				log_file->ops->section_end(log_file);
> +		}
> +	}
>  }
>  
>  /*
> - *  fwts_log_open()
> - *	open a log file. if name is stderr or stdout, then attach log to these
> - *	streams.
> + *  fwts_log_get_ops()
> + *	return log ops basedon log type
>   */
> -fwts_log *fwts_log_open(const char *owner, const char *name, const char *mode, fwts_log_type type)
> +static fwts_log_ops *fwts_log_get_ops(fwts_log_type type)
>  {
> -	fwts_log *newlog;
> -
> -	if ((newlog = calloc(1, sizeof(fwts_log))) == NULL)
> -		return NULL;
> -
> -	newlog->magic = LOG_MAGIC;
>  	switch (type) {
>  	case LOG_TYPE_JSON:
> -		newlog->ops = &fwts_log_json_ops;
> -		break;
> +		return &fwts_log_json_ops;
>  	case LOG_TYPE_PLAINTEXT:
> -		newlog->ops = &fwts_log_plaintext_ops;
> -		break;
> +		return &fwts_log_plaintext_ops;
>  	case LOG_TYPE_XML:
> -		newlog->ops = &fwts_log_xml_ops;
> -		break;
> +		return &fwts_log_xml_ops;
>  	case LOG_TYPE_HTML:
> -		newlog->ops = &fwts_log_html_ops;
> -		break;
> +		return &fwts_log_html_ops;
>  	case LOG_TYPE_NONE:
>  	default:
> -		newlog->ops = &fwts_log_plaintext_ops;
> -		break;
> +		return &fwts_log_plaintext_ops;
>  	}
> +}
>  
> -	if (owner) {
> -		if ((newlog->owner = calloc(1, strlen(owner)+1)) == NULL) {
> -			free(newlog);
> -			return NULL;
> -		}
> -		strcpy(newlog->owner, owner);
> -	}
> +/*
> + *  fwts_log_get_filename_type()
> + *	determine the filename type
> + */
> +fwts_log_filename_type fwts_log_get_filename_type(const char *filename)
> +{
> +	if (!strcmp(filename, "stderr"))
> +		return LOG_FILENAME_TYPE_STDERR;
> +	else if (!strcmp(filename, "stdout"))
> +		return LOG_FILENAME_TYPE_STDOUT;
> +	else
> +		return LOG_FILENAME_TYPE_FILE;
> +}
> +
> +/*
> + *  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,	/* Creator of the log */
> +	const char *filename,	/* Log file name */
> +	const char *mode,	/* open mode, see fopen() modes */
> +	fwts_log_type type)	/* Log type */
> +{
> +	fwts_log *newlog;
> +	unsigned int i;
> +	char *newname;
>  
> -	if (strcmp("stderr", name) == 0)
> -		newlog->fp = stderr;
> -	else if (strcmp("stdout", name) == 0)
> -		newlog->fp = stdout;
> -	else if ((newlog->fp = fopen(name, mode)) == NULL) {
> -		free(newlog);
> +	if ((newlog = calloc(1, sizeof(fwts_log))) == NULL)
>  		return NULL;
> -	}
>  
> -	if (log_line_width) {
> -		/* User has specified width, so use it */
> -		newlog->line_width = log_line_width;
> -	} else {
> -		newlog->line_width = fwts_tty_width(fileno(newlog->fp), LOG_LINE_WIDTH);
> -	}
> +	newlog->magic = LOG_MAGIC;
>  
> -	if (newlog->ops && newlog->ops->open)
> -		newlog->ops->open(newlog);
> +	fwts_log_set_owner(newlog, owner);
> +	fwts_list_init(&newlog->log_files);
> +
> +	/*
> +	 *  Scan through and see which log types have been specified
> +	 *  and open the log file with the appropriate ops to perform
> +	 *  the logging
> +	 */
> +	for (i=0; i<32; i++) {
> +		fwts_log_type mask = 1 << i;	/* The log type for this iteration */
> +
> +		/* If set then go and open up a log for this log type */
> +		if (type & mask) {
> +			fwts_log_file *log_file;
> +
> +			if ((log_file = calloc(1, sizeof(fwts_log_file))) == NULL) {
> +				fwts_log_close(newlog);
> +				return NULL;
> +			}
> +
> +			log_file->type = mask;
> +			log_file->ops  = fwts_log_get_ops(mask);
> +			log_file->log  = newlog;
> +			log_file->filename_type = fwts_log_get_filename_type(filename);
> +
> +			/*
> +			 *  To complicate matters we can have logs being
> +			 *  written to stderr, stdout or two a named file
> +			 */
> +			switch(log_file->filename_type) {
> +			case LOG_FILENAME_TYPE_STDERR:
> +				log_file->fp = stderr;
> +				break;
> +			case LOG_FILENAME_TYPE_STDOUT:
> +				log_file->fp = stdout;
> +				break;
> +			case LOG_FILENAME_TYPE_FILE:
> +				if ((newname = fwts_log_filename(filename, mask)) == NULL) {
> +					fwts_log_close(newlog);
> +					return NULL;
> +				}
> +				log_file->fp = fopen(newname, mode);
> +				free(newname);
> +
> +				if (log_file->fp == NULL) {
> +					fwts_log_close(newlog);
> +					return NULL;
> +				}
> +			}
> +
> +			/* Fix up the log specific line width */
> +			if (log_line_width) {
> +				/* User has specified width, so use it */
> +				log_file->line_width = log_line_width;
> +			} else {
> +				log_file->line_width =
> +					fwts_tty_width(fileno(log_file->fp), LOG_LINE_WIDTH);
> +			}
> +
> +			/* ..and add the log file to the list of logs */
> +			fwts_list_append(&newlog->log_files, log_file);
> +
> +			/* ..and do the log specific opening set up */
> +			if (log_file->ops && log_file->ops->open)
> +				log_file->ops->open(log_file);
> +		}
> +	}
>  
>  	return newlog;
>  }
>  
>  /*
>   *  fwts_log_close()
> - *	close a log file
> + *	close any opened log files, free up memory
>   */
>  int fwts_log_close(fwts_log *log)
>  {
>  	if (log && (log->magic == LOG_MAGIC)) {
> -		if (log->ops && log->ops->close)
> -			log->ops->close(log);
> +		fwts_list_link *item;
> +
> +		fwts_list_foreach(item, &log->log_files) {
> +			fwts_log_file *log_file = fwts_list_data(fwts_log_file *, item);
> +
> +			/* Do the log type specific close */
> +			if (log_file->ops && log_file->ops->close)
> +				log_file->ops->close(log_file);
> +
> +			/* Close opened log file */
> +			if (log_file->fp &&
> +			    log_file->filename_type == LOG_FILENAME_TYPE_FILE)
> +				fclose(log_file->fp);
> +		}
> +
> +		/* ..and free log files */
> +		fwts_list_free_items(&log->log_files, free);
>  
> -		if (log->fp && (log->fp != stdout && log->fp != stderr))
> -			fclose(log->fp);
>  		if (log->owner)
>  			free(log->owner);
> +
>  		free(log);
>  	}
>  	return FWTS_OK;
> diff --git a/src/lib/src/fwts_log_html.c b/src/lib/src/fwts_log_html.c
> index a70ac21..1ca79b8 100644
> --- a/src/lib/src/fwts_log_html.c
> +++ b/src/lib/src/fwts_log_html.c
> @@ -37,31 +37,31 @@ typedef struct {
>  static fwts_log_html_stack_t html_stack[MAX_HTML_STACK];
>  static int html_stack_index = 0;
>  
> -static void fwts_log_html(fwts_log *log, const char *fmt, ...)
> +static void fwts_log_html(fwts_log_file *log_file, const char *fmt, ...)
>  {
>  	va_list args;
>  
>  	va_start(args, fmt);
>  
> -	fprintf(log->fp, "%*s", html_stack_index * HTML_INDENT, "");
> -	vfprintf(log->fp, fmt, args);
> +	fprintf(log_file->fp, "%*s", html_stack_index * HTML_INDENT, "");
> +	vfprintf(log_file->fp, fmt, args);
>  
>  	va_end(args);
>  }
>  
>  
>  /*
> - *  fwts_log_vprintf_html()
> - *	vprintf to a log
> + *  fwts_log_print_html()
> + *	print to a log
>   */
> -static int fwts_log_vprintf_html(fwts_log *log,
> +static int fwts_log_print_html(
> +	fwts_log_file *log_file,
>  	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)
> +	const char *buffer)
>  {
>  	char *str;
>  	char *style;
> @@ -74,7 +74,7 @@ static int fwts_log_vprintf_html(fwts_log *log,
>  	if (field & (LOG_NEWLINE | LOG_SEPARATOR | LOG_DEBUG))
>  		return 0;
>  
> -	fwts_log_html(log, "<TR>\n");
> +	fwts_log_html(log_file, "<TR>\n");
>  
>  	if (field & LOG_VERBATUM) {
>  		code_start = "<PRE class=style_code>";
> @@ -86,29 +86,24 @@ static int fwts_log_vprintf_html(fwts_log *log,
>  
>  	switch (field & LOG_FIELD_MASK) {
>  	case LOG_ERROR:
> -		fwts_log_html(log, "  <TD class=style_error>Error</TD><TD COLSPAN=2>");
> -		vfprintf(log->fp, fmt, args);
> -		fprintf(log->fp, "</TD>\n");
> +		fwts_log_html(log_file, "  <TD class=style_error>Error</TD>"
> +			"<TD COLSPAN=2>%s</TD>\n", buffer);
>  		break;
>  	case LOG_WARNING:
> -		fwts_log_html(log, "  <TD class=style_error>Warning</TD><TD COLSPAN=2 class=style_advice_info>%s", code_start);
> -		vfprintf(log->fp, fmt, args);
> -		fprintf(log->fp, "%s</TD>\n", code_end);
> +		fwts_log_html(log_file, "  <TD class=style_error>Warning</TD>"
> +			"<TD COLSPAN=2 class=style_advice_info>%s%s%s</TD>\n",
> +			code_start, buffer, code_end);
>  		break;
>  	case LOG_HEADING:
> -		fwts_log_html(log, "<TD COLSPAN=2 class=style_heading>%s", code_start);
> -		vfprintf(log->fp, fmt, args);
> -		fprintf(log->fp, "%s</TD>\n", code_end);
> +		fwts_log_html(log_file, "<TD COLSPAN=2 class=style_heading>%s%s%s</TD>\n",
> +			code_start, buffer, code_end);
>  		break;
>  	case LOG_INFO:
> -		fwts_log_html(log, "  <TD></TD><TD COLSPAN=2 class=style_infos>%s", code_start);
> -		vfprintf(log->fp, fmt, args);
> -		fprintf(log->fp, "%s</TD>\n", code_end);
> +		fwts_log_html(log_file, "  <TD></TD><TD COLSPAN=2 class=style_infos>%s%s%s</TD>\n",
> +			code_start, buffer, code_end);
>  		break;
>  	case LOG_PASSED:
> -		fwts_log_html(log, "<TD class=style_passed>PASSED</TD><TD>");
> -		vfprintf(log->fp, fmt, args);
> -		fprintf(log->fp, "</TD>\n");
> +		fwts_log_html(log_file, "<TD class=style_passed>PASSED</TD><TD>%s</TD>\n", buffer);
>  		break;
>  	case LOG_FAILED:
>  		switch (level) {
> @@ -132,39 +127,33 @@ static int fwts_log_vprintf_html(fwts_log *log,
>  		}
>  		str = fwts_log_level_to_str(level);
>  
> -		fwts_log_html(log, "  <TD%s>%s [%s]</TD>\n", style, *status ? status : "", str);
> -
> -		fwts_log_html(log, "  <TD>");
> -		vfprintf(log->fp, fmt, args);
> -		fprintf(log->fp, "</TD>\n");
> +		fwts_log_html(log_file, "  <TD%s>%s [%s]</TD>\n", style, *status ? status : "", str);
> +		fwts_log_html(log_file, "  <TD>%s</TD>\n", buffer);
>  		break;
>  
>  	case LOG_SKIPPED:
> -		fwts_log_html(log, "<TD class=style_skipped>Skipped</TD><TD>%s", code_start);
> -		vfprintf(log->fp, fmt, args);
> -		fprintf(log->fp, "%s</TD>\n", code_end);
> +		fwts_log_html(log_file, "<TD class=style_skipped>Skipped</TD>"
> +			"<TD>%s%s%s</TD>\n", code_start, buffer, code_end);
>  		break;
>  
>  	case LOG_SUMMARY:
> -		fwts_log_html(log, "  <TD></TD><TD COLSPAN=2 class=style_summary>%s", code_start);
> -		vfprintf(log->fp, fmt, args);
> -		fprintf(log->fp, "%s</TD>\n", code_end);
> +		fwts_log_html(log_file, "  <TD></TD>"
> +			"<TD COLSPAN=2 class=style_summary>%s%s%s</TD>\n",
> +			code_start, buffer, code_end);
>  		break;
>  
>  	case LOG_ADVICE:
> -		fwts_log_html(log, "  <TD class=style_advice>Advice</TD><TD COLSPAN=2 class=style_advice_info>%s", code_start);
> -		vfprintf(log->fp, fmt, args);
> -		fprintf(log->fp, "%s</TD>\n", code_end);
> +		fwts_log_html(log_file, "  <TD class=style_advice>Advice</TD>"
> +			"<TD COLSPAN=2 class=style_advice_info>%s%s%s</TD>\n",
> +			code_start, buffer, code_end);
>  		break;
>  
>  	default:
>  		break;
>  	}
>  
> -	fwts_log_html(log, "</TR>\n");
> -	fflush(log->fp);
> -
> -	log->line_number++;
> +	fwts_log_html(log_file, "</TR>\n");
> +	fflush(log_file->fp);
>  
>  	return 0;
>  }
> @@ -173,7 +162,7 @@ static int fwts_log_vprintf_html(fwts_log *log,
>   *  fwts_log_underline_html()
>   *	write an underline across log, using character ch as the underline
>   */
> -static void fwts_log_underline_html(fwts_log *log, const int ch)
> +static void fwts_log_underline_html(fwts_log_file *log_file, const int ch)
>  {
>  	/* No-op for html */
>  }
> @@ -182,26 +171,26 @@ static void fwts_log_underline_html(fwts_log *log, const int ch)
>   *  fwts_log_newline()
>   *	write newline to log
>   */
> -static void fwts_log_newline_html(fwts_log *log)
> +static void fwts_log_newline_html(fwts_log_file *log_file)
>  {
>  	/* No-op for html */
>  }
>  
> -static void fwts_log_section_begin_html(fwts_log *log, const char *name)
> +static void fwts_log_section_begin_html(fwts_log_file *log_file, const char *name)
>  {
>  	html_stack[html_stack_index].name = name;
>  
>  	if (!strcmp(name, "summary")) {
> -		fwts_log_html(log, "<TR><TD class=style_heading COLSPAN=2>Summary</TD></TR>\n");
> +		fwts_log_html(log_file, "<TR><TD class=style_heading COLSPAN=2>Summary</TD></TR>\n");
>  	} else if (!strcmp(name, "heading")) {
> -		fwts_log_html(log, "<TR><TD class=style_heading COLSPAN=2>Firmware Test Suite</TD></TR>\n");
> +		fwts_log_html(log_file, "<TR><TD class=style_heading COLSPAN=2>Firmware Test Suite</TD></TR>\n");
>  	} else if (!strcmp(name, "subtest_info")) {
> -		fwts_log_html(log, "<TR><TD class=style_subtest COLSPAN=2></TD></TR>\n");
> +		fwts_log_html(log_file, "<TR><TD class=style_subtest COLSPAN=2></TD></TR>\n");
>  	} else if (!strcmp(name, "failure")) {
> -		fwts_log_html(log, "<TR><TD class=style_heading COLSPAN=2></TD></TR>\n");
> +		fwts_log_html(log_file, "<TR><TD class=style_heading COLSPAN=2></TD></TR>\n");
>  	}
>  
> -	fflush(log->fp);
> +	fflush(log_file->fp);
>  
>  	if (html_stack_index < MAX_HTML_STACK)
>  		html_stack_index++;
> @@ -211,11 +200,11 @@ static void fwts_log_section_begin_html(fwts_log *log, const char *name)
>  	}
>  }
>  
> -static void fwts_log_section_end_html(fwts_log *log)
> +static void fwts_log_section_end_html(fwts_log_file *log_file)
>  {
>  	if (html_stack_index > 0) {
>  		html_stack_index--;
> -		fflush(log->fp);
> +		fflush(log_file->fp);
>  	} else {
>  		fprintf(stderr, "html log stack underflow.\n");
>  		exit(EXIT_FAILURE);
> @@ -223,15 +212,15 @@ static void fwts_log_section_end_html(fwts_log *log)
>  
>  }
>  
> -static void fwts_log_open_html(fwts_log *log)
> +static void fwts_log_open_html(fwts_log_file *log_file)
>  {
> -	fwts_log_html(log, "<HTML>\n");
> -	fwts_log_html(log, "<HEAD>\n");
> -	fwts_log_html(log, "  <TITLE>fwts log</TITLE>\n");
> -	fwts_log_html(log, "</HEAD>\n");
> -	fwts_log_html(log, "<BODY>\n");
> -	fwts_log_html(log, "<STYLE>\n");
> -	fwts_log_html(log,
> +	fwts_log_html(log_file, "<HTML>\n");
> +	fwts_log_html(log_file, "<HEAD>\n");
> +	fwts_log_html(log_file, "  <TITLE>fwts log</TITLE>\n");
> +	fwts_log_html(log_file, "</HEAD>\n");
> +	fwts_log_html(log_file, "<BODY>\n");
> +	fwts_log_html(log_file, "<STYLE>\n");
> +	fwts_log_html(log_file,
>  		".style_critical { background-color: red; font-weight: bold; "
>  		"text-align: center; vertical-align: center  }\n"
>  		".style_high { background-color: orange; font-weight: bold; "
> @@ -256,27 +245,27 @@ static void fwts_log_open_html(fwts_log *log)
>  		".style_info { }\n"
>  		".style_code { font-family: \"courier\",\"mono\"; font-size:0.75em; overflow:auto; "
>  		"width:90%; line-height:0.82em; font-stretch:extra-condensed; word-wrap:normal }\n");
> -	fwts_log_html(log, "</STYLE>\n");
> -	fflush(log->fp);
> +	fwts_log_html(log_file, "</STYLE>\n");
> +	fflush(log_file->fp);
>  
> -	fwts_log_html(log, "<TABLE WIDTH=1024>\n");
> -	fwts_log_html(log, "</TR>\n");
> +	fwts_log_html(log_file, "<TABLE WIDTH=1024>\n");
> +	fwts_log_html(log_file, "</TR>\n");
>  
> -	fwts_log_section_begin_html(log, "fwts");
> +	fwts_log_section_begin_html(log_file, "fwts");
>  }
>  
> -static void fwts_log_close_html(fwts_log *log)
> +static void fwts_log_close_html(fwts_log_file *log_file)
>  {
> -	fwts_log_section_end_html(log);
> +	fwts_log_section_end_html(log_file);
>  
> -	fwts_log_html(log, "</TABLE>\n");
> -	fwts_log_html(log, "</BODY>\n");
> -	fwts_log_html(log, "</HTML>\n");
> -	fflush(log->fp);
> +	fwts_log_html(log_file, "</TABLE>\n");
> +	fwts_log_html(log_file, "</BODY>\n");
> +	fwts_log_html(log_file, "</HTML>\n");
> +	fflush(log_file->fp);
>  }
>  
>  fwts_log_ops fwts_log_html_ops = {
> -	.vprintf = 	 fwts_log_vprintf_html,
> +	.print = 	 fwts_log_print_html,
>  	.underline =	 fwts_log_underline_html,
>  	.newline =	 fwts_log_newline_html,
>  	.section_begin = fwts_log_section_begin_html,
> diff --git a/src/lib/src/fwts_log_json.c b/src/lib/src/fwts_log_json.c
> index 208847c..8dd65e4 100644
> --- a/src/lib/src/fwts_log_json.c
> +++ b/src/lib/src/fwts_log_json.c
> @@ -40,19 +40,19 @@ 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
> + *  fwts_log_printf_son()
> + *	print to a log
>   */
> -static int fwts_log_vprintf_json(fwts_log *log,
> +static int fwts_log_print_json(
> +	fwts_log_file *log_file,
>  	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)
> +	const char *buffer)
>  {
> -	char buffer[4096];
> +	char tmpbuf[4096];
>  	struct tm tm;
>  	time_t now;
>  	json_object *header;
> @@ -69,13 +69,13 @@ static int fwts_log_vprintf_json(fwts_log *log,
>  	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",
> +	json_object_object_add(header, "line_num", json_object_new_int(log_file->log->line_number));
> +	snprintf(tmpbuf, sizeof(tmpbuf), "%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",
> +	json_object_object_add(header, "date", json_object_new_string(tmpbuf));
> +	snprintf(tmpbuf, sizeof(tmpbuf), "%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, "time", json_object_new_string(tmpbuf));
>  	json_object_object_add(header, "field_type",
>  		json_object_new_string(fwts_log_field_to_str_full(field)));
>  
> @@ -92,13 +92,10 @@ static int fwts_log_vprintf_json(fwts_log *log,
>  	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;
>  }
>  
> @@ -106,7 +103,7 @@ static int fwts_log_vprintf_json(fwts_log *log,
>   *  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)
> +static void fwts_log_underline_json(fwts_log_file *log_file, const int ch)
>  {
>  	/* No-op for json */
>  }
> @@ -115,12 +112,12 @@ static void fwts_log_underline_json(fwts_log *log, const int ch)
>   *  fwts_log_newline()
>   *	write newline to log
>   */
> -static void fwts_log_newline_json(fwts_log *log)
> +static void fwts_log_newline_json(fwts_log_file *log_file)
>  {
>  	/* No-op for json */
>  }
>  
> -static void fwts_log_section_begin_json(fwts_log *log, const char *name)
> +static void fwts_log_section_begin_json(fwts_log_file *log_file, const char *name)
>  {
>  	json_object *json_obj;
>  	json_object *json_log;
> @@ -132,7 +129,7 @@ static void fwts_log_section_begin_json(fwts_log *log, const char *name)
>  	json_stack[json_stack_index].obj = json_obj;
>  	json_stack[json_stack_index].log = json_log;
>  
> -	if (json_stack_index > 0) 
> +	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)
> @@ -143,7 +140,7 @@ static void fwts_log_section_begin_json(fwts_log *log, const char *name)
>  	}
>  }
>  
> -static void fwts_log_section_end_json(fwts_log *log)
> +static void fwts_log_section_end_json(fwts_log_file *log_file)
>  {
>  	if (json_stack_index > 0)
>  		json_stack_index--;
> @@ -153,29 +150,30 @@ static void fwts_log_section_end_json(fwts_log *log)
>  	}
>  }
>  
> -static void fwts_log_open_json(fwts_log *log)
> +static void fwts_log_open_json(fwts_log_file *log_file)
>  {
> -	fwts_log_section_begin_json(log, "fwts");
> +	fwts_log_section_begin_json(log_file, "fwts");
>  }
>  
> -static void fwts_log_close_json(fwts_log *log)
> +static void fwts_log_close_json(fwts_log_file *log_file)
>  {
>  	const char *str;
>  	size_t len;
>  
> -	fwts_log_section_end_json(log);
> +	fwts_log_section_end_json(log_file);
>  
>  	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);
> +	fwrite(str, 1, len, log_file->fp);
> +	fwrite("\n", 1, 1, log_file->fp);
> +	fflush(log_file->fp);
> +
>  	json_object_put(json_stack[0].obj);
>  }
>  
>  fwts_log_ops fwts_log_json_ops = {
> -	.vprintf = 	 fwts_log_vprintf_json,
> +	.print = 	 fwts_log_print_json,
>  	.underline =	 fwts_log_underline_json,
>  	.newline =	 fwts_log_newline_json,
>  	.section_begin = fwts_log_section_begin_json,
> diff --git a/src/lib/src/fwts_log_plaintext.c b/src/lib/src/fwts_log_plaintext.c
> index 44c443f..7381ae3 100644
> --- a/src/lib/src/fwts_log_plaintext.c
> +++ b/src/lib/src/fwts_log_plaintext.c
> @@ -32,7 +32,8 @@
>   *  fwts_log_header_plaintext()
>   *	format up a tabulated log heading
>   */
> -static int fwts_log_header_plaintext(fwts_log *log,
> +static int fwts_log_header_plaintext(
> +	fwts_log_file *log_file,
>  	char *buffer,
>  	const int len,
>  	const fwts_log_field field,
> @@ -51,7 +52,7 @@ static int fwts_log_header_plaintext(fwts_log *log,
>  			ptr++;
>  			if (!strncmp(ptr, "line", 4)) {
>  				n += snprintf(buffer + n, len - n,
> -					"%5.5d", log->line_number);
> +					"%5.5d", log_file->log->line_number);
>  				ptr += 4;
>  			}
>  			if (!strncmp(ptr, "date", 4)) {
> @@ -76,8 +77,8 @@ static int fwts_log_header_plaintext(fwts_log *log,
>  					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);
> +			if (!strncmp(ptr,"owner", 5) && log_file->log->owner) {
> +				n += snprintf(buffer + n, len - n, "%-15.15s", log_file->log->owner);
>  				ptr += 5;
>  			}
>  		} else {
> @@ -90,19 +91,19 @@ static int fwts_log_header_plaintext(fwts_log *log,
>  
>  
>  /*
> - *  fwts_log_vprintf()
> - *	vprintf to a log
> + *  fwts_log_print()
> + *	print to a log
>   */
> -static int fwts_log_vprintf_plaintext(fwts_log *log,
> +static int fwts_log_print_plaintext(
> +	fwts_log_file *log_file,
>  	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)
> +	const char *buffer)
>  {
> -	char buffer[4096];
> +	char tmpbuf[8192];
>  	int n = 0;
>  	int header_len;
>  	int len = 0;
> @@ -115,15 +116,14 @@ static int fwts_log_vprintf_plaintext(fwts_log *log,
>  
>  	/* 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);
> +	n = header_len = fwts_log_header_plaintext(log_file, tmpbuf, sizeof(tmpbuf), field, level);
> +	n += snprintf(tmpbuf + n, sizeof(tmpbuf) - n, "%s%s", prefix, buffer);
>  
>  	/* Break text into multi-lines if necessary */
>  	if (field & LOG_VERBATUM)
> -		lines = fwts_list_from_text(buffer + header_len);
> +		lines = fwts_list_from_text(tmpbuf + header_len);
>  	else
> -		lines = fwts_format_text(buffer + header_len, log->line_width - header_len);
> +		lines = fwts_format_text(tmpbuf + header_len, log_file->line_width - header_len);
>  
>  	len = n;
>  
> @@ -133,17 +133,16 @@ static int fwts_log_vprintf_plaintext(fwts_log *log,
>  		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);
> +			fwts_log_header_plaintext(log_file, tmpbuf, sizeof(tmpbuf), field, level);
> +			fwrite(tmpbuf, 1, header_len, log_file->fp);
>  		}
> -		fwrite(text, 1, strlen(text), log->fp);
> -		fwrite("\n", 1, 1, log->fp);
> -		fflush(log->fp);
> -		log->line_number++;
> +		fwrite(text, 1, strlen(text), log_file->fp);
> +		fwrite("\n", 1, 1, log_file->fp);
> +		fflush(log_file->fp);
>  		len += strlen(text) + 1;
>  	}
>  	fwts_text_list_free(lines);
> -	
> +
>  	return len;
>  }
>  
> @@ -151,11 +150,11 @@ static int fwts_log_vprintf_plaintext(fwts_log *log,
>   *  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)
> +static void fwts_log_underline_plaintext(fwts_log_file *log_file, const int ch)
>  {
>  	int n;
>  	char *buffer;
> -	size_t width = log->line_width + 1;
> +	size_t width = log_file->line_width + 1;
>  
>  	if (!((LOG_SEPARATOR & LOG_FIELD_MASK) & fwts_log_filter))
>  		return;
> @@ -165,14 +164,13 @@ static void fwts_log_underline_plaintext(fwts_log *log, const int ch)
>  		return;	/* Unlikely, and just abort */
>  
>  	/* Write in leading optional line prefix */
> -	n = fwts_log_header_plaintext(log, buffer, width, LOG_SEPARATOR, LOG_LEVEL_NONE);
> +	n = fwts_log_header_plaintext(log_file, 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++;
> +	fwrite(buffer, 1, width, log_file->fp);
> +	fflush(log_file->fp);
>  
>  	free(buffer);
>  }
> @@ -181,15 +179,14 @@ static void fwts_log_underline_plaintext(fwts_log *log, const int ch)
>   *  fwts_log_newline_plaintext()
>   *	write newline to log
>   */
> -static void fwts_log_newline_plaintext(fwts_log *log)
> +static void fwts_log_newline_plaintext(fwts_log_file *log_file)
>  {
> -	fwrite("\n", 1, 1, log->fp);
> -	fflush(log->fp);
> -	log->line_number++;
> +	fwrite("\n", 1, 1, log_file->fp);
> +	fflush(log_file->fp);
>  }
>  
>  fwts_log_ops fwts_log_plaintext_ops = {
> -	.vprintf = 	fwts_log_vprintf_plaintext,
> +	.print = 	fwts_log_print_plaintext,
>  	.underline =	fwts_log_underline_plaintext,
>  	.newline =	fwts_log_newline_plaintext
>  };
> diff --git a/src/lib/src/fwts_log_xml.c b/src/lib/src/fwts_log_xml.c
> index 57b530b..19e5e94 100644
> --- a/src/lib/src/fwts_log_xml.c
> +++ b/src/lib/src/fwts_log_xml.c
> @@ -38,19 +38,18 @@ static fwts_log_xml_stack_t xml_stack[MAX_XML_STACK];
>  static int xml_stack_index = 0;
>  
>  /*
> - *  fwts_log_vprintf_xml()
> - *	vprintf to a log
> + *  fwts_log_print_xml()
> + *	print to a log
>   */
> -static int fwts_log_vprintf_xml(fwts_log *log,
> +static int fwts_log_print_xml(
> +	fwts_log_file *log_file,
>  	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)
> +	const char *buffer)
>  {
> -	char buffer[4096];
>  	struct tm tm;
>  	time_t now;
>  	char *str;
> @@ -64,21 +63,21 @@ static int fwts_log_vprintf_xml(fwts_log *log,
>  	time(&now);
>  	localtime_r(&now, &tm);
>  
> -	fprintf(log->fp, "%*s<logentry>\n", xml_stack_index * XML_INDENT, "");
> +	fprintf(log_file->fp, "%*s<logentry>\n", xml_stack_index * XML_INDENT, "");
>  
> -	fprintf(log->fp, "%*s<line_num>%d</line_num>\n",
> +	fprintf(log_file->fp, "%*s<line_num>%d</line_num>\n",
>  		(xml_stack_index + 1) * XML_INDENT,
> -		"", log->line_number);
> +		"", log_file->log->line_number);
>  
> -	fprintf(log->fp, "%*s<date>%2.2d/%2.2d/%-2.2d</date>\n",
> +	fprintf(log_file->fp, "%*s<date>%2.2d/%2.2d/%-2.2d</date>\n",
>  		(xml_stack_index + 1) * XML_INDENT,
>  		"", tm.tm_mday, tm.tm_mon + 1, (tm.tm_year+1900) % 100);
>  
> -	fprintf(log->fp, "%*s<time>%2.2d:%2.2d:%2.2d</time>\n",
> +	fprintf(log_file->fp, "%*s<time>%2.2d:%2.2d:%2.2d</time>\n",
>  		(xml_stack_index + 1) * XML_INDENT,
>  		"", tm.tm_hour, tm.tm_min, tm.tm_sec);
>  
> -	fprintf(log->fp, "%*s<field_type>%s</field_type>\n",
> +	fprintf(log_file->fp, "%*s<field_type>%s</field_type>\n",
>  		(xml_stack_index + 1) * XML_INDENT,
>  		"", fwts_log_field_to_str_full(field));
>  
> @@ -86,26 +85,23 @@ static int fwts_log_vprintf_xml(fwts_log *log,
>  	if (!strcmp(str, " "))
>  		str = "None";
>  
> -	fprintf(log->fp, "%*s<level>%s</level>\n",
> +	fprintf(log_file->fp, "%*s<level>%s</level>\n",
>  		(xml_stack_index + 1) * XML_INDENT, "", str);
>  
> -	fprintf(log->fp, "%*s<status>%s</status>\n",
> +	fprintf(log_file->fp, "%*s<status>%s</status>\n",
>  		(xml_stack_index + 1) * XML_INDENT,
>  		"", *status ? status : "None");
>  
> -	fprintf(log->fp, "%*s<failure_label>%s</failure_label>\n",
> +	fprintf(log_file->fp, "%*s<failure_label>%s</failure_label>\n",
>  		(xml_stack_index + 1) * XML_INDENT,
>  		"", label && *label ? label : "None");
>  
> -	vsnprintf(buffer, sizeof(buffer), fmt, args);
> -	fprintf(log->fp, "%*s<log_text>%s</log_text>\n",
> +	fprintf(log_file->fp, "%*s<log_text>%s</log_text>\n",
>  		(xml_stack_index + 1) * XML_INDENT,
>  		"", buffer);
>  
> -	fprintf(log->fp, "%*s</logentry>\n", xml_stack_index * XML_INDENT, "");
> -	fflush(log->fp);
> -
> -	log->line_number++;
> +	fprintf(log_file->fp, "%*s</logentry>\n", xml_stack_index * XML_INDENT, "");
> +	fflush(log_file->fp);
>  
>  	return 0;
>  }
> @@ -114,7 +110,7 @@ static int fwts_log_vprintf_xml(fwts_log *log,
>   *  fwts_log_underline_xml()
>   *	write an underline across log, using character ch as the underline
>   */
> -static void fwts_log_underline_xml(fwts_log *log, const int ch)
> +static void fwts_log_underline_xml(fwts_log_file *log_file, const int ch)
>  {
>  	/* No-op for xml */
>  }
> @@ -123,17 +119,17 @@ static void fwts_log_underline_xml(fwts_log *log, const int ch)
>   *  fwts_log_newline()
>   *	write newline to log
>   */
> -static void fwts_log_newline_xml(fwts_log *log)
> +static void fwts_log_newline_xml(fwts_log_file *log_file)
>  {
>  	/* No-op for xml */
>  }
>  
> -static void fwts_log_section_begin_xml(fwts_log *log, const char *name)
> +static void fwts_log_section_begin_xml(fwts_log_file *log_file, const char *name)
>  {
>  	xml_stack[xml_stack_index].name = name;
>  
> -	fprintf(log->fp, "%*s<%s>\n", xml_stack_index * XML_INDENT, "", name);
> -	fflush(log->fp);
> +	fprintf(log_file->fp, "%*s<%s>\n", xml_stack_index * XML_INDENT, "", name);
> +	fflush(log_file->fp);
>  
>  	if (xml_stack_index < MAX_XML_STACK)
>  		xml_stack_index++;
> @@ -143,13 +139,13 @@ static void fwts_log_section_begin_xml(fwts_log *log, const char *name)
>  	}
>  }
>  
> -static void fwts_log_section_end_xml(fwts_log *log)
> +static void fwts_log_section_end_xml(fwts_log_file *log_file)
>  {
>  	if (xml_stack_index > 0) {
>  		xml_stack_index--;
> -		fprintf(log->fp, "%*s</%s>\n", xml_stack_index * XML_INDENT,
> +		fprintf(log_file->fp, "%*s</%s>\n", xml_stack_index * XML_INDENT,
>  			"", xml_stack[xml_stack_index].name);
> -		fflush(log->fp);
> +		fflush(log_file->fp);
>  	} else {
>  		fprintf(stderr, "xml log stack underflow.\n");
>  		exit(EXIT_FAILURE);
> @@ -157,26 +153,26 @@ static void fwts_log_section_end_xml(fwts_log *log)
>  
>  }
>  
> -static void fwts_log_open_xml(fwts_log *log)
> +static void fwts_log_open_xml(fwts_log_file *log_file)
>  {
>  	char *xml_header = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
>  
> -	fwrite(xml_header, 1, strlen(xml_header), log->fp);
> -	fflush(log->fp);
> +	fwrite(xml_header, 1, strlen(xml_header), log_file->fp);
> +	fflush(log_file->fp);
>  
> -	fwts_log_section_begin_xml(log, "fwts");
> +	fwts_log_section_begin_xml(log_file, "fwts");
>  }
>  
> -static void fwts_log_close_xml(fwts_log *log)
> +static void fwts_log_close_xml(fwts_log_file *log_file)
>  {
> -	fwts_log_section_end_xml(log);
> +	fwts_log_section_end_xml(log_file);
>  
> -	fwrite("\n", 1, 1, log->fp);
> -	fflush(log->fp);
> +	fwrite("\n", 1, 1, log_file->fp);
> +	fflush(log_file->fp);
>  }
>  
>  fwts_log_ops fwts_log_xml_ops = {
> -	.vprintf = 	 fwts_log_vprintf_xml,
> +	.print = 	 fwts_log_print_xml,
>  	.underline =	 fwts_log_underline_xml,
>  	.newline =	 fwts_log_newline_xml,
>  	.section_begin = fwts_log_section_begin_xml,

Gave this a spin with fwts -b --log-type html,plaintext ... results can
be found here:

http://ouwish.com/~vanhoof/pickup/cking/fwts_2012-06-11/

Tested-by: Chris Van Hoof <vanhoof@canonical.com>
Keng-Yu Lin June 13, 2012, 9:49 a.m. UTC | #2
On Mon, Jun 11, 2012 at 6:00 PM, Colin King <colin.king@canonical.com> wrote:
> From: Colin Ian King <colin.king@canonical.com>
>
> Since we now can output many different log types we should also
> allow for multiple log types to be written during a run.  This involves
> some considerable re-working of the logging engine.
>
> 1. The fw->log_type is now a  bit map of log_types
> 2. We add a list of log types to be written to fwts_log, this is a list
>   of fwts_log_file types.
> 3. We need to re-work the log name handling so that we can open multiple
>   log files with different suffixes depending on the log type.
> 4. To reduce the amount of vsnprintf() of the formatted log output we
>   now handle this in the log fwts_log_printf() and pass down the formatted
>   output to the different logging handlers rather than keep on re-formatting
>   at the lowest logging handler layer.
>
> There are a lot of changing is this patch. I tried to break it down, but
> since there are so many interdependant changes I had to resort to one big
> patch
>
> Signed-off-by: Colin Ian King <colin.king@canonical.com>
> ---
>  src/lib/include/fwts_log.h       |   66 +++++--
>  src/lib/src/fwts_framework.c     |   94 +++++-----
>  src/lib/src/fwts_log.c           |  363 +++++++++++++++++++++++++++++---------
>  src/lib/src/fwts_log_html.c      |  133 +++++++-------
>  src/lib/src/fwts_log_json.c      |   52 +++---
>  src/lib/src/fwts_log_plaintext.c |   63 ++++---
>  src/lib/src/fwts_log_xml.c       |   72 ++++----
>  7 files changed, 527 insertions(+), 316 deletions(-)
>
> diff --git a/src/lib/include/fwts_log.h b/src/lib/include/fwts_log.h
> index a659baa..513bf88 100644
> --- a/src/lib/include/fwts_log.h
> +++ b/src/lib/include/fwts_log.h
> @@ -23,7 +23,10 @@
>  #include <stdio.h>
>  #include <stdarg.h>
>
> -#define LOG_MAGIC      0xfe23ab13
> +#include "fwts_list.h"
> +
> +#define LOG_MAGIC              (0xfe23ab13)
> +#define LOG_MAX_BUF_SIZE       (4096)          /* Max output per log line */
>
>  typedef enum {
>        LOG_RESULT          = 0x00000001,
> @@ -58,31 +61,56 @@ typedef enum {
>        LOG_LEVEL_INFO      = 0x00000010,
>  } fwts_log_level;
>
> +/*
> + *  different types of log file
> + */
>  typedef enum {
>        LOG_TYPE_NONE       = 0x00000000,
>        LOG_TYPE_PLAINTEXT  = 0x00000001,
>        LOG_TYPE_JSON       = 0x00000002,
> -       LOG_TYPE_XML        = 0x00000003,
> -       LOG_TYPE_HTML       = 0x00000004,
> +       LOG_TYPE_XML        = 0x00000004,
> +       LOG_TYPE_HTML       = 0x00000008,
>  } fwts_log_type;
>
> +/*
> + *   different types of output log
> + */
> +typedef enum {
> +       LOG_FILENAME_TYPE_STDOUT = 0x00000001,  /* log output to stdout */
> +       LOG_FILENAME_TYPE_STDERR = 0x00000002,  /* log output to stderr */
> +       LOG_FILENAME_TYPE_FILE   = 0x00000003,  /* log output to a file */
> +} fwts_log_filename_type;
> +
> +/*
> + *  top level log descriptor
> + */
>  typedef struct log_t {
> -       unsigned int magic;
> -       FILE *fp;
> -       char *owner;
> -       int line_width;
> -       int line_number;
> -       struct fwts_log_ops_t *ops;
> +       unsigned int magic;                     /* magic ID of the log */
> +       fwts_list log_files;                    /* list of fwts_log_file */
> +       int line_number;                        /* keeps track of the line numbering */
> +       char *owner;                            /* who is writing to this log */
>  } fwts_log;
>
> +/*
> + *  info for a specific log type
> + */
> +typedef struct {
> +       FILE *fp;                               /* file descriptor for log */
> +       fwts_log *log;                          /* parent log struct */
> +       fwts_log_type type;                     /* log type */
> +       fwts_log_filename_type filename_type;   /* log filename type */
> +       struct fwts_log_ops_t *ops;             /* log operators */
> +       int line_width;                         /* width of log in chars */
> +} fwts_log_file;
> +
>  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 *);
> +       int (*print)(fwts_log_file *log_file, const fwts_log_field field, const fwts_log_level level, const char *status, const char *label, const char *prefix, const char *buffer);
> +       void (*underline)(fwts_log_file *log_file, int ch);
> +       void (*newline)(fwts_log_file *log_file);
> +       void (*section_begin)(fwts_log_file *log_file, const char *tag);
> +       void (*section_end)(fwts_log_file *log_file);
> +       void (*open)(fwts_log_file *log_file);
> +       void (*close)(fwts_log_file *log_file);
>  } fwts_log_ops;
>
>  extern fwts_log_ops fwts_log_plaintext_ops;
> @@ -116,6 +144,12 @@ 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);
> +fwts_log_filename_type fwts_log_get_filename_type(const char *name);
> +
> +static inline int fwts_log_type_count(fwts_log_type type)
> +{
> +       return __builtin_popcount(type);
> +}
>
>  #define fwts_log_result(fw, fmt, args...)      \
>        fwts_log_printf(fw->results, LOG_RESULT, LOG_LEVEL_NONE, "", "", "", fmt, ## args)
> diff --git a/src/lib/src/fwts_framework.c b/src/lib/src/fwts_framework.c
> index f808d8e..a6c2ba2 100644
> --- a/src/lib/src/fwts_framework.c
> +++ b/src/lib/src/fwts_framework.c
> @@ -28,7 +28,8 @@
>
>  #include "fwts.h"
>
> -#define RESULTS_LOG    "results.log"
> +/* Suffix ".log", ".xml", etc gets automatically appended */
> +#define RESULTS_LOG    "results"
>
>  #define FWTS_RUN_ALL_FLAGS             \
>        (FWTS_BATCH |                   \
> @@ -96,34 +97,6 @@ static const char *fwts_copyright[] = {
>  };
>
>  /*
> - *  fwts_framework_log_suffix()
> - *     set the log name suffix
> - */
> -static void fwts_framework_log_suffix(fwts_framework *fw, const char *suffix)
> -{
> -       char *ptr;
> -       char *new;
> -       size_t len;
> -
> -       /* Locate old suffix and kill it */
> -       ptr = rindex(fw->results_logname, '.');
> -       if (ptr != NULL)
> -               *ptr = '\0';
> -
> -       /* Space for old log name sans old suffix + new suffix + '.' + '\0' */
> -       len = strlen(fw->results_logname) + strlen(suffix) + 2;
> -
> -       if ((new = calloc(len, 1)) == NULL) {
> -               fprintf(stderr, "Cannot allocate log name.\n");
> -               exit(EXIT_FAILURE);
> -       }
> -
> -       snprintf(new, len, "%s.%s", fw->results_logname, suffix);
> -       free(fw->results_logname);
> -       fw->results_logname = new;
> -}
> -
> -/*
>  *  fwts_framework_compare_priority()
>  *     used to register tests sorted on run priority
>  */
> @@ -677,7 +650,7 @@ static fwts_framework_test *fwts_framework_test_find(fwts_framework *fw, const c
>  *  fwts_framework_log()
>  *     log a test result
>  */
> -void fwts_framework_log(fwts_framework *fw,
> +void fwts_framework_log(fwts_framework *fw,
>        fwts_log_field field,
>        const char *label,
>        fwts_log_level level,
> @@ -860,6 +833,39 @@ static int fwts_framework_skip_test_parse(fwts_framework *fw, const char *arg, f
>        return FWTS_OK;
>  }
>
> +/*
> + *  fwts_framework_log_type_parse()
> + *     parse optarg of comma separated log types
> + */
> +static int fwts_framework_log_type_parse(fwts_framework *fw, const char *arg)
> +{
> +       char *str;
> +       char *token;
> +       char *saveptr = NULL;
> +
> +       fw->log_type = 0;
> +
> +       for (str = (char*)arg; (token = strtok_r(str, ",", &saveptr)) != NULL; str = NULL) {
> +               if (!strcmp(token, "plaintext"))
> +                       fw->log_type |= LOG_TYPE_PLAINTEXT;
> +               else if (!strcmp(token, "json"))
> +                       fw->log_type |= LOG_TYPE_JSON;
> +               else if (!strcmp(token, "xml"))
> +                       fw->log_type |= LOG_TYPE_XML;
> +               else if (!strcmp(token, "html"))
> +                       fw->log_type |= LOG_TYPE_HTML;
> +               else {
> +                       fprintf(stderr, "--log-type can be plaintext, xml, html or json.\n");
> +                       return FWTS_ERROR;
> +               }
> +       }
> +
> +       if (!fw->log_type)
> +               fw->log_type = LOG_TYPE_PLAINTEXT;
> +
> +       return FWTS_OK;
> +}
> +
>  int fwts_framework_options_handler(fwts_framework *fw, int argc, char * const argv[], int option_char, int long_index)
>  {
>        switch (option_char) {
> @@ -975,22 +981,8 @@ int fwts_framework_options_handler(fwts_framework *fw, int argc, char * const ar
>                        fwts_iasl_disassemble_all_to_file(fw);
>                        return FWTS_COMPLETE;
>                case 32: /* --log-type */
> -                       if (!strcmp(optarg, "plaintext")) {
> -                               fw->log_type = LOG_TYPE_PLAINTEXT;
> -                               fwts_framework_log_suffix(fw, "log");
> -                       } else if (!strcmp(optarg, "json")) {
> -                               fw->log_type = LOG_TYPE_JSON;
> -                               fwts_framework_log_suffix(fw, "json");
> -                       } else if (!strcmp(optarg, "xml")) {
> -                               fw->log_type = LOG_TYPE_XML;
> -                               fwts_framework_log_suffix(fw, "xml");
> -                       } else if (!strcmp(optarg, "html")) {
> -                               fw->log_type = LOG_TYPE_HTML;
> -                               fwts_framework_log_suffix(fw, "html");
> -                       } else {
> -                               fprintf(stderr, "--log-type can be either plaintext, xml, html or json.\n");
> -                               return FWTS_ERROR;
> -                       }
> +                       fwts_framework_log_type_parse(fw, optarg);
> +                       /* FIX ME - check return */
>                        break;
>                }
>                break;
> @@ -1136,6 +1128,16 @@ int fwts_framework_args(const int argc, char **argv)
>                goto tidy_close;
>        }
>
> +       /* Ensure we have just one log type specified for non-filename logging */
> +       if (fwts_log_type_count(fw->log_type) > 1 &&
> +           fwts_log_get_filename_type(fw->results_logname) != LOG_FILENAME_TYPE_FILE) {
> +               fprintf(stderr,
> +                       "Cannot specify more than one log type when "
> +                       "logging to stderr or stdout\n");
> +               ret = FWTS_ERROR;
> +               goto tidy_close;
> +       }
> +
>        /* Results log */
>        if ((fw->results = fwts_log_open("fwts",
>                        fw->results_logname,
> diff --git a/src/lib/src/fwts_log.c b/src/lib/src/fwts_log.c
> index fea7c41..3ab7930 100644
> --- a/src/lib/src/fwts_log.c
> +++ b/src/lib/src/fwts_log.c
> @@ -315,48 +315,125 @@ void fwts_log_set_format(const char *str)
>  }
>
>  /*
> - *  fwts_log_vprintf()
> - *     printf to a log
> + *  fwts_log_type_filename_suffix()
> + *     return a filename suffix on a given log type
>  */
> -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, ...)
> +static char *fwts_log_type_filename_suffix(fwts_log_type type)
>  {
> -       va_list args;
> -       int ret;
> +       switch (type) {
> +       case LOG_TYPE_JSON:
> +               return ".json";
> +       case LOG_TYPE_XML:
> +               return ".xml";
> +       case LOG_TYPE_HTML:
> +               return ".html";
> +       case LOG_TYPE_NONE:
> +       case LOG_TYPE_PLAINTEXT:
> +       default:
> +               return ".log";
> +       }
> +}
> +
> +/*
> + *  fwts_log_filename_new_suffix()
> + *     return the log name with suffix based on log type
> + */
> +static char *fwts_log_filename(const char *filename, fwts_log_type type)
> +{
> +       char *ptr;
> +       char *new_name;
> +       char *suffix;
> +       size_t suffix_len;
> +       size_t trunc_len;
> +       size_t filename_len;
> +
> +       suffix = fwts_log_type_filename_suffix(type);
> +       suffix_len = strlen(suffix);
> +
> +       /*
> +        * Locate an existing suffix, if it is one we recognise
> +        * then remove it and append the appropriate one
> +        */
> +       ptr = rindex(filename, '.');
> +       if (ptr &&
> +               (!strcmp(ptr, ".log") ||
> +                !strcmp(ptr, ".json") ||
> +                !strcmp(ptr, ".xml") ||
> +                !strcmp(ptr, ".html"))) {
> +
> +               trunc_len = ptr - filename;
> +               if ((new_name = calloc(trunc_len + suffix_len + 1, 1)) == NULL) {
> +                       fprintf(stderr, "Cannot allocate log name.\n");
> +                       return NULL;
> +               }
> +               strncpy(new_name, filename, trunc_len);
> +               strcat(new_name, suffix); /* strcat OK because calloc zero'd all of new_name */
> +               return new_name;
> +       }
>
> -       va_start(args, fmt);
> -       ret = fwts_log_vprintf(log, field, level, status, label, prefix, fmt, args);
> -       va_end(args);
> +       /*
> +        * We didn't find a suffix or a known suffix, so append
> +        * the appropriate one to the given log filename
> +        */
> +       filename_len = strlen(filename);
> +       if ((new_name = calloc(filename_len + suffix_len + 1, 1)) == NULL) {
> +               fprintf(stderr, "Cannot allocate log name.\n");
> +               return NULL;
> +       }
>
> -       return ret;
> +       strcpy(new_name, filename);
> +       strcat(new_name, suffix);
> +
> +       return new_name;
>  }
>
>  /*
>  *  fwts_log_vprintf()
> - *     vprintf to a log
> + *     printf to a log
>  */
> -int fwts_log_vprintf(fwts_log *log,
> +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)
> +       const char *fmt, ...)
>  {
> +       va_list args;
> +       int ret = 0;
> +
> +       char buffer[LOG_MAX_BUF_SIZE];
> +
>        if (!((field & LOG_FIELD_MASK) & fwts_log_filter))
> -               return 0;
> +               return ret;
> +
> +       if (log && log->magic == LOG_MAGIC) {
> +               fwts_list_link *item;
> +
> +               /*
> +                * With the possibility of having multiple logs being written
> +                * to per call of fwts_log_printf() it is more efficient to
> +                * vsnprintf() here and then pass the formatted output down to
> +                * each log handler rather than re-formatting each time in each
> +                * handler
> +                */
> +               va_start(args, fmt);
> +               ret = vsnprintf(buffer, sizeof(buffer), fmt, args);
> +               if (ret < 0)
> +                       return ret;
> +
> +               fwts_list_foreach(item, &log->log_files) {
> +                       fwts_log_file *log_file = fwts_list_data(fwts_log_file *, item);
> +
> +                       if (log_file->ops && log_file->ops->print)
> +                               log_file->ops->print(log_file, field, level,
> +                                       status, label, prefix, buffer);
> +               }
> +               log->line_number++;
>
> -       if (log && log->magic == LOG_MAGIC &&
> -           log->ops && log->ops->underline)
> -               return log->ops->vprintf(log, field, level, status, label, prefix, fmt, args);
> -       else
> -               return 0;
> +               va_end(args);
> +       }
> +       return ret;
>  }
>
>  /*
> @@ -365,9 +442,16 @@ int fwts_log_vprintf(fwts_log *log,
>  */
>  void fwts_log_underline(fwts_log *log, const int ch)
>  {
> -       if (log && log->magic == LOG_MAGIC &&
> -           log->ops && log->ops->underline)
> -               log->ops->underline(log, ch);
> +       if (log && log->magic == LOG_MAGIC) {
> +               fwts_list_link *item;
> +
> +               fwts_list_foreach(item, &log->log_files) {
> +                       fwts_log_file *log_file = fwts_list_data(fwts_log_file *, item);
> +
> +                       if (log_file->ops && log_file->ops->underline)
> +                               log_file->ops->underline(log_file, ch);
> +               }
> +       }
>  }
>
>  /*
> @@ -376,9 +460,17 @@ void fwts_log_underline(fwts_log *log, const int ch)
>  */
>  void fwts_log_newline(fwts_log *log)
>  {
> -       if (log && log->magic == LOG_MAGIC &&
> -           log->ops && log->ops->underline)
> -               log->ops->newline(log);
> +       if (log && log->magic == LOG_MAGIC) {
> +               fwts_list_link *item;
> +
> +               fwts_list_foreach(item, &log->log_files) {
> +                       fwts_log_file *log_file = fwts_list_data(fwts_log_file *, item);
> +
> +                       if (log_file->ops && log_file->ops->newline)
> +                               log_file->ops->newline(log_file);
> +               }
> +               log->line_number++;
> +       }
>  }
>
>  int fwts_log_set_owner(fwts_log *log, const char *owner)
> @@ -395,96 +487,199 @@ int fwts_log_set_owner(fwts_log *log, const char *owner)
>        return FWTS_ERROR;
>  }
>
> +
> +/*
> + *  fwts_log_section_begin()
> + *     mark a start of a named section.  For structured logging
> + *     such as XML and JSON this pushes a new named tagged section
> + */
>  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);
> +       if (log && log->magic == LOG_MAGIC) {
> +               fwts_list_link *item;
> +
> +               fwts_list_foreach(item, &log->log_files) {
> +                       fwts_log_file *log_file = fwts_list_data(fwts_log_file *, item);
> +
> +                       if (log_file->ops && log_file->ops->section_begin)
> +                               log_file->ops->section_begin(log_file, name);
> +               }
> +       }
>  }
>
> +/*
> + *  fwts_log_section_end()
> + *     mark end of a named section.  For structured logging
> + *     such as XML and JSON this pops the end of a tagged section
> + */
>  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);
> +       if (log && log->magic == LOG_MAGIC) {
> +               fwts_list_link *item;
> +
> +               fwts_list_foreach(item, &log->log_files) {
> +                       fwts_log_file *log_file = fwts_list_data(fwts_log_file *, item);
> +
> +                       if (log_file->ops && log_file->ops->section_end)
> +                               log_file->ops->section_end(log_file);
> +               }
> +       }
>  }
>
>  /*
> - *  fwts_log_open()
> - *     open a log file. if name is stderr or stdout, then attach log to these
> - *     streams.
> + *  fwts_log_get_ops()
> + *     return log ops basedon log type
>  */
> -fwts_log *fwts_log_open(const char *owner, const char *name, const char *mode, fwts_log_type type)
> +static fwts_log_ops *fwts_log_get_ops(fwts_log_type type)
>  {
> -       fwts_log *newlog;
> -
> -       if ((newlog = calloc(1, sizeof(fwts_log))) == NULL)
> -               return NULL;
> -
> -       newlog->magic = LOG_MAGIC;
>        switch (type) {
>        case LOG_TYPE_JSON:
> -               newlog->ops = &fwts_log_json_ops;
> -               break;
> +               return &fwts_log_json_ops;
>        case LOG_TYPE_PLAINTEXT:
> -               newlog->ops = &fwts_log_plaintext_ops;
> -               break;
> +               return &fwts_log_plaintext_ops;
>        case LOG_TYPE_XML:
> -               newlog->ops = &fwts_log_xml_ops;
> -               break;
> +               return &fwts_log_xml_ops;
>        case LOG_TYPE_HTML:
> -               newlog->ops = &fwts_log_html_ops;
> -               break;
> +               return &fwts_log_html_ops;
>        case LOG_TYPE_NONE:
>        default:
> -               newlog->ops = &fwts_log_plaintext_ops;
> -               break;
> +               return &fwts_log_plaintext_ops;
>        }
> +}
>
> -       if (owner) {
> -               if ((newlog->owner = calloc(1, strlen(owner)+1)) == NULL) {
> -                       free(newlog);
> -                       return NULL;
> -               }
> -               strcpy(newlog->owner, owner);
> -       }
> +/*
> + *  fwts_log_get_filename_type()
> + *     determine the filename type
> + */
> +fwts_log_filename_type fwts_log_get_filename_type(const char *filename)
> +{
> +       if (!strcmp(filename, "stderr"))
> +               return LOG_FILENAME_TYPE_STDERR;
> +       else if (!strcmp(filename, "stdout"))
> +               return LOG_FILENAME_TYPE_STDOUT;
> +       else
> +               return LOG_FILENAME_TYPE_FILE;
> +}
> +
> +/*
> + *  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,      /* Creator of the log */
> +       const char *filename,   /* Log file name */
> +       const char *mode,       /* open mode, see fopen() modes */
> +       fwts_log_type type)     /* Log type */
> +{
> +       fwts_log *newlog;
> +       unsigned int i;
> +       char *newname;
>
> -       if (strcmp("stderr", name) == 0)
> -               newlog->fp = stderr;
> -       else if (strcmp("stdout", name) == 0)
> -               newlog->fp = stdout;
> -       else if ((newlog->fp = fopen(name, mode)) == NULL) {
> -               free(newlog);
> +       if ((newlog = calloc(1, sizeof(fwts_log))) == NULL)
>                return NULL;
> -       }
>
> -       if (log_line_width) {
> -               /* User has specified width, so use it */
> -               newlog->line_width = log_line_width;
> -       } else {
> -               newlog->line_width = fwts_tty_width(fileno(newlog->fp), LOG_LINE_WIDTH);
> -       }
> +       newlog->magic = LOG_MAGIC;
>
> -       if (newlog->ops && newlog->ops->open)
> -               newlog->ops->open(newlog);
> +       fwts_log_set_owner(newlog, owner);
> +       fwts_list_init(&newlog->log_files);
> +
> +       /*
> +        *  Scan through and see which log types have been specified
> +        *  and open the log file with the appropriate ops to perform
> +        *  the logging
> +        */
> +       for (i=0; i<32; i++) {
> +               fwts_log_type mask = 1 << i;    /* The log type for this iteration */
> +
> +               /* If set then go and open up a log for this log type */
> +               if (type & mask) {
> +                       fwts_log_file *log_file;
> +
> +                       if ((log_file = calloc(1, sizeof(fwts_log_file))) == NULL) {
> +                               fwts_log_close(newlog);
> +                               return NULL;
> +                       }
> +
> +                       log_file->type = mask;
> +                       log_file->ops  = fwts_log_get_ops(mask);
> +                       log_file->log  = newlog;
> +                       log_file->filename_type = fwts_log_get_filename_type(filename);
> +
> +                       /*
> +                        *  To complicate matters we can have logs being
> +                        *  written to stderr, stdout or two a named file
> +                        */
> +                       switch(log_file->filename_type) {
> +                       case LOG_FILENAME_TYPE_STDERR:
> +                               log_file->fp = stderr;
> +                               break;
> +                       case LOG_FILENAME_TYPE_STDOUT:
> +                               log_file->fp = stdout;
> +                               break;
> +                       case LOG_FILENAME_TYPE_FILE:
> +                               if ((newname = fwts_log_filename(filename, mask)) == NULL) {
> +                                       fwts_log_close(newlog);
> +                                       return NULL;
> +                               }
> +                               log_file->fp = fopen(newname, mode);
> +                               free(newname);
> +
> +                               if (log_file->fp == NULL) {
> +                                       fwts_log_close(newlog);
> +                                       return NULL;
> +                               }
> +                       }
> +
> +                       /* Fix up the log specific line width */
> +                       if (log_line_width) {
> +                               /* User has specified width, so use it */
> +                               log_file->line_width = log_line_width;
> +                       } else {
> +                               log_file->line_width =
> +                                       fwts_tty_width(fileno(log_file->fp), LOG_LINE_WIDTH);
> +                       }
> +
> +                       /* ..and add the log file to the list of logs */
> +                       fwts_list_append(&newlog->log_files, log_file);
> +
> +                       /* ..and do the log specific opening set up */
> +                       if (log_file->ops && log_file->ops->open)
> +                               log_file->ops->open(log_file);
> +               }
> +       }
>
>        return newlog;
>  }
>
>  /*
>  *  fwts_log_close()
> - *     close a log file
> + *     close any opened log files, free up memory
>  */
>  int fwts_log_close(fwts_log *log)
>  {
>        if (log && (log->magic == LOG_MAGIC)) {
> -               if (log->ops && log->ops->close)
> -                       log->ops->close(log);
> +               fwts_list_link *item;
> +
> +               fwts_list_foreach(item, &log->log_files) {
> +                       fwts_log_file *log_file = fwts_list_data(fwts_log_file *, item);
> +
> +                       /* Do the log type specific close */
> +                       if (log_file->ops && log_file->ops->close)
> +                               log_file->ops->close(log_file);
> +
> +                       /* Close opened log file */
> +                       if (log_file->fp &&
> +                           log_file->filename_type == LOG_FILENAME_TYPE_FILE)
> +                               fclose(log_file->fp);
> +               }
> +
> +               /* ..and free log files */
> +               fwts_list_free_items(&log->log_files, free);
>
> -               if (log->fp && (log->fp != stdout && log->fp != stderr))
> -                       fclose(log->fp);
>                if (log->owner)
>                        free(log->owner);
> +
>                free(log);
>        }
>        return FWTS_OK;
> diff --git a/src/lib/src/fwts_log_html.c b/src/lib/src/fwts_log_html.c
> index a70ac21..1ca79b8 100644
> --- a/src/lib/src/fwts_log_html.c
> +++ b/src/lib/src/fwts_log_html.c
> @@ -37,31 +37,31 @@ typedef struct {
>  static fwts_log_html_stack_t html_stack[MAX_HTML_STACK];
>  static int html_stack_index = 0;
>
> -static void fwts_log_html(fwts_log *log, const char *fmt, ...)
> +static void fwts_log_html(fwts_log_file *log_file, const char *fmt, ...)
>  {
>        va_list args;
>
>        va_start(args, fmt);
>
> -       fprintf(log->fp, "%*s", html_stack_index * HTML_INDENT, "");
> -       vfprintf(log->fp, fmt, args);
> +       fprintf(log_file->fp, "%*s", html_stack_index * HTML_INDENT, "");
> +       vfprintf(log_file->fp, fmt, args);
>
>        va_end(args);
>  }
>
>
>  /*
> - *  fwts_log_vprintf_html()
> - *     vprintf to a log
> + *  fwts_log_print_html()
> + *     print to a log
>  */
> -static int fwts_log_vprintf_html(fwts_log *log,
> +static int fwts_log_print_html(
> +       fwts_log_file *log_file,
>        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)
> +       const char *buffer)
>  {
>        char *str;
>        char *style;
> @@ -74,7 +74,7 @@ static int fwts_log_vprintf_html(fwts_log *log,
>        if (field & (LOG_NEWLINE | LOG_SEPARATOR | LOG_DEBUG))
>                return 0;
>
> -       fwts_log_html(log, "<TR>\n");
> +       fwts_log_html(log_file, "<TR>\n");
>
>        if (field & LOG_VERBATUM) {
>                code_start = "<PRE class=style_code>";
> @@ -86,29 +86,24 @@ static int fwts_log_vprintf_html(fwts_log *log,
>
>        switch (field & LOG_FIELD_MASK) {
>        case LOG_ERROR:
> -               fwts_log_html(log, "  <TD class=style_error>Error</TD><TD COLSPAN=2>");
> -               vfprintf(log->fp, fmt, args);
> -               fprintf(log->fp, "</TD>\n");
> +               fwts_log_html(log_file, "  <TD class=style_error>Error</TD>"
> +                       "<TD COLSPAN=2>%s</TD>\n", buffer);
>                break;
>        case LOG_WARNING:
> -               fwts_log_html(log, "  <TD class=style_error>Warning</TD><TD COLSPAN=2 class=style_advice_info>%s", code_start);
> -               vfprintf(log->fp, fmt, args);
> -               fprintf(log->fp, "%s</TD>\n", code_end);
> +               fwts_log_html(log_file, "  <TD class=style_error>Warning</TD>"
> +                       "<TD COLSPAN=2 class=style_advice_info>%s%s%s</TD>\n",
> +                       code_start, buffer, code_end);
>                break;
>        case LOG_HEADING:
> -               fwts_log_html(log, "<TD COLSPAN=2 class=style_heading>%s", code_start);
> -               vfprintf(log->fp, fmt, args);
> -               fprintf(log->fp, "%s</TD>\n", code_end);
> +               fwts_log_html(log_file, "<TD COLSPAN=2 class=style_heading>%s%s%s</TD>\n",
> +                       code_start, buffer, code_end);
>                break;
>        case LOG_INFO:
> -               fwts_log_html(log, "  <TD></TD><TD COLSPAN=2 class=style_infos>%s", code_start);
> -               vfprintf(log->fp, fmt, args);
> -               fprintf(log->fp, "%s</TD>\n", code_end);
> +               fwts_log_html(log_file, "  <TD></TD><TD COLSPAN=2 class=style_infos>%s%s%s</TD>\n",
> +                       code_start, buffer, code_end);
>                break;
>        case LOG_PASSED:
> -               fwts_log_html(log, "<TD class=style_passed>PASSED</TD><TD>");
> -               vfprintf(log->fp, fmt, args);
> -               fprintf(log->fp, "</TD>\n");
> +               fwts_log_html(log_file, "<TD class=style_passed>PASSED</TD><TD>%s</TD>\n", buffer);
>                break;
>        case LOG_FAILED:
>                switch (level) {
> @@ -132,39 +127,33 @@ static int fwts_log_vprintf_html(fwts_log *log,
>                }
>                str = fwts_log_level_to_str(level);
>
> -               fwts_log_html(log, "  <TD%s>%s [%s]</TD>\n", style, *status ? status : "", str);
> -
> -               fwts_log_html(log, "  <TD>");
> -               vfprintf(log->fp, fmt, args);
> -               fprintf(log->fp, "</TD>\n");
> +               fwts_log_html(log_file, "  <TD%s>%s [%s]</TD>\n", style, *status ? status : "", str);
> +               fwts_log_html(log_file, "  <TD>%s</TD>\n", buffer);
>                break;
>
>        case LOG_SKIPPED:
> -               fwts_log_html(log, "<TD class=style_skipped>Skipped</TD><TD>%s", code_start);
> -               vfprintf(log->fp, fmt, args);
> -               fprintf(log->fp, "%s</TD>\n", code_end);
> +               fwts_log_html(log_file, "<TD class=style_skipped>Skipped</TD>"
> +                       "<TD>%s%s%s</TD>\n", code_start, buffer, code_end);
>                break;
>
>        case LOG_SUMMARY:
> -               fwts_log_html(log, "  <TD></TD><TD COLSPAN=2 class=style_summary>%s", code_start);
> -               vfprintf(log->fp, fmt, args);
> -               fprintf(log->fp, "%s</TD>\n", code_end);
> +               fwts_log_html(log_file, "  <TD></TD>"
> +                       "<TD COLSPAN=2 class=style_summary>%s%s%s</TD>\n",
> +                       code_start, buffer, code_end);
>                break;
>
>        case LOG_ADVICE:
> -               fwts_log_html(log, "  <TD class=style_advice>Advice</TD><TD COLSPAN=2 class=style_advice_info>%s", code_start);
> -               vfprintf(log->fp, fmt, args);
> -               fprintf(log->fp, "%s</TD>\n", code_end);
> +               fwts_log_html(log_file, "  <TD class=style_advice>Advice</TD>"
> +                       "<TD COLSPAN=2 class=style_advice_info>%s%s%s</TD>\n",
> +                       code_start, buffer, code_end);
>                break;
>
>        default:
>                break;
>        }
>
> -       fwts_log_html(log, "</TR>\n");
> -       fflush(log->fp);
> -
> -       log->line_number++;
> +       fwts_log_html(log_file, "</TR>\n");
> +       fflush(log_file->fp);
>
>        return 0;
>  }
> @@ -173,7 +162,7 @@ static int fwts_log_vprintf_html(fwts_log *log,
>  *  fwts_log_underline_html()
>  *     write an underline across log, using character ch as the underline
>  */
> -static void fwts_log_underline_html(fwts_log *log, const int ch)
> +static void fwts_log_underline_html(fwts_log_file *log_file, const int ch)
>  {
>        /* No-op for html */
>  }
> @@ -182,26 +171,26 @@ static void fwts_log_underline_html(fwts_log *log, const int ch)
>  *  fwts_log_newline()
>  *     write newline to log
>  */
> -static void fwts_log_newline_html(fwts_log *log)
> +static void fwts_log_newline_html(fwts_log_file *log_file)
>  {
>        /* No-op for html */
>  }
>
> -static void fwts_log_section_begin_html(fwts_log *log, const char *name)
> +static void fwts_log_section_begin_html(fwts_log_file *log_file, const char *name)
>  {
>        html_stack[html_stack_index].name = name;
>
>        if (!strcmp(name, "summary")) {
> -               fwts_log_html(log, "<TR><TD class=style_heading COLSPAN=2>Summary</TD></TR>\n");
> +               fwts_log_html(log_file, "<TR><TD class=style_heading COLSPAN=2>Summary</TD></TR>\n");
>        } else if (!strcmp(name, "heading")) {
> -               fwts_log_html(log, "<TR><TD class=style_heading COLSPAN=2>Firmware Test Suite</TD></TR>\n");
> +               fwts_log_html(log_file, "<TR><TD class=style_heading COLSPAN=2>Firmware Test Suite</TD></TR>\n");
>        } else if (!strcmp(name, "subtest_info")) {
> -               fwts_log_html(log, "<TR><TD class=style_subtest COLSPAN=2></TD></TR>\n");
> +               fwts_log_html(log_file, "<TR><TD class=style_subtest COLSPAN=2></TD></TR>\n");
>        } else if (!strcmp(name, "failure")) {
> -               fwts_log_html(log, "<TR><TD class=style_heading COLSPAN=2></TD></TR>\n");
> +               fwts_log_html(log_file, "<TR><TD class=style_heading COLSPAN=2></TD></TR>\n");
>        }
>
> -       fflush(log->fp);
> +       fflush(log_file->fp);
>
>        if (html_stack_index < MAX_HTML_STACK)
>                html_stack_index++;
> @@ -211,11 +200,11 @@ static void fwts_log_section_begin_html(fwts_log *log, const char *name)
>        }
>  }
>
> -static void fwts_log_section_end_html(fwts_log *log)
> +static void fwts_log_section_end_html(fwts_log_file *log_file)
>  {
>        if (html_stack_index > 0) {
>                html_stack_index--;
> -               fflush(log->fp);
> +               fflush(log_file->fp);
>        } else {
>                fprintf(stderr, "html log stack underflow.\n");
>                exit(EXIT_FAILURE);
> @@ -223,15 +212,15 @@ static void fwts_log_section_end_html(fwts_log *log)
>
>  }
>
> -static void fwts_log_open_html(fwts_log *log)
> +static void fwts_log_open_html(fwts_log_file *log_file)
>  {
> -       fwts_log_html(log, "<HTML>\n");
> -       fwts_log_html(log, "<HEAD>\n");
> -       fwts_log_html(log, "  <TITLE>fwts log</TITLE>\n");
> -       fwts_log_html(log, "</HEAD>\n");
> -       fwts_log_html(log, "<BODY>\n");
> -       fwts_log_html(log, "<STYLE>\n");
> -       fwts_log_html(log,
> +       fwts_log_html(log_file, "<HTML>\n");
> +       fwts_log_html(log_file, "<HEAD>\n");
> +       fwts_log_html(log_file, "  <TITLE>fwts log</TITLE>\n");
> +       fwts_log_html(log_file, "</HEAD>\n");
> +       fwts_log_html(log_file, "<BODY>\n");
> +       fwts_log_html(log_file, "<STYLE>\n");
> +       fwts_log_html(log_file,
>                ".style_critical { background-color: red; font-weight: bold; "
>                "text-align: center; vertical-align: center  }\n"
>                ".style_high { background-color: orange; font-weight: bold; "
> @@ -256,27 +245,27 @@ static void fwts_log_open_html(fwts_log *log)
>                ".style_info { }\n"
>                ".style_code { font-family: \"courier\",\"mono\"; font-size:0.75em; overflow:auto; "
>                "width:90%; line-height:0.82em; font-stretch:extra-condensed; word-wrap:normal }\n");
> -       fwts_log_html(log, "</STYLE>\n");
> -       fflush(log->fp);
> +       fwts_log_html(log_file, "</STYLE>\n");
> +       fflush(log_file->fp);
>
> -       fwts_log_html(log, "<TABLE WIDTH=1024>\n");
> -       fwts_log_html(log, "</TR>\n");
> +       fwts_log_html(log_file, "<TABLE WIDTH=1024>\n");
> +       fwts_log_html(log_file, "</TR>\n");
>
> -       fwts_log_section_begin_html(log, "fwts");
> +       fwts_log_section_begin_html(log_file, "fwts");
>  }
>
> -static void fwts_log_close_html(fwts_log *log)
> +static void fwts_log_close_html(fwts_log_file *log_file)
>  {
> -       fwts_log_section_end_html(log);
> +       fwts_log_section_end_html(log_file);
>
> -       fwts_log_html(log, "</TABLE>\n");
> -       fwts_log_html(log, "</BODY>\n");
> -       fwts_log_html(log, "</HTML>\n");
> -       fflush(log->fp);
> +       fwts_log_html(log_file, "</TABLE>\n");
> +       fwts_log_html(log_file, "</BODY>\n");
> +       fwts_log_html(log_file, "</HTML>\n");
> +       fflush(log_file->fp);
>  }
>
>  fwts_log_ops fwts_log_html_ops = {
> -       .vprintf =       fwts_log_vprintf_html,
> +       .print =         fwts_log_print_html,
>        .underline =     fwts_log_underline_html,
>        .newline =       fwts_log_newline_html,
>        .section_begin = fwts_log_section_begin_html,
> diff --git a/src/lib/src/fwts_log_json.c b/src/lib/src/fwts_log_json.c
> index 208847c..8dd65e4 100644
> --- a/src/lib/src/fwts_log_json.c
> +++ b/src/lib/src/fwts_log_json.c
> @@ -40,19 +40,19 @@ 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
> + *  fwts_log_printf_son()
> + *     print to a log
>  */
> -static int fwts_log_vprintf_json(fwts_log *log,
> +static int fwts_log_print_json(
> +       fwts_log_file *log_file,
>        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)
> +       const char *buffer)
>  {
> -       char buffer[4096];
> +       char tmpbuf[4096];
>        struct tm tm;
>        time_t now;
>        json_object *header;
> @@ -69,13 +69,13 @@ static int fwts_log_vprintf_json(fwts_log *log,
>        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",
> +       json_object_object_add(header, "line_num", json_object_new_int(log_file->log->line_number));
> +       snprintf(tmpbuf, sizeof(tmpbuf), "%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",
> +       json_object_object_add(header, "date", json_object_new_string(tmpbuf));
> +       snprintf(tmpbuf, sizeof(tmpbuf), "%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, "time", json_object_new_string(tmpbuf));
>        json_object_object_add(header, "field_type",
>                json_object_new_string(fwts_log_field_to_str_full(field)));
>
> @@ -92,13 +92,10 @@ static int fwts_log_vprintf_json(fwts_log *log,
>        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;
>  }
>
> @@ -106,7 +103,7 @@ static int fwts_log_vprintf_json(fwts_log *log,
>  *  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)
> +static void fwts_log_underline_json(fwts_log_file *log_file, const int ch)
>  {
>        /* No-op for json */
>  }
> @@ -115,12 +112,12 @@ static void fwts_log_underline_json(fwts_log *log, const int ch)
>  *  fwts_log_newline()
>  *     write newline to log
>  */
> -static void fwts_log_newline_json(fwts_log *log)
> +static void fwts_log_newline_json(fwts_log_file *log_file)
>  {
>        /* No-op for json */
>  }
>
> -static void fwts_log_section_begin_json(fwts_log *log, const char *name)
> +static void fwts_log_section_begin_json(fwts_log_file *log_file, const char *name)
>  {
>        json_object *json_obj;
>        json_object *json_log;
> @@ -132,7 +129,7 @@ static void fwts_log_section_begin_json(fwts_log *log, const char *name)
>        json_stack[json_stack_index].obj = json_obj;
>        json_stack[json_stack_index].log = json_log;
>
> -       if (json_stack_index > 0)
> +       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)
> @@ -143,7 +140,7 @@ static void fwts_log_section_begin_json(fwts_log *log, const char *name)
>        }
>  }
>
> -static void fwts_log_section_end_json(fwts_log *log)
> +static void fwts_log_section_end_json(fwts_log_file *log_file)
>  {
>        if (json_stack_index > 0)
>                json_stack_index--;
> @@ -153,29 +150,30 @@ static void fwts_log_section_end_json(fwts_log *log)
>        }
>  }
>
> -static void fwts_log_open_json(fwts_log *log)
> +static void fwts_log_open_json(fwts_log_file *log_file)
>  {
> -       fwts_log_section_begin_json(log, "fwts");
> +       fwts_log_section_begin_json(log_file, "fwts");
>  }
>
> -static void fwts_log_close_json(fwts_log *log)
> +static void fwts_log_close_json(fwts_log_file *log_file)
>  {
>        const char *str;
>        size_t len;
>
> -       fwts_log_section_end_json(log);
> +       fwts_log_section_end_json(log_file);
>
>        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);
> +       fwrite(str, 1, len, log_file->fp);
> +       fwrite("\n", 1, 1, log_file->fp);
> +       fflush(log_file->fp);
> +
>        json_object_put(json_stack[0].obj);
>  }
>
>  fwts_log_ops fwts_log_json_ops = {
> -       .vprintf =       fwts_log_vprintf_json,
> +       .print =         fwts_log_print_json,
>        .underline =     fwts_log_underline_json,
>        .newline =       fwts_log_newline_json,
>        .section_begin = fwts_log_section_begin_json,
> diff --git a/src/lib/src/fwts_log_plaintext.c b/src/lib/src/fwts_log_plaintext.c
> index 44c443f..7381ae3 100644
> --- a/src/lib/src/fwts_log_plaintext.c
> +++ b/src/lib/src/fwts_log_plaintext.c
> @@ -32,7 +32,8 @@
>  *  fwts_log_header_plaintext()
>  *     format up a tabulated log heading
>  */
> -static int fwts_log_header_plaintext(fwts_log *log,
> +static int fwts_log_header_plaintext(
> +       fwts_log_file *log_file,
>        char *buffer,
>        const int len,
>        const fwts_log_field field,
> @@ -51,7 +52,7 @@ static int fwts_log_header_plaintext(fwts_log *log,
>                        ptr++;
>                        if (!strncmp(ptr, "line", 4)) {
>                                n += snprintf(buffer + n, len - n,
> -                                       "%5.5d", log->line_number);
> +                                       "%5.5d", log_file->log->line_number);
>                                ptr += 4;
>                        }
>                        if (!strncmp(ptr, "date", 4)) {
> @@ -76,8 +77,8 @@ static int fwts_log_header_plaintext(fwts_log *log,
>                                        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);
> +                       if (!strncmp(ptr,"owner", 5) && log_file->log->owner) {
> +                               n += snprintf(buffer + n, len - n, "%-15.15s", log_file->log->owner);
>                                ptr += 5;
>                        }
>                } else {
> @@ -90,19 +91,19 @@ static int fwts_log_header_plaintext(fwts_log *log,
>
>
>  /*
> - *  fwts_log_vprintf()
> - *     vprintf to a log
> + *  fwts_log_print()
> + *     print to a log
>  */
> -static int fwts_log_vprintf_plaintext(fwts_log *log,
> +static int fwts_log_print_plaintext(
> +       fwts_log_file *log_file,
>        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)
> +       const char *buffer)
>  {
> -       char buffer[4096];
> +       char tmpbuf[8192];
>        int n = 0;
>        int header_len;
>        int len = 0;
> @@ -115,15 +116,14 @@ static int fwts_log_vprintf_plaintext(fwts_log *log,
>
>        /* 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);
> +       n = header_len = fwts_log_header_plaintext(log_file, tmpbuf, sizeof(tmpbuf), field, level);
> +       n += snprintf(tmpbuf + n, sizeof(tmpbuf) - n, "%s%s", prefix, buffer);
>
>        /* Break text into multi-lines if necessary */
>        if (field & LOG_VERBATUM)
> -               lines = fwts_list_from_text(buffer + header_len);
> +               lines = fwts_list_from_text(tmpbuf + header_len);
>        else
> -               lines = fwts_format_text(buffer + header_len, log->line_width - header_len);
> +               lines = fwts_format_text(tmpbuf + header_len, log_file->line_width - header_len);
>
>        len = n;
>
> @@ -133,17 +133,16 @@ static int fwts_log_vprintf_plaintext(fwts_log *log,
>                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);
> +                       fwts_log_header_plaintext(log_file, tmpbuf, sizeof(tmpbuf), field, level);
> +                       fwrite(tmpbuf, 1, header_len, log_file->fp);
>                }
> -               fwrite(text, 1, strlen(text), log->fp);
> -               fwrite("\n", 1, 1, log->fp);
> -               fflush(log->fp);
> -               log->line_number++;
> +               fwrite(text, 1, strlen(text), log_file->fp);
> +               fwrite("\n", 1, 1, log_file->fp);
> +               fflush(log_file->fp);
>                len += strlen(text) + 1;
>        }
>        fwts_text_list_free(lines);
> -
> +
>        return len;
>  }
>
> @@ -151,11 +150,11 @@ static int fwts_log_vprintf_plaintext(fwts_log *log,
>  *  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)
> +static void fwts_log_underline_plaintext(fwts_log_file *log_file, const int ch)
>  {
>        int n;
>        char *buffer;
> -       size_t width = log->line_width + 1;
> +       size_t width = log_file->line_width + 1;
>
>        if (!((LOG_SEPARATOR & LOG_FIELD_MASK) & fwts_log_filter))
>                return;
> @@ -165,14 +164,13 @@ static void fwts_log_underline_plaintext(fwts_log *log, const int ch)
>                return; /* Unlikely, and just abort */
>
>        /* Write in leading optional line prefix */
> -       n = fwts_log_header_plaintext(log, buffer, width, LOG_SEPARATOR, LOG_LEVEL_NONE);
> +       n = fwts_log_header_plaintext(log_file, 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++;
> +       fwrite(buffer, 1, width, log_file->fp);
> +       fflush(log_file->fp);
>
>        free(buffer);
>  }
> @@ -181,15 +179,14 @@ static void fwts_log_underline_plaintext(fwts_log *log, const int ch)
>  *  fwts_log_newline_plaintext()
>  *     write newline to log
>  */
> -static void fwts_log_newline_plaintext(fwts_log *log)
> +static void fwts_log_newline_plaintext(fwts_log_file *log_file)
>  {
> -       fwrite("\n", 1, 1, log->fp);
> -       fflush(log->fp);
> -       log->line_number++;
> +       fwrite("\n", 1, 1, log_file->fp);
> +       fflush(log_file->fp);
>  }
>
>  fwts_log_ops fwts_log_plaintext_ops = {
> -       .vprintf =      fwts_log_vprintf_plaintext,
> +       .print =        fwts_log_print_plaintext,
>        .underline =    fwts_log_underline_plaintext,
>        .newline =      fwts_log_newline_plaintext
>  };
> diff --git a/src/lib/src/fwts_log_xml.c b/src/lib/src/fwts_log_xml.c
> index 57b530b..19e5e94 100644
> --- a/src/lib/src/fwts_log_xml.c
> +++ b/src/lib/src/fwts_log_xml.c
> @@ -38,19 +38,18 @@ static fwts_log_xml_stack_t xml_stack[MAX_XML_STACK];
>  static int xml_stack_index = 0;
>
>  /*
> - *  fwts_log_vprintf_xml()
> - *     vprintf to a log
> + *  fwts_log_print_xml()
> + *     print to a log
>  */
> -static int fwts_log_vprintf_xml(fwts_log *log,
> +static int fwts_log_print_xml(
> +       fwts_log_file *log_file,
>        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)
> +       const char *buffer)
>  {
> -       char buffer[4096];
>        struct tm tm;
>        time_t now;
>        char *str;
> @@ -64,21 +63,21 @@ static int fwts_log_vprintf_xml(fwts_log *log,
>        time(&now);
>        localtime_r(&now, &tm);
>
> -       fprintf(log->fp, "%*s<logentry>\n", xml_stack_index * XML_INDENT, "");
> +       fprintf(log_file->fp, "%*s<logentry>\n", xml_stack_index * XML_INDENT, "");
>
> -       fprintf(log->fp, "%*s<line_num>%d</line_num>\n",
> +       fprintf(log_file->fp, "%*s<line_num>%d</line_num>\n",
>                (xml_stack_index + 1) * XML_INDENT,
> -               "", log->line_number);
> +               "", log_file->log->line_number);
>
> -       fprintf(log->fp, "%*s<date>%2.2d/%2.2d/%-2.2d</date>\n",
> +       fprintf(log_file->fp, "%*s<date>%2.2d/%2.2d/%-2.2d</date>\n",
>                (xml_stack_index + 1) * XML_INDENT,
>                "", tm.tm_mday, tm.tm_mon + 1, (tm.tm_year+1900) % 100);
>
> -       fprintf(log->fp, "%*s<time>%2.2d:%2.2d:%2.2d</time>\n",
> +       fprintf(log_file->fp, "%*s<time>%2.2d:%2.2d:%2.2d</time>\n",
>                (xml_stack_index + 1) * XML_INDENT,
>                "", tm.tm_hour, tm.tm_min, tm.tm_sec);
>
> -       fprintf(log->fp, "%*s<field_type>%s</field_type>\n",
> +       fprintf(log_file->fp, "%*s<field_type>%s</field_type>\n",
>                (xml_stack_index + 1) * XML_INDENT,
>                "", fwts_log_field_to_str_full(field));
>
> @@ -86,26 +85,23 @@ static int fwts_log_vprintf_xml(fwts_log *log,
>        if (!strcmp(str, " "))
>                str = "None";
>
> -       fprintf(log->fp, "%*s<level>%s</level>\n",
> +       fprintf(log_file->fp, "%*s<level>%s</level>\n",
>                (xml_stack_index + 1) * XML_INDENT, "", str);
>
> -       fprintf(log->fp, "%*s<status>%s</status>\n",
> +       fprintf(log_file->fp, "%*s<status>%s</status>\n",
>                (xml_stack_index + 1) * XML_INDENT,
>                "", *status ? status : "None");
>
> -       fprintf(log->fp, "%*s<failure_label>%s</failure_label>\n",
> +       fprintf(log_file->fp, "%*s<failure_label>%s</failure_label>\n",
>                (xml_stack_index + 1) * XML_INDENT,
>                "", label && *label ? label : "None");
>
> -       vsnprintf(buffer, sizeof(buffer), fmt, args);
> -       fprintf(log->fp, "%*s<log_text>%s</log_text>\n",
> +       fprintf(log_file->fp, "%*s<log_text>%s</log_text>\n",
>                (xml_stack_index + 1) * XML_INDENT,
>                "", buffer);
>
> -       fprintf(log->fp, "%*s</logentry>\n", xml_stack_index * XML_INDENT, "");
> -       fflush(log->fp);
> -
> -       log->line_number++;
> +       fprintf(log_file->fp, "%*s</logentry>\n", xml_stack_index * XML_INDENT, "");
> +       fflush(log_file->fp);
>
>        return 0;
>  }
> @@ -114,7 +110,7 @@ static int fwts_log_vprintf_xml(fwts_log *log,
>  *  fwts_log_underline_xml()
>  *     write an underline across log, using character ch as the underline
>  */
> -static void fwts_log_underline_xml(fwts_log *log, const int ch)
> +static void fwts_log_underline_xml(fwts_log_file *log_file, const int ch)
>  {
>        /* No-op for xml */
>  }
> @@ -123,17 +119,17 @@ static void fwts_log_underline_xml(fwts_log *log, const int ch)
>  *  fwts_log_newline()
>  *     write newline to log
>  */
> -static void fwts_log_newline_xml(fwts_log *log)
> +static void fwts_log_newline_xml(fwts_log_file *log_file)
>  {
>        /* No-op for xml */
>  }
>
> -static void fwts_log_section_begin_xml(fwts_log *log, const char *name)
> +static void fwts_log_section_begin_xml(fwts_log_file *log_file, const char *name)
>  {
>        xml_stack[xml_stack_index].name = name;
>
> -       fprintf(log->fp, "%*s<%s>\n", xml_stack_index * XML_INDENT, "", name);
> -       fflush(log->fp);
> +       fprintf(log_file->fp, "%*s<%s>\n", xml_stack_index * XML_INDENT, "", name);
> +       fflush(log_file->fp);
>
>        if (xml_stack_index < MAX_XML_STACK)
>                xml_stack_index++;
> @@ -143,13 +139,13 @@ static void fwts_log_section_begin_xml(fwts_log *log, const char *name)
>        }
>  }
>
> -static void fwts_log_section_end_xml(fwts_log *log)
> +static void fwts_log_section_end_xml(fwts_log_file *log_file)
>  {
>        if (xml_stack_index > 0) {
>                xml_stack_index--;
> -               fprintf(log->fp, "%*s</%s>\n", xml_stack_index * XML_INDENT,
> +               fprintf(log_file->fp, "%*s</%s>\n", xml_stack_index * XML_INDENT,
>                        "", xml_stack[xml_stack_index].name);
> -               fflush(log->fp);
> +               fflush(log_file->fp);
>        } else {
>                fprintf(stderr, "xml log stack underflow.\n");
>                exit(EXIT_FAILURE);
> @@ -157,26 +153,26 @@ static void fwts_log_section_end_xml(fwts_log *log)
>
>  }
>
> -static void fwts_log_open_xml(fwts_log *log)
> +static void fwts_log_open_xml(fwts_log_file *log_file)
>  {
>        char *xml_header = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
>
> -       fwrite(xml_header, 1, strlen(xml_header), log->fp);
> -       fflush(log->fp);
> +       fwrite(xml_header, 1, strlen(xml_header), log_file->fp);
> +       fflush(log_file->fp);
>
> -       fwts_log_section_begin_xml(log, "fwts");
> +       fwts_log_section_begin_xml(log_file, "fwts");
>  }
>
> -static void fwts_log_close_xml(fwts_log *log)
> +static void fwts_log_close_xml(fwts_log_file *log_file)
>  {
> -       fwts_log_section_end_xml(log);
> +       fwts_log_section_end_xml(log_file);
>
> -       fwrite("\n", 1, 1, log->fp);
> -       fflush(log->fp);
> +       fwrite("\n", 1, 1, log_file->fp);
> +       fflush(log_file->fp);
>  }
>
>  fwts_log_ops fwts_log_xml_ops = {
> -       .vprintf =       fwts_log_vprintf_xml,
> +       .print =         fwts_log_print_xml,
>        .underline =     fwts_log_underline_xml,
>        .newline =       fwts_log_newline_xml,
>        .section_begin = fwts_log_section_begin_xml,
> --
> 1.7.10.4
>
Acked-by: Keng-Yu Lin <kengyu@canonical.com>
Alex Hung June 18, 2012, 3:12 a.m. UTC | #3
On 06/11/2012 06:00 PM, Colin King wrote:
> From: Colin Ian King<colin.king@canonical.com>
>
> Since we now can output many different log types we should also
> allow for multiple log types to be written during a run.  This involves
> some considerable re-working of the logging engine.
>
> 1. The fw->log_type is now a  bit map of log_types
> 2. We add a list of log types to be written to fwts_log, this is a list
>     of fwts_log_file types.
> 3. We need to re-work the log name handling so that we can open multiple
>     log files with different suffixes depending on the log type.
> 4. To reduce the amount of vsnprintf() of the formatted log output we
>     now handle this in the log fwts_log_printf() and pass down the formatted
>     output to the different logging handlers rather than keep on re-formatting
>     at the lowest logging handler layer.
>
> There are a lot of changing is this patch. I tried to break it down, but
> since there are so many interdependant changes I had to resort to one big
> patch
>
> Signed-off-by: Colin Ian King<colin.king@canonical.com>
> ---
>   src/lib/include/fwts_log.h       |   66 +++++--
>   src/lib/src/fwts_framework.c     |   94 +++++-----
>   src/lib/src/fwts_log.c           |  363 +++++++++++++++++++++++++++++---------
>   src/lib/src/fwts_log_html.c      |  133 +++++++-------
>   src/lib/src/fwts_log_json.c      |   52 +++---
>   src/lib/src/fwts_log_plaintext.c |   63 ++++---
>   src/lib/src/fwts_log_xml.c       |   72 ++++----
>   7 files changed, 527 insertions(+), 316 deletions(-)
>

Acked-by: Alex Hung <alex.hung@canonical.com>
diff mbox

Patch

diff --git a/src/lib/include/fwts_log.h b/src/lib/include/fwts_log.h
index a659baa..513bf88 100644
--- a/src/lib/include/fwts_log.h
+++ b/src/lib/include/fwts_log.h
@@ -23,7 +23,10 @@ 
 #include <stdio.h>
 #include <stdarg.h>
 
-#define LOG_MAGIC	0xfe23ab13
+#include "fwts_list.h"
+
+#define LOG_MAGIC		(0xfe23ab13)
+#define LOG_MAX_BUF_SIZE	(4096)		/* Max output per log line */
 
 typedef enum {
 	LOG_RESULT	    = 0x00000001,
@@ -58,31 +61,56 @@  typedef enum {
 	LOG_LEVEL_INFO      = 0x00000010,
 } fwts_log_level;
 
+/*
+ *  different types of log file
+ */
 typedef enum {
 	LOG_TYPE_NONE       = 0x00000000,
 	LOG_TYPE_PLAINTEXT  = 0x00000001,
 	LOG_TYPE_JSON       = 0x00000002,
-	LOG_TYPE_XML        = 0x00000003,
-	LOG_TYPE_HTML       = 0x00000004,
+	LOG_TYPE_XML        = 0x00000004,
+	LOG_TYPE_HTML       = 0x00000008,
 } fwts_log_type;
 
+/*
+ *   different types of output log
+ */
+typedef enum {
+	LOG_FILENAME_TYPE_STDOUT = 0x00000001,	/* log output to stdout */
+	LOG_FILENAME_TYPE_STDERR = 0x00000002,	/* log output to stderr */
+	LOG_FILENAME_TYPE_FILE   = 0x00000003,	/* log output to a file */
+} fwts_log_filename_type;
+
+/*
+ *  top level log descriptor
+ */
 typedef struct log_t {
-	unsigned int magic;
-	FILE *fp;
-	char *owner;
-	int line_width;
-	int line_number;
-	struct fwts_log_ops_t *ops;
+	unsigned int magic;			/* magic ID of the log */
+	fwts_list log_files;			/* list of fwts_log_file */
+	int line_number;			/* keeps track of the line numbering */
+	char *owner;				/* who is writing to this log */
 } fwts_log;
 
+/*
+ *  info for a specific log type
+ */
+typedef struct {
+	FILE *fp;				/* file descriptor for log */
+	fwts_log *log;				/* parent log struct */
+	fwts_log_type type;			/* log type */
+	fwts_log_filename_type filename_type;	/* log filename type */
+	struct fwts_log_ops_t *ops;		/* log operators */
+	int line_width;				/* width of log in chars */
+} fwts_log_file;
+
 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 *);
+	int (*print)(fwts_log_file *log_file, const fwts_log_field field, const fwts_log_level level, const char *status, const char *label, const char *prefix, const char *buffer);
+	void (*underline)(fwts_log_file *log_file, int ch);
+	void (*newline)(fwts_log_file *log_file);
+	void (*section_begin)(fwts_log_file *log_file, const char *tag);
+	void (*section_end)(fwts_log_file *log_file);
+	void (*open)(fwts_log_file *log_file);
+	void (*close)(fwts_log_file *log_file);
 } fwts_log_ops;
 
 extern fwts_log_ops fwts_log_plaintext_ops;
@@ -116,6 +144,12 @@  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);
+fwts_log_filename_type fwts_log_get_filename_type(const char *name);
+
+static inline int fwts_log_type_count(fwts_log_type type)
+{
+	return __builtin_popcount(type);
+}
 
 #define fwts_log_result(fw, fmt, args...)	\
 	fwts_log_printf(fw->results, LOG_RESULT, LOG_LEVEL_NONE, "", "", "", fmt, ## args)
diff --git a/src/lib/src/fwts_framework.c b/src/lib/src/fwts_framework.c
index f808d8e..a6c2ba2 100644
--- a/src/lib/src/fwts_framework.c
+++ b/src/lib/src/fwts_framework.c
@@ -28,7 +28,8 @@ 
 
 #include "fwts.h"
 
-#define RESULTS_LOG	"results.log"
+/* Suffix ".log", ".xml", etc gets automatically appended */
+#define RESULTS_LOG	"results"
 
 #define FWTS_RUN_ALL_FLAGS		\
 	(FWTS_BATCH |			\
@@ -96,34 +97,6 @@  static const char *fwts_copyright[] = {
 };
 
 /*
- *  fwts_framework_log_suffix()
- *	set the log name suffix
- */
-static void fwts_framework_log_suffix(fwts_framework *fw, const char *suffix)
-{
-	char *ptr;
-	char *new;
-	size_t len;
-
-	/* Locate old suffix and kill it */
-	ptr = rindex(fw->results_logname, '.');
-	if (ptr != NULL)
-		*ptr = '\0';
-
-	/* Space for old log name sans old suffix + new suffix + '.' + '\0' */
-	len = strlen(fw->results_logname) + strlen(suffix) + 2;
-
-	if ((new = calloc(len, 1)) == NULL) {
-		fprintf(stderr, "Cannot allocate log name.\n");
-		exit(EXIT_FAILURE);
-	}
-
-	snprintf(new, len, "%s.%s", fw->results_logname, suffix);
-	free(fw->results_logname);
-	fw->results_logname = new;
-}
-
-/*
  *  fwts_framework_compare_priority()
  *	used to register tests sorted on run priority
  */
@@ -677,7 +650,7 @@  static fwts_framework_test *fwts_framework_test_find(fwts_framework *fw, const c
  *  fwts_framework_log()
  *	log a test result
  */
-void fwts_framework_log(fwts_framework *fw, 
+void fwts_framework_log(fwts_framework *fw,
 	fwts_log_field field,
 	const char *label,
 	fwts_log_level level,
@@ -860,6 +833,39 @@  static int fwts_framework_skip_test_parse(fwts_framework *fw, const char *arg, f
 	return FWTS_OK;
 }
 
+/*
+ *  fwts_framework_log_type_parse()
+ *	parse optarg of comma separated log types
+ */
+static int fwts_framework_log_type_parse(fwts_framework *fw, const char *arg)
+{
+	char *str;
+	char *token;
+	char *saveptr = NULL;
+
+	fw->log_type = 0;
+
+	for (str = (char*)arg; (token = strtok_r(str, ",", &saveptr)) != NULL; str = NULL) {
+		if (!strcmp(token, "plaintext"))
+			fw->log_type |= LOG_TYPE_PLAINTEXT;
+		else if (!strcmp(token, "json"))
+			fw->log_type |= LOG_TYPE_JSON;
+		else if (!strcmp(token, "xml"))
+			fw->log_type |= LOG_TYPE_XML;
+		else if (!strcmp(token, "html"))
+			fw->log_type |= LOG_TYPE_HTML;
+		else {
+			fprintf(stderr, "--log-type can be plaintext, xml, html or json.\n");
+			return FWTS_ERROR;
+		}
+	}
+
+	if (!fw->log_type)
+		fw->log_type = LOG_TYPE_PLAINTEXT;
+
+	return FWTS_OK;
+}
+
 int fwts_framework_options_handler(fwts_framework *fw, int argc, char * const argv[], int option_char, int long_index)
 {
 	switch (option_char) {
@@ -975,22 +981,8 @@  int fwts_framework_options_handler(fwts_framework *fw, int argc, char * const ar
 			fwts_iasl_disassemble_all_to_file(fw);
 			return FWTS_COMPLETE;
 		case 32: /* --log-type */
-			if (!strcmp(optarg, "plaintext")) {
-				fw->log_type = LOG_TYPE_PLAINTEXT;
-				fwts_framework_log_suffix(fw, "log");
-			} else if (!strcmp(optarg, "json")) {
-				fw->log_type = LOG_TYPE_JSON;
-				fwts_framework_log_suffix(fw, "json");
-			} else if (!strcmp(optarg, "xml")) {
-				fw->log_type = LOG_TYPE_XML;
-				fwts_framework_log_suffix(fw, "xml");
-			} else if (!strcmp(optarg, "html")) {
-				fw->log_type = LOG_TYPE_HTML;
-				fwts_framework_log_suffix(fw, "html");
-			} else {
-				fprintf(stderr, "--log-type can be either plaintext, xml, html or json.\n");
-				return FWTS_ERROR;
-			}
+			fwts_framework_log_type_parse(fw, optarg);
+			/* FIX ME - check return */
 			break;
 		}
 		break;
@@ -1136,6 +1128,16 @@  int fwts_framework_args(const int argc, char **argv)
 		goto tidy_close;
 	}
 
+	/* Ensure we have just one log type specified for non-filename logging */
+	if (fwts_log_type_count(fw->log_type) > 1 &&
+	    fwts_log_get_filename_type(fw->results_logname) != LOG_FILENAME_TYPE_FILE) {
+		fprintf(stderr,
+			"Cannot specify more than one log type when "
+			"logging to stderr or stdout\n");
+		ret = FWTS_ERROR;
+		goto tidy_close;
+	}
+
 	/* Results log */
 	if ((fw->results = fwts_log_open("fwts",
 			fw->results_logname,	
diff --git a/src/lib/src/fwts_log.c b/src/lib/src/fwts_log.c
index fea7c41..3ab7930 100644
--- a/src/lib/src/fwts_log.c
+++ b/src/lib/src/fwts_log.c
@@ -315,48 +315,125 @@  void fwts_log_set_format(const char *str)
 }
 
 /*
- *  fwts_log_vprintf()
- *	printf to a log
+ *  fwts_log_type_filename_suffix()
+ *	return a filename suffix on a given log type
  */
-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, ...)
+static char *fwts_log_type_filename_suffix(fwts_log_type type)
 {
-	va_list	args;
-	int ret;
+	switch (type) {
+	case LOG_TYPE_JSON:
+		return ".json";
+	case LOG_TYPE_XML:
+		return ".xml";
+	case LOG_TYPE_HTML:
+		return ".html";
+	case LOG_TYPE_NONE:
+	case LOG_TYPE_PLAINTEXT:
+	default:
+		return ".log";
+	}
+}
+
+/*
+ *  fwts_log_filename_new_suffix()
+ *	return the log name with suffix based on log type
+ */
+static char *fwts_log_filename(const char *filename, fwts_log_type type)
+{
+	char *ptr;
+	char *new_name;
+	char *suffix;
+	size_t suffix_len;
+	size_t trunc_len;
+	size_t filename_len;
+
+	suffix = fwts_log_type_filename_suffix(type);
+	suffix_len = strlen(suffix);
+
+	/*
+	 * Locate an existing suffix, if it is one we recognise
+	 * then remove it and append the appropriate one
+	 */
+	ptr = rindex(filename, '.');
+	if (ptr &&
+		(!strcmp(ptr, ".log") ||
+		 !strcmp(ptr, ".json") ||
+		 !strcmp(ptr, ".xml") ||
+		 !strcmp(ptr, ".html"))) {
+
+		trunc_len = ptr - filename;
+		if ((new_name = calloc(trunc_len + suffix_len + 1, 1)) == NULL) {
+			fprintf(stderr, "Cannot allocate log name.\n");
+			return NULL;
+		}
+		strncpy(new_name, filename, trunc_len);
+		strcat(new_name, suffix); /* strcat OK because calloc zero'd all of new_name */
+		return new_name;
+	}
 
-	va_start(args, fmt);
-	ret = fwts_log_vprintf(log, field, level, status, label, prefix, fmt, args);
-	va_end(args);
+	/*
+	 * We didn't find a suffix or a known suffix, so append
+	 * the appropriate one to the given log filename
+	 */
+	filename_len = strlen(filename);
+	if ((new_name = calloc(filename_len + suffix_len + 1, 1)) == NULL) {
+		fprintf(stderr, "Cannot allocate log name.\n");
+		return NULL;
+	}
 
-	return ret;
+	strcpy(new_name, filename);
+	strcat(new_name, suffix);
+
+	return new_name;
 }
 
 /*
  *  fwts_log_vprintf()
- *	vprintf to a log
+ *	printf to a log
  */
-int fwts_log_vprintf(fwts_log *log,
+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)
+	const char *fmt, ...)
 {
+	va_list	args;
+	int ret = 0;
+
+	char buffer[LOG_MAX_BUF_SIZE];
+
 	if (!((field & LOG_FIELD_MASK) & fwts_log_filter))
-		return 0;
+		return ret;
+
+	if (log && log->magic == LOG_MAGIC) {
+		fwts_list_link *item;
+
+		/*
+		 * With the possibility of having multiple logs being written
+		 * to per call of fwts_log_printf() it is more efficient to
+		 * vsnprintf() here and then pass the formatted output down to
+		 * each log handler rather than re-formatting each time in each
+		 * handler
+		 */
+		va_start(args, fmt);
+		ret = vsnprintf(buffer, sizeof(buffer), fmt, args);
+		if (ret < 0)
+			return ret;
+
+		fwts_list_foreach(item, &log->log_files) {
+			fwts_log_file *log_file = fwts_list_data(fwts_log_file *, item);
+
+			if (log_file->ops && log_file->ops->print)
+				log_file->ops->print(log_file, field, level,
+					status, label, prefix, buffer);
+		}
+		log->line_number++;
 
-	if (log && log->magic == LOG_MAGIC &&
-	    log->ops && log->ops->underline)
-		return log->ops->vprintf(log, field, level, status, label, prefix, fmt, args);
-	else
-		return 0;
+		va_end(args);
+	}
+	return ret;
 }
 
 /*
@@ -365,9 +442,16 @@  int fwts_log_vprintf(fwts_log *log,
  */
 void fwts_log_underline(fwts_log *log, const int ch)
 {
-	if (log && log->magic == LOG_MAGIC &&
-	    log->ops && log->ops->underline)
-		log->ops->underline(log, ch);
+	if (log && log->magic == LOG_MAGIC) {
+		fwts_list_link *item;
+
+		fwts_list_foreach(item, &log->log_files) {
+			fwts_log_file *log_file = fwts_list_data(fwts_log_file *, item);
+
+			if (log_file->ops && log_file->ops->underline)
+				log_file->ops->underline(log_file, ch);
+		}
+	}
 }
 
 /*
@@ -376,9 +460,17 @@  void fwts_log_underline(fwts_log *log, const int ch)
  */
 void fwts_log_newline(fwts_log *log)
 {
-	if (log && log->magic == LOG_MAGIC &&
-	    log->ops && log->ops->underline)
-		log->ops->newline(log);
+	if (log && log->magic == LOG_MAGIC) {
+		fwts_list_link *item;
+
+		fwts_list_foreach(item, &log->log_files) {
+			fwts_log_file *log_file = fwts_list_data(fwts_log_file *, item);
+
+			if (log_file->ops && log_file->ops->newline)
+				log_file->ops->newline(log_file);
+		}
+		log->line_number++;
+	}
 }
 
 int fwts_log_set_owner(fwts_log *log, const char *owner)
@@ -395,96 +487,199 @@  int fwts_log_set_owner(fwts_log *log, const char *owner)
 	return FWTS_ERROR;
 }
 
+
+/*
+ *  fwts_log_section_begin()
+ *	mark a start of a named section.  For structured logging
+ *	such as XML and JSON this pushes a new named tagged section
+ */
 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);
+	if (log && log->magic == LOG_MAGIC) {
+		fwts_list_link *item;
+
+		fwts_list_foreach(item, &log->log_files) {
+			fwts_log_file *log_file = fwts_list_data(fwts_log_file *, item);
+
+			if (log_file->ops && log_file->ops->section_begin)
+				log_file->ops->section_begin(log_file, name);
+		}
+	}
 }
 
+/*
+ *  fwts_log_section_end()
+ *	mark end of a named section.  For structured logging
+ *	such as XML and JSON this pops the end of a tagged section
+ */
 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);
+	if (log && log->magic == LOG_MAGIC) {
+		fwts_list_link *item;
+
+		fwts_list_foreach(item, &log->log_files) {
+			fwts_log_file *log_file = fwts_list_data(fwts_log_file *, item);
+
+			if (log_file->ops && log_file->ops->section_end)
+				log_file->ops->section_end(log_file);
+		}
+	}
 }
 
 /*
- *  fwts_log_open()
- *	open a log file. if name is stderr or stdout, then attach log to these
- *	streams.
+ *  fwts_log_get_ops()
+ *	return log ops basedon log type
  */
-fwts_log *fwts_log_open(const char *owner, const char *name, const char *mode, fwts_log_type type)
+static fwts_log_ops *fwts_log_get_ops(fwts_log_type type)
 {
-	fwts_log *newlog;
-
-	if ((newlog = calloc(1, sizeof(fwts_log))) == NULL)
-		return NULL;
-
-	newlog->magic = LOG_MAGIC;
 	switch (type) {
 	case LOG_TYPE_JSON:
-		newlog->ops = &fwts_log_json_ops;
-		break;
+		return &fwts_log_json_ops;
 	case LOG_TYPE_PLAINTEXT:
-		newlog->ops = &fwts_log_plaintext_ops;
-		break;
+		return &fwts_log_plaintext_ops;
 	case LOG_TYPE_XML:
-		newlog->ops = &fwts_log_xml_ops;
-		break;
+		return &fwts_log_xml_ops;
 	case LOG_TYPE_HTML:
-		newlog->ops = &fwts_log_html_ops;
-		break;
+		return &fwts_log_html_ops;
 	case LOG_TYPE_NONE:
 	default:
-		newlog->ops = &fwts_log_plaintext_ops;
-		break;
+		return &fwts_log_plaintext_ops;
 	}
+}
 
-	if (owner) {
-		if ((newlog->owner = calloc(1, strlen(owner)+1)) == NULL) {
-			free(newlog);
-			return NULL;
-		}
-		strcpy(newlog->owner, owner);
-	}
+/*
+ *  fwts_log_get_filename_type()
+ *	determine the filename type
+ */
+fwts_log_filename_type fwts_log_get_filename_type(const char *filename)
+{
+	if (!strcmp(filename, "stderr"))
+		return LOG_FILENAME_TYPE_STDERR;
+	else if (!strcmp(filename, "stdout"))
+		return LOG_FILENAME_TYPE_STDOUT;
+	else
+		return LOG_FILENAME_TYPE_FILE;
+}
+
+/*
+ *  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,	/* Creator of the log */
+	const char *filename,	/* Log file name */
+	const char *mode,	/* open mode, see fopen() modes */
+	fwts_log_type type)	/* Log type */
+{
+	fwts_log *newlog;
+	unsigned int i;
+	char *newname;
 
-	if (strcmp("stderr", name) == 0)
-		newlog->fp = stderr;
-	else if (strcmp("stdout", name) == 0)
-		newlog->fp = stdout;
-	else if ((newlog->fp = fopen(name, mode)) == NULL) {
-		free(newlog);
+	if ((newlog = calloc(1, sizeof(fwts_log))) == NULL)
 		return NULL;
-	}
 
-	if (log_line_width) {
-		/* User has specified width, so use it */
-		newlog->line_width = log_line_width;
-	} else {
-		newlog->line_width = fwts_tty_width(fileno(newlog->fp), LOG_LINE_WIDTH);
-	}
+	newlog->magic = LOG_MAGIC;
 
-	if (newlog->ops && newlog->ops->open)
-		newlog->ops->open(newlog);
+	fwts_log_set_owner(newlog, owner);
+	fwts_list_init(&newlog->log_files);
+
+	/*
+	 *  Scan through and see which log types have been specified
+	 *  and open the log file with the appropriate ops to perform
+	 *  the logging
+	 */
+	for (i=0; i<32; i++) {
+		fwts_log_type mask = 1 << i;	/* The log type for this iteration */
+
+		/* If set then go and open up a log for this log type */
+		if (type & mask) {
+			fwts_log_file *log_file;
+
+			if ((log_file = calloc(1, sizeof(fwts_log_file))) == NULL) {
+				fwts_log_close(newlog);
+				return NULL;
+			}
+
+			log_file->type = mask;
+			log_file->ops  = fwts_log_get_ops(mask);
+			log_file->log  = newlog;
+			log_file->filename_type = fwts_log_get_filename_type(filename);
+
+			/*
+			 *  To complicate matters we can have logs being
+			 *  written to stderr, stdout or two a named file
+			 */
+			switch(log_file->filename_type) {
+			case LOG_FILENAME_TYPE_STDERR:
+				log_file->fp = stderr;
+				break;
+			case LOG_FILENAME_TYPE_STDOUT:
+				log_file->fp = stdout;
+				break;
+			case LOG_FILENAME_TYPE_FILE:
+				if ((newname = fwts_log_filename(filename, mask)) == NULL) {
+					fwts_log_close(newlog);
+					return NULL;
+				}
+				log_file->fp = fopen(newname, mode);
+				free(newname);
+
+				if (log_file->fp == NULL) {
+					fwts_log_close(newlog);
+					return NULL;
+				}
+			}
+
+			/* Fix up the log specific line width */
+			if (log_line_width) {
+				/* User has specified width, so use it */
+				log_file->line_width = log_line_width;
+			} else {
+				log_file->line_width =
+					fwts_tty_width(fileno(log_file->fp), LOG_LINE_WIDTH);
+			}
+
+			/* ..and add the log file to the list of logs */
+			fwts_list_append(&newlog->log_files, log_file);
+
+			/* ..and do the log specific opening set up */
+			if (log_file->ops && log_file->ops->open)
+				log_file->ops->open(log_file);
+		}
+	}
 
 	return newlog;
 }
 
 /*
  *  fwts_log_close()
- *	close a log file
+ *	close any opened log files, free up memory
  */
 int fwts_log_close(fwts_log *log)
 {
 	if (log && (log->magic == LOG_MAGIC)) {
-		if (log->ops && log->ops->close)
-			log->ops->close(log);
+		fwts_list_link *item;
+
+		fwts_list_foreach(item, &log->log_files) {
+			fwts_log_file *log_file = fwts_list_data(fwts_log_file *, item);
+
+			/* Do the log type specific close */
+			if (log_file->ops && log_file->ops->close)
+				log_file->ops->close(log_file);
+
+			/* Close opened log file */
+			if (log_file->fp &&
+			    log_file->filename_type == LOG_FILENAME_TYPE_FILE)
+				fclose(log_file->fp);
+		}
+
+		/* ..and free log files */
+		fwts_list_free_items(&log->log_files, free);
 
-		if (log->fp && (log->fp != stdout && log->fp != stderr))
-			fclose(log->fp);
 		if (log->owner)
 			free(log->owner);
+
 		free(log);
 	}
 	return FWTS_OK;
diff --git a/src/lib/src/fwts_log_html.c b/src/lib/src/fwts_log_html.c
index a70ac21..1ca79b8 100644
--- a/src/lib/src/fwts_log_html.c
+++ b/src/lib/src/fwts_log_html.c
@@ -37,31 +37,31 @@  typedef struct {
 static fwts_log_html_stack_t html_stack[MAX_HTML_STACK];
 static int html_stack_index = 0;
 
-static void fwts_log_html(fwts_log *log, const char *fmt, ...)
+static void fwts_log_html(fwts_log_file *log_file, const char *fmt, ...)
 {
 	va_list args;
 
 	va_start(args, fmt);
 
-	fprintf(log->fp, "%*s", html_stack_index * HTML_INDENT, "");
-	vfprintf(log->fp, fmt, args);
+	fprintf(log_file->fp, "%*s", html_stack_index * HTML_INDENT, "");
+	vfprintf(log_file->fp, fmt, args);
 
 	va_end(args);
 }
 
 
 /*
- *  fwts_log_vprintf_html()
- *	vprintf to a log
+ *  fwts_log_print_html()
+ *	print to a log
  */
-static int fwts_log_vprintf_html(fwts_log *log,
+static int fwts_log_print_html(
+	fwts_log_file *log_file,
 	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)
+	const char *buffer)
 {
 	char *str;
 	char *style;
@@ -74,7 +74,7 @@  static int fwts_log_vprintf_html(fwts_log *log,
 	if (field & (LOG_NEWLINE | LOG_SEPARATOR | LOG_DEBUG))
 		return 0;
 
-	fwts_log_html(log, "<TR>\n");
+	fwts_log_html(log_file, "<TR>\n");
 
 	if (field & LOG_VERBATUM) {
 		code_start = "<PRE class=style_code>";
@@ -86,29 +86,24 @@  static int fwts_log_vprintf_html(fwts_log *log,
 
 	switch (field & LOG_FIELD_MASK) {
 	case LOG_ERROR:
-		fwts_log_html(log, "  <TD class=style_error>Error</TD><TD COLSPAN=2>");
-		vfprintf(log->fp, fmt, args);
-		fprintf(log->fp, "</TD>\n");
+		fwts_log_html(log_file, "  <TD class=style_error>Error</TD>"
+			"<TD COLSPAN=2>%s</TD>\n", buffer);
 		break;
 	case LOG_WARNING:
-		fwts_log_html(log, "  <TD class=style_error>Warning</TD><TD COLSPAN=2 class=style_advice_info>%s", code_start);
-		vfprintf(log->fp, fmt, args);
-		fprintf(log->fp, "%s</TD>\n", code_end);
+		fwts_log_html(log_file, "  <TD class=style_error>Warning</TD>"
+			"<TD COLSPAN=2 class=style_advice_info>%s%s%s</TD>\n",
+			code_start, buffer, code_end);
 		break;
 	case LOG_HEADING:
-		fwts_log_html(log, "<TD COLSPAN=2 class=style_heading>%s", code_start);
-		vfprintf(log->fp, fmt, args);
-		fprintf(log->fp, "%s</TD>\n", code_end);
+		fwts_log_html(log_file, "<TD COLSPAN=2 class=style_heading>%s%s%s</TD>\n",
+			code_start, buffer, code_end);
 		break;
 	case LOG_INFO:
-		fwts_log_html(log, "  <TD></TD><TD COLSPAN=2 class=style_infos>%s", code_start);
-		vfprintf(log->fp, fmt, args);
-		fprintf(log->fp, "%s</TD>\n", code_end);
+		fwts_log_html(log_file, "  <TD></TD><TD COLSPAN=2 class=style_infos>%s%s%s</TD>\n",
+			code_start, buffer, code_end);
 		break;
 	case LOG_PASSED:
-		fwts_log_html(log, "<TD class=style_passed>PASSED</TD><TD>");
-		vfprintf(log->fp, fmt, args);
-		fprintf(log->fp, "</TD>\n");
+		fwts_log_html(log_file, "<TD class=style_passed>PASSED</TD><TD>%s</TD>\n", buffer);
 		break;
 	case LOG_FAILED:
 		switch (level) {
@@ -132,39 +127,33 @@  static int fwts_log_vprintf_html(fwts_log *log,
 		}
 		str = fwts_log_level_to_str(level);
 
-		fwts_log_html(log, "  <TD%s>%s [%s]</TD>\n", style, *status ? status : "", str);
-
-		fwts_log_html(log, "  <TD>");
-		vfprintf(log->fp, fmt, args);
-		fprintf(log->fp, "</TD>\n");
+		fwts_log_html(log_file, "  <TD%s>%s [%s]</TD>\n", style, *status ? status : "", str);
+		fwts_log_html(log_file, "  <TD>%s</TD>\n", buffer);
 		break;
 
 	case LOG_SKIPPED:
-		fwts_log_html(log, "<TD class=style_skipped>Skipped</TD><TD>%s", code_start);
-		vfprintf(log->fp, fmt, args);
-		fprintf(log->fp, "%s</TD>\n", code_end);
+		fwts_log_html(log_file, "<TD class=style_skipped>Skipped</TD>"
+			"<TD>%s%s%s</TD>\n", code_start, buffer, code_end);
 		break;
 
 	case LOG_SUMMARY:
-		fwts_log_html(log, "  <TD></TD><TD COLSPAN=2 class=style_summary>%s", code_start);
-		vfprintf(log->fp, fmt, args);
-		fprintf(log->fp, "%s</TD>\n", code_end);
+		fwts_log_html(log_file, "  <TD></TD>"
+			"<TD COLSPAN=2 class=style_summary>%s%s%s</TD>\n",
+			code_start, buffer, code_end);
 		break;
 
 	case LOG_ADVICE:
-		fwts_log_html(log, "  <TD class=style_advice>Advice</TD><TD COLSPAN=2 class=style_advice_info>%s", code_start);
-		vfprintf(log->fp, fmt, args);
-		fprintf(log->fp, "%s</TD>\n", code_end);
+		fwts_log_html(log_file, "  <TD class=style_advice>Advice</TD>"
+			"<TD COLSPAN=2 class=style_advice_info>%s%s%s</TD>\n",
+			code_start, buffer, code_end);
 		break;
 
 	default:
 		break;
 	}
 
-	fwts_log_html(log, "</TR>\n");
-	fflush(log->fp);
-
-	log->line_number++;
+	fwts_log_html(log_file, "</TR>\n");
+	fflush(log_file->fp);
 
 	return 0;
 }
@@ -173,7 +162,7 @@  static int fwts_log_vprintf_html(fwts_log *log,
  *  fwts_log_underline_html()
  *	write an underline across log, using character ch as the underline
  */
-static void fwts_log_underline_html(fwts_log *log, const int ch)
+static void fwts_log_underline_html(fwts_log_file *log_file, const int ch)
 {
 	/* No-op for html */
 }
@@ -182,26 +171,26 @@  static void fwts_log_underline_html(fwts_log *log, const int ch)
  *  fwts_log_newline()
  *	write newline to log
  */
-static void fwts_log_newline_html(fwts_log *log)
+static void fwts_log_newline_html(fwts_log_file *log_file)
 {
 	/* No-op for html */
 }
 
-static void fwts_log_section_begin_html(fwts_log *log, const char *name)
+static void fwts_log_section_begin_html(fwts_log_file *log_file, const char *name)
 {
 	html_stack[html_stack_index].name = name;
 
 	if (!strcmp(name, "summary")) {
-		fwts_log_html(log, "<TR><TD class=style_heading COLSPAN=2>Summary</TD></TR>\n");
+		fwts_log_html(log_file, "<TR><TD class=style_heading COLSPAN=2>Summary</TD></TR>\n");
 	} else if (!strcmp(name, "heading")) {
-		fwts_log_html(log, "<TR><TD class=style_heading COLSPAN=2>Firmware Test Suite</TD></TR>\n");
+		fwts_log_html(log_file, "<TR><TD class=style_heading COLSPAN=2>Firmware Test Suite</TD></TR>\n");
 	} else if (!strcmp(name, "subtest_info")) {
-		fwts_log_html(log, "<TR><TD class=style_subtest COLSPAN=2></TD></TR>\n");
+		fwts_log_html(log_file, "<TR><TD class=style_subtest COLSPAN=2></TD></TR>\n");
 	} else if (!strcmp(name, "failure")) {
-		fwts_log_html(log, "<TR><TD class=style_heading COLSPAN=2></TD></TR>\n");
+		fwts_log_html(log_file, "<TR><TD class=style_heading COLSPAN=2></TD></TR>\n");
 	}
 
-	fflush(log->fp);
+	fflush(log_file->fp);
 
 	if (html_stack_index < MAX_HTML_STACK)
 		html_stack_index++;
@@ -211,11 +200,11 @@  static void fwts_log_section_begin_html(fwts_log *log, const char *name)
 	}
 }
 
-static void fwts_log_section_end_html(fwts_log *log)
+static void fwts_log_section_end_html(fwts_log_file *log_file)
 {
 	if (html_stack_index > 0) {
 		html_stack_index--;
-		fflush(log->fp);
+		fflush(log_file->fp);
 	} else {
 		fprintf(stderr, "html log stack underflow.\n");
 		exit(EXIT_FAILURE);
@@ -223,15 +212,15 @@  static void fwts_log_section_end_html(fwts_log *log)
 
 }
 
-static void fwts_log_open_html(fwts_log *log)
+static void fwts_log_open_html(fwts_log_file *log_file)
 {
-	fwts_log_html(log, "<HTML>\n");
-	fwts_log_html(log, "<HEAD>\n");
-	fwts_log_html(log, "  <TITLE>fwts log</TITLE>\n");
-	fwts_log_html(log, "</HEAD>\n");
-	fwts_log_html(log, "<BODY>\n");
-	fwts_log_html(log, "<STYLE>\n");
-	fwts_log_html(log,
+	fwts_log_html(log_file, "<HTML>\n");
+	fwts_log_html(log_file, "<HEAD>\n");
+	fwts_log_html(log_file, "  <TITLE>fwts log</TITLE>\n");
+	fwts_log_html(log_file, "</HEAD>\n");
+	fwts_log_html(log_file, "<BODY>\n");
+	fwts_log_html(log_file, "<STYLE>\n");
+	fwts_log_html(log_file,
 		".style_critical { background-color: red; font-weight: bold; "
 		"text-align: center; vertical-align: center  }\n"
 		".style_high { background-color: orange; font-weight: bold; "
@@ -256,27 +245,27 @@  static void fwts_log_open_html(fwts_log *log)
 		".style_info { }\n"
 		".style_code { font-family: \"courier\",\"mono\"; font-size:0.75em; overflow:auto; "
 		"width:90%; line-height:0.82em; font-stretch:extra-condensed; word-wrap:normal }\n");
-	fwts_log_html(log, "</STYLE>\n");
-	fflush(log->fp);
+	fwts_log_html(log_file, "</STYLE>\n");
+	fflush(log_file->fp);
 
-	fwts_log_html(log, "<TABLE WIDTH=1024>\n");
-	fwts_log_html(log, "</TR>\n");
+	fwts_log_html(log_file, "<TABLE WIDTH=1024>\n");
+	fwts_log_html(log_file, "</TR>\n");
 
-	fwts_log_section_begin_html(log, "fwts");
+	fwts_log_section_begin_html(log_file, "fwts");
 }
 
-static void fwts_log_close_html(fwts_log *log)
+static void fwts_log_close_html(fwts_log_file *log_file)
 {
-	fwts_log_section_end_html(log);
+	fwts_log_section_end_html(log_file);
 
-	fwts_log_html(log, "</TABLE>\n");
-	fwts_log_html(log, "</BODY>\n");
-	fwts_log_html(log, "</HTML>\n");
-	fflush(log->fp);
+	fwts_log_html(log_file, "</TABLE>\n");
+	fwts_log_html(log_file, "</BODY>\n");
+	fwts_log_html(log_file, "</HTML>\n");
+	fflush(log_file->fp);
 }
 
 fwts_log_ops fwts_log_html_ops = {
-	.vprintf = 	 fwts_log_vprintf_html,
+	.print = 	 fwts_log_print_html,
 	.underline =	 fwts_log_underline_html,
 	.newline =	 fwts_log_newline_html,
 	.section_begin = fwts_log_section_begin_html,
diff --git a/src/lib/src/fwts_log_json.c b/src/lib/src/fwts_log_json.c
index 208847c..8dd65e4 100644
--- a/src/lib/src/fwts_log_json.c
+++ b/src/lib/src/fwts_log_json.c
@@ -40,19 +40,19 @@  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
+ *  fwts_log_printf_son()
+ *	print to a log
  */
-static int fwts_log_vprintf_json(fwts_log *log,
+static int fwts_log_print_json(
+	fwts_log_file *log_file,
 	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)
+	const char *buffer)
 {
-	char buffer[4096];
+	char tmpbuf[4096];
 	struct tm tm;
 	time_t now;
 	json_object *header;
@@ -69,13 +69,13 @@  static int fwts_log_vprintf_json(fwts_log *log,
 	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",
+	json_object_object_add(header, "line_num", json_object_new_int(log_file->log->line_number));
+	snprintf(tmpbuf, sizeof(tmpbuf), "%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",
+	json_object_object_add(header, "date", json_object_new_string(tmpbuf));
+	snprintf(tmpbuf, sizeof(tmpbuf), "%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, "time", json_object_new_string(tmpbuf));
 	json_object_object_add(header, "field_type",
 		json_object_new_string(fwts_log_field_to_str_full(field)));
 
@@ -92,13 +92,10 @@  static int fwts_log_vprintf_json(fwts_log *log,
 	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;
 }
 
@@ -106,7 +103,7 @@  static int fwts_log_vprintf_json(fwts_log *log,
  *  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)
+static void fwts_log_underline_json(fwts_log_file *log_file, const int ch)
 {
 	/* No-op for json */
 }
@@ -115,12 +112,12 @@  static void fwts_log_underline_json(fwts_log *log, const int ch)
  *  fwts_log_newline()
  *	write newline to log
  */
-static void fwts_log_newline_json(fwts_log *log)
+static void fwts_log_newline_json(fwts_log_file *log_file)
 {
 	/* No-op for json */
 }
 
-static void fwts_log_section_begin_json(fwts_log *log, const char *name)
+static void fwts_log_section_begin_json(fwts_log_file *log_file, const char *name)
 {
 	json_object *json_obj;
 	json_object *json_log;
@@ -132,7 +129,7 @@  static void fwts_log_section_begin_json(fwts_log *log, const char *name)
 	json_stack[json_stack_index].obj = json_obj;
 	json_stack[json_stack_index].log = json_log;
 
-	if (json_stack_index > 0) 
+	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)
@@ -143,7 +140,7 @@  static void fwts_log_section_begin_json(fwts_log *log, const char *name)
 	}
 }
 
-static void fwts_log_section_end_json(fwts_log *log)
+static void fwts_log_section_end_json(fwts_log_file *log_file)
 {
 	if (json_stack_index > 0)
 		json_stack_index--;
@@ -153,29 +150,30 @@  static void fwts_log_section_end_json(fwts_log *log)
 	}
 }
 
-static void fwts_log_open_json(fwts_log *log)
+static void fwts_log_open_json(fwts_log_file *log_file)
 {
-	fwts_log_section_begin_json(log, "fwts");
+	fwts_log_section_begin_json(log_file, "fwts");
 }
 
-static void fwts_log_close_json(fwts_log *log)
+static void fwts_log_close_json(fwts_log_file *log_file)
 {
 	const char *str;
 	size_t len;
 
-	fwts_log_section_end_json(log);
+	fwts_log_section_end_json(log_file);
 
 	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);
+	fwrite(str, 1, len, log_file->fp);
+	fwrite("\n", 1, 1, log_file->fp);
+	fflush(log_file->fp);
+
 	json_object_put(json_stack[0].obj);
 }
 
 fwts_log_ops fwts_log_json_ops = {
-	.vprintf = 	 fwts_log_vprintf_json,
+	.print = 	 fwts_log_print_json,
 	.underline =	 fwts_log_underline_json,
 	.newline =	 fwts_log_newline_json,
 	.section_begin = fwts_log_section_begin_json,
diff --git a/src/lib/src/fwts_log_plaintext.c b/src/lib/src/fwts_log_plaintext.c
index 44c443f..7381ae3 100644
--- a/src/lib/src/fwts_log_plaintext.c
+++ b/src/lib/src/fwts_log_plaintext.c
@@ -32,7 +32,8 @@ 
  *  fwts_log_header_plaintext()
  *	format up a tabulated log heading
  */
-static int fwts_log_header_plaintext(fwts_log *log,
+static int fwts_log_header_plaintext(
+	fwts_log_file *log_file,
 	char *buffer,
 	const int len,
 	const fwts_log_field field,
@@ -51,7 +52,7 @@  static int fwts_log_header_plaintext(fwts_log *log,
 			ptr++;
 			if (!strncmp(ptr, "line", 4)) {
 				n += snprintf(buffer + n, len - n,
-					"%5.5d", log->line_number);
+					"%5.5d", log_file->log->line_number);
 				ptr += 4;
 			}
 			if (!strncmp(ptr, "date", 4)) {
@@ -76,8 +77,8 @@  static int fwts_log_header_plaintext(fwts_log *log,
 					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);
+			if (!strncmp(ptr,"owner", 5) && log_file->log->owner) {
+				n += snprintf(buffer + n, len - n, "%-15.15s", log_file->log->owner);
 				ptr += 5;
 			}
 		} else {
@@ -90,19 +91,19 @@  static int fwts_log_header_plaintext(fwts_log *log,
 
 
 /*
- *  fwts_log_vprintf()
- *	vprintf to a log
+ *  fwts_log_print()
+ *	print to a log
  */
-static int fwts_log_vprintf_plaintext(fwts_log *log,
+static int fwts_log_print_plaintext(
+	fwts_log_file *log_file,
 	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)
+	const char *buffer)
 {
-	char buffer[4096];
+	char tmpbuf[8192];
 	int n = 0;
 	int header_len;
 	int len = 0;
@@ -115,15 +116,14 @@  static int fwts_log_vprintf_plaintext(fwts_log *log,
 
 	/* 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);
+	n = header_len = fwts_log_header_plaintext(log_file, tmpbuf, sizeof(tmpbuf), field, level);
+	n += snprintf(tmpbuf + n, sizeof(tmpbuf) - n, "%s%s", prefix, buffer);
 
 	/* Break text into multi-lines if necessary */
 	if (field & LOG_VERBATUM)
-		lines = fwts_list_from_text(buffer + header_len);
+		lines = fwts_list_from_text(tmpbuf + header_len);
 	else
-		lines = fwts_format_text(buffer + header_len, log->line_width - header_len);
+		lines = fwts_format_text(tmpbuf + header_len, log_file->line_width - header_len);
 
 	len = n;
 
@@ -133,17 +133,16 @@  static int fwts_log_vprintf_plaintext(fwts_log *log,
 		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);
+			fwts_log_header_plaintext(log_file, tmpbuf, sizeof(tmpbuf), field, level);
+			fwrite(tmpbuf, 1, header_len, log_file->fp);
 		}
-		fwrite(text, 1, strlen(text), log->fp);
-		fwrite("\n", 1, 1, log->fp);
-		fflush(log->fp);
-		log->line_number++;
+		fwrite(text, 1, strlen(text), log_file->fp);
+		fwrite("\n", 1, 1, log_file->fp);
+		fflush(log_file->fp);
 		len += strlen(text) + 1;
 	}
 	fwts_text_list_free(lines);
-	
+
 	return len;
 }
 
@@ -151,11 +150,11 @@  static int fwts_log_vprintf_plaintext(fwts_log *log,
  *  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)
+static void fwts_log_underline_plaintext(fwts_log_file *log_file, const int ch)
 {
 	int n;
 	char *buffer;
-	size_t width = log->line_width + 1;
+	size_t width = log_file->line_width + 1;
 
 	if (!((LOG_SEPARATOR & LOG_FIELD_MASK) & fwts_log_filter))
 		return;
@@ -165,14 +164,13 @@  static void fwts_log_underline_plaintext(fwts_log *log, const int ch)
 		return;	/* Unlikely, and just abort */
 
 	/* Write in leading optional line prefix */
-	n = fwts_log_header_plaintext(log, buffer, width, LOG_SEPARATOR, LOG_LEVEL_NONE);
+	n = fwts_log_header_plaintext(log_file, 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++;
+	fwrite(buffer, 1, width, log_file->fp);
+	fflush(log_file->fp);
 
 	free(buffer);
 }
@@ -181,15 +179,14 @@  static void fwts_log_underline_plaintext(fwts_log *log, const int ch)
  *  fwts_log_newline_plaintext()
  *	write newline to log
  */
-static void fwts_log_newline_plaintext(fwts_log *log)
+static void fwts_log_newline_plaintext(fwts_log_file *log_file)
 {
-	fwrite("\n", 1, 1, log->fp);
-	fflush(log->fp);
-	log->line_number++;
+	fwrite("\n", 1, 1, log_file->fp);
+	fflush(log_file->fp);
 }
 
 fwts_log_ops fwts_log_plaintext_ops = {
-	.vprintf = 	fwts_log_vprintf_plaintext,
+	.print = 	fwts_log_print_plaintext,
 	.underline =	fwts_log_underline_plaintext,
 	.newline =	fwts_log_newline_plaintext
 };
diff --git a/src/lib/src/fwts_log_xml.c b/src/lib/src/fwts_log_xml.c
index 57b530b..19e5e94 100644
--- a/src/lib/src/fwts_log_xml.c
+++ b/src/lib/src/fwts_log_xml.c
@@ -38,19 +38,18 @@  static fwts_log_xml_stack_t xml_stack[MAX_XML_STACK];
 static int xml_stack_index = 0;
 
 /*
- *  fwts_log_vprintf_xml()
- *	vprintf to a log
+ *  fwts_log_print_xml()
+ *	print to a log
  */
-static int fwts_log_vprintf_xml(fwts_log *log,
+static int fwts_log_print_xml(
+	fwts_log_file *log_file,
 	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)
+	const char *buffer)
 {
-	char buffer[4096];
 	struct tm tm;
 	time_t now;
 	char *str;
@@ -64,21 +63,21 @@  static int fwts_log_vprintf_xml(fwts_log *log,
 	time(&now);
 	localtime_r(&now, &tm);
 
-	fprintf(log->fp, "%*s<logentry>\n", xml_stack_index * XML_INDENT, "");
+	fprintf(log_file->fp, "%*s<logentry>\n", xml_stack_index * XML_INDENT, "");
 
-	fprintf(log->fp, "%*s<line_num>%d</line_num>\n",
+	fprintf(log_file->fp, "%*s<line_num>%d</line_num>\n",
 		(xml_stack_index + 1) * XML_INDENT,
-		"", log->line_number);
+		"", log_file->log->line_number);
 
-	fprintf(log->fp, "%*s<date>%2.2d/%2.2d/%-2.2d</date>\n",
+	fprintf(log_file->fp, "%*s<date>%2.2d/%2.2d/%-2.2d</date>\n",
 		(xml_stack_index + 1) * XML_INDENT,
 		"", tm.tm_mday, tm.tm_mon + 1, (tm.tm_year+1900) % 100);
 
-	fprintf(log->fp, "%*s<time>%2.2d:%2.2d:%2.2d</time>\n",
+	fprintf(log_file->fp, "%*s<time>%2.2d:%2.2d:%2.2d</time>\n",
 		(xml_stack_index + 1) * XML_INDENT,
 		"", tm.tm_hour, tm.tm_min, tm.tm_sec);
 
-	fprintf(log->fp, "%*s<field_type>%s</field_type>\n",
+	fprintf(log_file->fp, "%*s<field_type>%s</field_type>\n",
 		(xml_stack_index + 1) * XML_INDENT,
 		"", fwts_log_field_to_str_full(field));
 
@@ -86,26 +85,23 @@  static int fwts_log_vprintf_xml(fwts_log *log,
 	if (!strcmp(str, " "))
 		str = "None";
 
-	fprintf(log->fp, "%*s<level>%s</level>\n",
+	fprintf(log_file->fp, "%*s<level>%s</level>\n",
 		(xml_stack_index + 1) * XML_INDENT, "", str);
 
-	fprintf(log->fp, "%*s<status>%s</status>\n",
+	fprintf(log_file->fp, "%*s<status>%s</status>\n",
 		(xml_stack_index + 1) * XML_INDENT,
 		"", *status ? status : "None");
 
-	fprintf(log->fp, "%*s<failure_label>%s</failure_label>\n",
+	fprintf(log_file->fp, "%*s<failure_label>%s</failure_label>\n",
 		(xml_stack_index + 1) * XML_INDENT,
 		"", label && *label ? label : "None");
 
-	vsnprintf(buffer, sizeof(buffer), fmt, args);
-	fprintf(log->fp, "%*s<log_text>%s</log_text>\n",
+	fprintf(log_file->fp, "%*s<log_text>%s</log_text>\n",
 		(xml_stack_index + 1) * XML_INDENT,
 		"", buffer);
 
-	fprintf(log->fp, "%*s</logentry>\n", xml_stack_index * XML_INDENT, "");
-	fflush(log->fp);
-
-	log->line_number++;
+	fprintf(log_file->fp, "%*s</logentry>\n", xml_stack_index * XML_INDENT, "");
+	fflush(log_file->fp);
 
 	return 0;
 }
@@ -114,7 +110,7 @@  static int fwts_log_vprintf_xml(fwts_log *log,
  *  fwts_log_underline_xml()
  *	write an underline across log, using character ch as the underline
  */
-static void fwts_log_underline_xml(fwts_log *log, const int ch)
+static void fwts_log_underline_xml(fwts_log_file *log_file, const int ch)
 {
 	/* No-op for xml */
 }
@@ -123,17 +119,17 @@  static void fwts_log_underline_xml(fwts_log *log, const int ch)
  *  fwts_log_newline()
  *	write newline to log
  */
-static void fwts_log_newline_xml(fwts_log *log)
+static void fwts_log_newline_xml(fwts_log_file *log_file)
 {
 	/* No-op for xml */
 }
 
-static void fwts_log_section_begin_xml(fwts_log *log, const char *name)
+static void fwts_log_section_begin_xml(fwts_log_file *log_file, const char *name)
 {
 	xml_stack[xml_stack_index].name = name;
 
-	fprintf(log->fp, "%*s<%s>\n", xml_stack_index * XML_INDENT, "", name);
-	fflush(log->fp);
+	fprintf(log_file->fp, "%*s<%s>\n", xml_stack_index * XML_INDENT, "", name);
+	fflush(log_file->fp);
 
 	if (xml_stack_index < MAX_XML_STACK)
 		xml_stack_index++;
@@ -143,13 +139,13 @@  static void fwts_log_section_begin_xml(fwts_log *log, const char *name)
 	}
 }
 
-static void fwts_log_section_end_xml(fwts_log *log)
+static void fwts_log_section_end_xml(fwts_log_file *log_file)
 {
 	if (xml_stack_index > 0) {
 		xml_stack_index--;
-		fprintf(log->fp, "%*s</%s>\n", xml_stack_index * XML_INDENT,
+		fprintf(log_file->fp, "%*s</%s>\n", xml_stack_index * XML_INDENT,
 			"", xml_stack[xml_stack_index].name);
-		fflush(log->fp);
+		fflush(log_file->fp);
 	} else {
 		fprintf(stderr, "xml log stack underflow.\n");
 		exit(EXIT_FAILURE);
@@ -157,26 +153,26 @@  static void fwts_log_section_end_xml(fwts_log *log)
 
 }
 
-static void fwts_log_open_xml(fwts_log *log)
+static void fwts_log_open_xml(fwts_log_file *log_file)
 {
 	char *xml_header = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
 
-	fwrite(xml_header, 1, strlen(xml_header), log->fp);
-	fflush(log->fp);
+	fwrite(xml_header, 1, strlen(xml_header), log_file->fp);
+	fflush(log_file->fp);
 
-	fwts_log_section_begin_xml(log, "fwts");
+	fwts_log_section_begin_xml(log_file, "fwts");
 }
 
-static void fwts_log_close_xml(fwts_log *log)
+static void fwts_log_close_xml(fwts_log_file *log_file)
 {
-	fwts_log_section_end_xml(log);
+	fwts_log_section_end_xml(log_file);
 
-	fwrite("\n", 1, 1, log->fp);
-	fflush(log->fp);
+	fwrite("\n", 1, 1, log_file->fp);
+	fflush(log_file->fp);
 }
 
 fwts_log_ops fwts_log_xml_ops = {
-	.vprintf = 	 fwts_log_vprintf_xml,
+	.print = 	 fwts_log_print_xml,
 	.underline =	 fwts_log_underline_xml,
 	.newline =	 fwts_log_newline_xml,
 	.section_begin = fwts_log_section_begin_xml,