lstash 0.2.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/lstash/client.rb CHANGED
@@ -1,49 +1,66 @@
1
- require 'logger'
2
- require 'date'
3
- require 'hashie'
1
+ require "logger"
2
+ require "date"
4
3
 
5
4
  class NullLogger < Logger
6
- def initialize(*args); end
7
- def add(*args, &block); end
5
+ def initialize(*args)
6
+ end
7
+
8
+ def add(*args, &block)
9
+ end
8
10
  end
9
11
 
10
12
  module Lstash
11
-
12
13
  class Client
13
-
14
14
  class ConnectionError < StandardError; end
15
15
 
16
- PER_PAGE = 5000.freeze # best time, lowest resource usage
17
- DEFAULT_COUNT_STEP = 3600.freeze # 1 hour
18
- DEFAULT_GREP_STEP = 120.freeze # 2 minutes
16
+ class ShardMismatchError < StandardError; end
17
+
18
+ PER_PAGE = 5000 # best time, lowest resource usage
19
+ COUNT_STEP = 3600 # 1 hours
20
+ GREP_STEP = 3600 # 1 hour
19
21
 
20
22
  def initialize(es_client, options = {})
21
23
  raise ConnectionError, "No elasticsearch client specified" if es_client.nil?
22
24
 
23
25
  @es_client = es_client
24
- @logger = options[:logger] || (options[:debug] ? debug_logger : NullLogger.new)
26
+ @logger = options[:logger] || (options[:debug] ? debug_logger : NullLogger.new)
27
+ @wildcard = options[:wildcard]
25
28
  end
26
29
 
27
- def count(query, step = DEFAULT_COUNT_STEP)
30
+ def count(query)
28
31
  @logger.debug "count from=#{query.from} to=#{query.to}"
29
32
 
30
33
  count = 0
31
- query.each_period(step) do |index, hour_query|
32
- count += count_messages(index, hour_query)
34
+ use_wildcard = @wildcard.nil? ? true : @wildcard
35
+ if use_wildcard
36
+ count = count_messages(query.wildcard_indices, query)
37
+ else
38
+ count = 0
39
+ query.each_period(COUNT_STEP) do |index, hour_query|
40
+ count += count_messages(index, hour_query)
41
+ end
33
42
  end
34
43
  @logger.debug "total count=#{count}"
35
44
  count
36
45
  end
37
46
 
38
- def grep(query, step = DEFAULT_GREP_STEP)
47
+ def grep(query)
39
48
  @logger.debug "grep from=#{query.from} to=#{query.to}"
40
49
 
41
50
  count = 0
42
- query.each_period(step) do |index, hour_query|
43
- grep_messages(index, hour_query) do |message|
51
+ use_wildcard = @wildcard.nil? ? false : @wildcard
52
+ if use_wildcard
53
+ grep_messages(query.wildcard_indices, query) do |message|
44
54
  count += 1
45
55
  yield message if block_given?
46
56
  end
57
+ else
58
+ query.each_period(GREP_STEP) do |index, hour_query|
59
+ grep_messages(index, hour_query) do |message|
60
+ count += 1
61
+ yield message if block_given?
62
+ end
63
+ end
47
64
  end
48
65
 
49
66
  @logger.debug "total count=#{count}"
@@ -53,12 +70,10 @@ module Lstash
53
70
  private
54
71
 
55
72
  def count_messages(index, query)
56
- result = Hashie::Mash.new @es_client.send(:count,
57
- index: index,
58
- body: query.filter
59
- )
60
- @logger.debug "count index=#{index} from=#{query.from} to=#{query.to} count=#{result['count']}"
61
- result['count']
73
+ result = @es_client.count(index: index, body: {query: query.filter})
74
+ validate_shards!(result["_shards"])
75
+ @logger.debug "count index=#{index} from=#{query.from.utc} to=#{query.to.utc} count=#{result["count"]}"
76
+ result["count"]
62
77
  end
63
78
 
64
79
  def grep_messages(index, query)
