mongodb_logger 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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