Linux Event Logging for Enterprise-Class Systems

Writing Plugins for EVLOG

Steve Woodruff <>

An evlog plugin basically is a dynamic library and should be installed under /opt/evlog/plugins. Each plugin should have a configuration file (recommended to be installed under /etc/evlog.d/plugins). During startup, the evlogd's backend manager will dlopen all plugins (files with .so extension under /opt/evlog/plugins).

This document covers the basic steps required to create a plugin for evlogd. Evlogd plugins allow customized processing of events as they are delivered to evlogd.

Each plugin should include this include file:

include "callback.h"

There are three functions which every evlogd plugin must implement:

  1. _evlQueryInterface()
  2. _evlInitCallBack()
  3. _evlInitPlugIn()
  4. a backend function to be called when an event is being logged.

They are described here in detail:

char * _evlQueryInterface(void);

This function returns the name of the plugin's backend function.

char * _evlQueryInterface(void) {
  return strdup("myplugin_func");

Note that this means our callback function's name is "myplugin_func"

void _evlInitCallBack(evl_callback_t cb_func);

Upon loading the plugin, this function is invoked, the function should set the global pointer cb to a callback function defined in evlogd.c.
This callback mainly provides the plugin access a scores of functionality available to the log daemon such as logging directly to the log, forward event record to notify daemon.

evl_callback_t cb = NULL;

void _evlInitCallBack(evl_callback_t cb_func) {
   cb = cb_func;
  /* Initialize plugin's data structure here */

The evl_callback function is defined in callback.h and evlogd.c as follow:

typedef int (*evl_callback_t ) (int cmd, const char *data1, const char *data2);

int evl_callback(int cmd, const char * data1, const char * data2)
        switch(cmd) {
        case CB_WR_NOTIFY:
                writeEvtToNfyDaemon((char *)data1);
        case CB_MK_EVL_REC:
                mk_evl_rec(&evl_log,  LOG_LOGMGMT, 1, LOG_NOTICE, "%s", data1);
                /* Don't do anything */
                fprintf(stderr, "Called back, but do nothing here.\n");

int _evlInitPlugIn(void);

Here is where the bulk of the plugin initialization should be done. It is also where the plugin can check and see whether it should proceed with its own loading. Typically this function calls getConfigStringParam() to check if the plugin is "Enabled" or "Disabled" via a config file. The getConfigStringParam() and some other helper functions are defined in "../shared/rmt_common.h" Typically, the plugin config files are located in /etc/evlog.d/plugins and are formatted as:

# This file contents configuration information for the "myplugin" plugin.
# Disable= <yes/no> - default is yes

If the Disable flag is set to "yes" then the function should return -1 and the plugin will be unloaded.

Another typical use for this function is to spawn a child thread or a process which can be used to do event processing. When an event arrives, the plugin callback function is invoked with the event details. However it is best to forward that event on to a child thread for processing so that control returns to evlogd as quickly as possible. The received event can be passed to the child thread by means of a fifo, socket, message queue, or any other method you can think of. Therefore, initialization of this communication channel (fifo, socket, queue, etc...) should also be done here in _evlInitPlugin().

This function returns 0 on success; otherwise the plugin will be unloaded by evlogd. Here is an example of _evlInitPlugin() which spawns a child thread and uses a fifo for communication between the callback function and the child thread. Note that the details of some initialization functions are omitted here, but their implementation should be obvious. Also note the use of the cb fucntion pointer and CB_MK_EVL_REC to post an event from the plugin:

int _evlInitPlugIn() {
  char disable[4]="yes";
  extern int myplugin_child;
  /* Check to see if this plugin is disabled */
  getConfigStringParam(cevm_conf_path, "Disable", disable, sizeof(disable));
  if (!strcasecmp(disable, "yes")) {
    (* cb) (CB_MK_EVL_REC, "myplugin: plugin is disabled.", NULL);
    return -1; /* return - pending unload */

  /* this is an example of creating an evl record from your plugin! */
  (* cb) (CB_MK_EVL_REC, "myplugin: plugin initialized.", NULL);
  if ((myplugin_child = fork()) < 0) {
    (* cb) (CB_MK_EVL_REC, "myplugin: plugin fork failed.", NULL);
    return -1;
  if (myplugin_child == 0) {
    /* Create a fifo - The child thread will read from this fifo  */
  else {
    sleep(1);  /* give the child a second to create fifo */
    /* open the fifo so when an event arrives we can write it to the fifo */
  return 0;

The next function we'll implement is the actual backend function for our plugin. As we noted in _evlQueryInterface, the function name will be "myplugin_func".

int myplugin_func(const char *data1, const char *data2, evl_callback_t cb_func);

Currently, *data2 is not used, and cb_func is the pointer to the evl_callback function, the same pointer that is passed to the _evlInitCallBack. In the future the third parameter will be deprecated.

The data stored in *data1 is actually a copy of the event buffer. This has two parts: a header and then the rest of the event. Since we want our child thread to do all of the processing of this event, the below example will simply write the event on our fifo.

int myplugin_func(const char *data1, const char *data2, evl_callback_t cb_func){
  struct posix_log_entry *rhdr;
  rhdr = (struct posix_log_entry *) data1;
  if(write(cevm_fifo_fd, data1, rhdr->log_size + REC_HDR_SIZE) !=
                                          rhdr->log_size + REC_HDR_SIZE) {
    (* cb) (CB_MK_EVL_REC, "myplugin: write to fifo failed!", 0);
  /* if you don't return 0, the plugin is unloaded... so be careful! */
  return 0;

Finally, we will implment the child thread which will process the delivered events. Note that it will open up the fifo for reading and then waits forever for an event to be written to the fifo by the backend function!

static void myplugin_childthread() {
  int fifo_fd, bytes_read;
  struct posix_log_entry *rhdr;
  char xbuf[1024];
  char varbuf[POSIX_LOG_ENTRY_MAXLEN];
  char recbuf[POSIX_LOG_ENTRY_MAXLEN];
  myplugin_child_open_fifo();  /* open fifo for reading */
  FD_SET(fifo_fd, &myplugin_all_fds);
  myplugin_maxfd = fifo_fd;
  for (;;) {
    while(FD_ISSET(fifo_fd, &myplugin_all_fds)) {
      bcopy((char *)&myplugin_all_fds, (char *)&myplugin_read_fds,
      if((select(myplugin_maxfd + 1, &myplugin_read_fds, 
                         (fd_set *) 0, (fd_set *) 0, 0)) < 0) {
        sprintf(xbuf, "myplugin plugin: select failed: %s\n", strerror(errno));
        (* cb)(CB_MK_EVL_REC, xbuf, 0);
      else {
        /* an event arrived!  Let's dump out its pre-formatted text */
        memset(varbuf, 0, sizeof(varbuf));
        if((bytes_read = read(fd, buf, MAX_BUFREAD_LEN)) > 0) {
          rhdr = (struct posix_log_entry *)buf;
          memcpy(varbuf, (char *)(buf + REC_HDR_SIZE), rhdr->log_size);
          evl_format_evrec_variable(rhdr, varbuf, recbuf, sizeof(recbuf),
                    (size_t *) &reqlen);
          fprintf(stderr, "EVENT: [%s]\n", recbuf);
        else {
          /* odd.  that shouldn't fail.  maybe a bad fifo? */

To compile our library, we should use a Makefile similiar to this:

INCLUDEDIRS = -I../../../include -I$(KERN_INC) -I../ \
        -I/root/rpmbuild/BUILD/evlog-1.5.0/user/cmd/evlogd \
        -I/root/rpmbuild/BUILD/evlog-1.5.0/user/include \
        -I/usr/src/redhat/BUILD/evlog-1.5.0/user/cmd/evlogd \
LIBDIRS = -L/usr/lib -L../../../lib
LIBS = -levl -lc -lpthread -lrt
PLUGINS_BIN_DIR =$(DESTDIR)/opt/evlog/plugins
# Put your backend plugin name here

all: $(NAME).so
$(NAME).so: $(NAME).os $(SHARED_OS)
        rm -f $(NAME).so
        ld -share $(LIBS) -o $(NAME).so $(NAME).os $(SHARED_OS) -lgcc_s
$(NAME).os: $(NAME).c $(HEADERS)
        $(CC) -c $(CFLAGS_SO) $(DEBUG) -o $(NAME).os $(NAME).c
        $(CC) -c $(CFLAGS_SO) $(DEBUG) -o $(SHARED_OS) $(SHARED_SRC)
        -rm -f $(NAME).so $(NAME).os $(SHARED_OS) *~
clobber: clean
        test -e $(PLUGINS_BIN_DIR) || mkdir -p $(PLUGINS_BIN_DIR)
        install -m 700 $(NAME).so $(PLUGINS_BIN_DIR)
        test -e $(PLUGINS_CONF_DIR) || mkdir -p $(PLUGINS_CONF_DIR)
        install -m 600 $(NAME).conf $(PLUGINS_CONF_DIR)