@@ -66,37 +81,43 @@ module Lstash
66
81
  scroll_params = {}
67
82
  offset = 0
68
83
  method = :search
69
- while (messages.nil? || messages.count > 0) do
70
- result = Hashie::Mash.new @es_client.send(method, {
71
- index: index,
72
- scroll: '5m',
73
- body: query.search(offset, PER_PAGE),
74
- }.merge(scroll_params))
84
+ while messages.nil? || messages.count > 0
85
+ result = @es_client.send(method, {
86
+ index: index,
87
+ scroll: "5m",
88
+ body: (method == :search) ? query.search(offset, PER_PAGE) : scroll_params
89
+ })
75
90
 
76
- messages = result.hits.hits
91
+ validate_shards!(result["_shards"])
77
92
 
93
+ messages = result["hits"]["hits"]
78
94
  offset += messages.count
79
- scroll_params = {scroll_id: result._scroll_id}
95
+ scroll_params = {scroll_id: result["_scroll_id"]}
80
96
 
81
97
  messages.each do |h|
82
- next if h.fields.nil?
83
- yield h.fields.message if block_given?
98
+ next if h["_source"].nil?
99
+ yield h["_source"]["message"] if block_given?
84
100
  end
85
101
 
86
102
  method = :scroll
103
+
104
+ @logger.debug "grep index=#{index} from=#{query.from.utc} to=#{query.to.utc} count=#{offset}"
87
105
  end
88
- @logger.debug "grep index=#{index} from=#{query.from} to=#{query.to} count=#{offset}"
89
- Hashie::Mash.new @es_client.clear_scroll(scroll_params)
106
+ @es_client.clear_scroll(body: scroll_params) unless scroll_params.empty?
90
107
  end
91
108
 
92
109
  def debug_logger
93
- logger = Logger.new(STDERR)
110
+ logger = Logger.new($stderr)
94
111
  logger.formatter = proc do |severity, datetime, progname, msg|
95
112
  "#{datetime} #{msg}\n"
96
113
  end
97
114
  logger
98
115
  end
99
116
 
117
+ def validate_shards!(shards)
118
+ if shards["total"] != shards["successful"]
119
+ raise ShardMismatchError, "Shard mismatch: total: #{shards["total"]}, successful: #{shards["successful"]}"
120
+ end
121
+ end
100
122
  end
101
-
102
123
  end
data/lib/lstash/query.rb CHANGED
@@ -1,47 +1,50 @@
1
- require 'time'
2
- require 'date'
3
- require 'ostruct'
1
+ require "time"
2
+ require "date"
3
+ require "ostruct"
4
4
 
5
5
  module Lstash
6
-
7
6
  class Query
8
-
9
7
  class FormatError < StandardError; end
8
+
10
9
  class QueryMissing < StandardError; end
11
10
 
12
- LOGSTASH_PREFIX = 'logstash-'.freeze
13
- WILDCARD_QUERY = '*'.freeze
11
+ LOGSTASH_PREFIX = "logstash-".freeze
12
+ WILDCARD_QUERY = "*".freeze
14
13
 
15
14
  attr_accessor :from, :to
16
15
 
17
16
  def initialize(query_string = nil, arguments = {})
18
17
  @query_string = query_string
19
18
 
20
- @anchor = time_parse(arguments[:anchor], 'today')
21
- @from = time_parse(arguments[:from], 'today')
22
- @to = time_parse(arguments[:to], 'now')
19
+ @anchor = time_parse(arguments[:anchor], "today")
20
+ @from = time_parse(arguments[:from], "yesterday")
21
+ @to = time_parse(arguments[:to], "today")
23
22
 
24
23
  @to = Time.now if @to > Time.now # prevent accessing non-existing times / indices
25
24
  end
26
25
 
27
26
  def index_name(date)
28
- "#{LOGSTASH_PREFIX}#{date.strftime('%Y.%m.%d')}"
27
+ "#{LOGSTASH_PREFIX}#{date.strftime("%Y.%m.%d")}"
28
+ end
29
+
30
+ def wildcard_indices
31
+ "logstash-*"
29
32
  end
