Plugin Architecture

Eventgen allows for plugins which extend our core functionality. There are three types of Plugins:

Anatomy of a Plugin

Plugins inherit from a base plugin class and are placed in their appropriate directory, either in Eventgen app itself or inside another Splunk App’s lib/plugins/<type> directory. Let’s take a look at the simplest plugin available to us, the Devnull output plugin:

from outputplugin import OutputPlugin
from logging_config import logger


class DevNullOutputPlugin(OutputPlugin):
    name = 'devnull'
    MAXQUEUELENGTH = 1000
    useOutputQueue = True

    def __init__(self, sample, output_counter=None):
        OutputPlugin.__init__(self, sample, output_counter)
        self.firsttime = True

    def flush(self, q):
        logger.info('flush data to devnull')
        if self.firsttime:
            self.f = open('/dev/null', 'w')
            self.firsttime = False
        buf = '\n'.join(x['_raw'].rstrip() for x in q)
        self.f.write(buf)


def load():
    """Returns an instance of the plugin"""
    return DevNullOutputPlugin

First, we import the OutputPlugin superclass. For output plugins, they define a constant MAXQUEUELENGTH to determine the maximum amount of items in queue before forcing a queue flush.

useOutputQueue is set to True here to use the output queue which functions as a reduce step when you need to maintain a single thread or a limited number of threads outputting data

__init__() is very simple. It calls its superclass init and sets one variable, firsttime. flush() is also very simple. If it’s the first time, open the file /dev/null, otherwise, output the queue by writing it to the already open file.

Every Eventgen plugin defines a class and a load() method. The load() method is a universal function for determinig the class defined in the file.

Now, let’s look at a slightly more complicated plugin, splunkstream.py in lib/plugins/output/splunkstream.py. We’re going to look just at the top of the class as its being defined:

class SplunkStreamOutputPlugin(OutputPlugin):
    MAXQUEUELENGTH = 100

    validSettings = [ 'splunkMethod', 'splunkUser', 'splunkPass', 'splunkHost', 'splunkPort' ]
    complexSettings = { 'splunkMethod': ['http', 'https'] }
    intSettings = [ 'splunkPort' ]

MAXQUEUELENGTH should look normal, but these other class variables need a little explanation.

Configuration Validation

Config validation is a modular system in Eventgen, and plugins must be allowed to specify additional configuration parameters that the main Eventgen will consider valid and store.

Note that eventgen.conf.spec generation is not yet automated, which means plugins must ship with the default distribution and eventgen.conf.spec must be maintained manually. Eventually spec file generation will be automated as well.

The main configuration of Eventgen validates itself by a list of configuration parameters assigned by type, and each of the configuration parameters is validated by that type. The settings list is required:

The following lists are optional and likely to be used by many plugins:

Methods required per plugin type

Each plugin type will define a different method required.

Plugin Type Method Returns Notes
Rater rate() Integer count of events to generate N/A
Generator gen(count, earliest, latest) Success (0) Events get put into an output queue by calling the Sample’s send() or bulksend() methods in the output object.
Output flush(q) Success (0) Gets a deque list q to operate upon and output as configured.

Example Generator Plugin

We reviewed a simple Output Plugin earlier, let’s look at a simple Generator Plugin:

import datetime
from datetime import timedelta

from generatorplugin import GeneratorPlugin
from logging_config import logger


class WindbagGenerator(GeneratorPlugin):
    def __init__(self, sample):
        GeneratorPlugin.__init__(self, sample)

    def gen(self, count, earliest, latest, samplename=None):
        if count < 0:
            logger.warning('Sample size not found for count=-1 and generator=windbag, defaulting to count=60')
            count = 60
        time_interval = timedelta.total_seconds((latest - earliest)) / count
        for i in xrange(count):
            current_time_object = earliest + datetime.timedelta(0, time_interval * (i + 1))
            msg = '{0} -0700 WINDBAG Event {1} of {2}'.format(current_time_object, (i + 1), count)
            self._out.send(msg)
        return 0


def load():
    return WindbagGenerator

For this generator plugin, notice we inherit from GeneratorPlugin instead of OutputPlugin. This plugin is also quite simple.

Secondly, it defines a gen() method, which generates count events between earliest and latest time. In this case, we ignore the timestamp and return just event text. Then we call bulksend. This plugin has several performance optimizations: using a list constructor instead of a loop and using bulksend instead of send. Let’s see how this could be implemented in a slightly less performant but easier to understand way:

    def gen(self, count, earliest, latest, samplename=None):
        for i in xrange(count):
            current_time_object = earliest + datetime.timedelta(0, time_interval * (i + 1))
            msg = '{0} -0700 WINDBAG Event {1} of {2}'.format(current_time_object, (i + 1), count)
            self._out.send(msg)
        return 0

Here, we use send() instead of bulksend() and a loop to make it easier to understand.

Shipping a Plugin

When you’ve developed a plugin that you want to use in your app, shipping it with your app is easy. Place any Eventgen plugin in your Splunk app’s bin/ directory and we’ll search for and find any plugins referenced by a outputMode, generator or rater config statement.