(function() { // TODO(sissel): Write something that will use history.pushState and fall back // to document.location.hash madness. var logstash = { params: { offset: 0, count: 50, }, search: function(query, options) { if (query == undefined || query == "") { return; } /* Default options */ if (typeof(options) == 'undefined') { options = { graph: true }; } var display_query = query.replace("<", "<").replace(">", ">") $("#querystatus, #results h1").html("Loading query '" + display_query + "' (offset:" + logstash.params.offset + ", count:" + logstash.params.count + ") ") //console.log(logstash.params) logstash.params.q = query; document.location.hash = escape(JSON.stringify(logstash.params)); /* Load the search results */ $("#results").load("/api/search?format=html", logstash.params); if (options.graph != false) { /* Load the default histogram graph */ logstash.params.interval = 3600000; /* 1 hour, default */ logstash.histogram(); } /* if options.graph != false */ $("#query").val(logstash.params.q); }, /* search */ histogram: function(tries) { if (typeof(tries) == 'undefined') { tries = 7; } /* GeoCities mode on the graph while waiting ... * This won't likely survive 1.0, but it's fun for now... */ $("#visual").html("
"); jQuery.getJSON("/api/histogram", logstash.params, function(histogram, text, jqxhr) { /* Load the data into the graph */ var flot_data = []; // histogram is an array of { "key": ..., "count": ... } for (var i in histogram) { flot_data.push([parseInt(histogram[i]["key"]), histogram[i]["count"]]) } logstash.plot(flot_data, logstash.params.interval); //console.log(histogram); /* Try to be intelligent about how we choose the histogram interval. * If there are too few data points, try a smaller interval. * If there are too many data points, try a larger interval. * Give up after a few tries and go with the last result. * * This queries the backend several times, but should be reasonably * speedy as this behaves roughly as a binary search. */ //if (flot_data.length < 6 && flot_data.length > 0 && tries > 0) { //console.log("Histogram bucket " + logstash.params.interval + " has only " + flot_data.length + " data points, trying smaller..."); //logstash.params.interval /= 2; //if (logstash.params.interval < 1000) { //tries = 0; /* stop trying, too small... */ //logstash.plot(flot_data, logstash.params.interval); //return; //} //logstash.histogram(tries - 1); //} else if (flot_data.length > 50 && tries > 0) { //console.log("Histogram bucket " + logstash.params.interval + " too many (" + flot_data.length + ") data points, trying larger interval..."); //logstash.params.interval *= 2; //logstash.histogram(tries - 1); //} else { //console.log("Histo:" + logstash.params.interval); //logstash.plot(flot_data, logstash.params.interval); //} }); }, parse_params: function(href) { var query = href.replace(/^[^?]*\?/, ""); if (query == href) { //console.log("No query params in link " + href); /* No query params */ return {}; } //console.log({ "query": query }); var param_list = query.split("&"); params = {}; //console.log({ "Parsed params": params }); for (var p in param_list) { var a = param_list[p].split("="); var key = a[0]; var value = a[1]; params[key] = unescape(value); } return params; }, appendquery: function(query) { var newquery = $("#query").val(); newquery += " " + query; logstash.search(newquery.trim()); }, /* appendquery */ plot: function(data, interval) { var target = $("#visual"); target.css("display", "block"); var plot = $.plot(target, [ { /* data */ data: data, bars: { show: true, barWidth: interval, } } ], { /* options */ xaxis: { mode: "time" }, grid: { hoverable: true, clickable: true }, } ); target.bind("plotclick", function(e, pos, item) { if (item) { start = logstash.ms_to_iso8601(item.datapoint[0]); end = logstash.ms_to_iso8601(item.datapoint[0] + interval); /* Clicking on the graph means a new search, means * we probably don't want to keep the old offset since * the search results will change. */ logstash.params.offset = 0; logstash.appendquery("@timestamp:[" + start + " TO " + end + "]"); } }); }, /* plot */ ms_to_iso8601: function(milliseconds) { /* From: * https://developer.mozilla.org/en/JavaScript/Reference/global_objects/date#Example.3a_ISO_8601_formatted_dates */ var d = new Date(milliseconds); function pad(n){return n<10 ? '0'+n : n} return d.getUTCFullYear()+'-' + pad(d.getUTCMonth()+1)+'-' + pad(d.getUTCDate())+'T' + pad(d.getUTCHours())+':' + pad(d.getUTCMinutes())+':' + pad(d.getUTCSeconds())+'Z' }, }; /* logstash */ window.logstash = logstash; $().ready(function() { if (location.hash.length > 1) { try { logstash.params = JSON.parse(unescape(location.hash.substring(1))); } catch (e) { // Do nothing } logstash.search(logstash.params.q); } else { /* No hash. See if there's a query param. */ var params = logstash.parse_params(location.href); //console.log(params) for (var p in params) { logstash.params[p] = params[p]; } logstash.search(logstash.params.q) } $(window).hashchange(function() { logstash.params = JSON.parse(unescape(location.hash.substring(1))); query = logstash.params.q if (query != $("#query").val()) { scroll(0, 0); logstash.search(query); } }); $("a.pager, a.querychanger").live("click", function() { /* TODO(sissel): Allow 'control click' and 'middle click' to act normally */ var href = $(this).attr("href"); var params = logstash.parse_params(href); for (var p in params) { logstash.params[p] = params[p]; } logstash.search(logstash.params.q, { graph: false }) return false; }); var result_row_selector = "table.results tr.event"; $(result_row_selector).live("click", function() { var data = $("td.message", this).data("full"); if (typeof(data) == "string") { data = JSON.parse(data); } /* Apply template to the dialog */ var query = $("#query").val().replace(/^\s+|\s+$/g, "") var sanitize = function(str) { if (!/^".*"$/.test(str)) { str = '"' + str + '"'; } return escape(str); }; var template = $.template("inspector", "
  • " + "(${type}) ${field}:" + "{{each(idx, val) value}}" + "" + "${val}" + ", " + "{{/each}}" + "
  • "); /* TODO(sissel): recurse through the data */ var fields = new Array(); for (var i in data["@fields"]) { var value = data["@fields"][i] if (/^[, ]*$/.test(value)) { continue; /* Skip empty data fields */ } if (!(value instanceof Array)) { value = [value]; } fields.push( { type: "field", field: i, value: value }) } for (var i in data) { if (i == "@fields") continue; var value = data[i] if (!(value instanceof Array)) { value = [value]; } if (i.charAt(0) == "@") { /* metadata */ fields.push( { type: "metadata", field: i, value: value }); } else { /* data */ if (/^[, ]*$/.test(value)) { continue; /* Skip empty data fields */ } fields.push( { type: "field", field: i, value: value }) } } for (var i in data) { if (i == "_source") { continue; /* already processed this one */ } value = data[i] if (!(value instanceof Array)) { value = [value]; } fields.push( { type: "metadata", field: i, value: value }) } fields.sort(function(a, b) { if (a.type+a.field < b.type+b.field) { return -1; } if (a.type+a.field > b.type+b.field) { return 1; } return 0; }); $(result_row_selector).removeClass("selected") $(this).addClass("selected"); var entry = this; $("#inspector li").remove() $("#inspector") .append($.tmpl("inspector", fields, { "sanitize": sanitize })) .dialog({ width: 400, title: "Fields for this log" , closeOnEscape: true, position: ["right", "top"], }); }); $("#inspector li a").live("click", function(ev) { var field = $(this).data("field"); var value = $(this).data("value"); var query = $("#query"); var newcondition = unescape(field) + ":" + unescape(value); var newquery = query.val(); if (ev.shiftKey) { // Shift-click will make a "and not" condition query.val(newquery + " -" + newcondition) } else { query.val(newquery + " " + newcondition) } logstash.search(query.val()) return false; }); $("#searchbutton").bind("click submit", function(ev) { var query = $("#query").val().replace(/^\s+|\s+$/g, "") /* Search now, we pressed the submit button */ logstash.search(query) return false; }); }); /* $().ready */ })(); /* function scoping */