logbox 0.2.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/.bundle/config +3 -0
  2. data/.rvmrc +2 -0
  3. data/Gemfile +17 -0
  4. data/Gemfile.lock +30 -0
  5. data/README +14 -0
  6. data/Rakefile +74 -0
  7. data/VERSION +1 -0
  8. data/bin/download_logs +20 -0
  9. data/bin/obsstats +39 -0
  10. data/bin/rotate +17 -0
  11. data/bin/viewobs +198 -0
  12. data/lib/logbox.rb +9 -0
  13. data/lib/logbox/ansi_colors.rb +28 -0
  14. data/lib/logbox/log_parser.rb +79 -0
  15. data/lib/logbox/mockup_log.rb +44 -0
  16. data/lib/logbox/observation.rb +162 -0
  17. data/lib/logbox/observation_compiler.rb +311 -0
  18. data/lib/logbox/observation_mover.rb +142 -0
  19. data/lib/logbox/stream_wrapper.rb +20 -0
  20. data/lib/logbox/stream_wrapper/gzip_multi_file.rb +90 -0
  21. data/lib/logbox/stream_wrapper/observation_filter.rb +113 -0
  22. data/lib/logbox/stream_wrapper/order_blob_splitter.rb +96 -0
  23. data/lib/setup_environment.rb +15 -0
  24. data/logbox.gemspec +110 -0
  25. data/test/bin_viewobs_test.rb +42 -0
  26. data/test/fixtures/aws_keys_yaml.txt +3 -0
  27. data/test/fixtures/double-obs.log +1 -0
  28. data/test/fixtures/error_line.log +1 -0
  29. data/test/fixtures/log-for-md5.log +1 -0
  30. data/test/fixtures/log0.log +0 -0
  31. data/test/fixtures/log1.log +1 -0
  32. data/test/fixtures/log1.log.gz +0 -0
  33. data/test/fixtures/log2.log +2 -0
  34. data/test/fixtures/log2.log.gz +0 -0
  35. data/test/fixtures/log_invalid_mixed_encoding.log +1 -0
  36. data/test/fixtures/observation_filter.log +5 -0
  37. data/test/fixtures/unquoted_ugliness.log +2 -0
  38. data/test/log_parser_test.rb +84 -0
  39. data/test/observation_compiler_test.rb +216 -0
  40. data/test/observation_mover_test.rb +135 -0
  41. data/test/observation_test.rb +114 -0
  42. data/test/stream_wrapper/gzip_multi_file_test.rb +147 -0
  43. data/test/stream_wrapper/observation_filter_test.rb +171 -0
  44. data/test/stream_wrapper/order_blob_splitter_test.rb +129 -0
  45. data/test/test_helper.rb +23 -0
  46. metadata +177 -0
