logbox 0.2.10

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