turnstile-rb 2.0.1 → 3.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +3 -0
  3. data/.travis.yml +2 -2
  4. data/LICENSE.txt +2 -1
  5. data/README.md +200 -11
  6. data/Rakefile +1 -0
  7. data/bin/turnstile +4 -6
  8. data/example/custom_csv_matcher.rb +22 -0
  9. data/lib/turnstile.rb +37 -8
  10. data/lib/turnstile/cli/launcher.rb +83 -0
  11. data/lib/turnstile/cli/parser.rb +166 -0
  12. data/lib/turnstile/cli/runner.rb +58 -0
  13. data/lib/turnstile/collector.rb +2 -2
  14. data/lib/turnstile/collector/actor.rb +81 -0
  15. data/lib/turnstile/collector/controller.rb +121 -0
  16. data/lib/turnstile/collector/flusher.rb +36 -0
  17. data/lib/turnstile/collector/formats.rb +7 -34
  18. data/lib/turnstile/collector/formats/custom_matcher.rb +19 -0
  19. data/lib/turnstile/collector/formats/delimited_matcher.rb +30 -0
  20. data/lib/turnstile/collector/formats/json_matcher.rb +30 -0
  21. data/lib/turnstile/collector/log_reader.rb +84 -46
  22. data/lib/turnstile/collector/{matcher.rb → regexp_matcher.rb} +4 -5
  23. data/lib/turnstile/collector/session.rb +7 -0
  24. data/lib/turnstile/commands.rb +20 -0
  25. data/lib/turnstile/commands/base.rb +20 -0
  26. data/lib/turnstile/commands/flushdb.rb +21 -0
  27. data/lib/turnstile/commands/print_keys.rb +19 -0
  28. data/lib/turnstile/commands/show.rb +89 -0
  29. data/lib/turnstile/configuration.rb +23 -0
  30. data/lib/turnstile/dependencies.rb +31 -0
  31. data/lib/turnstile/logger.rb +9 -40
  32. data/lib/turnstile/logger/helper.rb +42 -0
  33. data/lib/turnstile/logger/provider.rb +74 -0
  34. data/lib/turnstile/observer.rb +5 -9
  35. data/lib/turnstile/redis/adapter.rb +97 -0
  36. data/lib/turnstile/redis/connection.rb +116 -0
  37. data/lib/turnstile/redis/spy.rb +42 -0
  38. data/lib/turnstile/sampler.rb +9 -2
  39. data/lib/turnstile/tracker.rb +14 -14
  40. data/lib/turnstile/version.rb +51 -12
  41. data/lib/turnstile/web_app.rb +29 -0
  42. data/spec/spec_helper.rb +18 -3
  43. data/spec/support/logging.rb +17 -0
  44. data/spec/turnstile/adapter_spec.rb +59 -46
  45. data/spec/turnstile/collector/flusher_spec.rb +16 -0
  46. data/spec/turnstile/collector/log_reader_spec.rb +127 -77
  47. data/spec/turnstile/commands/show_spec.rb +40 -0
  48. data/spec/turnstile/tracker_spec.rb +21 -7
  49. data/spec/turnstile_spec.rb +3 -0
  50. data/turnstile-rb.gemspec +5 -2
  51. metadata +89 -22
  52. data/Gemfile +0 -6
  53. data/lib/turnstile/adapter.rb +0 -61
  54. data/lib/turnstile/collector/runner.rb +0 -72
  55. data/lib/turnstile/collector/updater.rb +0 -86
  56. data/lib/turnstile/parser.rb +0 -107
  57. data/lib/turnstile/runner.rb +0 -54
  58. data/lib/turnstile/summary.rb +0 -57
  59. data/spec/turnstile/summary_spec.rb +0 -41
