redis-slowlog-stasher 0.0.3

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/LICENSE ADDED
@@ -0,0 +1,27 @@
1
+ Copyright (c) 2014, 3dna Corp
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+
10
+ * Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+
14
+ * Neither the name of the 3dna Corp nor the names of its
15
+ contributors may be used to endorse or promote products derived from
16
+ this software without specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,27 @@
1
+ redis-slowlog-stasher
2
+
3
+ # what?
4
+
5
+ [redis](http://redis.io) has this awesome feature called the [slowlog](http://redis.io/commands/slowlog), which keeps track of commands that take a long time to run.
6
+
7
+ # why?
8
+
9
+ because redis is single threaded, if you have a command that takes too long, it'll block all other commands, and on a high traffic redis server, this can be disastrous.
10
+
11
+ # how?
12
+
13
+ funny enough, this program is designed to use [rabbitmq](http://www.rabbitmq.com/)as a transport for the logs. Why rabbitmq? Because I already have a rabbitmq set up for "reliable" log transport and that's what I'm going to keep using for now.
14
+
15
+ The basic idea, though, is that it calls the `slowlog` command on the redis server periodically and any new messages in the slowlog get sent over to logstash. Pretty simple, right?
16
+
17
+ # who?
18
+
19
+ This script was written by Jeremy Kitchen while working at NationBuilder in May, 2014
20
+
21
+ # license
22
+
23
+ I consider this to be a pretty trivial thing, so I won't be making any claims of copyright. Please feel free to use, modify, steal, sell, use for bad things, whatever.
24
+
25
+ In jurisdictions where applicable, I'm placing this code into the public domain. Where it's not I'm granting a non-exclusive non-revokable royalty free license to use the code as you please.
26
+
27
+ If you like it, drop me a [gittip](https://www.gittip.com/kitchen) or buy me a beverage some time.
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'redis_slowlog_stasher'
4
+
5
+
6
+ require 'time'
7
+ require 'redis'
8
+ require 'bunny'
9
+ require 'uri'
10
+ require 'json'
11
+
12
+
13
+ options = RedisSlowlogStasher::Argparser.parse(ARGV)
14
+ logger = options.logger
15
+ redis_arg, rabbit_arg = ARGV
16
+
17
+ # methinks this logic should just be in the StateFile?
18
+ # if you pass it a nil filename write is a noop and read gives you 0,0?
19
+ if options.state_file
20
+ previous_entry_timestamp, previous_entry_id = options.state_file.read
21
+ else
22
+ previous_entry_timestamp, previous_entry_id = [0,0]
23
+ end
24
+
25
+ redis_uri = URI.parse(redis_arg)
26
+ redis = Redis.new(host: redis_uri.host, port: redis_uri.port, password: redis_uri.user)
27
+
28
+ rabbit = Bunny.new(rabbit_arg)
29
+ rabbit.start
30
+ channel = rabbit.create_channel
31
+ exchange = channel.direct(options.exchange)
32
+
33
+ base_event_object = {}
34
+ if options.type
35
+ base_event_object.merge!({'type' => options.type})
36
+ end
37
+ if options.tags
38
+ base_event_object.merge!({'tags' => options.tags})
39
+ end
40
+ if options.fields
41
+ base_event_object.merge!(options.fields)
42
+ end
43
+
44
+ # start the main loop
45
+ loop do
46
+ logger.debug("loop iteration")
47
+ slowlog_entries = redis.slowlog('get',options.check_entries)
48
+
49
+ slowlog_entries.reverse.each do |slowlog_entry|
50
+ # "parse" the entry
51
+ entry_id, entry_timestamp, duration, args = slowlog_entry
52
+ command = args.shift
53
+
54
+ #logger.debug("previous_entry_id: #{previous_entry_id}, entry_id: #{entry_id}; previous_entry_timestamp: #{previous_entry_timestamp}, entry_timestamp: #{entry_timestamp}")
55
+
56
+ # TODO: combine all of these into one ugly if
57
+ if (entry_id > previous_entry_id) || (entry_id < previous_entry_id && entry_timestamp >= previous_entry_timestamp) || (entry_id == previous_entry_id && entry_timestamp != previous_entry_timestamp)
58
+ logger.info("new entry id #{entry_id} duration #{duration} with timestamp #{entry_timestamp}: #{command} #{args}")
59
+ entry_hash = base_event_object.merge({
60
+ '@timestamp' => Time.at(entry_timestamp).utc.iso8601,
61
+ 'version' => 1,
62
+ 'duration' => duration,
63
+ 'command' => command,
64
+ 'args' => args,
65
+ })
66
+
67
+ logger.debug(entry_hash.to_json)
68
+ # log it
69
+ exchange.publish(entry_hash.to_json, routing_key: options.routing_key)
70
+
71
+ # update trackers
72
+ previous_entry_timestamp = entry_timestamp
73
+ previous_entry_id = entry_id
74
+ if options.state_file
75
+ options.state_file.write(entry_timestamp, entry_id)
76
+ end
77
+ end
78
+ end
79
+
80
+ sleep options.check_interval
81
+ end
82
+
@@ -0,0 +1,6 @@
1
+ require 'redis_slowlog_stasher/statefile'
2
+ require 'redis_slowlog_stasher/version'
3
+ require 'redis_slowlog_stasher/argparser'
4
+
5
+ module RedisSlowlogStasher
6
+ end
@@ -0,0 +1,88 @@
1
+ require 'ostruct'
2
+ require 'optparse'
3
+ module RedisSlowlogStasher
4
+ class Argparser
5
+ def self.parse(args)
6
+ options = OpenStruct.new
7
+
8
+ options.check_interval = 10
9
+ options.check_entries = 25
10
+ options.exchange = 'logstash'
11
+ options.routing_key = 'logstash'
12
+
13
+ options.logger = Logger.new(STDOUT)
14
+
15
+ opts = OptionParser.new do |opts|
16
+ opts.banner = 'Usage: redis-slowlog-stasher [options] redis-server rabbitmq-amqp-uri'
17
+
18
+ opts.separator ""
19
+ opts.separator "options:"
20
+
21
+ opts.on("--exchange EXCHANGE", String, "the name of the exchange to use") do |exchange|
22
+ options.exchange = exchange
23
+ end
24
+
25
+ opts.on("--routing-key KEY", String, "the routing key to use") do |routing_key|
26
+ options.routing_key = routing_key
27
+ end
28
+
29
+ opts.on("--state-file FILE", String, "the path to the state file which tells this program which",
30
+ "slowlog entry was the last one processed so we can attempt",
31
+ "to not lose any messages and not duplicate any, either.") do |state_file|
32
+ options.state_file = StateFile.new(state_file)
33
+ end
34
+
35
+ opts.on("--type TYPE", String, "the value of the type field of the event") do | type |
36
+ options.type = type
37
+ end
38
+
39
+ opts.on("--tags tag1,tag2,tag3", Array, "a list of tags to add to the event") do | tags |
40
+ options.tags ||= []
41
+ options.tags.concat(tags)
42
+ end
43
+
44
+ opts.on("--add-field field1=value1,field2=value2", Array, "a list of option fields and their values to add to the event") do |fields|
45
+ options.fields ||= {}
46
+ options.fields.merge!(Hash[fields.map { |field| field.split('=', 2) }])
47
+ end
48
+
49
+ opts.on("--check-interval SECONDS", Float, "how frequently to check the slowlog for new entries. In seconds") do |check_interval|
50
+ options.check_interval = check_interval
51
+ end
52
+
53
+ opts.on("--check-entries COUNT", Integer, "how many entries to check for each interval.") do |check_entries|
54
+ options.check_entries = check_entries
55
+ end
56
+
57
+ opts.on("--log-level LEVEL", ["FATAL","ERROR","WARN","INFO","DEBUG"], "sets the level of output verbosity") do |log_level|
58
+ case log_level
59
+ when "FATAL"
60
+ logger.level = Logger::FATAL
61
+ when "ERROR"
62
+ logger.level = Logger::ERROR
63
+ when "WARN"
64
+ logger.level = Logger::WARN
65
+ when "INFO"
66
+ logger.level = Logger::INFO
67
+ when "DEBUG"
68
+ logger.level = Logger::DEBUG
69
+ end
70
+ end
71
+
72
+ opts.on_tail("-h", "--help", "Show this message") do
73
+ puts opts
74
+ exit
75
+ end
76
+
77
+ opts.on_tail("--version", "Show version") do
78
+ puts '0.0.1'
79
+ exit
80
+ end
81
+
82
+ end
83
+
84
+ opts.parse!(args)
85
+ options
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,21 @@
1
+ # TODO: if you give it a nil filename, read gives you 0,0, write is a noop?
2
+ class StateFile
3
+ def initialize(filename)
4
+ @filename = filename
5
+ end
6
+
7
+ def read
8
+ File.open(@filename, 'r') do |state_file|
9
+ state_file.gets.chomp.split(':').map(&:to_i)
10
+ end
11
+ rescue Errno::ENOENT
12
+ # if the file doesn't exist, just return the thing
13
+ [0,0]
14
+ end
15
+
16
+ def write(entry_timestamp, entry_id)
17
+ File.open(@filename, 'w') do |state_file|
18
+ state_file.puts("#{entry_timestamp}:#{entry_id}")
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,3 @@
1
+ module RedisSlowlogStasher
2
+ VERSION = '0.0.3'
3
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: redis-slowlog-stasher
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jeremy Kitchen
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-05-14 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: redis
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 3.0.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 3.0.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: bunny
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: 1.0.0
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: 1.0.0
46
+ description: watches the redis slowlog command and sends structured slowlog entries
47
+ to logstash for processing and storage
48
+ email: jeremy@nationbuilder.com
49
+ executables:
50
+ - redis-slowlog-stasher
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - lib/redis_slowlog_stasher.rb
55
+ - lib/redis_slowlog_stasher/statefile.rb
56
+ - lib/redis_slowlog_stasher/argparser.rb
57
+ - lib/redis_slowlog_stasher/version.rb
58
+ - bin/redis-slowlog-stasher
59
+ - README.md
60
+ - LICENSE
61
+ homepage: https://github.com/3dna/redis-slowlog-stasher
62
+ licenses:
63
+ - BSD-3-Clause
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ! '>='
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ! '>='
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ requirements: []
81
+ rubyforge_project:
82
+ rubygems_version: 1.8.24
83
+ signing_key:
84
+ specification_version: 3
85
+ summary: put redis slowlog entries into logstash
86
+ test_files: []
87
+ has_rdoc: