lstash 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MWVmNzk3MjRlYTIxNjRkMzNiYjliODg1ZTEzZmNkOTJlMDE4ZDA5Ng==
5
+ data.tar.gz: !binary |-
6
+ ZDRiNGE0ZmZhZTViOGI0YjFmNjU3ZTJjYTAxNWY1Y2Y0MTdhODZkZg==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ N2QyNTU3ZTBiNDRiZDgxMTViNWRhM2RjNTZkZTM5ZmI0M2NiNjkyZTE3YTEy
10
+ ZjJlNmIxM2YzMTVlM2MxY2JmNDMwNTgyZjcxODRiMGNiNGE0YzI0ZDk5Yjkw
11
+ NmRlMDA3YWUyOGRlYmNhMjc5NTNlYWEwNDIxOGEyZGVjMzFhNTc=
12
+ data.tar.gz: !binary |-
13
+ NTU0ZDk2MTkyMzk4ODgzZjk1YzI3YTIyYTQ5ZmExMGFiYWIxY2JjZWVjNDI0
14
+ ZjcwMTZlNWMxOTY1NTcyMDhiNWQ2NzhiYmI0YWRjMGY3MTgxM2YwMjZmNDE2
15
+ ZGI3NDFmMTM4OWI5MmM1ZWZmMGNhZTJkOTg1NDFlMzg5NzYyOTY=
data/.autotest ADDED
@@ -0,0 +1,2 @@
1
+ require 'autotest/bundler'
2
+ require 'autotest/fsevent'
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ lstash
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-1.9.3
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
5
+ - 2.0.0
6
+ - 2.1.1
7
+ - jruby-19mode
data/CHANGELOG.md ADDED
@@ -0,0 +1,37 @@
1
+ ### v0.0.6 / 2014-09-01
2
+
3
+ Bug Fixes
4
+
5
+ * Pushing version 0.0.5 failed and it was yanked from rubygems.org. Need to push new version 0.0.6.
6
+
7
+ ### 0.0.5 / 2014-09-01
8
+
9
+ Bug Fixes
10
+
11
+ * Running as binary didn't load HTTP client properly. Add 'patron' dependency
12
+ to force loading an appropriate HTTP client.
13
+
14
+ ### 0.0.4 / 2014-08-29
15
+
16
+ Bug Fixes
17
+
18
+ * Use .ruby-[version|gemset] to support any ruby environment manager.
19
+
20
+ ### 0.0.3 / 2014-08-28
21
+
22
+ Bug Fixes
23
+
24
+ * Run CI on travis-ci.org.
25
+ * Fixate timezone to assumed timezone for specs.
26
+ * Run Ruby 2.1.1 instead of 2.1.0.
27
+
28
+ ### 0.0.2 / 2014-08-28
29
+
30
+ Enhancements
31
+
32
+ * Updated documentation.
33
+ * Rename debug option (-v) to (-d).
34
+
35
+ ### 0.0.1 / 2014-08-28
36
+
37
+ Initial release.
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in lstash.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Klaas Jan Wierenga
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,154 @@
1
+ # lstash
2
+
3
+ [![Build Status](https://travis-ci.org/kjwierenga/lstash.svg?branch=master)](https://travis-ci.org/kjwierenga/lstash)
4
+
5
+ Lstash is a gem and command line utility to count or grep log messages in a certain time frame from a Logstash Elasticsearch server.
6
+
7
+ ## Installation
8
+
9
+ Or install it yourself as:
10
+
11
+ $ gem install lstash
12
+
13
+ ## Running lstash from the command line
14
+
15
+ $ lstash
16
+ Commands:
17
+ lstash count QUERY # count number of log messages matching the QUERY
18
+ lstash grep QUERY # grep log messages from Logstash
19
+ lstash help [COMMAND] # Describe available commands or one specific command
20
+
21
+ ## The `count` command
22
+
23
+ Usage:
24
+ lstash count QUERY
25
+
26
+ Description:
27
+ Count log messages matching the QUERY from Logstash and output this count to stdout. QUERY can use Apache Lucene query
28
+ parser syntax.
29
+
30
+ Example to count the number of HAProxy log messages in yesterdays month.
31
+
32
+ lstash count 'program:haproxy' --from firstday --to today --anchor yesterday
33
+
34
+ ## The `grep` command
35
+
36
+ Usage:
37
+ lstash grep QUERY
38
+
39
+ Description:
40
+ Grep log messages matching the QUERY from Logstash in ascending timestamp order and output to stdout. QUERY can use Apache Lucene query parser syntax.
41
+
42
+ Example to grep HAProxy log messages from the beginning of this month upto now
43
+
44
+ lstash grep 'program:haproxy' --from firstday --to now
45
+
46
+ ## Command line options
47
+
48
+ Options:
49
+ -f, [--from=start of time range] # date/time, 'now', 'today', 'yesterday', or 'firstday'
50
+ -t, [--to=end of time range] # date/time, 'now', 'today', 'yesterday', or 'firstday'
51
+ -a, [--anchor=anchor date/time] # used as reference date for firstday
52
+ -e, [--es-url=Elasticsearch endpoint for Logstash] # or ES_URL environment variable
53
+
54
+ All times will be relative to the timezone of the machine on which you are running lstash.
55
+
56
+ ## Elasticsearch configuration
57
+
58
+ By default `lstash` will connnect to Elasticsearch on your localhost as `http://localhost:9200`. To connect
59
+ to a different server you can set the `ES_URL` environment variable. URL scheme `http` and port `9200` are default
60
+ and may be omitted.
61
+
62
+ Example
63
+
64
+ export ES_URL=log.mydomain.com
65
+ lstash count program:haproxy
66
+
67
+ Or
68
+
69
+ lstash count program:haproxy --es-url log.mydomain.com
70
+
71
+ ## Examples
72
+
73
+ Count the number of haproxy log messages matching QUERY from Aug 1 at midnight (0:00 am) upto (not including) Aug 2 at midnight (0:00 am).
74
+
75
+ lstash count program:haproxy --from "Aug 1" --to "Aug 2"
76
+
77
+ Grep all haproxy log messages using for one day (Aug 24 1 0:00 am upto and including Aug 2 23:59).
78
+
79
+ lstash grep program:haproxy --from "Aug 1" --to "Aug 2"
80
+
81
+ Assuming today is Sep 1 2014. Count all haproxy log messages in the previous month.
82
+
83
+ lstash count program:haproxy --anchor yesterday --from firstday --to today -d
84
+ time range: [2014-08-01 00:00:00 +0200..2014-09-01 00:00:00 +0200]
85
+ logstash-2014.07.31: 1
86
+ logstash-2014.08.01: 13
87
+ logstash-2014.08.02: 14
88
+ logstash-2014.08.03: 1654
89
+ logstash-2014.08.04: 6
90
+ logstash-2014.08.05: 20
91
+ logstash-2014.08.06: 219
92
+ logstash-2014.08.07: 32
93
+ logstash-2014.08.08: 14
94
+ logstash-2014.08.09: 28
95
+ logstash-2014.08.10: 799
96
+ logstash-2014.08.11: 18
97
+ logstash-2014.08.12: 8
98
+ logstash-2014.08.13: 23
99
+ logstash-2014.08.14: 25
100
+ logstash-2014.08.15: 69
101
+ logstash-2014.08.16: 19
102
+ logstash-2014.08.17: 1160
103
+ logstash-2014.08.18: 284
104
+ logstash-2014.08.19: 61
105
+ logstash-2014.08.20: 26
106
+ logstash-2014.08.21: 16
107
+ logstash-2014.08.22: 145
108
+ logstash-2014.08.23: 72
109
+ logstash-2014.08.24: 792
110
+ logstash-2014.08.25: 31
111
+ logstash-2014.08.26: 33
112
+ logstash-2014.08.27: 51
113
+ logstash-2014.08.28: 8
114
+ logstash-2014.08.29: 23
115
+ logstash-2014.08.30: 25
116
+ logstash-2014.08.31: 69
117
+ 5633
118
+
119
+ ## Using lstash as a gem in your project
120
+
121
+ Add this line to your application's Gemfile:
122
+
123
+ gem 'lstash'
124
+
125
+ And then execute:
126
+
127
+ $ bundle
128
+
129
+ Usage:
130
+
131
+ $ bundle console
132
+
133
+ # connect to elasticsearch and create the Lstash client
134
+ elasticsearch = Elasticsearch::Client.new(url: 'log.mydomain.com')
135
+ client = Lstash::Client.new(elasticsearch)
136
+
137
+ # create the query
138
+ query = Lstash::Query.new('program:haproxy', from: 'today', to: 'now')
139
+
140
+ # count
141
+ client.count(query)
142
+
143
+ # grep
144
+ client.grep(query) do |message|
145
+ puts message
146
+ end
147
+
148
+ ## Contributing
149
+
150
+ 1. Fork it
151
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
152
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
153
+ 4. Push to the branch (`git push origin my-new-feature`)
154
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/lstash ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'lstash/cli'
4
+
5
+ Lstash::CLI.start(ARGV)
data/lib/lstash/cli.rb ADDED
@@ -0,0 +1,77 @@
1
+ # external dependencies
2
+ require 'thor'
3
+ require 'patron' # use Patron HTTP library for optimal performance
4
+ require 'elasticsearch'
5
+
6
+ # local files we need
7
+ require 'lstash/query'
8
+ require 'lstash/client'
9
+
10
+ module Lstash
11
+
12
+ class CLI < Thor
13
+
14
+ class_option :from, :banner => 'start of time range', :aliases => '-f', :desc => "date/time, 'now', 'today', 'yesterday', or 'firstday'"
15
+ class_option :to, :banner => 'end of time range', :aliases => '-t', :desc => "date/time, 'now', 'today', 'yesterday', or 'firstday'"
16
+ class_option :anchor, :banner => 'anchor date/time', :aliases => '-a', :desc => "used as reference date for firstday"
17
+ class_option :es_url, :banner => 'Elasticsearch endpoint for Logstash', :aliases => '-e', :desc => "or ES_URL environment variable"
18
+ class_option :debug, :banner => 'debug log to stderr', :aliases => '-d', :type => :boolean
19
+
20
+ long_desc <<-LONGDESC
21
+ Grep log messages matching the QUERY from Logstash in ascending timestamp order
22
+ and output to stdout. QUERY can use Apache Lucene query parser syntax.
23
+
24
+ Example to grep HAProxy log messages from the beginning of this month upto now
25
+
26
+ lstash grep 'program:haproxy' --from firstday --to now
27
+ LONGDESC
28
+ desc "grep QUERY", "grep log messages from Logstash"
29
+ def grep(query_string)
30
+ run_command(query_string) do |es_client, query|
31
+ Lstash::Client.new(es_client, options).grep(query) do |message|
32
+ puts message
33
+ end
34
+ end
35
+ end
36
+
37
+ long_desc <<-LONGDESC
38
+ Count log messages matching the QUERY from Logstash and output this count to stdout.
39
+ QUERY can use Apache Lucene query parser syntax.
40
+
41
+ Example to count the number of HAProxy log messages in yesterdays month.
42
+
43
+ lstash count 'program:haproxy' --from firstday --to today --anchor yesterday
44
+ LONGDESC
45
+ desc "count QUERY", "count number of log messages matching the QUERY"
46
+ def count(query_string)
47
+ run_command(query_string) do |es_client, query|
48
+ count = Lstash::Client.new(es_client, options).count(query)
49
+ puts count
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def run_command(query_string)
56
+ es_client = ::Elasticsearch::Client.new(
57
+ url: options[:es_url] || ENV['ES_URL'] || 'localhost',
58
+ log: !!ENV['DEBUG']
59
+ )
60
+ query = Lstash::Query.new(query_string, options)
61
+
62
+ yield es_client, query
63
+
64
+ rescue Exception => e
65
+ raise Thor::Error.new(e.message)
66
+ end
67
+
68
+ protected
69
+
70
+ # Make sure we exit on failure with an error code
71
+ def self.exit_on_failure?
72
+ true
73
+ end
74
+
75
+ end
76
+
77
+ end
@@ -0,0 +1,94 @@
1
+ require 'logger'
2
+ require 'date'
3
+ require 'hashie'
4
+
5
+ class NullLogger < Logger
6
+ def initialize(*args); end
7
+ def add(*args, &block); end
8
+ end
9
+
10
+ module Lstash
11
+
12
+ class Client
13
+
14
+ class ConnectionError < StandardError; end
15
+
16
+ PER_PAGE = 5000 # best time, lowest resource usage
17
+
18
+ def initialize(es_client, options = {})
19
+ raise ConnectionError, "No elasticsearch client specified" if es_client.nil?
20
+
21
+ @es_client = es_client
22
+ @logger = options[:logger] || (options[:debug] ? debug_logger : NullLogger.new)
23
+ end
24
+
25
+ def count(query)
26
+ @logger.debug "time range: [%s..%s]" % [query.time_range.from, query.time_range.to]
27
+
28
+ count = 0
29
+ query.indices.each do |index|
30
+ count += count_messages(index, query)
31
+ end
32
+
33
+ count
34
+ end
35
+
36
+ def grep(query)
37
+ @logger.debug "time range: [%s..%s]" % [query.time_range.from, query.time_range.to]
38
+
39
+ query.indices.each do |index|
40
+ grep_messages(index, query) do |message|
41
+ yield message if block_given?
42
+ end
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def count_messages(index, query)
49
+ result = Hashie::Mash.new @es_client.send(:count,
50
+ index: index,
51
+ body: query.body[:query]
52
+ )
53
+ @logger.debug "count #{index}: #{result['count']} "
54
+ result['count']
55
+ end
56
+
57
+ def grep_messages(index, query)
58
+ messages = nil
59
+ scroll_params = {}
60
+ offset = 0
61
+ method = :search
62
+ while (messages.nil? || messages.count > 0) do
63
+ result = Hashie::Mash.new @es_client.send(method, {
64
+ index: index,
65
+ scroll: '10m',
66
+ body: query.body.merge(from: offset, size: PER_PAGE),
67
+ }.merge(scroll_params))
68
+
69
+ messages = result.hits.hits
70
+
71
+ offset += messages.count
72
+ scroll_params = {scroll_id: result._scroll_id}
73
+
74
+ messages.each do |h|
75
+ yield h._source.message if block_given?
76
+ end
77
+
78
+ method = :scroll
79
+ end
80
+ @logger.debug "grep #{index}: #{offset}"
81
+ Hashie::Mash.new @es_client.clear_scroll(scroll_params)
82
+ end
83
+
84
+ def debug_logger
85
+ logger = Logger.new(STDERR)
86
+ logger.formatter = proc do |severity, datetime, progname, msg|
87
+ "#{msg}\n"
88
+ end
89
+ logger
90
+ end
91
+
92
+ end
93
+
94
+ end
@@ -0,0 +1,159 @@
1
+ require 'time'
2
+ require 'date'
3
+ require 'ostruct'
4
+
5
+ module Lstash
6
+
7
+ class Query
8
+
9
+ class FormatError < StandardError; end
10
+ class QueryMissing < StandardError; end
11
+
12
+ LOGSTASH_PREFIX = 'logstash-'
13
+ WILDCARD_QUERY = '*'
14
+
15
+ def initialize(query = nil, arguments = {})
16
+ @query = query
17
+
18
+ @anchor = time_parse(arguments[:anchor], 'today')
19
+ @from = time_parse(arguments[:from], 'today')
20
+ @to = time_parse(arguments[:to], 'now')
21
+
22
+ @to = Time.now if @to > Time.now # prevent accessing non-existing times / indices
23
+ end
24
+
25
+ def time_range
26
+ OpenStruct.new(from: @from, to: @to)
27
+ end
28
+
29
+ def date_range
30
+ (@from.utc.to_date .. @to.utc.to_date)
31
+ end
32
+
33
+ def indices
34
+ date_range.map { |d| "#{LOGSTASH_PREFIX}#{d.strftime('%Y.%m.%d')}" }
35
+ end
36
+
37
+ def body
38
+ {
39
+ sort: sort_order,
40
+
41
+ # return in order of ascending timestamp
42
+ query: {
43
+ filtered: {
44
+ query: es_query,
45
+ filter: es_filter
46
+ }
47
+ }
48
+ }
49
+ end
50
+
51
+ private
52
+
53
+ def time_parse(time_string, default)
54
+ time_string = time_string.strip rescue nil
55
+ time_string ||= default
56
+ case time_string
57
+ when 'firstday'
58
+ midnight_at_beginning_of_month
59
+ when 'now'
60
+ Time.now
61
+ when 'today'
62
+ midnight_today
63
+ when 'yesterday'
64
+ midnight_yesterday
65
+ else
66
+ Time.parse(time_string)
67
+ end
68
+ rescue ArgumentError
69
+ raise FormatError, "Invalid time format: #{time_string}"
70
+ end
71
+
72
+ def query
73
+ q = @query.dup.strip rescue ''
74
+ q = WILDCARD_QUERY if q.empty?
75
+ q
76
+ end
77
+
78
+ def sort_order
79
+ [ { '@timestamp' => { order: 'asc' } } ]
80
+ end
81
+
82
+ def es_query
83
+ {
84
+ bool: {
85
+ should: [
86
+ {
87
+ query_string: {
88
+ query: query
89
+ }
90
+ }
91
+ ]
92
+ }
93
+ }
94
+ end
95
+
96
+ def es_filter
97
+ {
98
+ bool: {
99
+ must: [
100
+ range: {
101
+ '@timestamp' => {
102
+ from: to_msec(time_range.from),
103
+ to: to_msec(time_range.to)
104
+ }
105
+ },
106
+ # fquery: {
107
+ # query: {
108
+ # query_string: {
109
+ # query: query
110
+ # }
111
+ # }
112
+ # }
113
+ ],
114
+ # must_not: [
115
+ # fquery: {
116
+ # query: {
117
+ # query_string: {
118
+ # query: query
119
+ # }
120
+ # }
121
+ # }
122
+ # ],
123
+ # should: [
124
+ # fquery: {
125
+ # query: {
126
+ # query_string: {
127
+ # query: query
128
+ # }
129
+ # }
130
+ # }
131
+ # ]
132
+ }
133
+ }
134
+ end
135
+
136
+ # Return the date of the first day of date's month
137
+ def midnight_at_beginning_of_month
138
+ Date.new(anchor_time.year, anchor_time.month, 1).to_time
139
+ end
140
+
141
+ def midnight_today
142
+ Date.today.to_time
143
+ end
144
+
145
+ def midnight_yesterday
146
+ (Date.today-1).to_time
147
+ end
148
+
149
+ def anchor_time
150
+ @anchor
151
+ end
152
+
153
+ def to_msec(time)
154
+ time.to_i * 1000
155
+ end
156
+
157
+ end
158
+
159
+ end
@@ -0,0 +1,3 @@
1
+ module Lstash
2
+ VERSION = "0.0.6"
3
+ end
data/lib/lstash.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'elasticsearch'
2
+ require 'lstash/version'
3
+ require 'lstash/query'
4
+ require 'lstash/client'
5
+
6
+ module Lstash
7
+ # Your code goes here...
8
+ end
data/lstash.gemspec ADDED
@@ -0,0 +1,35 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'lstash/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "lstash"
8
+ spec.version = Lstash::VERSION
9
+ spec.authors = ["Klaas Jan Wierenga"]
10
+ spec.email = ["k.j.wierenga@gmail.com"]
11
+ spec.description = %q{Count or grep log messages in a specified time range from a Logstash Elasticsearch server.}
12
+ spec.summary = %q{The lstash gem allows you to count or grep log messages in a specific time range from a Logstash Elasticsearch server. }
13
+ spec.homepage = "http://bitbucket.org/kjwierenga/lstash"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+ spec.extra_rdoc_files = [ 'LICENSE.txt', 'README.md' ]
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.3"
23
+ spec.add_development_dependency "rake"
24
+ spec.add_development_dependency "rspec"
25
+ spec.add_development_dependency "rspec-its"
26
+ spec.add_development_dependency "rspec-autotest"
27
+ spec.add_development_dependency "autotest-standalone"
28
+ spec.add_development_dependency "autotest-fsevent"
29
+ spec.add_development_dependency "timecop"
30
+
31
+ spec.add_dependency "patron"
32
+ spec.add_dependency "elasticsearch", "~> 0.4"
33
+ spec.add_dependency "hashie"
34
+ spec.add_dependency "thor"
35
+ end
@@ -0,0 +1,107 @@
1
+ require 'spec_helper'
2
+ require 'lstash/cli'
3
+
4
+ class Lstash::CLI < Thor
5
+ def self.exit_on_failure?
6
+ false
7
+ end
8
+ end
9
+
10
+ describe Lstash::CLI do
11
+
12
+ context "options" do
13
+ subject { Lstash::CLI.options(args) }
14
+
15
+ let(:args) { %w(extract --time from --to to --one --two --three --four) }
16
+
17
+ its(:keys) { should eq args }
18
+ end
19
+
20
+ context "count" do
21
+
22
+ context "with valid arguments" do
23
+ let(:args) { %w(count "program:haproxy" --es-url localhost) }
24
+
25
+ it "should succeed" do
26
+ client = double('client')
27
+
28
+ allow(Lstash::Client).to receive(:new).and_return(client)
29
+ allow(client).to receive(:count).and_return(100)
30
+
31
+ output = capture_stdout { Lstash::CLI.start(args) }
32
+
33
+ expect(output).to eq "100\n"
34
+ end
35
+ end
36
+
37
+ context "with invalid --es-url" do
38
+ let(:args) { %w(count "program:haproxy" --es-url '') }
39
+
40
+ it "should print error message" do
41
+ output = capture_stderr { Lstash::CLI.start(args) }
42
+
43
+ expect(output).to eq "the scheme http does not accept registry part: '':9200 (or bad hostname?)\n"
44
+ end
45
+ end
46
+
47
+ context "without query" do
48
+ let(:args) { %w() }
49
+ it "should print help message" do
50
+ output = capture_stdout { Lstash::CLI.start(args) }
51
+
52
+ expect(output).to match("Commands:\n rspec count QUERY")
53
+ end
54
+ end
55
+
56
+ context "with anchor date" do
57
+ let(:args) { %w(count program:haproxy --from firstday --to today --anchor yesterday) }
58
+
59
+ it "should succeed" do
60
+ Timecop.freeze('2014-08-01 14:58') do
61
+ es_client = double('es_client')
62
+
63
+ allow(Elasticsearch::Client).to receive(:new) { es_client }
64
+
65
+ expect(es_client).to receive(:count).with(satisfy { |args|
66
+ expect_time_range(args, [
67
+ Time.parse('2014-07-01').to_i*1000,
68
+ Time.parse('2014-08-01').to_i*1000
69
+ ])
70
+ })
71
+
72
+ Lstash::CLI.start(args)
73
+ end
74
+ end
75
+ end
76
+
77
+ context "without anchor date" do
78
+ let(:args) { %w(count program:haproxy --from yesterday --to today) }
79
+
80
+ it "should succeed" do
81
+ Timecop.freeze('2014-08-01 14:58') do
82
+ es_client = double('es_client')
83
+
84
+ allow(Elasticsearch::Client).to receive(:new) { es_client }
85
+
86
+ expect(es_client).to receive(:count).with(satisfy { |args|
87
+ expect_time_range(args, [
88
+ Time.parse('2014-07-31').to_i*1000,
89
+ Time.parse('2014-08-01').to_i*1000
90
+ ])
91
+ }).and_return([{count:1}])
92
+
93
+ Lstash::CLI.start(args)
94
+ end
95
+ end
96
+ end
97
+
98
+ end
99
+
100
+ private
101
+
102
+ def expect_time_range(args, time_range)
103
+ expect(args[:body][:filtered][:filter][:bool][:must].first[:range]['@timestamp'][:from]).to eq time_range.first
104
+ expect(args[:body][:filtered][:filter][:bool][:must].first[:range]['@timestamp'][:to]).to eq time_range.last
105
+ end
106
+
107
+ end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+ require 'lstash/client'
3
+
4
+ describe Lstash::Client do
5
+
6
+ let(:es_client) { double('es_client') }
7
+ subject { Lstash::Client.new(es_client) }
8
+
9
+ it "should initialize properly" do
10
+ expect(subject).not_to be nil
11
+ end
12
+
13
+ context "with query" do
14
+
15
+ let(:query) { double('query', time_range: OpenStruct.new) }
16
+
17
+ context "count" do
18
+ it "should return number of messages matching query" do
19
+ allow(query).to receive(:indices).and_return (['logstash-2014-08-01', 'logstash-2014-08-02'])
20
+ allow(query).to receive(:body).and_return ({})
21
+
22
+ allow(es_client).to receive(:count).and_return({'count' => 100},{'count' => 100})
23
+
24
+ expect(subject.count(query)).to eq 200
25
+ end
26
+ end
27
+
28
+ context "grep" do
29
+ let(:query) { double('query', time_range: OpenStruct.new) }
30
+
31
+ it "should return the messages matching the query" do
32
+ allow(query).to receive(:indices).and_return (['logstash-2014-08-01', 'logstash-2014-08-02'])
33
+ allow(query).to receive(:body).and_return ({})
34
+
35
+ allow(es_client).to receive(:search).and_return(
36
+ hits([
37
+ 'this is the first log line',
38
+ 'this is the second log line'
39
+ ])
40
+ )
41
+
42
+ allow(es_client).to receive(:scroll).and_return(hits([]))
43
+
44
+ allow(es_client).to receive(:clear_scroll)
45
+
46
+ subject.grep(query)
47
+ end
48
+ end
49
+
50
+ end
51
+
52
+ def hits(messages)
53
+ {
54
+ hits: {
55
+ hits: messages.map { |m| { _source: { message: m }}}
56
+ }
57
+ }
58
+ end
59
+
60
+ end
@@ -0,0 +1,145 @@
1
+ require 'spec_helper'
2
+ require 'lstash/query'
3
+
4
+ describe Lstash::Query do
5
+
6
+ context "running on 2014-08-03" do
7
+ let(:time) { '2014-08-03 15:54:33' }
8
+ let(:query) { nil }
9
+ let(:options) { {} }
10
+
11
+ subject { Lstash::Query.new(query, options) }
12
+
13
+ before { Timecop.freeze(Time.parse(time)) }
14
+ after { Timecop.return }
15
+
16
+ it { should_not be nil }
17
+ # it "should initialize properly" do
18
+ # expect(subject).not_to be nil
19
+ # end
20
+
21
+ its('time_range.from') { should eq Time.parse('2014-08-03 00:00:00.000000000 +0200') }
22
+ its('time_range.to') { should eq Time.parse('2014-08-03 15:54:33.000000000 +0200') }
23
+
24
+ its(:date_range) { should eq (Date.parse('2014-08-02')..Date.parse('2014-08-03')) }
25
+
26
+ its(:indices) { should eq [ "logstash-2014.08.02", "logstash-2014.08.03" ] }
27
+
28
+ context "with specific query" do
29
+ let(:query) { 'program:haproxy' }
30
+ its(:query) { should eq query }
31
+ end
32
+
33
+ context "without query" do
34
+ let(:query) { nil }
35
+ its(:query) { should eql '*' }
36
+ end
37
+
38
+ context "from 'yesterday'" do
39
+ let(:options) { { from: 'yesterday' }}
40
+ its('time_range.from') { should eq Time.parse('2014-08-02 00:00:00.000000000 +0200') }
41
+ end
42
+
43
+ context "from 'today'" do
44
+ let(:options) { { from: 'today' }}
45
+ its('time_range.from') { should eq Time.parse('2014-08-03 00:00:00.000000000 +0200') }
46
+ end
47
+
48
+ context "from 'now'" do
49
+ let(:options) { { from: 'now' }}
50
+ its('time_range.from') { should eq Time.parse('2014-08-03 15:54:33.000000000 +0200') }
51
+ end
52
+
53
+ context "to 'yesterday'" do
54
+ let(:options) { { to: 'yesterday' }}
55
+ its('time_range.to') { should eq Time.parse('2014-08-02 00:00:00.000000000 +0200') }
56
+ end
57
+
58
+ context "to 'today'" do
59
+ let(:options) { { to: 'today' }}
60
+ its('time_range.to') { should eq Time.parse('2014-08-03 00:00:00.000000000 +0200') }
61
+ end
62
+
63
+ context "to 'now'" do
64
+ let(:options) { { to: 'now' }}
65
+ its('time_range.to') { should eq Time.parse('2014-08-03 15:54:33.000000000 +0200') }
66
+ end
67
+
68
+ context "from 'firstday'" do
69
+
70
+ let(:options) { { from: 'firstday' } }
71
+ its('time_range.from') { should eq Time.parse('2014-08-01 00:00:00.000000000 +0200') }
72
+
73
+ context "anchor 'yesterday'" do
74
+ let(:anchor) { 'yesterday' }
75
+ its('time_range.from') { should eq Time.parse('2014-08-01 00:00:00.000000000 +0200') }
76
+ end
77
+
78
+ context "anchor 'today'" do
79
+ let(:anchor) { 'today' }
80
+ its('time_range.from') { should eq Time.parse('2014-08-01 00:00:00.000000000 +0200') }
81
+ end
82
+
83
+ context "anchor '2014-07-17'" do
84
+ let(:options) { { from: 'firstday', anchor: '2014-07-17' } }
85
+ its('time_range.from') { should eq Time.parse('2014-07-01 00:00:00.000000000 +0200') }
86
+ end
87
+
88
+ context "date range" do
89
+ let(:options) { { from: 'firstday', anchor: 'yesterday' } }
90
+ its(:date_range) { should eq (Date.parse('2014-07-31')..Date.parse('2014-08-03')) }
91
+ end
92
+
93
+ context "indices" do
94
+ let(:options) { { from: 'firstday', anchor: 'yesterday' } }
95
+ its(:indices) {
96
+ should eq [
97
+ "logstash-2014.07.31",
98
+ "logstash-2014.08.01",
99
+ "logstash-2014.08.02",
100
+ "logstash-2014.08.03",
101
+ ]
102
+ }
103
+ end
104
+
105
+ end
106
+
107
+ context "body" do
108
+ its(:body) { should eq ({
109
+ :sort => [{"@timestamp"=>{:order=>"asc"}}],
110
+ :query => {:filtered=>{
111
+ :query => { :bool => { :should => [ { :query_string => { :query=>"*" }}]}},
112
+ :filter=> { :bool => { :must => [ { :range => { "@timestamp" => { :from => 1407016800000, :to => 1407074073000}}}]}}}}
113
+ })}
114
+ end
115
+
116
+ end
117
+
118
+ context "running on 2014-08-01" do
119
+ let(:time) { '2014-08-01 12:53:03' }
120
+ let(:query) { nil }
121
+ let(:options) { {} }
122
+
123
+ subject { Lstash::Query.new(query, options) }
124
+
125
+ before { Timecop.freeze(Time.parse(time)) }
126
+ after { Timecop.return }
127
+
128
+ context "from 'firstday' with 'yesterday' anchor" do
129
+ let(:options) { { anchor: 'yesterday', from: 'firstday' } }
130
+
131
+ its('time_range.from') { should eq Time.parse('2014-07-01 00:00:00.000000000 +0200') }
132
+ its('time_range.to') { should eq Time.parse('2014-08-01 12:53:03.000000000 +0200') }
133
+ end
134
+
135
+ context "from 'firstday' with default 'today' anchor" do
136
+ let(:options) { { from: 'firstday', to: 'now' } }
137
+
138
+ its('time_range.from') { should eq Time.parse('2014-08-01 00:00:00.000000000 +0200') }
139
+ its('time_range.to') { should eq Time.parse('2014-08-01 12:53:03.000000000 +0200') }
140
+ end
141
+
142
+ end
143
+
144
+ end
145
+
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+ require 'lstash/cli'
3
+
4
+ describe Lstash do
5
+ it 'should have a version number' do
6
+ expect(Lstash::VERSION).not_to be nil
7
+ end
8
+ end
@@ -0,0 +1,33 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'lstash'
3
+
4
+ require 'rspec/its'
5
+
6
+ require 'timecop'
7
+
8
+ ENV['ES_URL'] = nil
9
+ ENV['TZ'] = 'Europe/Amsterdam' # Test in a specific timezone.
10
+
11
+ RSpec.configure do |config|
12
+ config.order = 'random'
13
+ end
14
+
15
+ require 'stringio'
16
+
17
+ def capture_stdout(&blk)
18
+ old = $stdout
19
+ $stdout = fake = StringIO.new
20
+ blk.call
21
+ fake.string
22
+ ensure
23
+ $stdout = old
24
+ end
25
+
26
+ def capture_stderr(&blk)
27
+ old = $stderr
28
+ $stderr = fake = StringIO.new
29
+ blk.call
30
+ fake.string
31
+ ensure
32
+ $stderr = old
33
+ end
metadata ADDED
@@ -0,0 +1,245 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lstash
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.6
5
+ platform: ruby
6
+ authors:
7
+ - Klaas Jan Wierenga
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-09-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec-its
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec-autotest
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ! '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ! '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: autotest-standalone
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ! '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: autotest-fsevent
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ! '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ! '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: timecop
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ! '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: patron
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ! '>='
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ! '>='
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: elasticsearch
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ~>
144
+ - !ruby/object:Gem::Version
145
+ version: '0.4'
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ~>
151
+ - !ruby/object:Gem::Version
152
+ version: '0.4'
153
+ - !ruby/object:Gem::Dependency
154
+ name: hashie
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ! '>='
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :runtime
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ! '>='
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: thor
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ! '>='
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :runtime
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ! '>='
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ description: Count or grep log messages in a specified time range from a Logstash
182
+ Elasticsearch server.
183
+ email:
184
+ - k.j.wierenga@gmail.com
185
+ executables:
186
+ - lstash
187
+ extensions: []
188
+ extra_rdoc_files:
189
+ - LICENSE.txt
190
+ - README.md
191
+ files:
192
+ - .autotest
193
+ - .gitignore
194
+ - .rspec
195
+ - .ruby-gemset
196
+ - .ruby-version
197
+ - .travis.yml
198
+ - CHANGELOG.md
199
+ - Gemfile
200
+ - LICENSE.txt
201
+ - README.md
202
+ - Rakefile
203
+ - bin/lstash
204
+ - lib/lstash.rb
205
+ - lib/lstash/cli.rb
206
+ - lib/lstash/client.rb
207
+ - lib/lstash/query.rb
208
+ - lib/lstash/version.rb
209
+ - lstash.gemspec
210
+ - spec/lstash/cli_spec.rb
211
+ - spec/lstash/client_spec.rb
212
+ - spec/lstash/query_spec.rb
213
+ - spec/lstash_spec.rb
214
+ - spec/spec_helper.rb
215
+ homepage: http://bitbucket.org/kjwierenga/lstash
216
+ licenses:
217
+ - MIT
218
+ metadata: {}
219
+ post_install_message:
220
+ rdoc_options: []
221
+ require_paths:
222
+ - lib
223
+ required_ruby_version: !ruby/object:Gem::Requirement
224
+ requirements:
225
+ - - ! '>='
226
+ - !ruby/object:Gem::Version
227
+ version: '0'
228
+ required_rubygems_version: !ruby/object:Gem::Requirement
229
+ requirements:
230
+ - - ! '>='
231
+ - !ruby/object:Gem::Version
232
+ version: '0'
233
+ requirements: []
234
+ rubyforge_project:
235
+ rubygems_version: 2.4.1
236
+ signing_key:
237
+ specification_version: 4
238
+ summary: The lstash gem allows you to count or grep log messages in a specific time
239
+ range from a Logstash Elasticsearch server.
240
+ test_files:
241
+ - spec/lstash/cli_spec.rb
242
+ - spec/lstash/client_spec.rb
243
+ - spec/lstash/query_spec.rb
244
+ - spec/lstash_spec.rb
245
+ - spec/spec_helper.rb