Nagios plugin to parse JSON from an HTTP response

Update 2015-10-07: This plugin has evolved – please check the latest README for up to date details.

 

Hello all !  I wrote a plugin for Nagios that will parse JSON from an HTTP response.  If that sounds interesting to you, feel free to check out my check_http_json repo on Github.  The plugin has been tested with Ruby 1.8.7 and 1.9.3.  Pull requests welcome !

Usage: ./check_http_json.rb -u  -e  -w  -c 
-h, --help                       Help info.
-v, --verbose                    Additional human output.
-u, --uri URI                    Target URI. Incompatible with -f.
    --user USERNAME              HTTP basic authentication username.
    --pass PASSWORD              HTTP basic authentication password.
-f, --file PATH                  Target file. Incompatible with -u.
-e, --element ELEMENT            Desired element (ex. foo=>bar=>ish is foo.bar.ish).
-E, --element_regex REGEX        Desired element expressed as regular expression.
-d, --delimiter CHARACTER        Element delimiter (default is period).
-w, --warn VALUE                 Warning threshold (integer).
-c, --crit VALUE                 Critical threshold (integer).
-r, --result STRING              Expected string result. No need for -w or -c.
-R, --result_regex REGEX         Expected string result expressed as regular expression. No need for -w or -c.
-W, --result_warn STRING         Warning if element is [string]. -C is required.
-C, --result_crit STRING         Critical if element is [string]. -W is required.
-t, --timeout SECONDS            Wait before HTTP timeout.

The --warn and --crit arguments conform to the Nagios threshold format guidelines.

If a simple result of either string or regular expression (-r or -R) is specified :

  • A match is OK and anything else is CRIT.
  • The warn / crit thresholds will be ignored.

If the warn and crit results (-W and -C) are specified :

  • A match is WARN or CRIT and anything else is OK.
  • The warn / crit thresholds will be ignored.

Note that (-r or -R) and (-W and -C) are mutually exclusive.

Note also that the response must be pure JSON. Bad things happen if this isn’t the case.

How you choose to implement the plugin is, of course, up to you.  Here’s one suggestion:

# check json from http
define command{
 command_name    check_http_json-string
 command_line    /etc/nagios3/plugins/check_http_json.rb -u 'http://$HOSTNAME$:$ARG1$/$ARG2$' -e '$ARG3$' -r '$ARG4$'
}
define command{
 command_name    check_http_json-int
 command_line    /etc/nagios3/plugins/check_http_json.rb -u 'http://$HOSTNAME$:$ARG1$/$ARG2$' -e '$ARG3$' -w '$ARG4$' -c '$ARG5$'
}

# make use of http json check
define service{
 service_description     elasticsearch-cluster-status
 check_command           check_http_json-string!9200!_cluster/health!status!green
}
define service{
 service_description     elasticsearch-cluster-nodes
 check_command           check_http_json-int!9200!_cluster/health!number_of_nodes!4:!3:
}

Author: phrawzty

I have a computer.