30
33
 
31
34
  def search(from, size)
32
35
  {
33
- sort: sort_order,
34
- fields: %w(message),
35
- query: filter,
36
- from: from,
37
- size: size
36
+ sort: sort_order,
37
+ _source: %w[message],
38
+ query: filter,
39
+ from: from,
40
+ size: size
38
41
  }
39
42
  end
40
43
 
41
44
  def filter
42
45
  {
43
- filtered: {
44
- query: es_query,
46
+ bool: {
47
+ must: es_query,
45
48
  filter: es_filter
46
49
  }
47
50
  }
@@ -52,32 +55,37 @@ module Lstash
52
55
  time_iterate(@from.utc, @to.utc - 1, step) do |start_at|
53
56
  yield index_name(start_at.to_date),
54
57
  Query.new(@query_string,
55
- anchor: @anchor,
56
- from: start_at,
57
- to: start_at + step)
58
+ anchor: @anchor,
59
+ from: start_at,
60
+ to: start_at + step)
58
61
  end
59
62
  end
60
63
 
61
64
  private
62
65
 
63
66
  def time_iterate(start_time, end_time, step, &block)
64
- begin
67
+ while start_time < end_time
65
68
  yield(start_time)
66
- end while (start_time += step) < end_time
69
+ start_time += step
70
+ end
67
71
  end
68
72
 
69
73
  def time_parse(time_or_string, default)
70
74
  return time_or_string if time_or_string.is_a? Time
71
- time_string = time_or_string.strip rescue nil
75
+ time_string = begin
76
+ time_or_string.strip
77
+ rescue
78
+ nil
79
+ end
72
80
  time_string ||= default
73
81
  case time_string
74
- when 'firstday'
82
+ when "firstday"
75
83
  midnight_at_beginning_of_month
76
- when 'now'
84
+ when "now"
77
85
  Time.now
78
- when 'today'
86
+ when "today"
79
87
  midnight_today
80
- when 'yesterday'
88
+ when "yesterday"
81
89
  midnight_yesterday
82
90
  else
83
91
  Time.parse(time_string)
@@ -87,66 +95,37 @@ module Lstash
87
95
  end
88
96
 
89
97
  def query_string
90
- q = @query_string.dup.strip rescue ''
98
+ q = begin
99
+ @query_string.dup.strip
100
+ rescue
101
+ ""
102
+ end
91
103
  q = WILDCARD_QUERY if q.empty?
92
104
  q
93
105
  end
94
106
 
95
107
  def sort_order
96
108
  # return results in order of ascending timestamp
97
- [ { '@timestamp' => { order: 'asc' } } ]
109
+ [{"@timestamp" => {order: "asc"}}]
98
110
  end
99
111
 
100
112
  def es_query