@@ -0,0 +1,166 @@
1
+ require 'optparse'
2
+ require 'colored2'
3
+ require 'forwardable'
4
+
5
+ require 'turnstile/version'
6
+
7
+ module Turnstile
8
+ module CLI
9
+ class Parser
10
+ extend Forwardable
11
+ def_delegators :@system, :stdout, :stdin, :stderr
12
+
13
+ attr_accessor :options, :argv, :system
14
+
15
+ def initialize(argv, system)
16
+ self.system = system
17
+ self.argv = argv.dup
18
+ self.options = Hashie::Mash.new
19
+ self.argv << '-h' if argv.empty?
20
+ end
21
+
22
+ def parse
23
+ begin
24
+ OptionParser.new do |opts|
25
+ opts.banner = ' '
26
+
27
+ opts.separator 'DESCRIPTION:'.bold.magenta
28
+ opts.separator ' ' + ::Turnstile::GEM_DESCRIPTION.gsub(/\n/, "\n ").strip
29
+ opts.separator ''
30
+
31
+ opts.separator "USAGE:\n".bold.magenta +
32
+ " # Tail the log file as a proper daemon\n".bold.black +
33
+ " turnstile -f <file> [ --daemon ] [ options ]\n\n".yellow +
34
+
35
+ " # Add a single item and exit\n".bold.black +
36
+ " turnstile -a 'platform:ip:user' [ options ]\n\n".yellow +
37
+
38
+ " # Print the summary stats and exit\n".bold.black +
39
+ ' turnstile -s [ json | csv | nad ] [ options ]'.yellow
40
+
41
+ opts.separator ''
42
+ opts.separator 'DETAILS:'.bold.magenta
43
+ opts.separator ' ' + ::Turnstile::DESCRIPTION.gsub(/\n/, "\n ").strip
44
+
45
+ opts.separator ''
46
+ opts.separator 'OPTIONS:'.bold.magenta
47
+
48
+
49
+ opts.separator "\n Mode of Operation:".bold.green
50
+ opts.on('-f', '--file FILE',
51
+ 'Starts Turnstile in a file-tailing mode',
52
+ ' ') do |file|
53
+ options[:file] = file
54
+ end
55
+
56
+ opts.on('-s', '--show [FORMAT]',
57
+ 'Print current stats and exit. Optional ',
58
+ 'format can be "json" (default), "nad",',
59
+ '"yaml", or "csv"', ' ') do |v|
60
+ options[:show] = true
61
+ options[:show_format] = (v || 'json').to_sym
62
+ end
63
+
64
+ opts.on('-w', '--web [PORT]',
65
+ 'Starts a Sinatra app on a given port',
66
+ 'offering /turnstile/<json|yaml> end point.',
67
+ 'Can be used with file tailing mode, or',
68
+ 'standalone. Default port is ' +
69
+ ::Turnstile::DEFAULT_PORT.to_s,
70
+ ' '
71
+ ) do |v|
72
+ options[:web] = true
73
+ Turnstile.config.port = v.to_i if v
74
+ end
75
+
76
+ opts.on('-a', '--add TOKEN',
77
+ 'Registers an event from the token, such as ',
78
+ '"ios:123.4.4.4:32442". Use -l to customize',
79
+ 'the delimiter', ' ') do |v|
80
+ options[:token] = v
81
+ end
82
+
83
+ opts.on('-p', '--print-keys', 'Prints all Turnstile keys in Redis', ' ') do |v|
84
+ options[:print_keys] = true
85
+ end
86
+
87
+ opts.on('--flushdb', 'Wipes Redis database, and exit', ' ') do |v|
88
+ options[:flushdb] = true
89
+ end
90
+
91
+
92
+ opts.separator ' Tailing log file:'.bold.green
93
+
94
+ opts.on('-d', '--daemonize', 'Daemonize to watch the logs', ' ') do |v|
95
+ options[:daemonize] = true
96
+ end
97
+
98
+ opts.on('-b', '--read-backwards [LINES]',
99
+ 'Like tail, read last LINES lines',
100
+ 'instead of tailing from the end', ' ') do |lines|
101
+ options[:tail] = lines ? lines.to_i : -1
102
+ end
103
+
104
+ opts.on('-F', '--format FORMAT',
105
+ 'Log file format (see above)', ' ') do |format|
106
+ options[:filetype] = format
107
+ end
108
+ opts.on('-l', '--delimiter CHAR',
109
+ 'Forces "delimited" file type, and ',
110
+ 'uses CHAR as the delimiter', ' ') do |v|
111
+ options[:delimiter] = v
112
+ end
113
+
114
+ opts.on('-c', '--config FILE',
115
+ 'Ruby config file that can define the',
116
+ 'custom matcher, supporting arbitrary ',
117
+ 'complex logs') do |file|
118
+ options[:config_file] = file
119
+ end
120
+
121
+ opts.separator "\n Redis Server:".bold.green
122
+
123
+ opts.on('-r', '--redis-url URL', 'Redis server URL') { |host| Turnstile.config.redis_url = host }
124
+ opts.on('--redis-host HOST', 'Redis server host') { |host| Turnstile.config.redis_host = host }
125
+ opts.on('--redis-port PORT', 'Redis server port') { |port| Turnstile.config.redis_port = port }
126
+ opts.on('--redis-db DB', 'Redis server db') { |db| Turnstile.config.redis_db = db }
127
+ opts.on('--hiredis', 'Use hiredis high performance library') do |_value|
128
+ begin
129
+ require 'redis/connection/hiredis'
130
+ rescue LoadError => e
131
+ raise HiredisDriverNotFound, "Can not use hiredis driver: #{e.message}"
132
+ end
133
+ Turnstile.config.redis_use_hiredis = true
134
+ end
135
+
136
+ opts.separator "\n Miscellaneous:".bold.green
137
+
138
+ opts.on('-i', '--idle-sleep SECONDS',
139
+ 'When no work was detected, pause the ',
140
+ 'threads for several seconds.') do |v|
141
+ Turnstile.config.flush_interval = v.to_i
142
+ end
143
+
144
+ opts.on('-v', '--verbose', 'Print status to stdout') { |v| options[:verbose] = true }
145
+ opts.on('-t', '--trace', 'Enable trace mode') do |v|
146
+ options[:trace] = true
147
+ Turnstile.config.trace = true
148
+ end
149
+ opts.on_tail('-h', '--help', 'Show this message') do
150
+ puts opts
151
+ return
152
+ end
153
+ end.parse!(argv)
154
+ options
155
+ rescue OptionParser::MissingArgument => e
156
+ terr 'Invalid Usage: ' + e.message.red, stderr
157
+ nil
158
+ rescue Exception => e
159
+ terr e.message.bold.red, stderr
160
+ nil
161
+ end
162
+ end
163
+
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,58 @@
1
+ require 'optparse'
2
+ require 'colored2'
3
+
4
+ require_relative '../version'
5
+ require_relative '../configuration'
6
+
7
+ require_relative 'launcher'
8
+ require_relative 'parser'
9
+
10
+ module Turnstile
11
+ module CLI
12
+ class Runner
13
+ attr_reader :argv, :stdin, :stdout, :stderr, :kernel
14
+
15
+ # Allow everything fun to be injected from the outside while defaulting to normal implementations.
16
+ def initialize(argv, stdin = STDIN, stdout = STDOUT, stderr = STDERR, kernel = Kernel)
17
+ @argv, @stdin, @stdout, @stderr, @kernel = argv, stdin, stdout, stderr, kernel
18
+ end
19
+
20
+ def execute!
21
+ exit_code = begin
22
+ Colored2.disable! unless stdout.tty?
23
+
24
+ $stderr = stderr
25
+ $stdin = stdin
26
+ $stdout = stdout
27
+
28
+ options = Parser.new(argv, self).parse
29
+ Configuration.from_file(options.config_file) if options && options.config_file
30
+ Launcher.new(options).launch if options
31
+
32
+ # Thor::Base#start does not have a return value, assume success if no exception is raised.
33
+ 0
34
+ rescue StandardError => e
35
+ # The ruby interpreter would pipe this to STDERR and exit 1 in the case of an unhandled exception
36
+ if options && options[:trace]
37
+ b = e.backtrace
38
+ terr("#{b.shift}: #{e.message} (#{e.class})")
39
+ terr(b.map { |s| "\tfrom #{s}" }.join("\n"))
40
+ else
41
+ terr(e.message)
42
+ end
43
+ 1
44
+ rescue SystemExit => e
45
+ e.status
46
+ ensure
47
+ $stderr = STDERR
48
+ $stdin = STDIN
49
+ $stdout = STDOUT
50
+ end
51
+
52
+ # Proxy our exit code back to the injected kernel.
53
+ @kernel.exit(exit_code)
54
+ end
55
+ end
56
+ end
57
+ end
58
+
@@ -4,5 +4,5 @@ module Turnstile
4
4
  end
