From 45762f497c5a2277f09eee4c54935c4b19e3d35c Mon Sep 17 00:00:00 2001 From: nirenjan Date: Tue, 13 Jul 2021 10:57:22 -0700 Subject: [PATCH] Initial commit --- .gitignore | 37 +++++++++++ LICENSE | 21 +++++++ README.md | 6 ++ pinelog.c | 179 +++++++++++++++++++++++++++++++++++++++++++++++++++++ pinelog.h | 151 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 394 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 pinelog.c create mode 100644 pinelog.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6166c2c --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +# Compiled object files +*.o + +# Generated objects (source, executables, tarballs, etc.) + +# Vim swap files +.*.swp + +# Autotools objects +.deps +.dirstamp +.libs +ar-lib +autom4te.cache +m4 +compile +config.* +configure +depcomp +install-sh +libtool +ltmain.sh +missing +Makefile +Makefile.in +*.la +*.lo +*.m4 +stamp-h1 +tap-driver.sh +test-driver +*.log +*.trs +*.pc + +# Build directory +/build/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ff3c685 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Nirenjan Krishnan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..730bf10 --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +Pinelog - a lightweight logging API +=================================== + +Pinelog is a lightweight logging API for C programs that's designed to be +included in your program source code. Parameters for Pinelog are configured at +build time by means of preprocessor flags. diff --git a/pinelog.c b/pinelog.c new file mode 100644 index 0000000..1f9713a --- /dev/null +++ b/pinelog.c @@ -0,0 +1,179 @@ +/* + * Pinelog lightweight logging library + * + * Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org) + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include + +#include "pinelog.h" + +/********************************************************************** + * Global variables + *********************************************************************/ + +/** Stream buffer */ +static FILE *output_stream = NULL; + +/** Default logging level */ +static int log_level = LOG_LVL_ERROR; + +/* Initialize defaults */ +void log_init_defaults(void) +{ + output_stream = stdout; + log_level = LOG_LVL_ERROR; +} + +int log_set_output_stream(FILE *stream) +{ + if (stream == NULL) { + return EINVAL; + } + + /* If current output stream is not stdout or stderr, then close it */ + if (output_stream != stdout && output_stream != stderr) { + fclose(output_stream); + } + + setlinebuf(stream); + output_stream = stream; + return 0; +} + +int log_set_output_file(const char *file) +{ + FILE *stream; + if (file == NULL) { + return EINVAL; + } + + errno = 0; + stream = fopen(file, "w"); + if (stream == NULL) { + return errno; + } + + return log_set_output_stream(stream); +} + +int log_get_level(void) +{ + return log_level; +} + +int log_set_level(int level) +{ + if (level < LOG_LVL_NONE || level > LOG_LVL_TRACE) { + return EINVAL; + } + + log_level = level; + return 0; +} + +/********************************************************************** + * Configure logging parameters + *********************************************************************/ +#ifndef PINELOG_SHOW_DATE +#define PINELOG_SHOW_DATE 0 +#endif + +#ifndef PINELOG_SHOW_LEVEL +#define PINELOG_SHOW_LEVEL 0 +#endif + +#ifndef PINELOG_SHOW_BACKTRACE +#define PINELOG_SHOW_BACKTRACE 0 +#endif + +/********************************************************************** + * Configure level strings + *********************************************************************/ +#ifndef PINELOG_FATAL +#define PINELOG_FATAL "FATAL" +#endif + +#ifndef PINELOG_ERROR +#define PINELOG_ERROR "ERROR" +#endif + +#ifndef PINELOG_WARNING +#define PINELOG_WARNING "WARNING" +#endif + +#ifndef PINELOG_INFO +#define PINELOG_INFO "INFO" +#endif + +#ifndef PINELOG_DEBUG +#define PINELOG_DEBUG "DEBUG" +#endif + +#ifndef PINELOG_TRACE +#define PINELOG_TRACE "TRACE" +#endif + +/********************************************************************** + * Log the message to the output stream + *********************************************************************/ +void log_message(int level, const char *file, int line, const char *fmt, ...) +{ + va_list ap; + + /* Don't log anything if the level is not severe enough */ + if (level > log_level || level < 0) { + return; + } + + /* Cap the log level */ + if (level > LOG_LVL_TRACE) { + level = LOG_LVL_TRACE; + } + + /* Validate and set output stream */ + if (output_stream == NULL) { + output_stream = stdout; + } + + #if PINELOG_SHOW_DATE + do { + time_t t; + struct tm *tmp; + char date_string[20]; + t = time(NULL); + tmp = localtime(&t); + strftime(date_string, sizeof(date_string), "%F %T ", tmp); + fputs(date_string, out_stream); + } while (0); + #endif + + #if PINELOG_SHOW_LEVEL + do { + static const char *level_strings[] = { + PINELOG_FATAL, + PINELOG_ERROR, + PINELOG_WARNING, + PINELOG_INFO, + PINELOG_DEBUG, + PINELOG_TRACE, + }; + + fputs(level_strings[level], out_stream); + fputs(": ", out_stream); + } while (0) + #endif + + #if PINELOG_SHOW_BACKTRACE + fprintf(out_stream, "%s:%d ", file, line); + #endif + + va_start(ap, fmt); + vfprintf(output_stream, fmt, ap); + va_end(ap); +} diff --git a/pinelog.h b/pinelog.h new file mode 100644 index 0000000..c378484 --- /dev/null +++ b/pinelog.h @@ -0,0 +1,151 @@ +/* + * Pinelog lightweight logging library + * + * Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org) + * + * SPDX-License-Identifier: MIT + */ + +/** + * @file logging.h + * @brief Logging utility library + * + * This file contains the prototypes for the pinelog logging library + * used by any programs that need to log messages. + * + * @author Nirenjan Krishnan (nirenjan@nirenjan.org) + */ +#ifndef LOGGING_H +#define LOGGING_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Logging levels + * + * The log levels indicate the lowest severity level that will actually be + * logged to the logging framework. + */ +enum { + /** No messages will be logged */ + LOG_LVL_NONE = -1, + + /** Only fatal messages will be logged */ + LOG_LVL_FATAL, + + /** Error messages. This is the default log level */ + LOG_LVL_ERROR, + + /** Warning messages */ + LOG_LVL_WARNING, + + /** Informational messages */ + LOG_LVL_INFO, + + /** Debug messages */ + LOG_LVL_DEBUG, + + /** Trace messages */ + LOG_LVL_TRACE, +}; + +/** + * @brief Initialize the logging library and set the defaults + */ +void log_init_defaults(void); + +/** + * @brief Set the output stream. Must be a FILE pointer. + * + * @param[in] stream Pointer to the output stream + * + * @returns 0 on success, EINVAL if the pointer is not valid. + */ +int log_set_output_stream(FILE *stream); + +/** + * @brief Set the output file. + * + * @param[in] file Filename to write to + * + * @returns 0 on success, EINVAL if the filename pointer is not valid, other + * error if the file could not be opened for writing. + */ +int log_set_output_file(const char *file); + +/** + * @brief Set the logging level + * + * @param[in] level Level to filter + * + * @returns 0 on success, EINVAL if the level is not valid + */ +int log_set_level(int level); + +/** + * @brief Get the logging level + * + * @returns the configured logging level + */ +int log_get_level(void); + +/** + * @brief Log a message to the logger + * + * This is the actual function that logs the message. The application should + * never need to call this directly, but instead, should always use the + * \code LOG_* macros. + * + * @param[in] level Level to log the message at + * @param[in] fmt Format string + * + * @returns None + */ +void log_message(int level, const char *file, int line, const char *fmt, ...); + +#define LOG_FATAL(fmt, ...) do { \ + if (LOG_LVL_FATAL <= log_get_level()) { \ + log_message(LOG_LVL_FATAL, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \ + } \ + exit(1); \ +} while (0) + +#define LOG_ERROR(fmt, ...) do { \ + if (LOG_LVL_ERROR <= log_get_level()) { \ + log_message(LOG_LVL_ERROR, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \ + } \ +} while (0) + +#define LOG_WARN(fmt, ...) do { \ + if (LOG_LVL_WARNING <= log_get_level()) { \ + log_message(LOG_LVL_WARNING, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \ + } \ +} while(0) + +#define LOG_INFO(fmt, ...) do { \ + if (LOG_LVL_INFO <= log_get_level()) { \ + log_message(LOG_LVL_INFO, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \ + } \ +} while(0) + +#define LOG_DEBUG(fmt, ...) do { \ + if (LOG_LVL_DEBUG <= log_get_level()) { \ + log_message(LOG_LVL_DEBUG, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \ + } \ +} while(0) + +#define LOG_TRACE(fmt, ...) do { \ + if (LOG_LVL_TRACE <= log_get_level()) { \ + log_message(LOG_LVL_TRACE, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \ + } \ +} while(0) + +#ifdef __cplusplus +} +#endif + +#endif // !defined LOGGING_H