lstash 0.2.0 → 1.0.0

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.
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