Deploy:Extend Splunk search language with legacy scripts

From Splunk Wiki

Jump to: navigation, search

This is an example how to extend Splunk search language with legacy scripts you have used for analysis and reporting of logs that are now stored in Splunk. In this example I'm using dummy script called linelength.awk that counts frequencies of different line lengths in it's input, but similar method can be used to extend Splunk search language with more complex real life scripts as well.

Structure of Splunk application looks like this

${SPLUNK_HOME}/etc/apps/linelength/
 +-- bin
 |     linelength.awk
 |     linelength.py
 +-- default
 |     app.conf
 |     commands.conf
 +-- metadata
 |     default.meta
 +-- local

Get the latest source from https://bitbucket.org/kallu/splunkextension

Scripts

  • linelength.awk

Original script doing the magic. Or actually in this case there is no magic in here. There are 2 minor changes I made to make it work better with Splunk; excluding the 1st field that is Splunk timestamp added by python wrapper, and formating output in CSV to make it easier to parse.

#!/usr/bin/awk -f
#
# linelength - count line length frequencies
# (excluding the 1st field which is timestamp added by Splunk)
#
BEGIN { print "timestamp,len,freq"; }

# Read throught the input and keep track of
# the lastest timestamp for each word/field
{ l = length($0) - length($1) - 1; ll[l]++; ts = $1 }

END { for (len in ll) printf "%s,%s,%s\n", ts, len, ll[len] }

  • linelength.py

Python wrapper to run linelength.awk from Splunk

#!/usr/bin/python
import sys, os
import csv
import subprocess
import splunk.Intersplunk
import StringIO

Assume that your legacy script is found in the same directory as your python wrapper

my_prog = os.path.join(sys.path[0],'linelength.awk')

Start reading CSV formated data from STDIN right away. First row must contain column headers so the wrapper script knows what part of it should be forwarded to your script. Remember to set enableheader=false in commands.conf to prevent Splunk from adding it's own header.

reader = csv.DictReader(sys.stdin)

Then spawn your script. For Python subprocess tutorial see http://www.doughellmann.com/PyMOTW/subprocess/

p = subprocess.Popen(my_prog, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

Parse _time and _raw -fields from Splunk and pass them to your script STDIN

for row in reader:
   p.stdin.write(row['_time'] + ' ' + row['_raw'] + '\n')

Wait for the script to terminate and parse it's output

results = []
output = p.communicate()[0]
f = StringIO.StringIO(output)
reader = csv.DictReader(f)

Loop through the results and generate output to Splunk in key=value format that Splunk can parse automatically. You only need to have _raw and _time -fields for Splunk. Everything else is optional. Below code depends on certain fieldnames in CSV output from your script. Use it only as an example how remove and add fields.

for row in reader:
   splunk_time = row['timestamp']
   # delete fields that are not wanted in key=value output for Splunk
   del row['timestamp']
   # generate _raw field of key=value -pairs for Splunk to parse
   # Splunk can automatically recognize and parse key=value -format
   splunk_raw = []
   for key in row:
      splunk_raw.append(key + '=' + row[key])   
   row['_raw'] = ','.join(splunk_raw)
   row['_time'] = splunk_time
  
   results.append(row)
  
splunk.Intersplunk.outputResults(results)

Splunk config

Standard application metadata, nothing special here to see :-)

Here you define a name of new search command and how Splunk should execute your script. Most of these are what they look like, or Splunk docs explain what they do, but streaming attribute is bit different than you would first think. If you set streaming=false script will be run only once and it can receive at maximum 50k events. Setting streaming=true will tell Splunk to use script as "mapper" and spawn multiple instances of it, each receiving a slice of total events. There is no upper limit how many event can be processed by streaming script, but single instance will still receive max. 50k events. Difference between streaming and non-streaming scripts is explained here

[linelength]
type = python
filename = linelength.py
local = false
perf_warn_limit = 0
streaming = true
maxinputs = 0
passauth = false
retainsevents = false
generating = false
overrides_timeorder = true
enableheader = false

Access control and visibility of your application is defined here. The key thing is to export your command. Without this, your new command isn't visible outside of your application, ie. you can not use it in normal Splunk searches.

[commands]
export = system

Using linelength -command in Splunk searches

Now everything is ready and you can use your new search command to count stats from logs. There is one more catch however, if you just do

... | linelength

you won't get the results you expected :-( Remember that note about streaming=true above. As Splunk did spaw multiple instances of script, you have now your results split into pieces. In results you have multiple entries for the same line length. You still need your "reducer" to get overall results. Luckily in this case you can simply sum frequencies and get total results with

... | linelength | stats sum(freq) by len

If your metrics would have included items like "response time of 98th percentile", you can not simply sum, reducer would have been much more difficult (or impossible) to do. In those cases you have 2 options; modify your original script or set streaming=false to make sure only a single instance of script is run (-> don't need reducer) But keeping in mind that will limit you to max. 50k source events.


References

--Kallu 11:57, 10 July 2012 (PDT)

Personal tools
Hot Wiki Topics


About Splunk >
  • Search and navigate IT data from applications, servers and network devices in real-time.
  • Download Splunk