mongodb_logger 0.1.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.
Files changed (69) hide show
  1. data/.gitignore +20 -0
  2. data/.rvmrc +1 -0
  3. data/.travis.yml +17 -0
  4. data/Gemfile +4 -0
  5. data/README.md +177 -0
  6. data/Rakefile +110 -0
  7. data/SUPPORTED_RAILS_VERSIONS +13 -0
  8. data/TESTING.md +24 -0
  9. data/bin/mongodb_logger_web +24 -0
  10. data/config.ru +16 -0
  11. data/examples/server_config.yml +4 -0
  12. data/features/rails.feature +10 -0
  13. data/features/step_definitions/rails_application_steps.rb +48 -0
  14. data/features/support/env.rb +15 -0
  15. data/features/support/rails.rb +91 -0
  16. data/features/support/terminal.rb +94 -0
  17. data/lib/mongodb_logger.rb +31 -0
  18. data/lib/mongodb_logger/initializer_mixin.rb +26 -0
  19. data/lib/mongodb_logger/logger.rb +184 -0
  20. data/lib/mongodb_logger/railtie.rb +12 -0
  21. data/lib/mongodb_logger/replica_set_helper.rb +19 -0
  22. data/lib/mongodb_logger/server.rb +136 -0
  23. data/lib/mongodb_logger/server/model/filter.rb +37 -0
  24. data/lib/mongodb_logger/server/partials.rb +24 -0
  25. data/lib/mongodb_logger/server/public/images/ajax-loader.gif +0 -0
  26. data/lib/mongodb_logger/server/public/images/failure.png +0 -0
  27. data/lib/mongodb_logger/server/public/images/logo.png +0 -0
  28. data/lib/mongodb_logger/server/public/images/play-icon.png +0 -0
  29. data/lib/mongodb_logger/server/public/images/stop-icon.png +0 -0
  30. data/lib/mongodb_logger/server/public/images/success.png +0 -0
  31. data/lib/mongodb_logger/server/public/javascripts/jquery-1.7.min.js +4 -0
  32. data/lib/mongodb_logger/server/public/stylesheets/all.css +9 -0
  33. data/lib/mongodb_logger/server/public/stylesheets/grids.css +18 -0
  34. data/lib/mongodb_logger/server/public/stylesheets/group-buttons.css +83 -0
  35. data/lib/mongodb_logger/server/public/stylesheets/group-forms.css +60 -0
  36. data/lib/mongodb_logger/server/public/stylesheets/group-headers.css +8 -0
  37. data/lib/mongodb_logger/server/public/stylesheets/group-tables.css +42 -0
  38. data/lib/mongodb_logger/server/public/stylesheets/layout.css +168 -0
  39. data/lib/mongodb_logger/server/public/stylesheets/library.css +134 -0
  40. data/lib/mongodb_logger/server/public/stylesheets/reset.css +43 -0
  41. data/lib/mongodb_logger/server/public/stylesheets/spaces.css +42 -0
  42. data/lib/mongodb_logger/server/views/application.coffee +54 -0
  43. data/lib/mongodb_logger/server/views/error.erb +2 -0
  44. data/lib/mongodb_logger/server/views/layout.erb +32 -0
  45. data/lib/mongodb_logger/server/views/overview.erb +94 -0
  46. data/lib/mongodb_logger/server/views/shared/_log.erb +8 -0
  47. data/lib/mongodb_logger/server/views/shared/_log_info.erb +25 -0
  48. data/lib/mongodb_logger/server/views/shared/_tabs.erb +4 -0
  49. data/lib/mongodb_logger/server/views/show_log.erb +85 -0
  50. data/lib/mongodb_logger/server_config.rb +45 -0
  51. data/lib/mongodb_logger/version.rb +3 -0
  52. data/mongodb_logger.gemspec +37 -0
  53. data/test/active_record.rb +13 -0
  54. data/test/config/samples/database.yml +9 -0
  55. data/test/config/samples/database_no_file_logging.yml +10 -0
  56. data/test/config/samples/database_replica_set.yml +8 -0
  57. data/test/config/samples/database_with_auth.yml +9 -0
  58. data/test/config/samples/mongodb_logger.yml +2 -0
  59. data/test/config/samples/mongoid.yml +30 -0
  60. data/test/rails.rb +22 -0
  61. data/test/rails/app/controllers/order_controller.rb +20 -0
  62. data/test/rails/test/functional/order_controller_test.rb +56 -0
  63. data/test/rails/test/test_helper.rb +10 -0
  64. data/test/shoulda_macros/log_macros.rb +13 -0
  65. data/test/test.sh +4 -0
  66. data/test/test_helper.rb +88 -0
  67. data/test/unit/mongodb_logger_replica_test.rb +45 -0
  68. data/test/unit/mongodb_logger_test.rb +252 -0
  69. metadata +300 -0