101
- {
102
- bool: {
103
- should: [
104
- {
105
- query_string: {
106
- query: query_string
107
- }
108
- }
109
- ]
113
+ [
114
+ {
115
+ query_string: {
116
+ query: query_string
117
+ }
110
118
  }
111
- }
119
+ ]
112
120
  end
113
121
 
114
122
  def es_filter
115
123
  {
116
- bool: {
117
- must: [
118
- range: {
119
- '@timestamp' => {
120
- gte: to_msec(from),
121
- lt: to_msec(to)
122
- }
123
- },
124
- # fquery: {
125
- # query: {
126
- # query_string: {
127
- # query: query_string
128
- # }
129
- # }
130
- # }
131
- ],
132
- # must_not: [
133
- # fquery: {
134
- # query: {
135
- # query_string: {
136
- # query: query_string
137
- # }
138
- # }
139
- # }
140
- # ],
141
- # should: [
142
- # fquery: {
143
- # query: {
144
- # query_string: {
145
- # query: query_string
146
- # }
147
- # }
148
- # }
149
- # ]
124
+ range: {
125
+ "@timestamp" => {
126
+ gte: to_msec(from),
127
+ lt: to_msec(to)
128
+ }
150
129
  }
151
130
  }
152
131
  end
@@ -161,7 +140,7 @@ module Lstash
161
140
  end
162
141
 
163
142
  def midnight_yesterday
164
- (Date.today-1).to_time
143
+ (Date.today - 1).to_time
165
144
  end
166
145
 
167
146
  def anchor_time
@@ -171,7 +150,5 @@ module Lstash
171
150
  def to_msec(time)
172
151
  time.to_i * 1000
173
152
  end
174
-
175
153
  end
176
-
177
154
  end
@@ -1,3 +1,3 @@
1
1
  module Lstash
2
- VERSION = "0.2.0"
2
+ VERSION = "1.0.0"
3
3
  end
data/lib/lstash.rb CHANGED
@@ -1,7 +1,7 @@
1
- require 'elasticsearch'
2
- require 'lstash/version'
3
- require 'lstash/query'
4
- require 'lstash/client'
1
+ require "elasticsearch"
2
+ require "lstash/version"
3
+ require "lstash/query"
4
+ require "lstash/client"
5
5
 
6
6
  module Lstash
7
7
  # Your code goes here...
data/lstash.gemspec CHANGED
@@ -1,23 +1,21 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
1
+ lib = File.expand_path("../lib", __FILE__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'lstash/version'
3
+ require "lstash/version"
5
4
 
6
5
  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 = "https://github.com/kjwierenga/lstash"
14
- spec.license = "MIT"
6
+ spec.name = "lstash"
7
+ spec.version = Lstash::VERSION
8
+ spec.authors = ["Klaas Jan Wierenga"]
9
+ spec.email = ["k.j.wierenga@kerkdienstgemist.nl"]
10
+ spec.description = "Count or grep log messages in a specified time range from a Logstash Elasticsearch server."
11
+ spec.summary = "The lstash gem allows you to count or grep log messages in a specific time range from a Logstash Elasticsearch server. "
12
+ spec.homepage = "https://github.com/kdgm/lstash"
13
+ spec.license = "MIT"
15
14
 
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)/})
15
+ spec.files = `git ls-files`.split($/)
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
17
  spec.require_paths = ["lib"]
20
- spec.extra_rdoc_files = [ 'LICENSE.txt', 'README.md', 'CHANGELOG.md' ]
18
+ spec.extra_rdoc_files = ["LICENSE.txt", "README.md", "CHANGELOG.md"]
21
19
 
22
20
  spec.add_development_dependency "bundler", "~> 1.3"
23
21
  spec.add_development_dependency "rake", "~> 10.3.2"
@@ -25,9 +23,6 @@ Gem::Specification.new do |spec|
25
23
  spec.add_development_dependency "rspec-its", "~> 1.0.1"
26
24
  spec.add_development_dependency "timecop", "~> 0.7.1"
27
25
 
28
- spec.add_dependency "typhoeus", "~> 1.4.0"
29
- spec.add_dependency "elasticsearch", "~> 0.4"
30
- spec.add_dependency "hashie", "~> 4.1.0"
26
+ spec.add_dependency "elasticsearch", "~> 7.17.7"
31
27
  spec.add_dependency "thor", "~> 0.20.3"
32
- spec.add_dependency "faraday", "~> 0.17.4"
33
28
  end
@@ -1,16 +1,15 @@
1
- require 'spec_helper'
2
- require 'lstash/cli'
1
+ require "spec_helper"
2
+ require "lstash/cli"
3
3
 
4
- class Lstash::CLI < Thor
4
+ class Lstash::CLI < Lstash::CLIBase
5
5
  def self.exit_on_failure?
6
6
  false
7
7
  end
8
8
  end
9
9
 
10
10
  describe Lstash::CLI do
11
-
12
11
  before(:all) do
13
- Timecop.freeze('2014-08-01 14:58')
12
+ Timecop.freeze("2014-08-01 14:58")
14
13
  end
15
14
  after(:all) do
16
15
  Timecop.return
@@ -19,17 +18,16 @@ describe Lstash::CLI do
19
18
  context "options" do