14 thoughts on “Nagios plugin to parse JSON from an HTTP response”

  1. Dear Dan, may I ask you how to address the entry DOB in the following JSON response using your plugin? Thanks for help and sorry about pointing to such an old post 😉 Regards, Reinhard

    {
    “responseHeader”:{
    “status”:0,
    “QTime”:1,
    “params”:{
    “indent”:”true”,
    “q”:”firstName:MyFirstName ANDnlastName:MyLastName ANDncity:MyCity”,
    “wt”:”json”}},
    “response”:{“numFound”:1,”start”:0,”docs”:[
    {
    “id”:”37858200″,
    “Sex”:”M”,
    “firstName”:”MyFirstName”,
    “lastName”:”MyLastName”,
    “DOB”:”12345678″,
    “zipCode”:”1234″,
    “city”:”MyCity”,
    “streetName”:”MyStreet”,
    “streetNumber”:”1″,
    “Status”:”U”,
    “Wikex_Pers”:”12″,
    “Wikex_PAC”:”12″,
    “Wikex_Raster”:”12″,
    “_version_”:12345678}]
    }}

    Like

    1. Hello,

      You probably tried specifying the element as response.docs.DOB and the plugin couldn’t find it, right? 🙂 Pro tip: you can use the element_regex feature to help you track down cases like this. Consider:

      $ ./check_http_json.rb -f ./reinhard.json -E DOB -r 12345678
      OK: response.docs.0.DOB is 12345678

      As you can see, the dotted path contains a zero, which solves your problem but raises a new question: why is there a zero at all? If you look at the JSON again, you can see that the data structure described by response.docs is an array, which adds a layer of abstraction that needs to be labelled. In Ruby, arrays are referenced positionally (starting with zero), which is exactly where the label comes from.

      Hope that helps!

      Like

  2. Hi! Can you explain with examples, how works the regex argument? I want to evaluate a json key that is similar to another key into the json response. Then, the -E argument give me both of them. How can it match the exact key word using the -E argument? For example, I need to get a json element from “foo” key, but the plugin is giving me the “foo_bar” element because it is placed first in the json order.
    Sorry for my English 😦
    Artur

    Like

    1. Hello! You can see an example above in these very comments. Note that -E is meant to find things which are inexact, so the behaviour is harder to predict. If you already know the exact item (and your comment suggests that this is the case), then you’re better off just specifying that exact item directly to -e.

      Like

      1. Hello! Thanks a lot for your reply!
        I did it. I used the following command:
        ./check_http_json.rb -u ‘http://localhost/info.json’ -E ‘\bfoo\b’ -r ‘A’
        RESULT: OK: First ‘\bfoo\b’ (regex) does match A
        But I wonder now if the plugin can apply for json list objects. I see that the result only was applied for the first element in the json list, and skipped the next one.

        Like

    1. Hi! Thanks a lot! I tried out this option and it worked.
      I have another issue:
      Using this command: ./check_http_json.rb -u ‘http://localhost/info.json’ -E ‘\bfoo\b’ -r ‘A’ –element_regex_global from command line works fine. But when I setting up the icinga2 files, the \b (look at -E option above) symbols are parsed as literal and not as escape symbol (\b wrapping a string is for matching exact text in Ruby, according about I was googled). If I do not use \b, then the plugin match first another json field key like ‘foor_bar’, and I need to skip this.
      I’ve never programming in ruby, so I wonder if there is any trick to do that the plugin reads these escape symbols properly from the icinga2 config files .

      Here the files:

      commands.conf:
      object CheckCommand “check-json-response” {
      command = [ PluginDir + “/check_http_json.rb” ]
      arguments = {
      “-u” = “$url$”
      “-E” = “$json_field_name$”
      “-r” = “$expected_value$”
      “–element_regex_global” = {
      set_if = true
      }
      }
      }

      services.conf:
      apply Service “check-json-response-service” {
      import “generic-service”
      check_command = “check-json-response”
      check_interval = 5
      max_check_attempts = 5
      retry_interval = 2
      vars.url = “http://localhost/info.json”
      vars.json_field_name = “\bfoo\b”
      vars.expected_value = “A”
      assign where host.name == NodeName
      }

      Like

      1. If you’re trying to find an exact string, then why are you using regex in the first place? Just use -e to specify exactly what you’re looking for. It’s hard for me to guess what you want without seeing the JSON in any case. :/

        Like

  3. Because when I use ‘-e’, it matches another json field which its name start with the same string. For instance, I need to match the json field “city”, but the scrips is giving me the field “cityInitial”. Is it clear?
    If we have this json file:
    [{“country”: “Argentina”, “cityInitial”: “CH”, “city”: “Chacabuco”}, … and some others list items …
    ]
    How do you doing to get the exact json field “city” for checking its value?
    Thanks in advance for your support!

    Like

    1. -e should only match the exact string. If it’s matching something else, then I guess that’s a bug, but one that I’m not able to reproduce. If you can supply the source json – or a sanitised snippet thereof – that you’re querying, it would help me to debug.

      Like

      1. Hi! I’ve not seen this comment. For example, use the following json file:
        [{“country”: “Argentina”, “cityInitial”: “BS”, “city”: “Buenos Aires”}, {“country”: “Japan”, “cityInitial”: “TK”, “city”: “Tokyo”}, {“country”: “Australia”, “cityInitial”: “SY”, “city”: “Sydney”}]
        and I would like to check every ‘city’ element for matching ‘Buenos Aires’ as result.

        Like

      2. Oh, I understand the problem now: -e matches the exact field, but only the first response, whereas -E matches all responses, but not the exact field. It’s not really a bug but the implementation isn’t obvious.

        Recall that -E is parsed as a regular expression, so in this case because cityInitial appears first, and because -E city is parsed as -E /.*city.*/, the script correctly matches on cityInitial instead of city. The solution is to provide a more restrictive matching pattern – in your case, it’s as simple as indicating the end of the string (which is done with a dollar sign): -E city$

        This explains the behaviour you’re seeing, but unfortunately it won’t actually solve for your use case. Consider:

        $ cat x.json
        [{"country": "Argentina", "cityInitial": "BS", "city": "Buenos Aires"}, {"country": "Japan", "cityInitial": "TK", "city": "Tokyo"}, {"country": "Australia", "cityInitial": "SY", "city": "Sydney"}]
        
        $ ./check_http_json.rb -v -f ./x.json -E city$ -r "Buenos Aires" --element_regex_global
        + Found city$ as 0.city
        + Found city$ as 1.city
        + Found city$ as 2.city
        + The value of 0.city is Buenos Aires
        + The value of 1.city is Tokyo
        CRIT: 1.city is Tokyo
        

        Combining -E, --element_regex_global, and -r means that all of the results must match the given result string (which isn’t the case for your data set). It sounds like what you want is a way to see if [{"city":"Buenos Aires"}] exists anywhere in the result set, which this script is not currently designed to do. That said, if your data set is static or append-only (i.e. the positions don’t change), you can always pick your item position specifically. Consider:

        $ ./check_http_json.rb -v -f ./x.json -e 0.city -r "Buenos Aires"
        + The value of 0.city is Buenos Aires
        OK: 0.city does match Buenos Aires
        
        $ ./check_http_json.rb -v -f ./x.json -e 1.city -r "Tokyo"
        + The value of 1.city is Tokyo
        OK: 1.city does match Tokyo
        

        Hope that helps (or at least explains things).

        Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s