============================================================================= The logger This document explains the function and hence api of libatalk/util/logger.c it was written on 4th January 2002, by Simon Bazley (sibaz@sibaz.com) ============================================================================= The logger was written to provide a means of storing log messages relating to netatalk in a file rather than using the syslog facility. The feature list was increased to include a number of ideas culminating in the current code. ----------------------------------------------------------------------------- Feature list 1) logging to syslog (old behaviour). 2) logging the file name and line number of the caller. 3) logging to a file. 4) allowing callers to specify what logical area of code the log comes from. 4) maintaining different log levels for different logical areas of code. 5) logging separate files for separate areas if required. ----------------------------------------------------------------------------- Method The logger works by storing an array of data containers each responsible for logging to a single file. Initally the array is initalised containing NULL references to the data, except for the zeroth position with contains the defaults (default data is statically defined in the logger code, but can be overridden easily). As calls are made to the log_setup and syslog_setup functions with unused code area references, new memory is allocated then a pointer to that memory stored in the array. It is intended that all data containers are setup and initialised early on in the program life cycle. There is no means to erase initialised data containers other than by calling the log_close function, which should only be done after the last log messages has been sent. Logger behaviour is unspecified after log_close has been called. ============================================================================= API Functions ============================================================================= ----------------------------------------------------------------------------- void log_init(); Log_init allocates and zeros memory for the global data containers, then sets up then copies the statically defined default loggers into the zero position. It is called by both of the setup functions and will only do anything if the global data hasn't already been initialised. Its only made externally accessibly for completeness, and need never be used. ----------------------------------------------------------------------------- bool log_setup(char *filename, enum loglevels loglevel, enum logtypes logtype, int display_options); log_setup specifies the filename and the loglevel that should be used when logging logs with the specified logtype. The display_options indicate what system stuff should be prepended to each line in the log file, in the same way as the parameters in an openlog call. The options defined in the logger.h file correspond to equivalents in the syslog.h file. The values they represent are equivalent in both files, but should not be assumed. Some logoptions not in syslog have been added, see logger.h for details. ----------------------------------------------------------------------------- void syslog_setup(enum loglevels loglevel, enum logtypes logtype, int display_options, int facility); syslog_setup is equivalent to the log_setup call but setups data to log to the syslogger for the area specified instead of a file. syslog_setup will call openlog for each call to syslog_setup. The facility parameter is stored, but used purely in this call to openlog. I assume it you call syslog_setup multiple times with different values for display_options and facility, only the last call is used. Refer to syslog(3) for more details. The display options are global and are only stored once regardless of the logtype. The value is only used by the logger used in-so-far-as the option is not a feature of the syslogger. The loglevel of the given logtype is stored even though the syslogger is used. The loglevel passed to each call to LOG is translated before being sent to the syslogger. The assumtion is that a log_severe (for example) applies only to netatalk and hence should not be translated to LOG_CRIT, but instead LOG_ERR is used. (this translation is implemented in the function at the end of the logger.c file. This means should you want to you could log debug messages to the syslogger without having to alter the syslogger for every other application. Some logoptions not in syslog have been added, see logger.h for details. ----------------------------------------------------------------------------- void log_close(); log_close simply frees up all the memory and closes any open files. Ideally you could then recall log_setup and start again, but I think its too permanent for that. This also closes up the syslogger. ----------------------------------------------------------------------------- void set_processname(char *processname); set_processname stores the name of the funning process for use in identifying the caller in each line in the log. This is equivalent to the parameter used in the call to openlog, but since it is global for the process it seemed sensible to separate it from every log_setup call. ----------------------------------------------------------------------------- make_log_func set_log_location(char *srcfilename, int srclinenumber); This sets up the temporary variables indicating the caller name and line number logged in calls to make_log_entry. The return value is a pointer to the make_log_entry function, thus enabling a single command to call both functions, hidden behind a macro (the LOG macro, see later). ----------------------------------------------------------------------------- void make_log_entry(enum loglevels loglevel, enum logtypes logtype, char *message, ...); make_log_entry is an equivalent function to syslog except for the logtype parameter. The logtype given indicates which area of code the call has come from and is used to determine which log_setup call is used to log the message. ----------------------------------------------------------------------------- void load_proccessname_from_proc(); This function can be called instead of set_processname, to determine the processname from the /proc file system instead of from a given parameter. This probably only works on linux, but is included because it was the original method used to get the procname. ----------------------------------------------------------------------------- LOG Macro This is actually defined as a call to set_log_location with parameters __FILE__ and __LINE__. This returns a pointer to the make_log_entry function which is then what the compiler will use to pass the given parameters to. What this means on the face of it is the LOG Macro can be considered to be this void LOG(enum loglevels loglevel, enum logtypes logtype, char *message, ...); ----------------------------------------------------------------------------- LogTypes logtypes have been mentioned earlier. They specify the logical area of the code that a log call has come from. This information is then used in a number of ways (see earlier). logtypes is infact an enum defined in the logger.h file. To add a new logtype for a new area of code, add a new item to the logtypes enum, and a corresponding string to the LOGTYPE_STRING_IDENTIFIERS macro. The string identifier is used as part of the message string for each log message. ============================================================================= API in brief ============================================================================= ----------------------------------------------------------------------------- SyslogSetup in brief To setup the logger for use in a new application, call set_processname with the proccess's name, then call syslog_setup with your prefered default loglevel and the default logtype (logtype_default). The display_options and facility are dependant on how you choose to display your logs. For Example: set_processname("afpd"); syslog_setup(log_debug, logtype_default, logoption_ndelay | logoption_pid, logfacility_daemon); this sets the default loglevel to debug and the display options to include the pid with the each log message. The syslogger is told the application is of type LOG_DAEMON Make further calls to syslog_setup if other logtypes should have a different loglevel. ----------------------------------------------------------------------------- LogSetup in brief Logsetup is similar to syslogsetup except multiple calls are more common. First setup the filename and loglevel for the default logtype then call log_setup with the filename and loglevel for any logtypes that should take nondefault parameters. A NULL filename indicates that the default filename should be used (this removes the need to store multiple instances of the same string). For Example: log_setup("/var/log/netatalk.log", log_debug, logtype_default, logoption_ndelay | logoption_pid); ----------------------------------------------------------------------------- Logging in brief Logging is very similar to using the syslogger, just call the LOG macro in your code with the level of that log message, and the area of code it applies to (or logtype_default if you can't think of one), and a message. The message is passed with identical parameters to that in an sprintf statement. Give a format string as first parameter, then any variables in that string as extra parameters. The logger will only log the message if the loglevel given is more severe (lower in value) than that given in the call to logsetup for the given logtype (or default logtype if your given one is uninitialised). For Example: LOG(log_info, logtype_logger, "Logger %d setup complete", n);