logstash-lite 0.2.20110206003603 → 0.2.20110329105411

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,25 @@
1
+
2
+ require "logstash/namespace"
3
+ require "logstash/logging"
4
+
5
+ class LogStash::Search::FacetResult
6
+ # Array of LogStash::Search::FacetResult::Entry
7
+ attr_accessor :results
8
+
9
+ # How long this query took, in seconds (or fractions of).
10
+ attr_accessor :duration
11
+
12
+ # Error message, if any.
13
+ attr_accessor :error_message
14
+
15
+ def initialize(settings={})
16
+ @results = []
17
+ @duration = nil
18
+ @error_message = nil
19
+ end
20
+
21
+ def error?
22
+ return !@error_message.nil?
23
+ end
24
+ end # class LogStash::Search::FacetResult
25
+
@@ -0,0 +1,6 @@
1
+
2
+ require "logstash/search/facetresult"
3
+
4
+ class LogStash::Search::FacetResult::Entry
5
+ # nothing here
6
+ end # class LogStash::Search::FacetResult::Entry
@@ -0,0 +1,21 @@
1
+
2
+ require "json"
3
+ require "logstash/search/facetresult/entry"
4
+
5
+ class LogStash::Search::FacetResult::Histogram < LogStash::Search::FacetResult::Entry
6
+ # The name or key for this result.
7
+ attr_accessor :key
8
+ attr_accessor :mean
9
+ attr_accessor :total
10
+ attr_accessor :count
11
+
12
+ # sometimes a parent call to to_json calls us with args?
13
+ def to_json(*args)
14
+ return {
15
+ "key" => @key,
16
+ "mean" => @mean,
17
+ "total" => @total,
18
+ "count" => @count,
19
+ }.to_json
20
+ end
21
+ end
@@ -0,0 +1,35 @@
1
+ require "logstash/namespace"
2
+ require "logstash/logging"
3
+
4
+ class LogStash::Search::Query
5
+ # The query string
6
+ attr_accessor :query_string
7
+
8
+ # The offset to start at (like SQL's SELECT ... OFFSET n)
9
+ attr_accessor :offset
10
+
11
+ # The max number of results to return. (like SQL's SELECT ... LIMIT n)
12
+ attr_accessor :count
13
+
14
+ # New query object.
15
+ #
16
+ # 'settings' should be a hash containing:
17
+ #
18
+ # * :query_string - a string query for searching
19
+ # * :offset - (optional, default 0) offset to search from
20
+ # * :count - (optional, default 50) max number of results to return
21
+ def initialize(settings)
22
+ @query_string = settings[:query_string]
23
+ @offset = settings[:offset] || 0
24
+ @count = settings[:count] || 50
25
+ end
26
+
27
+ # Class method. Parses a query string and returns
28
+ # a LogStash::Search::Query instance
29
+ def self.parse(query_string)
30
+ # TODO(sissel): I would prefer not to invent my own query language.
31
+ # Can we be similar to Lucene, SQL, or other query languages?
32
+ return self.new(:query_string => query_string)
33
+ end
34
+
35
+ end # class LogStash::Search::Query
@@ -0,0 +1,39 @@
1
+ require "logstash/namespace"
2
+ require "logstash/logging"
3
+
4
+ class LogStash::Search::Result
5
+ # Array of LogStash::Event of results
6
+ attr_accessor :events
7
+
8
+ # How long this query took, in seconds (or fractions of).
9
+ attr_accessor :duration
10
+
11
+ # Offset in search
12
+ attr_accessor :offset
13
+
14
+ # Total records matched by this query, regardless of offset/count in query.
15
+ attr_accessor :total
16
+
17
+ # Error message, if any.
18
+ attr_accessor :error_message
19
+
20
+ def initialize(settings={})
21
+ @events = []
22
+ @duration = nil
23
+ @error_message = nil
24
+ end
25
+
26
+ def error?
27
+ return !@error_message.nil?
28
+ end
29
+
30
+ def to_json
31
+ return {
32
+ "events" => @events,
33
+ "duration" => @duration,
34
+ "offset" => @offset,
35
+ "total" => @total,
36
+ }.to_json
37
+ end # def to_json
38
+ end # class LogStash::Search::Result
39
+
@@ -0,0 +1,90 @@
1
+ require "em-http-request"
2
+ require "logstash/namespace"
3
+ require "logstash/logging"
4
+ require "logstash/event"
5
+ require "logstash/search/base"
6
+ require "logstash/search/query"
7
+ require "logstash/search/result"
8
+ require "logstash/search/facetresult"
9
+ require "logstash/search/facetresult/histogram"
10
+
11
+ class LogStash::Search::Twitter < LogStash::Search::Base
12
+ public
13
+ def initialize(settings={})
14
+ @host = (settings[:host] || "search.twitter.com")
15
+ @port = (settings[:port] || 80).to_i
16
+ @logger = LogStash::Logger.new(STDOUT)
17
+ end
18
+
19
+ public
20
+ def search(query)
21
+ raise "No block given for search call." if !block_given?
22
+ if query.is_a?(String)
23
+ query = LogStash::Search::Query.parse(query)
24
+ end
25
+
26
+ # TODO(sissel): only search a specific index?
27
+ http = EventMachine::HttpRequest.new("http://#{@host}:#{@port}/search.json?q=#{URI.escape(query.query_string)}&rpp=#{URI.escape(query.count) rescue query.count}")
28
+
29
+ @logger.info(["Query", query])
30
+
31
+ start_time = Time.now
32
+ req = http.get
33
+
34
+ result = LogStash::Search::Result.new
35
+ req.callback do
36
+ data = JSON.parse(req.response)
37
+ result.duration = Time.now - start_time
38
+
39
+ hits = (data["results"] || nil) rescue nil
40
+
41
+ if hits.nil? or !data["error"].nil?
42
+ # Use the error message if any, otherwise, return the whole
43
+ # data object as json as the error message for debugging later.
44
+ result.error_message = (data["error"] rescue false) || data.to_json
45
+ yield result
46
+ next
47
+ end
48
+
49
+ hits.each do |hit|
50
+ hit["@message"] = hit["text"]
51
+ hit["@timestamp"] = hit["created_at"]
52
+ hit.delete("text")
53
+ end
54
+
55
+ @logger.info(["Got search results",
56
+ { :query => query.query_string, :duration => data["duration"],
57
+ :result_count => hits.size }])
58
+
59
+ if req.response_header.status != 200
60
+ result.error_message = data["error"] || req.inspect
61
+ @error = data["error"] || req.inspect
62
+ end
63
+
64
+ # We want to yield a list of LogStash::Event objects.
65
+ hits.each do |hit|
66
+ result.events << LogStash::Event.new(hit)
67
+ end
68
+
69
+ # Total hits this search could find if not limited
70
+ result.total = hits.size
71
+ result.offset = 0
72
+
73
+ yield result
74
+ end
75
+
76
+ req.errback do
77
+ @logger.warn(["Query failed", query, req, req.response])
78
+ result.duration = Time.now - start_time
79
+ result.error_message = req.response
80
+
81
+ yield result
82
+ end
83
+ end # def search
84
+
85
+ def histogram(query, field, interval=nil)
86
+ # Nothing to histogram.
87
+ result = LogStash::Search::FacetResult.new
88
+ yield result
89
+ end
90
+ end # class LogStash::Search::ElasticSearch
@@ -0,0 +1,17 @@
1
+ require "sinatra/base"
2
+
3
+ module Sinatra
4
+ module RequireParam
5
+ def require_param(*fields)
6
+ missing = []
7
+ fields.each do |field|
8
+ if params[field].nil?
9
+ missing << field
10
+ end
11
+ end
12
+ return missing
13
+ end # def require_param
14
+ end # module RequireParam
15
+
16
+ helpers RequireParam
17
+ end # module Sinatra
@@ -1,4 +1,6 @@
1
1
  (function() {
2
+ // TODO(sissel): Write something that will use history.pushState and fall back
3
+ // to document.location.hash madness.
2
4
 
3
5
  var logstash = {
4
6
  params: {
@@ -6,21 +8,79 @@
6
8
  count: 50,
7
9
  },
8
10
 
9
- search: function(query) {
11
+ search: function(query, options) {
10
12
  if (query == undefined || query == "") {
11
13
  return;
12
14
  }
13
- //console.log("Searching: " + query);
15
+
16
+ /* Default options */
17
+ if (typeof(options) == 'undefined') {
18
+ options = { graph: true };
19
+ }
14
20
 
15
21
  var display_query = query.replace("<", "&lt;").replace(">", "&gt;")
16
- $("#querystatus").html("Loading query '" + display_query + "'")
22
+ $("#querystatus, #results h1").html("Loading query '" + display_query + "' (offset:" + logstash.params.offset + ", count:" + logstash.params.count + ") <img class='throbber' src='/media/construction.gif'>")
17
23
  //console.log(logstash.params)
18
24
  logstash.params.q = query;
19
25
  document.location.hash = escape(JSON.stringify(logstash.params));
20
- $("#results").load("/search/ajax", logstash.params);
26
+
27
+ /* Load the search results */
28
+ $("#results").load("/api/search?format=html", logstash.params);
29
+
30
+ if (options.graph != false) {
31
+ /* Load the default histogram graph */
32
+ logstash.params.interval = 3600000; /* 1 hour, default */
33
+ logstash.histogram();
34
+ } /* if options.graph != false */
21
35
  $("#query").val(logstash.params.q);
22
36
  }, /* search */
23
37
 
38
+ histogram: function(tries) {
39
+ if (typeof(tries) == 'undefined') {
40
+ tries = 7;
41
+ }
42
+
43
+ /* GeoCities mode on the graph while waiting ...
44
+ * This won't likely survive 1.0, but it's fun for now... */
45
+ $("#visual").html("<center><img src='/media/truckconstruction.gif'><center>");
46
+
47
+ jQuery.getJSON("/api/histogram", logstash.params, function(histogram, text, jqxhr) {
48
+ /* Load the data into the graph */
49
+ var flot_data = [];
50
+ // histogram is an array of { "key": ..., "count": ... }
51
+ for (var i in histogram) {
52
+ flot_data.push([parseInt(histogram[i]["key"]), histogram[i]["count"]])
53
+ }
54
+ logstash.plot(flot_data, logstash.params.interval);
55
+ //console.log(histogram);
56
+
57
+ /* Try to be intelligent about how we choose the histogram interval.
58
+ * If there are too few data points, try a smaller interval.
59
+ * If there are too many data points, try a larger interval.
60
+ * Give up after a few tries and go with the last result.
61
+ *
62
+ * This queries the backend several times, but should be reasonably
63
+ * speedy as this behaves roughly as a binary search. */
64
+ //if (flot_data.length < 6 && flot_data.length > 0 && tries > 0) {
65
+ //console.log("Histogram bucket " + logstash.params.interval + " has only " + flot_data.length + " data points, trying smaller...");
66
+ //logstash.params.interval /= 2;
67
+ //if (logstash.params.interval < 1000) {
68
+ //tries = 0; /* stop trying, too small... */
69
+ //logstash.plot(flot_data, logstash.params.interval);
70
+ //return;
71
+ //}
72
+ //logstash.histogram(tries - 1);
73
+ //} else if (flot_data.length > 50 && tries > 0) {
74
+ //console.log("Histogram bucket " + logstash.params.interval + " too many (" + flot_data.length + ") data points, trying larger interval...");
75
+ //logstash.params.interval *= 2;
76
+ //logstash.histogram(tries - 1);
77
+ //} else {
78
+ //console.log("Histo:" + logstash.params.interval);
79
+ //logstash.plot(flot_data, logstash.params.interval);
80
+ //}
81
+ });
82
+ },
83
+
24
84
  parse_params: function(href) {
25
85
  var query = href.replace(/^[^?]*\?/, "");
26
86
  if (query == href) {
@@ -48,14 +108,15 @@
48
108
  logstash.search(newquery.trim());
49
109
  }, /* appendquery */
50
110
 
51
- plot: function(data) {
111
+ plot: function(data, interval) {
52
112
  var target = $("#visual");
113
+ target.css("display", "block");
53
114
  var plot = $.plot(target,
54
115
  [ { /* data */
55
116
  data: data,
56
117
  bars: {
57
118
  show: true,
58
- barWidth: 3600000,
119
+ barWidth: interval,
59
120
  }
60
121
  } ],
61
122
  { /* options */
@@ -67,8 +128,12 @@
67
128
  target.bind("plotclick", function(e, pos, item) {
68
129
  if (item) {
69
130
  start = logstash.ms_to_iso8601(item.datapoint[0]);
70
- end = logstash.ms_to_iso8601(item.datapoint[0] + 3600000);
131
+ end = logstash.ms_to_iso8601(item.datapoint[0] + interval);
71
132
 
133
+ /* Clicking on the graph means a new search, means
134
+ * we probably don't want to keep the old offset since
135
+ * the search results will change. */
136
+ logstash.params.offset = 0;
72
137
  logstash.appendquery("@timestamp:[" + start + " TO " + end + "]");
73
138
  }
74
139
  });
@@ -125,13 +190,16 @@
125
190
  for (var p in params) {
126
191
  logstash.params[p] = params[p];
127
192
  }
128
- logstash.search(logstash.params.q)
193
+ logstash.search(logstash.params.q, { graph: false })
129
194
  return false;
130
195
  });
131
196
 
132
197
  var result_row_selector = "table.results tr.event";
133
198
  $(result_row_selector).live("click", function() {
134
- var data = eval($("td.message", this).data("full"));
199
+ var data = $("td.message", this).data("full");
200
+ if (typeof(data) == "string") {
201
+ data = JSON.parse(data);
202
+ }
135
203
 
136
204
  /* Apply template to the dialog */
137
205
  var query = $("#query").val().replace(/^\s+|\s+$/g, "")
@@ -155,8 +223,8 @@
155
223
 
156
224
  /* TODO(sissel): recurse through the data */
157
225
  var fields = new Array();
158
- for (var i in data._source["@fields"]) {
159
- var value = data._source["@fields"][i]
226
+ for (var i in data["@fields"]) {
227
+ var value = data["@fields"][i]
160
228
  if (/^[, ]*$/.test(value)) {
161
229
  continue; /* Skip empty data fields */
162
230
  }
@@ -166,9 +234,9 @@
166
234
  fields.push( { type: "field", field: i, value: value })
167
235
  }
168
236
 
169
- for (var i in data._source) {
237
+ for (var i in data) {
170
238
  if (i == "@fields") continue;
171
- var value = data._source[i]
239
+ var value = data[i]
172
240
  if (!(value instanceof Array)) {
173
241
  value = [value];
174
242
  }
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
+ # I don't want folks to have to learn to use yet another tool (rackup)
3
+ # just to launch logstash-web. So let's work like a standard ruby
4
+ # executable.
2
5
  ##rackup -Ilib:../lib -s thin
3
6
 
4
7
  $:.unshift("%s/../lib" % File.dirname(__FILE__))
@@ -6,22 +9,49 @@ $:.unshift(File.dirname(__FILE__))
6
9
 
7
10
  require "eventmachine"
8
11
  require "json"
9
- require "lib/elasticsearch"
12
+ require "logstash/search/elasticsearch"
13
+ require "logstash/search/query"
10
14
  require "logstash/namespace"
11
15
  require "rack"
12
16
  require "rubygems"
13
17
  require "sinatra/async"
18
+ require "logstash/web/helpers/require_param"
14
19
 
15
20
  class EventMachine::ConnectionError < RuntimeError; end
21
+ module LogStash::Web; end
16
22
 
17
23
  class LogStash::Web::Server < Sinatra::Base
18
24
  register Sinatra::Async
25
+ helpers Sinatra::RequireParam # logstash/web/helpers/require_param
26
+
19
27
  set :haml, :format => :html5
20
28
  set :logging, true
21
29
  set :public, "#{File.dirname(__FILE__)}/public"
22
30
  set :views, "#{File.dirname(__FILE__)}/views"
23
- elasticsearch = LogStash::Web::ElasticSearch.new
24
31
 
32
+ use Rack::CommonLogger
33
+ #use Rack::ShowExceptions
34
+
35
+ def initialize(settings={})
36
+ super
37
+ # TODO(sissel): Support alternate backends
38
+ backend_url = URI.parse(settings.backend_url)
39
+
40
+ case backend_url.scheme
41
+ when "elasticsearch"
42
+ @backend = LogStash::Search::ElasticSearch.new(
43
+ :host => backend_url.host,
44
+ :port => backend_url.port
45
+ )
46
+ when "twitter"
47
+ require "logstash/search/twitter"
48
+ @backend = LogStash::Search::Twitter.new(
49
+ :host => backend_url.host,
50
+ :port => backend_url.port
51
+ )
52
+ end # backend_url.scheme
53
+ end # def initialize
54
+
25
55
  aget '/style.css' do
26
56
  headers "Content-Type" => "text/css; charset=utf8"
27
57
  body sass :style
@@ -32,8 +62,11 @@ class LogStash::Web::Server < Sinatra::Base
32
62
  end # '/'
33
63
 
34
64
  aget '/search' do
35
- result_callback = proc do
65
+ result_callback = proc do |results|
36
66
  status 500 if @error
67
+ @results = results
68
+
69
+ p :got => results
37
70
 
38
71
  params[:format] ||= "html"
39
72
  case params[:format]
@@ -48,10 +81,10 @@ class LogStash::Web::Server < Sinatra::Base
48
81
  body erb :"search/results.txt", :layout => false
49
82
  when "json"
50
83
  headers({"Content-Type" => "text/plain" })
84
+ # TODO(sissel): issue/30 - needs refactoring here.
51
85
  hits = @hits.collect { |h| h["_source"] }
52
86
  response = {
53
87
  "hits" => hits,
54
- "facets" => (@results["facets"] rescue nil),
55
88
  }
56
89
 
57
90
  response["error"] = @error if @error
@@ -63,43 +96,79 @@ class LogStash::Web::Server < Sinatra::Base
63
96
  # have javascript enabled, we need to show the results in
64
97
  # case a user doesn't have javascript.
65
98
  if params[:q] and params[:q] != ""
66
- elasticsearch.search(params) do |results|
67
- @results = results
68
- @hits = (@results["hits"]["hits"] rescue [])
99
+ query = LogStash::Search::Query.new(
100
+ :query_string => params[:q],
101
+ :offset => params[:offset],
102
+ :count => params[:count]
103
+ )
104
+
105
+ @backend.search(query) do |results|
106
+ p :got => results
69
107
  begin
70
- result_callback.call
108
+ result_callback.call results
71
109
  rescue => e
72
- puts e
110
+ p :exception => e
73
111
  end
74
- end # elasticsearch.search
112
+ end # @backend.search
75
113
  else
76
- #@error = "No query given."
77
- @hits = []
78
- result_callback.call
114
+ results = LogStash::Search::Result.new(
115
+ :events => [],
116
+ :error_message => "No query given"
117
+ )
118
+ result_callback.call results
79
119
  end
80
120
  end # aget '/search'
81
121
 
82
- apost '/search/ajax' do
122
+ apost '/api/search' do
123
+ api_search
124
+ end # apost /api/search
125
+
126
+ aget '/api/search' do
127
+ api_search
128
+ end # aget /api/search
129
+
130
+ def api_search
131
+
83
132
  headers({"Content-Type" => "text/html" })
84
133
  count = params["count"] = (params["count"] or 50).to_i
85
134
  offset = params["offset"] = (params["offset"] or 0).to_i
86
- elasticsearch.search(params) do |results|
135
+ format = (params[:format] or "json")
136
+
137
+ query = LogStash::Search::Query.new(
138
+ :query_string => params[:q],
139
+ :offset => offset,
140
+ :count => count
141
+ )
142
+
143
+ @backend.search(query) do |results|
87
144
  @results = results
88
- if @results.include?("error")
89
- body haml :"search/error", :layout => !request.xhr?
145
+ if @results.error?
146
+ status 500
147
+ case format
148
+ when "html"
149
+ headers({"Content-Type" => "text/html" })
150
+ body haml :"search/error", :layout => !request.xhr?
151
+ when "text"
152
+ headers({"Content-Type" => "text/plain" })
153
+ body erb :"search/error.txt", :layout => false
154
+ when "txt"
155
+ headers({"Content-Type" => "text/plain" })
156
+ body erb :"search/error.txt", :layout => false
157
+ when "json"
158
+ headers({"Content-Type" => "text/plain" })
159
+ # TODO(sissel): issue/30 - needs refactoring here.
160
+ if @results.error?
161
+ body({ "error" => @results.error_message }.to_json)
162
+ else
163
+ body @results.to_json
164
+ end
165
+ end # case params[:format]
90
166
  next
91
167
  end
92
168
 
93
- @hits = (@results["hits"]["hits"] rescue [])
94
- @total = (@results["hits"]["total"] rescue 0)
95
- @graphpoints = []
96
- begin
97
- @results["facets"]["by_hour"]["entries"].each do |entry|
98
- @graphpoints << [entry["key"], entry["count"]]
99
- end
100
- rescue => e
101
- puts e
102
- end
169
+ @events = @results.events
170
+ @total = (@results.total rescue 0)
171
+ count = @results.events.size
103
172
 
104
173
  if count and offset
105
174
  if @total > (count + offset)
@@ -115,7 +184,7 @@ class LogStash::Web::Server < Sinatra::Base
115
184
  next_params["offset"] = [offset + count, @total - count].min
116
185
  @next_href = "?" + next_params.collect { |k,v| [URI.escape(k.to_s), URI.escape(v.to_s)].join("=") }.join("&")
117
186
  last_params = next_params.clone
118
- last_params["offset"] = @total - offset
187
+ last_params["offset"] = @total - count
119
188
  @last_href = "?" + last_params.collect { |k,v| [URI.escape(k.to_s), URI.escape(v.to_s)].join("=") }.join("&")
120
189
  end
121
190
 
@@ -124,24 +193,83 @@ class LogStash::Web::Server < Sinatra::Base
124
193
  prev_params["offset"] = [offset - count, 0].max
125
194
  @prev_href = "?" + prev_params.collect { |k,v| [URI.escape(k.to_s), URI.escape(v.to_s)].join("=") }.join("&")
126
195
 
127
- if prev_params["offset"] > 0
196
+ #if prev_params["offset"] > 0
128
197
  first_params = prev_params.clone
129
198
  first_params["offset"] = 0
130
199
  @first_href = "?" + first_params.collect { |k,v| [URI.escape(k.to_s), URI.escape(v.to_s)].join("=") }.join("&")
131
- end
200
+ #end
132
201
  end
133
202
 
134
- body haml :"search/ajax", :layout => !request.xhr?
135
- end # elasticsearch.search
136
- end # apost '/search/ajax'
203
+ # TODO(sissel): make a helper function taht goes hash -> cgi querystring
204
+ @refresh_href = "?" + params.collect { |k,v| [URI.escape(k.to_s), URI.escape(v.to_s)].join("=") }.join("&")
205
+
206
+ case format
207
+ when "html"
208
+ headers({"Content-Type" => "text/html" })
209
+ body haml :"search/ajax", :layout => !request.xhr?
210
+ when "text"
211
+ headers({"Content-Type" => "text/plain" })
212
+ body erb :"search/results.txt", :layout => false
213
+ when "txt"
214
+ headers({"Content-Type" => "text/plain" })
215
+ body erb :"search/results.txt", :layout => false
216
+ when "json"
217
+ headers({"Content-Type" => "text/plain" })
218
+ # TODO(sissel): issue/30 - needs refactoring here.
219
+ response = @results
220
+ body response.to_json
221
+ end # case params[:format]
222
+ end # @backend.search
223
+ end # def api_search
224
+
225
+ aget '/api/histogram' do
226
+ headers({"Content-Type" => "text/plain" })
227
+ missing = require_param(:q)
228
+ if !missing.empty?
229
+ status 500
230
+ body({ "error" => "Missing requiremed parameters",
231
+ "missing" => missing }.to_json)
232
+ next
233
+ end # if !missing.empty?
234
+
235
+ format = (params[:format] or "json") # default json
236
+ field = (params[:field] or "@timestamp") # default @timestamp
237
+ interval = (params[:interval] or 3600000).to_i # default 1 hour
238
+ @backend.histogram(params[:q], field, interval) do |results|
239
+ @results = results
240
+ if @results.error?
241
+ status 500
242
+ body({ "error" => @results.error_message }.to_json)
243
+ next
244
+ end
245
+
246
+ begin
247
+ a = results.results.to_json
248
+ rescue => e
249
+ status 500
250
+ body e.inspect
251
+ p :exception => e
252
+ p e
253
+ raise e
254
+ end
255
+ status 200
256
+ body a
257
+ end # @backend.search
258
+ end # aget '/api/histogram'
259
+
260
+ aget '/*' do
261
+ status 404 if @error
262
+ body "Invalid path."
263
+ end # aget /*
137
264
  end # class LogStash::Web::Server
138
265
 
139
266
  require "optparse"
140
- Settings = Struct.new(:daemonize, :logfile, :address, :port)
267
+ Settings = Struct.new(:daemonize, :logfile, :address, :port, :backend_url)
141
268
  settings = Settings.new
142
269
 
143
- settings.address = "0.0.0.0"
144
- settings.port = 9292
270
+ settings.address = "0.0.0.0"
271
+ settings.port = 9292
272
+ settings.backend_url = "elasticsearch://localhost:9200/"
145
273
 
146
274
  progname = File.basename($0)
147
275
 
@@ -163,6 +291,11 @@ opts = OptionParser.new do |opts|
163
291
  opts.on("-p", "--port PORT", "Port on which to start webserver. Default is 9292.") do |port|
164
292
  settings.port = port.to_i
165
293
  end
294
+
295
+ opts.on("-b", "--backend URL",
296
+ "The backend URL to use. Default is elasticserach://localhost:9200/") do |url|
297
+ settings.backend_url = url
298
+ end
166
299
  end
167
300
 
168
301
  opts.parse!
@@ -189,5 +322,5 @@ end
189
322
  Rack::Handler::Thin.run(
190
323
  Rack::CommonLogger.new( \
191
324
  Rack::ShowExceptions.new( \
192
- LogStash::Web::Server.new)),
325
+ LogStash::Web::Server.new(settings))),
193
326
  :Port => settings.port, :Host => settings.address)