lstash 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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