yarder 0.0.1

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 (77) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +87 -0
  3. data/Rakefile +38 -0
  4. data/lib/tasks/yarder_tasks.rake +4 -0
  5. data/lib/yarder.rb +15 -0
  6. data/lib/yarder/action_controller/log_subscriber.rb +74 -0
  7. data/lib/yarder/action_view/log_subscriber.rb +31 -0
  8. data/lib/yarder/active_record/log_subscriber.rb +75 -0
  9. data/lib/yarder/active_resource/log_subscriber.rb +34 -0
  10. data/lib/yarder/configuration.rb +8 -0
  11. data/lib/yarder/core_ext/object/blank.rb +105 -0
  12. data/lib/yarder/logger.rb +55 -0
  13. data/lib/yarder/rack/logger.rb +72 -0
  14. data/lib/yarder/railtie.rb +69 -0
  15. data/lib/yarder/tagged_logging.rb +102 -0
  16. data/lib/yarder/version.rb +3 -0
  17. data/test/action_controller/log_subscriber_test.rb +165 -0
  18. data/test/action_view/log_subscriber_test.rb +89 -0
  19. data/test/active_record/log_subscriber_test.rb +87 -0
  20. data/test/active_resource/log_subscriber_test.rb +42 -0
  21. data/test/dummy/README.rdoc +261 -0
  22. data/test/dummy/Rakefile +7 -0
  23. data/test/dummy/app/assets/javascripts/application.js +13 -0
  24. data/test/dummy/app/assets/javascripts/widgets.js +2 -0
  25. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  26. data/test/dummy/app/assets/stylesheets/scaffold.css +56 -0
  27. data/test/dummy/app/assets/stylesheets/widgets.css +4 -0
  28. data/test/dummy/app/controllers/application_controller.rb +3 -0
  29. data/test/dummy/app/controllers/log_subscriber_controller.rb +56 -0
  30. data/test/dummy/app/controllers/widgets_controller.rb +83 -0
  31. data/test/dummy/app/helpers/application_helper.rb +2 -0
  32. data/test/dummy/app/helpers/widgets_helper.rb +2 -0
  33. data/test/dummy/app/models/widget.rb +3 -0
  34. data/test/dummy/app/views/customers/_customer.html.erb +1 -0
  35. data/test/dummy/app/views/good_customers/_good_customer.html.erb +1 -0
  36. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  37. data/test/dummy/app/views/test/_customer.erb +1 -0
  38. data/test/dummy/app/views/test/hello_world.erb +1 -0
  39. data/test/dummy/app/views/widgets/_form.html.erb +17 -0
  40. data/test/dummy/app/views/widgets/edit.html.erb +6 -0
  41. data/test/dummy/app/views/widgets/index.html.erb +21 -0
  42. data/test/dummy/app/views/widgets/new.html.erb +5 -0
  43. data/test/dummy/app/views/widgets/show.html.erb +5 -0
  44. data/test/dummy/config.ru +4 -0
  45. data/test/dummy/config/application.rb +69 -0
  46. data/test/dummy/config/boot.rb +10 -0
  47. data/test/dummy/config/database.yml +22 -0
  48. data/test/dummy/config/environment.rb +5 -0
  49. data/test/dummy/config/environments/development.rb +31 -0
  50. data/test/dummy/config/environments/production.rb +64 -0
  51. data/test/dummy/config/environments/test.rb +35 -0
  52. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  53. data/test/dummy/config/initializers/inflections.rb +15 -0
  54. data/test/dummy/config/initializers/mime_types.rb +5 -0
  55. data/test/dummy/config/initializers/secret_token.rb +7 -0
  56. data/test/dummy/config/initializers/session_store.rb +8 -0
  57. data/test/dummy/config/initializers/wrap_parameters.rb +10 -0
  58. data/test/dummy/config/locales/en.yml +5 -0
  59. data/test/dummy/config/routes.rb +69 -0
  60. data/test/dummy/db/migrate/20120927084605_create_widgets.rb +8 -0
  61. data/test/dummy/db/schema.rb +16 -0
  62. data/test/dummy/log/development.log +19 -0
  63. data/test/dummy/log/test.log +10694 -0
  64. data/test/dummy/public/404.html +26 -0
  65. data/test/dummy/public/422.html +26 -0
  66. data/test/dummy/public/500.html +25 -0
  67. data/test/dummy/public/favicon.ico +0 -0
  68. data/test/dummy/script/rails +6 -0
  69. data/test/logger_test.rb +126 -0
  70. data/test/rack/logger_test.rb +68 -0
  71. data/test/support/fake_models.rb +12 -0
  72. data/test/support/integration_case.rb +5 -0
  73. data/test/support/multibyte_test_helpers.rb +19 -0
  74. data/test/tagged_logging_test.rb +155 -0
  75. data/test/test_helper.rb +31 -0
  76. data/test/yarder_test.rb +7 -0
  77. metadata +236 -0
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The page you were looking for doesn't exist (404)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/404.html -->
21
+ <div class="dialog">
22
+ <h1>The page you were looking for doesn't exist.</h1>
23
+ <p>You may have mistyped the address or the page may have moved.</p>
24
+ </div>
25
+ </body>
26
+ </html>
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The change you wanted was rejected (422)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/422.html -->
21
+ <div class="dialog">
22
+ <h1>The change you wanted was rejected.</h1>
23
+ <p>Maybe you tried to change something you didn't have access to.</p>
24
+ </div>
25
+ </body>
26
+ </html>
@@ -0,0 +1,25 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>We're sorry, but something went wrong (500)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/500.html -->
21
+ <div class="dialog">
22
+ <h1>We're sorry, but something went wrong.</h1>
23
+ </div>
24
+ </body>
25
+ </html>
File without changes
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3
+
4
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
5
+ require File.expand_path('../../config/boot', __FILE__)
6
+ require 'rails/commands'
@@ -0,0 +1,126 @@
1
+ require 'stringio'
2
+ require 'fileutils'
3
+ require 'tempfile'
4
+ require 'test_helper'
5
+
6
+ class BufferedLoggerTest < ActiveSupport::TestCase
7
+ include MultibyteTestHelpers
8
+
9
+ Logger = Yarder::Logger
10
+
11
+ def setup
12
+ @message = "A debug message"
13
+ @integer_message = 12345
14
+ @output = StringIO.new
15
+ @logger = Logger.new(@output)
16
+ end
17
+
18
+ def test_write_binary_data_to_existing_file
19
+ t = Tempfile.new ['development', 'log']
20
+ t.binmode
21
+ t.write 'hi mom!'
22
+ t.close
23
+
24
+ f = File.open(t.path, 'w')
25
+ f.binmode
26
+
27
+ logger = Logger.new f
28
+ logger.level = Logger::DEBUG
29
+
30
+ str = "\x80"
31
+ str.force_encoding("ASCII-8BIT")
32
+
33
+ logger.add Logger::DEBUG, str
34
+ ensure
35
+ logger.close
36
+ t.close true
37
+ end
38
+
39
+ def test_write_binary_data_create_file
40
+ fname = File.join Dir.tmpdir, 'lol', 'rofl.log'
41
+ FileUtils.mkdir_p File.dirname(fname)
42
+ f = File.open(fname, 'w')
43
+ f.binmode
44
+
45
+ logger = Logger.new f
46
+ logger.level = Logger::DEBUG
47
+
48
+ str = "\x80"
49
+ str.force_encoding("ASCII-8BIT")
50
+
51
+ logger.add Logger::DEBUG, str
52
+ ensure
53
+ logger.close
54
+ File.unlink fname
55
+ end
56
+
57
+ def test_should_log_debugging_message_when_debugging
58
+ @logger.level = Logger::DEBUG
59
+ @logger.add(Logger::DEBUG, @message)
60
+ assert @output.string.include?(@message)
61
+ end
62
+
63
+ def test_should_not_log_debug_messages_when_log_level_is_info
64
+ @logger.level = Logger::INFO
65
+ @logger.add(Logger::DEBUG, @message)
66
+ assert ! @output.string.include?(@message)
67
+ end
68
+
69
+ def test_should_add_message_passed_as_block_when_using_add
70
+ @logger.level = Logger::INFO
71
+ @logger.add(Logger::INFO) {@message}
72
+ assert @output.string.include?(@message)
73
+ end
74
+
75
+ def test_should_add_message_passed_as_block_when_using_shortcut
76
+ @logger.level = Logger::INFO
77
+ @logger.info {@message}
78
+ assert @output.string.include?(@message)
79
+ end
80
+
81
+ def test_should_convert_message_to_string
82
+ @logger.level = Logger::INFO
83
+ @logger.info @integer_message
84
+ assert @output.string.include?(@integer_message.to_s)
85
+ end
86
+
87
+ def test_should_convert_message_to_string_when_passed_in_block
88
+ @logger.level = Logger::INFO
89
+ @logger.info {@integer_message}
90
+ assert @output.string.include?(@integer_message.to_s)
91
+ end
92
+
93
+ def test_should_not_evaluate_block_if_message_wont_be_logged
94
+ @logger.level = Logger::INFO
95
+ evaluated = false
96
+ @logger.add(Logger::DEBUG) {evaluated = true}
97
+ assert evaluated == false
98
+ end
99
+
100
+ def test_should_not_mutate_message
101
+ message_copy = @message.dup
102
+ @logger.info @message
103
+ assert_equal message_copy, @message
104
+ end
105
+
106
+ def test_should_know_if_its_loglevel_is_below_a_given_level
107
+ Logger::Severity.constants.each do |level|
108
+ next if level.to_s == 'UNKNOWN'
109
+ @logger.level = Logger::Severity.const_get(level) - 1
110
+ assert @logger.send("#{level.downcase}?"), "didn't know if it was #{level.downcase}? or below"
111
+ end
112
+ end
113
+
114
+ # This test will fail on JRuby versions lower than 1.7 due to UTF-8 Encoding issues
115
+ # so only run on MRI and JRuby 1.7 and later
116
+ unless defined?(JRUBY_VERSION) && JRUBY_VERSION.to_f < 1.7
117
+ def test_buffer_multibyte
118
+ @logger.info(UNICODE_STRING)
119
+ @logger.info(BYTE_STRING)
120
+ assert @output.string.include?(UNICODE_STRING)
121
+ byte_string = @output.string.dup
122
+ byte_string.force_encoding("ASCII-8BIT")
123
+ assert byte_string.include?(BYTE_STRING)
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,68 @@
1
+ require 'test_helper'
2
+
3
+ # TODO These tests are fragile because they rely on the output being added
4
+ # to the last line of the log file. See if there is a better way to do this
5
+ class LoggerTest < ActiveSupport::IntegrationCase
6
+ class MyLogger < Yarder::Logger
7
+ def flush(*)
8
+ end
9
+
10
+ end
11
+
12
+ setup do
13
+ @output = StringIO.new
14
+ Rails.logger = Yarder::TaggedLogging.new(MyLogger.new(@output))
15
+ visit('/widgets')
16
+ end
17
+
18
+ test 'writes a hash to the log file when a request is received' do
19
+ assert_equal Hash, entry.class
20
+ end
21
+
22
+ test 'fills in the client_ip' do
23
+ assert_equal "127.0.0.1", entry['@fields']['client_ip']
24
+ end
25
+
26
+ test 'fills in the method' do
27
+ assert_equal "GET", entry['@fields']['method']
28
+ end
29
+
30
+ test 'fills in the path' do
31
+ assert_equal "/widgets", entry['@fields']['path']
32
+ end
33
+
34
+ test 'fills in the status' do
35
+ assert_equal "/widgets", entry['@fields']['path']
36
+ end
37
+
38
+ test 'fills in the total_duration' do
39
+ assert entry['@fields']['total_duration'].to_f >= 0, "total_duration was not a positive number"
40
+ end
41
+
42
+ test 'fills in the rendering_duration' do
43
+ assert entry['@fields']['rendering_duration'].to_f >= 0, "rendering_duration was not a positive number"
44
+ end
45
+
46
+ test 'fills in the sql_duration' do
47
+ assert entry['@fields']['rendering_duration'].to_f >= 0, "sql_duration was not a positive number"
48
+ end
49
+
50
+ test 'fills in the method name tag' do
51
+ assert_equal 32, entry['@fields']['uuid'].size
52
+ end
53
+
54
+ test 'fills in the string tag' do
55
+ assert_match "Hello", entry['@tags'].first
56
+ end
57
+
58
+ test 'fills in the proc tag' do
59
+ assert_match "Proc", entry['@tags'].last
60
+ end
61
+
62
+ #TODO Add tests for view and SQL rendering summaries
63
+
64
+ def entry
65
+ JSON.parse(@output.string)
66
+ end
67
+
68
+ end
@@ -0,0 +1,12 @@
1
+ require "active_model"
2
+
3
+ class Customer < Struct.new(:name, :id)
4
+ extend ActiveModel::Naming
5
+ include ActiveModel::Conversion
6
+ end
7
+
8
+ class BadCustomer < Customer
9
+ end
10
+
11
+ class GoodCustomer < Customer
12
+ end
@@ -0,0 +1,5 @@
1
+ # Define a bare test case to use with Capybara
2
+ class ActiveSupport::IntegrationCase < ActiveSupport::TestCase
3
+ include Capybara::DSL
4
+ include Rails.application.routes.url_helpers
5
+ end
@@ -0,0 +1,19 @@
1
+ # encoding: utf-8
2
+
3
+ module MultibyteTestHelpers
4
+ UNICODE_STRING = 'こにちわ'
5
+ ASCII_STRING = 'ohayo'
6
+ BYTE_STRING = "\270\236\010\210\245".force_encoding("ASCII-8BIT")
7
+
8
+ def chars(str)
9
+ ActiveSupport::Multibyte::Chars.new(str)
10
+ end
11
+
12
+ def inspect_codepoints(str)
13
+ str.to_s.unpack("U*").map{|cp| cp.to_s(16) }.join(' ')
14
+ end
15
+
16
+ def assert_equal_codepoints(expected, actual, message=nil)
17
+ assert_equal(inspect_codepoints(expected), inspect_codepoints(actual), message)
18
+ end
19
+ end
@@ -0,0 +1,155 @@
1
+ require 'yarder/logger'
2
+ require 'yarder/tagged_logging'
3
+
4
+ class TaggedLoggingTest < ActiveSupport::TestCase
5
+ class MyLogger < Yarder::Logger
6
+ end
7
+
8
+ setup do
9
+ @output = StringIO.new
10
+ @logger = Yarder::TaggedLogging.new(MyLogger.new(@output))
11
+ end
12
+
13
+ test 'sets logger.formatter if missing and extends it with a tagging API' do
14
+ logger = ::Logger.new(StringIO.new)
15
+ # assert_nil logger.formatter #TODO This assertion is failing but I do not think it is end of the world
16
+ Yarder::TaggedLogging.new(logger)
17
+ assert_not_nil logger.formatter
18
+ assert logger.formatter.respond_to?(:tagged)
19
+ end
20
+
21
+ test 'fills in the severity' do
22
+ @logger.info "Severity Test"
23
+ assert_equal "INFO", JSON.parse(@output.string)['@fields']['severity']
24
+ end
25
+
26
+ test "tagged once" do
27
+ @logger.tagged("BCX") { @logger.info "Funky time" }
28
+ assert_equal "BCX", JSON.parse(@output.string)['@tags'][0]
29
+ assert_equal "Funky time", JSON.parse(@output.string)['@message']
30
+ end
31
+
32
+ test "tagged twice" do
33
+ @logger.tagged("BCX") { @logger.tagged("Jason") { @logger.info "Funky time" } }
34
+ assert_equal "BCX", JSON.parse(@output.string)['@tags'][0]
35
+ assert_equal "Jason", JSON.parse(@output.string)['@tags'][1]
36
+ end
37
+
38
+ test "tagged thrice at once" do
39
+ @logger.tagged("BCX", "Jason", "New") { @logger.info "Funky time" }
40
+ assert_equal "BCX", JSON.parse(@output.string)['@tags'][0]
41
+ assert_equal "Jason", JSON.parse(@output.string)['@tags'][1]
42
+ assert_equal "New", JSON.parse(@output.string)['@tags'][2]
43
+ end
44
+
45
+ test "tagged are flattened" do
46
+ @logger.tagged("BCX", %w(Jason New)) { @logger.info "Funky time" }
47
+ assert_equal "BCX", JSON.parse(@output.string)['@tags'][0]
48
+ assert_equal "Jason", JSON.parse(@output.string)['@tags'][1]
49
+ assert_equal "New", JSON.parse(@output.string)['@tags'][2]
50
+ end
51
+
52
+
53
+ test "push and pop tags directly" do
54
+ assert_equal %w(A B C), @logger.push_tags('A', ['B', ' ', ['C']])
55
+ @logger.info 'a'
56
+ assert_equal %w(C), @logger.pop_tags
57
+ @logger.info 'b'
58
+ assert_equal %w(B), @logger.pop_tags(1)
59
+ @logger.info 'c'
60
+ assert_equal [], @logger.clear_tags!
61
+ @logger.info 'd'
62
+ # assert_equal "[A] [B] [C] a\n[A] [B] b\n[A] c\nd\n", JSON.parse(@output.string)['@tags']
63
+
64
+ first_log = JSON.parse(@output.string.split("\n")[0])
65
+ assert_equal "A", first_log['@tags'][0]
66
+ assert_equal "B", first_log['@tags'][1]
67
+ assert_equal "C", first_log['@tags'][2]
68
+ assert_equal "a", first_log['@message']
69
+
70
+ second_log = JSON.parse(@output.string.split("\n")[1])
71
+ assert_equal "A", second_log['@tags'][0]
72
+ assert_equal "B", second_log['@tags'][1]
73
+ assert_equal "b", second_log['@message']
74
+
75
+ third_log = JSON.parse(@output.string.split("\n")[2])
76
+ assert_equal "A", third_log['@tags'][0]
77
+ assert_equal "c", third_log['@message']
78
+
79
+ fourth_log = JSON.parse(@output.string.split("\n")[3])
80
+ assert fourth_log['@tags'].empty?
81
+ assert_equal "d", fourth_log['@message']
82
+ end
83
+
84
+
85
+
86
+ test "does not strip message content" do
87
+ @logger.info " Hello"
88
+ assert_equal " Hello", JSON.parse(@output.string)['@message']
89
+ end
90
+
91
+ test "provides access to the logger instance" do
92
+ @logger.tagged("BCX") { |logger| logger.info "Funky time" }
93
+ assert_equal "BCX", JSON.parse(@output.string)['@tags'][0]
94
+ assert_equal "Funky time", JSON.parse(@output.string)['@message']
95
+ end
96
+
97
+ test "tagged once with blank and nil" do
98
+ @logger.tagged(nil, "", "New") { @logger.info "Funky time" }
99
+ assert_equal "New", JSON.parse(@output.string)['@tags'].last
100
+ end
101
+
102
+ test "keeps each tag in their own thread" do
103
+ @logger.tagged("BCX") do
104
+ Thread.new do
105
+ @logger.tagged("OMG") { @logger.info "Cool story bro" }
106
+ end.join
107
+ @logger.info "Funky time"
108
+ end
109
+
110
+ # Sub-thread
111
+ main_thread = JSON.parse(@output.string.split("\n").first)
112
+ assert_equal "OMG", main_thread['@tags'][0]
113
+ assert_equal "Cool story bro", main_thread['@message']
114
+
115
+ # Main thread
116
+ main_thread = JSON.parse(@output.string.split("\n").last)
117
+ assert_equal "BCX", main_thread['@tags'][0]
118
+ assert_equal "Funky time", main_thread['@message']
119
+ end
120
+
121
+ test "cleans up the taggings on flush" do
122
+ @logger.tagged("BCX") do
123
+ Thread.new do
124
+ @logger.tagged("OMG") do
125
+ @logger.flush
126
+ @logger.info "Cool story bro"
127
+ end
128
+ end.join
129
+ end
130
+
131
+ first_log = JSON.parse(@output.string.split("\n").first)
132
+ assert first_log['@tags'].empty?
133
+ assert_equal "Cool story bro", first_log['@message']
134
+ end
135
+
136
+
137
+ test "mixed levels of tagging" do
138
+ @logger.tagged("BCX") do
139
+ @logger.tagged("Jason") { @logger.info "Funky time" }
140
+ @logger.info "Junky time!"
141
+ end
142
+
143
+ first_log = JSON.parse(@output.string.split("\n").first)
144
+ assert_equal "BCX", first_log['@tags'][0]
145
+ assert_equal "Jason", first_log['@tags'][1]
146
+ assert_equal "Funky time", first_log['@message']
147
+
148
+ second_log = JSON.parse(@output.string.split("\n").last)
149
+ assert_equal "BCX", second_log['@tags'][0]
150
+ assert_equal "Junky time!", second_log['@message']
151
+ end
152
+
153
+ end
154
+
155
+