20
19
  subject { Lstash::CLI.options(args) }
21
20
 
22
- let(:args) { %w(extract --time from --to to --one --two --three --four) }
21
+ let(:args) { %w[extract --time from --to to --one --two --three --four] }
23
22
 
24
23
  its(:keys) { should eq args }
25
24
  end
26
25
 
27
26
  context "count" do
28
-
29
27
  context "with full URI" do
30
- let(:args) { %w(count "*" --es-url http://localhost:9200) }
28
+ let(:args) { %w[count "*" --es-url http://localhost:9200] }
31
29
  it "should succeed" do
32
- client = double('client')
30
+ client = double("client")
33
31
 
34
32
  allow(Lstash::Client).to receive(:new).and_return(client)
35
33
  allow(client).to receive(:count).and_return(1000)
@@ -42,10 +40,10 @@ describe Lstash::CLI do
42
40
  end
43
41
 
44
42
  context "with valid arguments" do
45
- let(:args) { %w(count "program:haproxy" --es-url localhost) }
43
+ let(:args) { %w[count "program:haproxy" --es-url localhost] }
46
44
 
47
45
  it "should succeed" do
48
- client = double('client')
46
+ client = double("client")
49
47
 
50
48
  allow(Lstash::Client).to receive(:new).and_return(client)
51
49
  allow(client).to receive(:count).and_return(100)
@@ -57,7 +55,7 @@ describe Lstash::CLI do
57
55
  end
58
56
 
59
57
  context "with invalid --es-url" do
60
- let(:args) { %w(count "program:haproxy" --es-url '') }
58
+ let(:args) { %w[count "program:haproxy" --es-url ''] }
61
59
 
62
60
  it "should print error message" do
63
61
  output = capture_stderr { Lstash::CLI.start(args) }
@@ -67,7 +65,7 @@ describe Lstash::CLI do
67
65
  end
68
66
 
69
67
  context "without query" do
70
- let(:args) { %w() }
68
+ let(:args) { %w[] }
71
69
  it "should print help message" do
72
70
  output = capture_stdout { Lstash::CLI.start(args) }
73
71
 
@@ -76,35 +74,35 @@ describe Lstash::CLI do
76
74
  end
77
75
 
78
76
  context "with anchor date" do
79
- let(:args) { %w(count program:haproxy --from firstday --to today --anchor yesterday) }
77
+ let(:args) { %w[count program:haproxy --from firstday --to today --anchor yesterday --no-wildcard] }
80
78
 
81
79
  it "should return correct count" do
82
- es_client = double('es_client')
80
+ es_client = double("es_client")
83
81
 
84
82
  allow(Elasticsearch::Client).to receive(:new) { es_client }
83
+ allow_any_instance_of(Lstash::Client).to receive(:validate_shards!).and_return(true)
85
84
 
86
- expect(es_client).to receive(:count).exactly(31 * 24).times.and_return(count:100)
85
+ expect(es_client).to receive(:count).exactly(31 * 24).times.and_return("count" => 100)
87
86
 
88
87
  output = capture_stdout { Lstash::CLI.start(args) }
89
- expect(output).to match("#{31 * 24 * 100}")
88
+ expect(output).to match((31 * 24 * 100).to_s)
90
89
  end
91
90
  end
92
91
 
93
92
  context "without anchor date" do
94
- let(:args) { %w(count program:haproxy --from yesterday --to today) }
93
+ let(:args) { %w[count program:haproxy --from yesterday --to today --no-wildcard] }
95
94
 
96
95
  it "should return correct count" do
97
- es_client = double('es_client')
96
+ es_client = double("es_client")
98
97
 
99
98
  allow(Elasticsearch::Client).to receive(:new) { es_client }
99
+ allow_any_instance_of(Lstash::Client).to receive(:validate_shards!).and_return(true)
100
100
 
101
- expect(es_client).to receive(:count).exactly(24).times.and_return(count:100)
101
+ expect(es_client).to receive(:count).exactly(24).times.and_return("count" => 100)
102
102
 
103
103
  output = capture_stdout { Lstash::CLI.start(args) }
104
- expect(output).to match("#{24 * 100}")
104
+ expect(output).to match((24 * 100).to_s)
105
105
  end
106
106
  end
107
-
108
107
  end
109
-
110
108
  end
@@ -1,30 +1,30 @@
1
- require 'spec_helper'
2
- require 'lstash/client'
1
+ require "spec_helper"
2
+ require "lstash/client"
3
3
 
4
4
  describe Lstash::Client do
5
-
6
- let(:es_client) { double('es_client') }
7
- subject { Lstash::Client.new(es_client) }
5
+ let(:es_client) { double("es_client") }
6
+ subject { Lstash::Client.new(es_client, wildcard: false) }
7
+ before do
8
+ allow(subject).to receive(:validate_shards!).and_return(true)
9
+ end
8
10
 
9
11
  it "should initialize properly" do
10
12
  expect(subject).not_to be nil
11
13
  end
12
14
 
13
15
  context "with query" do
14
-
15
16
  context "count" do
16
17
  it "should return number of messages matching query" do
17
-
18
- query = Lstash::Query.new("*", from: Time.parse("2014-10-10 00:00"), to: Time.parse("2014-10-10 07:00"))
18
+ query = Lstash::Query.new("*", from: "2014-10-10 00:00", to: "2014-10-10 07:00")
19
19
 
20
20
  allow(es_client).to receive(:count).and_return(
21
- { 'count' => 100 },
22
- { 'count' => 200 },
23
- { 'count' => 300 },
24
- { 'count' => 400 },
25
- { 'count' => 500 },
26
- { 'count' => 600 },
27
- { 'count' => 700 }
21
+ {"count" => 100},
22
+ {"count" => 200},
23
+ {"count" => 300},
24
+ {"count" => 400},
25
+ {"count" => 500},
26
+ {"count" => 600},
27
+ {"count" => 700}
28
28
  )
29
29
 
30
30
  expect(subject.count(query)).to eq 2800
@@ -33,35 +33,31 @@ describe Lstash::Client do
33
33
 
34
34
  context "grep" do
35
35
  it "should return the messages matching the query" do
36
-
37
- query = Lstash::Query.new("*", from: Time.parse("2014-10-10 00:00"), to: Time.parse("2014-10-10 07:00"))
38
-
39
- expect(es_client).to receive(:search).and_return(
40
- hits(%w(1)),
41
- hits(%w(2 2)),
42
- hits(%w(3 3 3)),
43
- hits(%w(4 4 4 4)),
44
- hits(%w(5 5 5 5 5)),
45
- hits(%w(6 6 6 6 6 6)),
46
- hits(%w(7 7 7 7 7 7 7))
36
+ query = Lstash::Query.new("*", from: "2014-10-10 00:00", to: "2014-10-10 07:00")
37
+
38
+ allow(es_client).to receive(:search).and_return(
39
+ hits(%w[1]),
40
+ hits(%w[2 2]),
41
+ hits(%w[3 3 3]),
42
+ hits(%w[4 4 4 4]),
43
+ hits(%w[5 5 5 5 5]),
44
+ hits(%w[6 6 6 6 6 6]),
45
+ hits(%w[7 7 7 7 7 7 7])
47
46
  )
48
47
 
49
48
  allow(es_client).to receive(:scroll).and_return(hits([]))
50
49
 
51
- allow(es_client).to receive(:clear_scroll)
52
-
53
- expect(subject.grep(query, 3600)).to eq 28
50
+ expect(es_client).to receive(:clear_scroll).exactly(7).times
51
+ expect(subject.grep(query)).to eq 28
54
52
  end
55
53
  end
56
-
57
54
  end
58
55
 
59
56
  def hits(messages)
60
57
  {
61
- hits: {
62
- hits: messages.map { |m| { fields: { message: m }}}
58
+ "hits" => {
59
+ "hits" => messages.map { |m| {"_source" => {"message" => m}} }
63
60
  }
64
61
  }
65
62
  end
66
-
67
63
  end