Logging facilities An easy-to-use, fast and flexible message logging architecture. This is an adaptation of the log4c project, which is dead upstream, and which I was given the permission to fork under the LGPL licence by the authors. log4c itself was loosely based on the Apache project's Log4J, Log4CC, etc. project. Because C is not object oriented, a lot had to change. Overview There is 3 main concepts: category, priority and appender. These three concepts work together to enable developers to log messages according to message type and priority, and to control at runtime how these messages are formatted and where they are reported. Category hierarchy The first and foremost advantage of any logging API over plain printf() resides in its ability to disable certain log statements while allowing others to print unhindered. This capability assumes that the logging space, that is, the space of all possible logging statements, is categorized according to some developer-chosen criteria. This observation led to choosing category as the central concept of the system. Every category is declared by providing a name and an optional parent. If no parent is explicitly named, the root category, LOG_ROOT_CAT is the category's parent. A category is created by a macro call at the top level of a file. A category can be created with any one of the following macros: @GRAS_LOG_NEW_CATEGORY(MyCat); create a new root @GRAS_LOG_NEW_SUBCATEGORY(MyCat, ParentCat); Create a new category being child of the category ParentCat @GRAS_LOG_NEW_DEFAULT_CATEGORY(MyCat); Like GRAS_LOG_NEW_CATEGORY, but the new category is the default one in this file @GRAS_LOG_NEW_DEFAULT_SUBCATEGORY(MyCat, ParentCat); Like GRAS_LOG_NEW_SUBCATEGORY, but the new category is the default one in this file The parent cat can be defined in the same file or in another file, but each category may have only one definition. Typically, there will be a Category for each module and sub-module, so you can independently control logging for each module. Priority A category may be assigned a threshold priorty. The set of priorites are defined by the @gras_log_priority_t enum. Their values are DEBUG, VERBOSE, INFO, WARNING, ERROR and CRITICAL. If a given category is not assigned a threshold priority, then it inherits one from its closest ancestor with an assigned threshold. To ensure that all categories can eventually inherit a threshold, the root category always has an assigned threshold priority. Logging requests are made by invoking a logging macro on a category. All of the macros have a printf-style format string followed by arguments. Because most C compilers do not support vararg macros, there is a version of the macro for any number of arguments from 0 to 6. The macro name ends with the total number of arguments. Here is an example of the most basic type of macro: CLOG5(MyCat, gras_log_priority_warning, "Values are: %d and '%s'", 5, "oops"); This is a logging request with priority WARN. A logging request is said to be enabled if its priority is higher than or equal to the threshold priority of its category. Otherwise, the request is said to be disabled. A category without an assigned priority will inherit one from the hierarchy. It is possible to use any non-negative integer as a priority. If, as in the example, one of the standard priorites is used, then there is a convenience macro that is typically used instead. For example, the above example is equivalent to the shorter: CWARN4(MyCat, "Values are: %d and '%s'", 5, "oops"); Default category If @GRAS_LOG_NEW_DEFAULT_SUBCATEGORY(MyCat, Parent) or @GRAS_LOG_NEW_DEFAULT_CATEGORY(MyCat) is used to create the category, then the even shorter form can be used: WARN3("Values are: %d and '%s'", 5, "oops"); Only one default category can be created per file, though multiple non-defaults can be created and used. Example Here is a more complete example: #include "gras.h" /* create a category and a default subcategory */ GRAS_LOG_NEW_CATEGORY(VSS); GRAS_LOG_NEW_DEFAULT_SUBCATEGORY(SA, VSS); main() { /* Now set the parent's priority. (the string would typcially be a runtime option) */ gras_log_control_set("SA.thresh=3"); /* This request is enabled, because WARNING >= INFO. */ CWARN2(VSS, "Low fuel level."); /* This request is disabled, because DEBUG < INFO. */ CDEBUG2(VSS, "Starting search for nearest gas station."); /* The default category SA inherits its priority from VSS. Thus, the following request is enabled because INFO >= INFO. */ INFO1("Located nearest gas station."); /* This request is disabled, because DEBUG < INFO. */ DEBUG1("Exiting gas station search"); } Configuration Configuration is typically done during program initialization by invoking the gras_log_control_set() method. The control string passed to it typically comes from the command line. Look at the doucmentation for that function for the format of the control string. Performance Clever design insures efficiency. Except for the first invocation, a disabled logging request requires an a single comparison of a static variable to a constant. There is also compile time constant, @GRAS_LOG_STATIC_THRESHOLD, which causes all logging requests with a lower priority to be optimized to 0 cost by the compiler. By setting it to gras_log_priority_infinite, all logging requests are statically disabled and cost nothing. Released executables might typically be compiled with "-DGRAS_LOG_STATIC_THRESHOLD=gras_log_priority_infinite". Appenders Each category has an optional appender. An appender is a pointer to a structure whcih starts with a pointer to a doAppend() function. DoAppend() prints a message to a log. WHen a category is passed a message by one of the logging macros, the category performs the following actions: if the category has an appender, the message is passed to the appender's doAppend() function, if 'willLogToParent' is true for the category, the message is passed to the category's parent. By default, all categories except root have no appender and 'willLogToParent' is true. This situation causes all messages to be logged by the root category's appender. Typically, you would only change the root category's appender when you wanted, say, a different output format. Copying defaultLogAppender.c would be a good start. The default appender function currently prints to stderr, but more would be needed, like the one able to send the logs to a remote dedicated server. Misc and Caveats Do not use any of the macros that start with '_'. The current set of macros force each file to use categories declared in that file. This is intentional. Make the category a child of the file's module category. Log4J has a 'rolling file appender' which you can select with a run-time option and specify the max file size. This would be a nice default for non-kernel applications. Careful, category names are global variables. @gras_log_priority_none: @gras_log_priority_debug: @gras_log_priority_verbose: @gras_log_priority_info: @gras_log_priority_warning: @gras_log_priority_error: @gras_log_priority_critical: @gras_log_priority_infinite: @gras_log_priority_uninitialized: @cs: @Returns: @catName: @catName: @parent: @cname: @cname: @parent: @cname: @cname: @catName: @priority: @cat: @app: @c: @f: @a1: @a2: @a3: @a4: @a5: @a6: @c: @f: @a1: @a2: @a3: @a4: @a5: @a6: @c: @f: @a1: @a2: @a3: @a4: @a5: @a6: @c: @f: @a1: @a2: @a3: @a4: @a5: @a6: @c: @f: @a1: @a2: @a3: @a4: @a5: @a6: @c: @f: @a1: @a2: @a3: @a4: @a5: @a6: @f: @a1: @a2: @a3: @a4: @a5: @a6: @f: @a1: @a2: @a3: @a4: @a5: @a6: @f: @a1: @a2: @a3: @a4: @a5: @a6: @f: @a1: @a2: @a3: @a4: @a5: @a6: @f: @a1: @a2: @a3: @a4: @a5: @a6: @f: @a1: @a2: @a3: @a4: @a5: @a6: