Linux system development, Software

Simple logger with STDOUT, Files and syslog support for C projects in Linux

In this little note I wanna describe simple logging module for C programs in Linux. I’m using this code for years in my projects. This module supports different type of output “targets” – files, syslog and stdout.

Every printed message is preceded by name of the program, current date/time and type of the message – debug/error/warning/etc.
This is very useful, can improve readability and can provide easely filtering of the required messages. Programmer can configure logging level in a runtime.

Logging module provides set of the functions for configuring and message printing.

/*
 * Logging methods by levels
 */
void log_error(char* format, ...);
void log_warning(char* format, ...);
void log_status(char* format, ...);
void log_debug(char* format, ...);

/*
 * Log level configurator
 * Default is LOG_MAX_LEVEL_ERROR_WARNING_STATUS
 */ 

#define LOG_MAX_LEVEL_ERROR 0
#define LOG_MAX_LEVEL_ERROR_WARNING_STATUS 1
#define LOG_MAX_LEVEL_ERROR_WARNING_STATUS_DEBUG 2

void logger_set_log_level(const int level);

/*
 * Set target type
 * Default is syslog
 */
void logger_reset_state(void);
int logger_set_log_file(const char* filename);
void logger_set_out_stdout();

As you can see logging methods supported variable type of the arguments so you can use this methods like a regular printf providing different types of the formats and argument types.

Different logging levels can be configured which can be used for suppressing some message types if it needed, without program recompilation. Default log level is ERROR/WARNING/STATUS and DEBUG messages is disabled in a normal state.

By default all messages is goes to the syslog. But you can specify separate text file or use regular stdout. In file mode logger automatically handles non-existing files creating new file or appending message to already existing file.

Here is a full source code of this logging module:

#include <syslog.h>
#include <stdio.h>
#include <errno.h>
#include <time.h>
#include <stdarg.h>
#include <string.h>
#include "logger.h"

/*
 * Program name variable is provided by the libc
 */
extern const char* __progname;

/*
 * Logger internal sctructure
 */
struct logger_t {
	int max_log_level;
	int use_stdout;
	FILE* out_file;
	void (*logger_func) (const int level, const char*);
};

#define PROGRAM_NAME __progname

#define LOG_LEVEL_ERROR 0
#define LOG_LEVEL_WARNING 1
#define LOG_LEVEL_STATUS 2
#define LOG_LEVEL_DEBUG 3

/*
 * Prefixes for the different logging levels
 */
#define LOG_PREFIX_ERROR "ERROR"
#define LOG_PREFIX_WARNING "WARNING"
#define LOG_PREFIX_STATUS "STATUS"
#define LOG_PREFIX_DEBUG "DEBUG"

/*
*/  
static struct logger_t log_global_set;

static const char* LOG_LEVELS[] = { LOG_PREFIX_ERROR,
				    LOG_PREFIX_WARNING,
				    LOG_PREFIX_STATUS,
				    LOG_PREFIX_DEBUG };

void print_to_syslog(const int level, const char* message);
void print_to_file(const int level, const char* message);

/*
 * Close remaining file descriptor and reset global params
 */
void cleanup_internal()
{
	if (log_global_set.out_file) {
		if (!log_global_set.use_stdout) {
			fclose(log_global_set.out_file);
		}

		log_global_set.use_stdout = 0;
		log_global_set.out_file = NULL;
	}
}

/*
 * Reset internal state and set syslog as default target
 */ 
void logger_reset_state(void)
{
	log_global_set.max_log_level = LOG_MAX_LEVEL_ERROR_WARNING_STATUS;
	cleanup_internal();
	log_global_set.logger_func = print_to_syslog;
}

/*
 * Print to syslog
 */
void print_to_syslog(const int level, const char* message)
{
	syslog(LOG_INFO, "[%s] %s\n", LOG_LEVELS[level], message);
}

/*
 * Print to file which can be a regular text file or STDOUT "file"
 */
void print_to_file(const int level, const char* message)
{
	struct tm* current_tm;
	time_t time_now;

	time(&time_now);
	current_tm = localtime(&time_now);

	int res = fprintf(log_global_set.out_file,
			"%s: %02i:%02i:%02i [%s] %s\n"
				, PROGRAM_NAME
				, current_tm->tm_hour
				, current_tm->tm_min
				, current_tm->tm_sec
				, LOG_LEVELS[level]
				, message );

	if (res == -1) {
		print_to_syslog(LOG_LEVEL_ERROR, "Unable to write to log file!");
		return;
	}

	fflush(log_global_set.out_file);
}

/*
 */
void logger_set_log_level(const int level)
{
	log_global_set.max_log_level = level;
}

/*
 */
int logger_set_log_file(const char* filename)
{
	cleanup_internal();

	log_global_set.out_file = fopen(filename, "a");

	if (log_global_set.out_file == NULL) {
		log_error("Failed to open file %s error %s", filename, strerror(errno));
		return -1;
	}

	log_global_set.logger_func = print_to_file;

	return 0;
}

/*
 */
void logger_set_out_stdout()
{
	cleanup_internal();

	log_global_set.use_stdout = 1;
	log_global_set.logger_func = print_to_file;
	log_global_set.out_file = stdout;
}

/*
 * Logging functions
 */
void log_generic(const int level, const char* format, va_list args)
{
	char buffer[256];
	vsprintf(buffer, format, args);
	log_global_set.logger_func(level, buffer);
}

void log_error(char *format, ...)
{
	va_list args;
	va_start(args, format);
	log_generic(LOG_LEVEL_ERROR, format, args);
	va_end(args);
}

void log_warning(char *format, ...)
{
	if (log_global_set.max_log_level < LOG_MAX_LEVEL_ERROR_WARNING_STATUS) {
		return;
	}

	va_list args;
	va_start(args, format);
	log_generic(LOG_LEVEL_WARNING, format, args);
	va_end(args);
}

void log_status(char *format, ...)
{
	if (log_global_set.max_log_level < LOG_MAX_LEVEL_ERROR_WARNING_STATUS) {
		return;
	}

	va_list args;
	va_start(args, format);
	log_generic(LOG_LEVEL_STATUS, format, args);
	va_end(args);
}

void log_debug(char *format, ...)
{
	if (log_global_set.max_log_level <  LOG_MAX_LEVEL_ERROR_WARNING_STATUS_DEBUG) {
		return;
	}

	va_list args;
	va_start(args, format);
	log_generic(LOG_LEVEL_DEBUG, format, args);
	va_end(args);
}

And simple usage example

#include "logger.h"

int main()
{
	logger_reset_state();

	log_warning("This message goes to syslog");

	logger_set_out_stdout();

	log_status("Hello!");

	logger_set_log_file("log.txt");

	log_error("Logger in a file mode!");

	return 0;
}

Compile and run:

$ gcc main.c logger.c -o test_logger
$ ./test_logger

You should see greeting message on a console, also you can find log.txt file with appropriate content and grep program message from the syslog.

Thanks for reading!
Hope this logger will be useful in your cool projects 🙂

Tagged , , ,

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.