data/.bundle/config ADDED
@@ -0,0 +1,3 @@
1
+ ---
2
+ BUNDLE_DISABLE_SHARED_GEMS: "1"
3
+ BUNDLE_WITHOUT: ""
data/.rvmrc ADDED
@@ -0,0 +1,2 @@
1
+ rvm use 1.8.7-p302@logbox --create
2
+ rvm use 1.9.2-p0@logbox --create
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem 'right_aws', '~>2.0'
4
+ gem 'single_instance'
5
+ gem 'rake'
6
+
7
+ group :test do
8
+ gem 'shoulda', '2.10.3'
9
+ gem 'mocha', '0.9.8'
10
+ end
11
+
12
+ # required for jeweler
13
+ group :development do
14
+ gem "bundler", "~> 1.0.0"
15
+ gem "jeweler", "~> 1.5.1"
16
+ gem "rcov", ">= 0"
17
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,30 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ git (1.2.5)
5
+ jeweler (1.5.1)
6
+ bundler (~> 1.0.0)
7
+ git (>= 1.2.5)
8
+ rake
9
+ mocha (0.9.8)
10
+ rake
11
+ rake (0.8.7)
12
+ rcov (0.9.9)
13
+ right_aws (2.0.0)
14
+ right_http_connection (>= 1.2.1)
15
+ right_http_connection (1.2.4)
16
+ shoulda (2.10.3)
17
+ single_instance (0.2.0)
18
+
19
+ PLATFORMS
20
+ ruby
21
+
22
+ DEPENDENCIES
23
+ bundler (~> 1.0.0)
24
+ jeweler (~> 1.5.1)
25
+ mocha (= 0.9.8)
26
+ rake
27
+ rcov
28
+ right_aws (~> 2.0)
29
+ shoulda (= 2.10.3)
30
+ single_instance
data/README ADDED
@@ -0,0 +1,14 @@
1
+ == A Toolbox for Logs and Observations
2
+
3
+ Two main uses:
4
+
5
+ 1. Use the tools in bin to download and view logs etc.
6
+ 2. Include logbox.rb and use the code directly.
7
+
8
+ StreamWrapper, LogParser and Observation are the main entry points to the code.
9
+
10
+ == Development and testing
11
+
12
+ Running `rake` will run the test suite for ruby 1.8.7 and 1.9.2.
13
+
14
+ Run `rake setup` to generate shell commands that will enable you to run all the tests.
data/Rakefile ADDED
@@ -0,0 +1,74 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "logbox"
16
+ gem.executables = %w(download_logs obsstats rotate viewobs)
17
+ gem.homepage = "https://github.com/icehouse/logbox"
18
+ gem.license = "MIT"
19
+ gem.summary = %Q{A Toolbox for Logs and Observations}
20
+ gem.description = %Q{Log-related code and tools that are used cross different applications}
21
+ gem.email = "dev@icehouse.se"
22
+ gem.authors = ["dvrensk", "enarsson", "Jell"]
23
+ gem.files.include(["lib/*"])
24
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
25
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
26
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
27
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
28
+ end
29
+ Jeweler::RubygemsDotOrgTasks.new
30
+
31
+ require 'rake/testtask'
32
+ Rake::TestTask.new(:test) do |test|
33
+ test.libs << 'lib' << 'test'
34
+ test.pattern = 'test/**/*_test.rb'
35
+ test.verbose = true
36
+ end
37
+
38
+ require 'rcov/rcovtask'
39
+ Rcov::RcovTask.new do |test|
40
+ test.libs << 'test'
41
+ test.pattern = 'test/**/test_*.rb'
42
+ test.verbose = true
43
+ end
44
+
45
+ require 'rake/rdoctask'
46
+ Rake::RDocTask.new do |rdoc|
47
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
48
+
49
+ rdoc.rdoc_dir = 'rdoc'
50
+ rdoc.title = "logbox #{version}"
51
+ rdoc.rdoc_files.include('README*')
52
+ rdoc.rdoc_files.include('lib/**/*.rb')
53
+ end
54
+
55
+ RUBIES = %w[1.8.7-p302 1.9.2-p0]
56
+ desc "Run tests with ruby 1.8.7 and 1.9.2"
57
+ task :default do
58
+ RUBIES.each do |ruby|
59
+ sh "rvm #{ruby}@logbox rake test"
60
+ end
61
+ end
62
+
63
+ desc "Install bundler and gems for each environment in #{RUBIES.join','}"
64
+ task :setup do
65
+ cmd = []
66
+ RUBIES.each do |ruby|
67
+ cmd << "rvm --create #{ruby}@logbox"
68
+ cmd << "gem list | grep -q ^rake || gem install rake"
69
+ cmd << "gem list | grep -q ^bundler || gem install bundler"
70
+ cmd << "bundle install"
71
+ end
72
+ puts "# RUN THE FOLLOWING LINES IN YOUR SHELL"
73
+ puts cmd
74
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.10
data/bin/download_logs ADDED
@@ -0,0 +1,20 @@
1
+ #! /usr/bin/env ruby
2
+ $: << File.join(File.dirname(__FILE__), '..', 'lib')
3
+ require 'setup_environment'
4
+ require 'optparse'
5
+ require 'observation_compiler'
6
+
7
+ option_parser = OptionParser.new do |opts|
8
+ opts.banner = "Usage: download_logs number_of_days\n" +
9
+ "Downloads logs from all log servers and merge them to one log file per day. Observations are ordered."
10
+ end
11
+
12
+ ENV['OBSENTER_S3_KEY'] ||= "AKIAI6RLB45ZDVINPZ3Q"
13
+
14
+ if ARGV.size == 1 && ARGV[0] =~ /^\d+$/
15
+ start_date = Date.today - ARGV[0].to_i + 1
16
+ job = ObservationCompiler::Job.new(:processed_logs_path => File.exist?("/apps/observation_logs") ? "/apps/observation_logs" : "local_files")
17
+ job.fetch_and_merge(start_date..Date.today)
18
+ else
19
+ puts option_parser
20
+ end
data/bin/obsstats ADDED
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby
2
+ $: << File.join(File.dirname(__FILE__), '..', 'lib')
3
+ require 'setup_environment'
4
+ require 'observation'
5
+ require 'stream_wrapper'
6
+ require 'ansi_colors'
7
+ require 'set'
8
+ require 'yaml'
9
+
10
+ filters = {
11
+ :valid => true,
12
+ :skip_debug_observations => true
13
+ }
14
+ observations = StreamWrapper.open(ARGV, filters)
15
+
16
+ shops = Hash.new do |h,k|
17
+ h2 = Hash.new(0)
18
+ h2[:u_items] = Set.new
19
+ h2[:u_users] = Set.new
20
+ h[k] = h2
21
+ end
22
+
23
+ observations.each do |obs|
24
+ stats = shops["#{obs[:account_id]}/#{obs[:shop_id]}"]
25
+ stats[obs.type] += 1
26
+ stats[:u_items] << obs[:item_id] if obs[:item_id]
27
+ stats[:u_users] << obs[:user_id] if obs[:user_id]
28
+ end
29
+
30
+ shops.each_pair do |k,stats|
31
+ [:u_items, :u_users].each do |u|
32
+ stats[u] = stats[u].size
33
+ end
34
+ end
35
+
36
+ shops.keys.sort.each do |shop_id|
37
+ stats = shops[shop_id]
38
+ puts stats.to_yaml.sub('---', "*** #{shop_id}")
39
+ end
data/bin/rotate ADDED
@@ -0,0 +1,17 @@
1
+ #! /usr/bin/env ruby
2
+ $: << File.join(File.dirname(__FILE__), '..', 'lib')
3
+ require 'logbox/observation_mover'
4
+
5
+ if ARGV.first == '--act-as-fake-nginx'
6
+ ObservationMover.act_as_fake_nginx
7
+ else
8
+ ObservationMover.new(*ARGV).run
9
+ end
10
+
11
+ # To test, run with
12
+ # rm -rf testlogs
13
+ # bin/rotate --act-as-fake-nginx &
14
+ # and then
15
+ # bin/rotate testlogs/test.log $(pwd)/test.pid
16
+ # ls -l testlogs
17
+ # Repeat.
data/bin/viewobs ADDED
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env ruby
2
+ $: << File.join(File.dirname(__FILE__), '..', 'lib')
3
+ require 'setup_environment'
4
+ require 'log_parser'
5
+ require 'observation'
6
+ require 'ansi_colors'
7
+ require 'stream_wrapper'
8
+
9
+
10
+ # We don't need a stack dump when interrupted with Ctrl-C
11
+ trap("INT", "EXIT")
12
+
13
+ # Make symbols sortable.
14
+ class Symbol
15
+ def <=>(other)
16
+ self.to_s <=> other.to_s
17
+ end
18
+ end
19
+
20
+ class Viewobs
21
+ attr_accessor :options
22
+
23
+ def parse_options
24
+ require 'optparse'
25
+ options = { :delimiter => "\t" }
26
+ option_parser = OptionParser.new do |opts|
27
+ opts.banner = "Usage: viewobs.rb [options] files"
28
+ opts.separator ""
29
+ opts.separator "Observation filters:"
30
+ opts.on("-i IP", "--ip IP", "Filter on IP.") { |v| options[:filter_ip] = v }
31
+ opts.on("-s SHOP_ID", "--shop SHOP_ID", "Filter on shop ID.") { |v| options[:filter_shop_id] = v }
32
+ opts.on("-a ACCOUNT_ID", "--account ACCOUNT_ID", "Filter on account ID.") { |v| options[:filter_account_id] = v }
33
+ opts.on("-t TYPE", "--type TYPE", "Filter on observation type.") { |v| options[:filter_type] = v }
34
+ opts.on("-v", "--valid", "Display only valid observations.") { |v| options[:filter_valid] = v }
35
+ opts.separator ""
36
+ opts.separator "Display filters:"
37
+ opts.on("-A", "--all", "Display all attributes.") { |v| options[:all_attributes] = v }
38
+ opts.on("-u", "--user", "Display user set attributes.") { |v| options[:user_attributes] = v }
39
+ opts.on("-o", "--observation", "Display observation attributes.") { |v| options[:observation_attributes] = v }
40
+ opts.on("-e", "--errors", "Display errors (if any).") { |v| options[:errors] = v }
41
+ opts.on("-r", "--request", "Display raw request.") { |v| options[:request] = v }
42
+ opts.separator ""
43
+ opts.separator "Other options:"
44
+ opts.on("-f", "--follow", "Waits at the end of the log for updates.") { |v| options[:follow] = v }
45
+ opts.on("-c", "--colorize", "Colorize output.") { |v| String.colorize }
46
+ opts.on("-T", "--table", "Output as table or csv.", "Does not work with data from stdin (reads twice).") { options[:table] = true }
47
+ opts.on("-d CHAR", "--delimiter CHAR", "Delimiter for table (default is tab).") { |v| options[:delimiter] = v[0,1] }
48
+ opts.separator ""
49
+ end
50
+
51
+ begin
52
+ # Parse will leave any filename argument intact in ARGV but it will remove all options.
53
+ option_parser.parse!(ARGV)
54
+ rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
55
+ puts e
56
+ puts option_parser
57
+ exit 1
58
+ end
59
+ @options = options
60
+ end
61
+
62
+
63
+ def view_line(attributes, observation)
64
+ # Print basic information.
65
+ out = ""
66
+ out << "#{observation.attributes[:account_id].to_s.red}" if observation.attributes[:account_id]
67
+ out << " - #{observation.attributes[:shop_id].to_s.red}" if observation.attributes[:shop_id]
68
+ out << " - #{attributes[:o].blue}" if attributes[:o] # Observation type.
69
+ out << " - #{attributes[:timestamp].strftime('%Y-%m-%d %H.%M.%S')}"
70
+ out << " - #{attributes[:ip]}"
71
+ out << " - #{attributes[:status]}"
72
+ out << " - #{'valid'.green}" if observation.valid?
73
+ puts out
74
+
75
+ # Print extended information depending on options.
76
+ if options[:all_attributes]
77
+ attributes.keys.sort.each do |key|
78
+ puts " #{key.to_s.blue}: #{attributes[key]}" unless [:ip, :request, :timestamp, :status].include?(key)
79
+ end
80
+ puts
81
+ end
82
+ if options[:user_attributes]
83
+ attributes.keys.sort.each do |key|
84
+ puts " #{key.to_s[1..-1].blue}: #{attributes[key]}" if key.to_s[0..0] == '_'
85
+ end
86
+ puts
87
+ end
88
+ if options[:observation_attributes]
89
+ observation.attributes.keys.sort.each do |key|
90
+ puts " #{key.to_s.blue}: #{observation.attributes[key]}" if observation.attributes.has_key?(key) && key != :request
91
+ end
92
+ puts
93
+ end
94
+ if options[:errors]
95
+ puts observation.errors.inspect unless observation.valid?
96
+ puts
97
+ end
98
+ if options[:request]
99
+ puts attributes[:request]
100
+ puts
101
+ end
102
+ end
103
+
104
+ def view_table_line(attributes, observation)
105
+ # Print basic information.
106
+ out = []
107
+ out << observation.attributes[:account_id].to_s.red
108
+ out << observation.attributes[:shop_id].to_s.red
109
+ out << attributes[:o].blue # Observation type.
110
+ out << attributes[:timestamp].strftime('%Y-%m-%d %H.%M.%S')
111
+ out << attributes[:ip]
112
+ out << attributes[:status]
113
+ out << (observation.valid? ? 'valid' : 'invalid')
114
+
115
+ @all_keys.each do |key|
116
+ out << attributes[key] unless [:o, :ip, :request, :timestamp, :status].include?(key)
117
+ end
118
+
119
+ puts out.map { |s| s.to_s.tr("#{delimiter}\0- "," ") }.join(delimiter)
120
+ end
121
+
122
+ def view_until_end
123
+ # gets will take lines from any filename given in ARGV or from stdin.
124
+ pingdom_re = /pingdom/
125
+ filters = {
126
+ :account_id => options[:filter_account_id],
127
+ :shop_id => options[:filter_shop_id],
128
+ :observation_type => options[:filter_type]
129
+ }
130
+ stream = StreamWrapper.open(ARGV, filters)
131
+ while line = stream.gets
132
+ attributes = LogParser.parse_line(line)
133
+ observation = Observation.new(attributes)
134
+
135
+ # Skip lines from pingdom.
136
+ next if attributes[:request].match(pingdom_re)
137
+
138
+ # Filter depending on options.
139
+ if options[:filter_ip]
140
+ next unless attributes[:ip].match(options[:filter_ip])
141
+ end
142
+ if options[:filter_valid]
143
+ next unless observation.valid?
144
+ end
145
+
146
+ begin
147
+ if options[:gathering_keys]
148
+ gather_keys(attributes, observation)
149
+ elsif options[:table]
150
+ view_table_line(attributes, observation)
151
+ else
152
+ view_line(attributes, observation)
153
+ end
154
+ rescue Exception => e
155
+ $stderr.puts "Could not render line #{$.} (#{e.message})"
156
+ end
157
+ end
158
+ if options[:gathering_keys]
159
+ @all_keys = @all_keys.sort
160
+ end
161
+ end
162
+
163
+ def gather_keys (attributes, observation)
164
+ @all_keys ||= []
165
+ @all_keys = @all_keys | attributes.keys
166
+ end
167
+
168
+ def print_header
169
+ print %w[account_id shop_id o timestamp ip status valid].join(delimiter)
170
+ print delimiter
171
+ puts (@all_keys - [:o, :timestamp, :ip, :status, :request]).map { |sym| sym.to_s }.join(delimiter)
172
+ end
173
+
174
+ def delimiter
175
+ options[:delimiter]
176
+ end
177
+
178
+ end
179
+
180
+ if File.basename($0) == File.basename(__FILE__)
181
+ viewobs = Viewobs.new
182
+ viewobs.parse_options
183
+
184
+ if viewobs.options[:follow]
185
+ while true
186
+ viewobs.view_until_end
187
+ sleep(0.1)
188
+ end
189
+ else
190
+ if viewobs.options[:table]
191
+ viewobs.options[:gathering_keys] = true
192
+ viewobs.view_until_end
193
+ viewobs.print_header
194
+ viewobs.options[:gathering_keys] = false
195
+ end
196
+ viewobs.view_until_end
197
+ end
198
+ end
data/lib/logbox.rb ADDED
@@ -0,0 +1,9 @@
1
+ $: << File.join(File.dirname(__FILE__), 'logbox')
2
+ require 'stream_wrapper'
3
+ require 'log_parser'
4
+ require 'observation'
5
+ require 'iconv'
6
+
7
+ module Logbox
8
+ StringEncoder = Iconv.new('UTF-8//IGNORE', 'UTF-8')
9
+ end
@@ -0,0 +1,28 @@
1
+ class String
2
+
3
+ types = {
4
+ :bold => "\e[1m",
5
+ :underline => "\e[4m",
6
+ :black => "\e[30m",
7
+ :red => "\e[31m",
8
+ :green => "\e[32m",
9
+ :yellow => "\e[33m",
10
+ :blue => "\e[34m",
11
+ :magenta => "\e[35m",
12
+ :cyan => "\e[36m",
13
+ :white => "\e[37m",
14
+ }
15
+
16
+ types.each do |name, color_code|
17
+ define_method(name) do
18
+ @@colorize ? "#{color_code}#{self}\e[0m" : self
19
+ end
20
+ end
21
+
22
+ class << self
23
+ @@colorize = false
24
+ def colorize
25
+ @@colorize = true
26
+ end
27
+ end
28
+ end