5
5
 
6
6
  require_relative 'collector/log_reader'
7
- require_relative 'collector/updater'
8
- require_relative 'collector/runner'
7
+ require_relative 'collector/flusher'
8
+ require_relative 'collector/controller'
@@ -0,0 +1,81 @@
1
+ require_relative 'session'
2
+ require_relative '../logger/helper'
3
+ require_relative '../dependencies'
4
+ require 'turnstile'
5
+ require 'thread'
6
+ module Turnstile
7
+ module Collector
8
+ class Actor
9
+ include Logger::Helper
10
+
11
+ @name = 'abstract'
12
+
13
+ class << self
14
+ def actor_name(value = nil)
15
+ @actor_name = value if value
16
+ @actor_name
17
+ end
18
+ end
19
+
20
+ attr_accessor :queue,
21
+ :tracker,
22
+ :sleep_when_idle,
23
+ :options,
24
+ :stopping,
25
+ :thread
26
+
27
+ def initialize(queue:,
28
+ tracker: nil,
29
+ sleep_when_idle: Turnstile.config.flush_interval,
30
+ **opts)
31
+
32
+ self.queue = queue
33
+ self.sleep_when_idle = sleep_when_idle
34
+ self.tracker = tracker || Turnstile::Tracker.new
35
+ self.options = opts
36
+ self.stopping = false
37
+
38
+ Kernel.tdb "actor initialized: #{self.to_s}" if Turnstile.config.trace
39
+ end
40
+
41
+ def shutdown
42
+ self.stopping = true
43
+ end
44
+
45
+ def config
46
+ @config ||= Turnstile.config
47
+ end
48
+
49
+ def stopping?
50
+ self.stopping
51
+ end
52
+
53
+ def start
54
+ self.thread = create_thread(self, sleep_when_idle) do |actor, sleep_period|
55
+ loop do
56
+ items_remaining = actor.execute
57
+ break if actor.stopping?
58
+ sleep(sleep_period) unless items_remaining && items_remaining > 0
59
+ end
60
+ end
61
+ end
62
+
63
+ def to_s
64
+ "<turnsile-actor##{self.class.name.gsub(/.*::/, '').downcase}: queue size: #{queue.size}, idle=#{sleep_when_idle}"
65
+ end
66
+
67
+ # Return nil when there is nothing else to do
68
+ # Return ideally a number representing number of remaining items.
69
+ def execute
70
+ raise ArgumentError, 'Abstract Method'
71
+ end
72
+
73
+ def create_thread(*args, &_block)
74
+ Thread.new(self.class.actor_name, *args) do |actor_name, *args|
75
+ Thread.current[:name] = actor_name
76
+ yield(*args) if block_given?
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,121 @@
1
+ require 'csv'
2
+ require 'thread'
3
+ require 'hashie/extensions/symbolize_keys'
4
+ require 'daemons/daemonize'
5
+ require 'colored2'
6
+
7
+ require_relative 'flusher'
8
+ require_relative 'actor'
9
+
10
+ module Turnstile
11
+ module Collector
12
+ class Controller
13
+ attr_accessor :options, :actors, :threads
14
+
15
+ def initialize(*args)
16
+ self.options = args.last.is_a?(Hash) ? args.pop : {}
17
+
18
+ options.verbose ? Turnstile::Logger.enable : Turnstile::Logger.disable
19
+
20
+ wait_for_file(file)
21
+
22
+ self.actors = [self.reader, self.flusher]
23
+
24
+ Daemonize.daemonize if options[:daemonize]
25
+ STDOUT.sync = true if options[:verbose]
26
+ end
27
+
28
+ def start
29
+ self.threads = actors.map(&:start)
30
+ threads.map(&:join)
31
+ end
32
+
33
+ def stop
34
+ actors.each(&:shutdown)
35
+ end
36
+
37
+ def flusher
38
+ @flusher ||= Flusher.new(**flusher_arguments)
39
+ end
40
+
41
+ def tracker
42
+ @tracker ||= Turnstile::Tracker.new
43
+ end
44
+
45
+ def queue
46
+ @queue ||= Queue.new
47
+ end
48
+
49
+ def file
50
+ options.file
51
+ end
52
+
53
+ def reader
54
+ opts = reader_arguments
55
+ matcher = opts.delete(:matcher).to_sym
56
+ @reader ||= if log_reader_class.respond_to?(matcher)
57
+ log_reader_class.send(matcher, file, queue, **opts)
58
+ else
59
+ raise ArgumentError, "Invalid matcher #{matcher}, args #{reader_args}"
60
+ end
61
+ end
62
+
63
+ def symbolize(opts)
64
+ Hashie::Extensions::SymbolizeKeys.symbolize_keys(opts.to_h)
65
+ end
66
+
67
+ def config
68
+ @config ||= Turnstile.config
69
+ end
70
+
71
+ private
72
+
73
+ def flusher_arguments
74
+ symbolize(actor_argument_hash.merge(sleep_when_idle: config.flush_interval))
75
+ end
76
+
77
+ def reader_arguments
78
+ reader_args_hash = actor_argument_hash.merge(sleep_when_idle: config.flush_interval)
79
+ matcher, delimiter = select_matcher
80
+
81
+ reader_args_hash.merge!(delimiter: delimiter) if delimiter
82
+ reader_args_hash.merge!(matcher: matcher) if matcher
83
+
84
+ symbolize(reader_args_hash)
85
+ end
86
+
87
+ def actor_argument_hash
88
+ options.merge(queue: queue, tracker: tracker)
89
+ end
90
+
91
+ def wait_for_file(file)
92
+ sleep_period = 1
93
+ while !::File.exist?(file)
94
+ terr "File #{file.bold.yellow} does not exist, waiting for it to appear..."
95
+ terr 'Press Ctrl-C to abort.' if sleep_period == 1
96
+
97
+ sleep sleep_period
98
+ sleep_period *= 1.2
99
+ end
100
+ tdb "Detected file #{file.bold.yellow} now exists, continue..."
101
+ end
102
+
103
+ def log_reader_class
104
+ Turnstile::Collector::LogReader
105
+ end
106
+
107
+ def select_matcher
108
+ matcher = :default
109
+ delimiter = nil
110
+
111
+ if options[:delimiter]
112
+ matcher = :delimited
113
+ delimiter = options[:delimiter]
114
+ elsif options[:filetype]
115
+ matcher = options[:filetype].to_sym
116
+ end
117
+ return matcher, delimiter
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,36 @@
1
+ require_relative 'session'
2
+
3
+ module Turnstile
4
+ module Collector
5
+ class Flusher < Actor
6
+
7
+ actor_name :flusher
8
+
9
+ def execute
10
+ flush_current_buffer unless queue.empty?
11
+ queue.size
12
+ rescue Exception => e
13
+ puts e.backtrace.reverse.join("\n")
14
+ puts e.inspect.red
15
+ raise e
16
+ end
17
+
18
+ def flush_current_buffer
19
+ item = queue.pop
20
+ return unless item
21
+ session = parse(item)
22
+ tracker.track(session.uid,
23
+ session.platform,
24
+ session.ip) if session.uid
25
+ end
26
+
27
+ def parse(token)
28
+ # platform, IP, user
29
+ a = token.split(':')
30
+
31
+ # session is backwards
32
+ Session.new(a[2], a[0], a[1])
33
+ end
34
+ end
35
+ end
36
+ end