@@ -0,0 +1,15 @@
1
+ require 'active_support'
2
+ require 'rspec'
3
+
4
+ PROJECT_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..', '..')).freeze
5
+ TEMP_DIR = File.join(PROJECT_ROOT, 'tmp').freeze
6
+ LOCAL_RAILS_ROOT = File.join(TEMP_DIR, 'rails_root').freeze
7
+ BUILT_GEM_ROOT = File.join(TEMP_DIR, 'built_gems').freeze
8
+ LOCAL_GEM_ROOT = File.join(TEMP_DIR, 'local_gems').freeze
9
+
10
+ Before do
11
+ FileUtils.mkdir_p(TEMP_DIR)
12
+ FileUtils.rm_rf(BUILT_GEM_ROOT)
13
+ FileUtils.rm_rf(LOCAL_RAILS_ROOT)
14
+ FileUtils.mkdir_p(BUILT_GEM_ROOT)
15
+ end
@@ -0,0 +1,91 @@
1
+ module RailsHelpers
2
+ def rails_root_exists?
3
+ File.exists?(environment_path)
4
+ end
5
+
6
+ def environment_path
7
+ File.join(rails_root, 'config', 'environment.rb')
8
+ end
9
+
10
+ def application_controller_filename
11
+ controller_filename = File.join(rails_root, 'app', 'controllers', "application_controller.rb")
12
+ end
13
+
14
+ def rails_root
15
+ LOCAL_RAILS_ROOT
16
+ end
17
+
18
+ def rails30?
19
+ rails_version =~ /^3.0/
20
+ end
21
+
22
+ def rails31?
23
+ rails_version =~ /^3.1/
24
+ end
25
+
26
+ def rails_version
27
+ @rails_version ||= begin
28
+ rails_version = open(gemfile_path).read.match(/gem.*rails["'].*["'](.+)["']/)[1]
29
+ end
30
+ end
31
+
32
+ def gemfile_path
33
+ File.join(rails_root, 'Gemfile')
34
+ end
35
+
36
+ def rakefile_path
37
+ File.join(rails_root, 'Rakefile')
38
+ end
39
+
40
+ def routes_path
41
+ File.join(rails_root, 'config', 'routes.rb')
42
+ end
43
+
44
+ def logs_path
45
+ File.join(rails_root, 'log')
46
+ end
47
+
48
+ def bundle_gem(gem_name, version = nil)
49
+ File.open(gemfile_path, 'a') do |file|
50
+ gem = "gem '#{gem_name}'"
51
+ gem += ", '#{version}'" if version
52
+ file.puts(gem)
53
+ end
54
+ end
55
+
56
+ def require_thread
57
+ content = File.read(rakefile_path)
58
+ content = "require 'thread'\n#{content}"
59
+ File.open(rakefile_path, 'wb') { |file| file.write(content) }
60
+ end
61
+
62
+ def add_routes
63
+ content = File.read(routes_path)
64
+ flag = Regexp.escape("Application.routes.draw do\n")
65
+ content.gsub!(/#{flag}/m, '\0 resources :order')
66
+ File.open(routes_path, 'wb') { |file| file.write(content) }
67
+ end
68
+
69
+ def copy_tests
70
+ FileUtils.cp(
71
+ File.join(PROJECT_ROOT, 'test', 'rails', 'app', 'controllers', 'order_controller.rb'),
72
+ File.join(rails_root, 'app', 'controllers', 'order_controller.rb')
73
+ )
74
+ FileUtils.cp(
75
+ File.join(PROJECT_ROOT, 'test', 'config', 'samples', 'database.yml'),
76
+ File.join(rails_root, 'config', 'database.yml')
77
+ )
78
+ FileUtils.cp(
79
+ File.join(PROJECT_ROOT, 'test', 'rails', 'test', 'test_helper.rb'),
80
+ File.join(rails_root, 'test', 'test_helper.rb')
81
+ )
82
+ FileUtils.cp(
83
+ File.join(PROJECT_ROOT, 'test', 'rails', 'test', 'functional', 'order_controller_test.rb'),
84
+ File.join(rails_root, 'test', 'functional', 'order_controller_test.rb')
85
+ )
86
+ FileUtils.chmod 0777, logs_path
87
+ end
88
+
89
+ end
90
+
91
+ World(RailsHelpers)
@@ -0,0 +1,94 @@
1
+ require 'fileutils'
2
+
3
+ Before do
4
+ @terminal = Terminal.new
5
+ end
6
+
7
+ After do |story|
8
+ if story.failed?
9
+ # puts @terminal.output
10
+ end
11
+ end
12
+
13
+ class Terminal
14
+ attr_reader :output, :status
15
+ attr_accessor :environment_variables
16
+
17
+ def initialize
18
+ @cwd = FileUtils.pwd
19
+ @output = ""
20
+ @status = 0
21
+ @logger = Logger.new(File.join(TEMP_DIR, 'terminal.log'))
22
+
23
+ @environment_variables = {
24
+ "GEM_HOME" => LOCAL_GEM_ROOT,
25
+ "GEM_PATH" => "#{LOCAL_GEM_ROOT}:#{BUILT_GEM_ROOT}",
26
+ "PATH" => "#{gem_bin_path}:#{ENV['PATH']}"
27
+ }
28
+ end
29
+
30
+ def cd(directory)
31
+ @cwd = directory
32
+ end
33
+
34
+ def run(command)
35
+ output << "#{command}\n"
36
+ current_dir = FileUtils.pwd
37
+
38
+ # The ; forces ruby to shell out so the env settings work right
39
+ cmdline = "sh -c 'cd #{@cwd} && #{environment_settings} #{command} 2>&1 ; '"
40
+ logger.debug(cmdline)
41
+ result = `#{cmdline}`
42
+ logger.debug(result)
43
+ output << result
44
+
45
+ `sh -c 'cd #{current_dir}'`
46
+ @status = $?
47
+ end
48
+
49
+ def echo(string)
50
+ logger.debug(string)
51
+ end
52
+
53
+
54
+ def build_and_install_gem(gemspec)
55
+ pkg_dir = File.join(TEMP_DIR, 'pkg')
56
+ FileUtils.mkdir_p(pkg_dir)
57
+ output = `gem build #{gemspec} 2>&1`
58
+ gem_file = Dir.glob("*.gem").first
59
+ unless gem_file
60
+ raise "Gem didn't build:\n#{output}"
61
+ end
62
+ target = File.join(pkg_dir, gem_file)
63
+ FileUtils.mv(gem_file, target)
64
+ install_gem_to(LOCAL_GEM_ROOT, target)
65
+ end
66
+
67
+ def install_gem(gem)
68
+ install_gem_to(LOCAL_GEM_ROOT, gem)
69
+ end
70
+
71
+ def uninstall_gem(gem)
72
+ `gem uninstall -i #{LOCAL_GEM_ROOT} #{gem}`
73
+ end
74
+
75
+ def prepend_path(path)
76
+ @environment_variables['PATH'] = path + ":" + @environment_variables['PATH']
77
+ end
78
+
79
+ private
80
+
81
+ def install_gem_to(root, gem)
82
+ `gem install -i #{root} --no-ri --no-rdoc #{gem}`
83
+ end
84
+
85
+ def environment_settings
86
+ @environment_variables.map { |key, value| "#{key}=#{value}" }.join(' ')
87
+ end
88
+
89
+ def gem_bin_path
90
+ File.join(LOCAL_GEM_ROOT, "bin")
91
+ end
92
+
93
+ attr_reader :logger
94
+ end
@@ -0,0 +1,31 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ require 'mongo'
4
+ require 'mongodb_logger/logger'
5
+ require 'mongodb_logger/railtie' if defined?(Rails::Railtie)
6
+ require 'mongodb_logger/version'
7
+
8
+ module MongodbLogger
9
+ module Base
10
+ def self.included(base)
11
+ base.class_eval { around_filter :enable_mongodb_logger }
12
+ end
13
+
14
+ def enable_mongodb_logger
15
+ return yield unless Rails.logger.respond_to?(:mongoize)
16
+ f_params = case
17
+ when request.respond_to?(:filtered_parameters) then request.filtered_parameters
18
+ else params
19
+ end
20
+ Rails.logger.mongoize({
21
+ :method => request.method,
22
+ :action => action_name,
23
+ :controller => controller_name,
24
+ :path => request.path,
25
+ :url => request.url,
26
+ :params => f_params,
27
+ :ip => request.remote_ip
28
+ }) { yield }
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,26 @@
1
+ module MongodbLogger
2
+ module InitializerMixin
3
+
4
+ def rails30?
5
+ 3 == Rails::VERSION::MAJOR && 0 == Rails::VERSION::MINOR
6
+ end
7
+
8
+ def create_logger(config)
9
+ path = rails30? ? config.paths.log.to_a.first : config.paths['log'].first
10
+ level = ActiveSupport::BufferedLogger.const_get(config.log_level.to_s.upcase)
11
+ logger = MongodbLogger::Logger.new(:path => path, :level => level)
12
+ logger.auto_flushing = false if Rails.env.production?
13
+ logger
14
+ rescue StandardError => e
15
+ logger = ActiveSupport::BufferedLogger.new(STDERR)
16
+ logger.level = ActiveSupport::BufferedLogger::WARN
17
+ logger.warn(
18
+ "MongodbLogger Initializer Error: Unable to access log file. Please ensure that #{path} exists and is chmod 0666. " +
19
+ "The log level has been raised to WARN and the output directed to STDERR until the problem is fixed." + "\n" +
20
+ e.message + "\n" + e.backtrace.join("\n")
21
+ )
22
+ logger
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,184 @@
1
+ require 'erb'
2
+ require 'mongo'
3
+ require 'active_support'
4
+ require 'active_support/core_ext'
5
+ require 'mongodb_logger/replica_set_helper'
6
+
7
+ module MongodbLogger
8
+ class Logger < ActiveSupport::BufferedLogger
9
+ include ReplicaSetHelper
10
+
11
+ DEFAULT_COLLECTION_SIZE = 250.megabytes
12
+ # Looks for configuration files in this order
13
+ CONFIGURATION_FILES = ["mongodb_logger.yml", "mongoid.yml", "database.yml"]
14
+ LOG_LEVEL_SYM = [:debug, :info, :warn, :error, :fatal, :unknown]
15
+
16
+ attr_reader :db_configuration, :mongo_connection, :mongo_collection_name, :mongo_collection
17
+
18
+ def initialize(options={})
19
+ path = options[:path] || File.join(Rails.root, "log/#{Rails.env}.log")
20
+ level = options[:level] || DEBUG
21
+ internal_initialize
22
+ if disable_file_logging?
23
+ @level = level
24
+ @buffer = {}
25
+ @auto_flushing = 1
26
+ @guard = Mutex.new
27
+ else
28
+ super(path, level)
29
+ end
30
+ rescue => e
31
+ # should use a config block for this
32
+ Rails.env.production? ? (raise e) : (puts "Using BufferedLogger due to exception: " + e.message)
33
+ end
34
+
35
+ def add_metadata(options={})
36
+ options.each do |key, value|
37
+ unless [:messages, :request_time, :ip, :runtime, :application_name, :is_exception, :params, :method].include?(key.to_sym)
38
+ @mongo_record[key] = value
39
+ else
40
+ raise ArgumentError, ":#{key} is a reserved key for the mongodb logger. Please choose a different key"
41
+ end
42
+ end
43
+ end
44
+
45
+ def add(severity, message = nil, progname = nil, &block)
46
+ if @level <= severity && message.present? && @mongo_record.present?
47
+ # do not modify the original message used by the buffered logger
48
+ msg = logging_colorized? ? message.to_s.gsub(/(\e(\[([\d;]*[mz]?))?)?/, '').strip : message
49
+ @mongo_record[:messages][LOG_LEVEL_SYM[severity]] << msg
50
+ end
51
+ # may modify the original message
52
+ disable_file_logging? ? message : super
53
+ end
54
+
55
+ # Drop the capped_collection and recreate it
56
+ def reset_collection
57
+ @mongo_collection.drop
58
+ create_collection
59
+ end
60
+
61
+ def mongoize(options={})
62
+ @mongo_record = options.merge({
63
+ :messages => Hash.new { |hash, key| hash[key] = Array.new },
64
+ :request_time => Time.now.getutc,
65
+ :application_name => @application_name
66
+ })
67
+
68
+ runtime = Benchmark.measure{ yield }.real if block_given?
69
+ rescue Exception => e
70
+ add(3, e.message + "\n" + e.backtrace.join("\n"))
71
+ @mongo_record[:is_exception] = true
72
+ # Reraise the exception for anyone else who cares
73
+ raise e
74
+ ensure
75
+ # In case of exception, make sure runtime is set
76
+ @mongo_record[:runtime] = ((runtime ||= 0) * 1000).ceil
77
+ begin
78
+ @insert_block.call
79
+ rescue
80
+ # do extra work to inpect (and flatten)
81
+ force_serialize @mongo_record
82
+ @insert_block.call rescue nil
83
+ end
84
+ end
85
+
86
+ def authenticated?
87
+ @authenticated
88
+ end
89
+
90
+ private
91
+ # facilitate testing
92
+ def internal_initialize
93
+ configure
94
+ connect
95
+ check_for_collection
96
+ end
97
+
98
+ def disable_file_logging?
99
+ @db_configuration.fetch('disable_file_logging', false)
100
+ end
101
+
102
+ def configure
103
+ default_capsize = DEFAULT_COLLECTION_SIZE
104
+ @authenticated = false
105
+ @db_configuration = {
106
+ 'host' => 'localhost',
107
+ 'port' => 27017,
108
+ 'capsize' => default_capsize}.merge(resolve_config)
109
+ @mongo_collection_name = @db_configuration[:collection] || "#{Rails.env}_log"
110
+ @application_name = resolve_application_name
111
+ @safe_insert = @db_configuration['safe_insert'] || false
112
+
113
+ @insert_block = @db_configuration.has_key?('replica_set') && @db_configuration['replica_set'] ?
114
+ lambda { rescue_connection_failure{ insert_log_record(@safe_insert) } } :
115
+ lambda { insert_log_record }
116
+ end
117
+
118
+ def resolve_application_name
119
+ if @db_configuration.has_key?('application_name')
120
+ @db_configuration['application_name']
121
+ else
122
+ Rails.application.class.to_s.split("::").first
123
+ end
124
+ end
125
+
126
+ def resolve_config
127
+ config = {}
128
+ CONFIGURATION_FILES.each do |filename|
129
+ config_file = Rails.root.join("config", filename)
130
+ if config_file.file?
131
+ config = YAML.load(ERB.new(config_file.read).result)[Rails.env]
132
+ config = config['mongodb_logger'] if config && config.has_key?('mongodb_logger')
133
+ break unless config.blank?
134
+ end
135
+ end
136
+ config
137
+ end
138
+
139
+ def connect
140
+ @mongo_connection ||= Mongo::Connection.new(@db_configuration['host'],
141
+ @db_configuration['port'],
142
+ :auto_reconnect => true).db(@db_configuration['database'])
143
+
144
+ if @db_configuration['username'] && @db_configuration['password']
145
+ # the driver stores credentials in case reconnection is required
146
+ @authenticated = @mongo_connection.authenticate(@db_configuration['username'],
147
+ @db_configuration['password'])
148
+ end
149
+ end
150
+
151
+ def create_collection
152
+ @mongo_connection.create_collection(@mongo_collection_name,
153
+ {:capped => true, :size => @db_configuration['capsize']})
154
+ end
155
+
156
+ def check_for_collection
157
+ # setup the capped collection if it doesn't already exist
158
+ create_collection unless @mongo_connection.collection_names.include?(@mongo_collection_name)
159
+ @mongo_collection = @mongo_connection[@mongo_collection_name]
160
+ end
161
+
162
+ def insert_log_record(safe = false)
163
+ @mongo_collection.insert(@mongo_record, :safe => safe)
164
+ end
165
+
166
+ def logging_colorized?
167
+ # Cache it since these ActiveRecord attributes are assigned after logger initialization occurs in Rails boot
168
+ @colorized ||= Object.const_defined?(:ActiveRecord) && ActiveRecord::LogSubscriber.colorize_logging
169
+ end
170
+
171
+ # force the data in the db by inspecting each top level array and hash element
172
+ # this will flatten other hashes and arrays
173
+ def force_serialize(rec)
174
+ if msgs = rec[:messages]
175
+ LOG_LEVEL_SYM.each do |i|
176
+ msgs[i].collect! { |j| j.inspect } if msgs[i]
177
+ end
178
+ end
179
+ if pms = rec[:params]
180
+ pms.each { |i, j| pms[i] = j.inspect }
181
+ end
182
+ end
183
+ end
184
+ end