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,20 @@
1
+ class OrderController < ApplicationController
2
+ include MongodbLogger::Base
3
+ LOG_MESSAGE = "FOO"
4
+ LOG_USER_ID = 12345
5
+
6
+ def index
7
+ logger.debug LOG_MESSAGE
8
+ logger.add_metadata(:application_name_again => Rails.root.basename.to_s)
9
+ logger.add_metadata(:user_id => LOG_USER_ID)
10
+ render :text => "nothing"
11
+ end
12
+
13
+ def new
14
+ raise OrderController::LOG_MESSAGE
15
+ end
16
+
17
+ def create
18
+ render :text => "create"
19
+ end
20
+ end
@@ -0,0 +1,56 @@
1
+ require 'test_helper'
2
+
3
+ class OrderControllerTest < ActionController::TestCase
4
+ def setup
5
+ @mongodb_logger = Rails.logger
6
+ @mongodb_logger.reset_collection
7
+ common_setup
8
+ end
9
+
10
+ test "should have log level set" do
11
+ assert_equal ActiveSupport::BufferedLogger.const_get(Rails.configuration.log_level.to_s.upcase), Rails.logger.level
12
+ end
13
+
14
+ test "should have auto flushing set in development" do
15
+ assert @mongodb_logger.auto_flushing
16
+ end
17
+
18
+ test "should log a single record" do
19
+ get :index
20
+ assert_response :success
21
+ assert_equal 1, @collection.find({"controller" => "order","action"=> "index"}).count
22
+ end
23
+
24
+ test "should log a debug message" do
25
+ get :index
26
+ assert_equal OrderController::LOG_MESSAGE, @collection.find_one({}, :fields => ["messages"])["messages"]["debug"][0]
27
+ end
28
+
29
+ test "should log extra metadata" do
30
+ get :index
31
+ assert_equal Rails.root.basename.to_s, @collection.find_one({}, :fields => "application_name_again")["application_name_again"]
32
+ assert_equal OrderController::LOG_USER_ID, @collection.find_one({}, :fields => "user_id")["user_id"]
33
+ end
34
+
35
+ test "should log request parameters" do
36
+ get :index
37
+ log = @collection.find_one()
38
+ http_method = 'GET'
39
+ assert_equal http_method, log['method']
40
+ end
41
+
42
+ test "should log exceptions" do
43
+ assert_raise(RuntimeError, OrderController::LOG_MESSAGE) {get :new}
44
+ assert_equal 1, @collection.find_one({"messages.error" => /^#{OrderController::LOG_MESSAGE}/})["messages"]["error"].count
45
+ assert_equal 1, @collection.find_one({"is_exception" => true})["messages"]["error"].count
46
+ end
47
+
48
+ test "should not log passwords" do
49
+ post :create, :order => {:password => OrderController::LOG_MESSAGE }
50
+ assert_equal 1, @collection.find_one({"params.order.password" => "[FILTERED]"})["params"]["order"].count
51
+ end
52
+
53
+ test "should set the application name" do
54
+ assert_equal 'mongo_foo', @mongodb_logger.instance_variable_get(:@application_name)
55
+ end
56
+ end
@@ -0,0 +1,10 @@
1
+ ENV["RAILS_ENV"] = "test"
2
+ require File.expand_path('../../config/environment', __FILE__)
3
+ require 'rails/test_help'
4
+
5
+ class ActiveSupport::TestCase
6
+ def common_setup
7
+ @con = @mongodb_logger.mongo_connection
8
+ @collection = @con[@mongodb_logger.mongo_collection_name]
9
+ end
10
+ end
@@ -0,0 +1,13 @@
1
+ module LogMacros
2
+ def should_contain_one_log_record
3
+ should "contain a log record" do
4
+ assert_equal 1, @con[@mongodb_logger.mongo_collection_name].count
5
+ end
6
+ end
7
+
8
+ def should_use_database_name_in_config
9
+ should "use the database name in the config file" do
10
+ assert_equal "system_log", @mongodb_logger.db_configuration['database']
11
+ end
12
+ end
13
+ end
data/test/test.sh ADDED
@@ -0,0 +1,4 @@
1
+ #!/bin/bash
2
+ rake vendor_test_gems
3
+ rake test
4
+ rake cucumber:rails:all
@@ -0,0 +1,88 @@
1
+ require 'test/unit'
2
+
3
+ begin
4
+ require 'shoulda'
5
+ rescue LoadError
6
+ require 'rubygems'
7
+ require 'shoulda'
8
+ end
9
+
10
+ require 'mocha'
11
+ # mock rails class
12
+ require 'pathname'
13
+ require 'rails'
14
+ require 'fileutils'
15
+
16
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
17
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
18
+
19
+ Shoulda.autoload_macros("#{File.dirname(__FILE__)}/..")
20
+
21
+ class Test::Unit::TestCase
22
+ CONFIG_DIR = Rails.root.join("config")
23
+ SAMPLE_CONFIG_DIR = File.join(CONFIG_DIR, "samples")
24
+ DEFAULT_CONFIG = "database.yml"
25
+ DEFAULT_CONFIG_WITH_AUTH = "database_with_auth.yml"
26
+ DEFAULT_CONFIG_WITH_NO_FILE_LOGGING = "database_no_file_logging.yml"
27
+ MONGOID_CONFIG = "mongoid.yml"
28
+ REPLICA_SET_CONFIG = "database_replica_set.yml"
29
+ LOGGER_CONFIG = "mongodb_logger.yml"
30
+
31
+ def log(msg)
32
+ @mongodb_logger.mongoize({"id" => 1}) do
33
+ @mongodb_logger.debug(msg)
34
+ end
35
+ end
36
+
37
+ def log_params(msg)
38
+ @mongodb_logger.mongoize({:params => msg})
39
+ end
40
+
41
+ def log_exception(msg)
42
+ @mongodb_logger.mongoize({"id" => 1}) do
43
+ raise msg
44
+ end
45
+ end
46
+
47
+ def setup_for_config(source, dest=source)
48
+ File.delete(File.join(CONFIG_DIR, DEFAULT_CONFIG))
49
+ cp_config(source, dest)
50
+ @mongodb_logger.send(:configure)
51
+ end
52
+
53
+ def cp_config(source, dest=source)
54
+ FileUtils.cp(File.join(SAMPLE_CONFIG_DIR, source), File.join(CONFIG_DIR, dest))
55
+ end
56
+
57
+ def teardown_for_config(file)
58
+ File.delete(File.join(CONFIG_DIR, file))
59
+ end
60
+
61
+ def log_metadata(options)
62
+ @mongodb_logger.mongoize({"id" => 1}) do
63
+ @mongodb_logger.add_metadata(options)
64
+ end
65
+ end
66
+
67
+ def require_bogus_active_record
68
+ require 'active_record'
69
+ end
70
+
71
+ def common_setup
72
+ @con = @mongodb_logger.mongo_connection
73
+ @collection = @con[@mongodb_logger.mongo_collection_name]
74
+ end
75
+
76
+ def create_user
77
+ db_conf = @mongodb_logger.db_configuration
78
+ @user = db_conf['username']
79
+ mongo_connection = Mongo::Connection.new(db_conf['host'],
80
+ db_conf['port']).db(db_conf['database'])
81
+ mongo_connection.add_user(@user, db_conf['password'])
82
+ end
83
+
84
+ def remove_user
85
+ @mongodb_logger.mongo_connection.remove_user(@user)
86
+ end
87
+
88
+ end
@@ -0,0 +1,45 @@
1
+ require 'test_helper'
2
+ require 'mongodb_logger/logger'
3
+
4
+ # test the basic stuff
5
+ class MongodbLogger::MongodbLoggerReplicaTest < Test::Unit::TestCase
6
+ extend LogMacros
7
+
8
+ context "A MongodbLogger::MongoLogger" do
9
+ setup do
10
+ # Can use different configs, but most tests use database.yml
11
+ cp_config(REPLICA_SET_CONFIG, DEFAULT_CONFIG)
12
+ @mongodb_logger = MongodbLogger::Logger.new
13
+ @mongodb_logger.reset_collection
14
+ end
15
+
16
+ context "upon trying to insert into a replica set voting on a new master" do
17
+ setup do
18
+ puts "Please disconnect the current master and hit ENTER"
19
+ STDIN.gets
20
+ end
21
+
22
+ should "insert a record successfully" do
23
+ assert_nothing_raised{ log("Test") }
24
+ @mongodb_logger.rescue_connection_failure do
25
+ assert_equal 1, @mongodb_logger.mongo_collection.count
26
+ end
27
+ end
28
+
29
+ teardown do
30
+ puts "Please reconnect the current master, wait for the vote to complete, then hit ENTER"
31
+ STDIN.gets
32
+ end
33
+ end
34
+
35
+ should "insert a record successfully" do
36
+ assert_nothing_raised{ log("Test") }
37
+ assert_equal 1, @mongodb_logger.mongo_collection.count
38
+ end
39
+
40
+ teardown do
41
+ file = File.join(CONFIG_DIR, DEFAULT_CONFIG)
42
+ File.delete(file) if File.exist?(file)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,252 @@
1
+ require 'test_helper'
2
+ require 'mongodb_logger/logger'
3
+ require 'tempfile'
4
+ require 'pathname'
5
+
6
+ # test the basic stuff
7
+ class MongodbLogger::LoggerTest < Test::Unit::TestCase
8
+ extend LogMacros
9
+
10
+ EXCEPTION_MSG = "Foo"
11
+
12
+ context "A MongodbLogger::Logger" do
13
+ setup do
14
+ # Can use different configs, but most tests use database.yml
15
+ FileUtils.cp(File.join(SAMPLE_CONFIG_DIR, DEFAULT_CONFIG), CONFIG_DIR)
16
+ end
17
+
18
+ context "in instantiation" do
19
+ setup do
20
+ MongodbLogger::Logger.any_instance.stubs(:internal_initialize).returns(nil)
21
+ MongodbLogger::Logger.any_instance.stubs(:disable_file_logging?).returns(false)
22
+ @mongodb_logger = MongodbLogger::Logger.new
23
+ end
24
+
25
+ context "during configuration when using a separate " + LOGGER_CONFIG do
26
+ setup do
27
+ setup_for_config(LOGGER_CONFIG)
28
+ end
29
+
30
+ should_use_database_name_in_config
31
+
32
+ teardown do
33
+ teardown_for_config(LOGGER_CONFIG)
34
+ end
35
+ end
36
+
37
+ context "during configuration when using a separate " + MONGOID_CONFIG do
38
+ setup do
39
+ setup_for_config(MONGOID_CONFIG)
40
+ end
41
+
42
+ should_use_database_name_in_config
43
+
44
+ teardown do
45
+ teardown_for_config(MONGOID_CONFIG)
46
+ end
47
+ end
48
+
49
+ # this test will work without the --auth mongod arg
50
+ context "upon connecting with authentication settings" do
51
+ setup do
52
+ setup_for_config(DEFAULT_CONFIG_WITH_AUTH, DEFAULT_CONFIG)
53
+ create_user
54
+ end
55
+
56
+ should "authenticate with the credentials in the configuration" do
57
+ @mongodb_logger.send(:connect)
58
+ assert @mongodb_logger.authenticated?
59
+ end
60
+
61
+ teardown do
62
+ # config will be deleted by outer teardown
63
+ remove_user
64
+ end
65
+ end
66
+
67
+ context "after configuration" do
68
+ setup do
69
+ @mongodb_logger.send(:configure)
70
+ end
71
+
72
+ should "set the default host, port, and capsize if not configured" do
73
+ assert_equal 'localhost', @mongodb_logger.db_configuration['host']
74
+ assert_equal 27017, @mongodb_logger.db_configuration['port']
75
+ assert_equal MongodbLogger::Logger::DEFAULT_COLLECTION_SIZE, @mongodb_logger.db_configuration['capsize']
76
+ end
77
+
78
+ should "set the mongo collection name depending on the Rails environment" do
79
+ assert_equal "#{Rails.env}_log", @mongodb_logger.mongo_collection_name
80
+ end
81
+
82
+ should "set the application name when specified in the config file" do
83
+ assert_equal "mongo_foo", @mongodb_logger.instance_variable_get(:@application_name)
84
+ end
85
+
86
+ should "set safe insert when specified in the config file" do
87
+ assert @mongodb_logger.instance_variable_get(:@safe_insert)
88
+ end
89
+
90
+ should "use the database name in the config file" do
91
+ assert_equal "system_log", @mongodb_logger.db_configuration['database']
92
+ end
93
+
94
+ context "upon connecting to an empty database" do
95
+ setup do
96
+ @mongodb_logger.send(:connect)
97
+ common_setup
98
+ @collection.drop
99
+ end
100
+
101
+ should "expose a valid mongo connection" do
102
+ assert_instance_of Mongo::DB, @mongodb_logger.mongo_connection
103
+ end
104
+
105
+ should "not authenticate" do
106
+ assert !@mongodb_logger.authenticated?
107
+ end
108
+
109
+ should "create a capped collection in the database with the configured size" do
110
+ @mongodb_logger.send(:check_for_collection)
111
+ assert @con.collection_names.include?(@mongodb_logger.mongo_collection_name)
112
+ # new capped collections are X MB + 5888 bytes, but don't be too strict in case that changes
113
+ assert @collection.stats["storageSize"] < MongodbLogger::Logger::DEFAULT_COLLECTION_SIZE + 1.megabyte
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ context "after instantiation" do
120
+ setup do
121
+ @mongodb_logger = MongodbLogger::Logger.new
122
+ common_setup
123
+ @mongodb_logger.reset_collection
124
+ end
125
+
126
+ context "upon insertion of a log record when active record is not used" do
127
+ # mock ActiveRecord has not been included
128
+ setup do
129
+ log("Test")
130
+ end
131
+
132
+ should_contain_one_log_record
133
+
134
+ should "allow recreation of the capped collection to remove all records" do
135
+ @mongodb_logger.reset_collection
136
+ assert_equal 0, @collection.count
137
+ end
138
+ end
139
+
140
+ context "upon insertion of a colorized log record when ActiveRecord is used" do
141
+ setup do
142
+ @log_message = "TESTING"
143
+ require_bogus_active_record
144
+ log("\e[31m #{@log_message} \e[0m")
145
+ end
146
+
147
+ should "detect logging is colorized" do
148
+ assert @mongodb_logger.send(:logging_colorized?)
149
+ end
150
+
151
+ should_contain_one_log_record
152
+
153
+ should "strip out colorization from log messages" do
154
+ assert_equal 1, @collection.find({"messages.debug" => @log_message}).count
155
+ end
156
+ end
157
+
158
+ should "add application metadata to the log record" do
159
+ options = {"application" => self.class.name}
160
+ log_metadata(options)
161
+ assert_equal 1, @collection.find({"application" => self.class.name}).count
162
+ end
163
+
164
+ should "not raise an exception when bson-unserializable data is logged in the :messages key" do
165
+ log(Tempfile.new("foo"))
166
+ assert_equal 1, @collection.count
167
+ end
168
+
169
+ should "not raise an exception when bson-unserializable data is logged in the :params key" do
170
+ log_params({:foo => Tempfile.new("bar")})
171
+ assert_equal 1, @collection.count
172
+ end
173
+
174
+ context "when an exception is raised" do
175
+ should "log the exception" do
176
+ assert_raise(RuntimeError, EXCEPTION_MSG) {log_exception(EXCEPTION_MSG)}
177
+ assert_equal 1, @collection.find_one({"messages.error" => /^#{EXCEPTION_MSG}/})["messages"]["error"].count
178
+ assert_equal 1, @collection.find_one({"is_exception" => true})["messages"]["error"].count
179
+ end
180
+ end
181
+ end
182
+
183
+ context "logging at INFO level" do
184
+ setup do
185
+ @mongodb_logger = MongodbLogger::Logger.new(:level => MongodbLogger::Logger::INFO)
186
+ common_setup
187
+ @mongodb_logger.reset_collection
188
+ log("INFO")
189
+ end
190
+
191
+ should_contain_one_log_record
192
+
193
+ should "not log DEBUG messages" do
194
+ assert_equal 0, @collection.find_one({}, :fields => ["messages"])["messages"].count
195
+ end
196
+ end
197
+ teardown do
198
+ file = File.join(CONFIG_DIR, DEFAULT_CONFIG)
199
+ File.delete(file) if File.exist?(file)
200
+ end
201
+ end
202
+
203
+ context "A MongodbLogger::Logger without file logging" do
204
+ setup do
205
+ FileUtils.cp(File.join(SAMPLE_CONFIG_DIR, DEFAULT_CONFIG_WITH_NO_FILE_LOGGING), File.join(CONFIG_DIR, DEFAULT_CONFIG))
206
+ @log_file = Pathname.new('log.out')
207
+ FileUtils.touch(@log_file)
208
+ end
209
+
210
+ context "in instantiation" do
211
+ should "not call super in the initialize method" do
212
+ MongodbLogger::Logger.any_instance.expects(:open).never # Stubbing out super doesn't work, so we use this side effect instead.
213
+ MongodbLogger::Logger.new
214
+ end
215
+
216
+ should "set level" do
217
+ level = stub('level')
218
+ logger = MongodbLogger::Logger.new(:level => level)
219
+ assert_equal level, logger.level
220
+ end
221
+ should "set buffer" do
222
+ assert_equal({}, MongodbLogger::Logger.new.instance_variable_get(:@buffer))
223
+ end
224
+ should "set auto flushing" do
225
+ assert_equal 1, MongodbLogger::Logger.new.instance_variable_get(:@auto_flushing)
226
+ end
227
+ should "set guard" do
228
+ assert MongodbLogger::Logger.new.instance_variable_get(:@guard).is_a?(Mutex)
229
+ end
230
+ end
231
+
232
+ context "after instantiation" do
233
+ context "upon insertion of a log record" do
234
+ setup do
235
+ @mongodb_logger = MongodbLogger::Logger.new(:path => @log_file)
236
+ log("Test")
237
+ end
238
+
239
+ should "not log the record to a file" do
240
+ assert_equal '', open(@log_file).read
241
+ end
242
+ end
243
+ end
244
+
245
+ teardown do
246
+ file = File.join(CONFIG_DIR, DEFAULT_CONFIG)
247
+ File.delete(file) if File.exist?(file)
248
+ File.delete(@log_file)
249
+ end
250
+ end
251
+
252
+ end