loga 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +25 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +19 -0
  5. data/.rubocop_todo.yml +33 -0
  6. data/Appraisals +14 -0
  7. data/Gemfile +8 -0
  8. data/README.md +147 -0
  9. data/Rakefile +9 -0
  10. data/circle.yml +23 -0
  11. data/gemfiles/rails32.gemfile +11 -0
  12. data/gemfiles/rails40.gemfile +11 -0
  13. data/gemfiles/sinatra14.gemfile +11 -0
  14. data/gemfiles/unit.gemfile +9 -0
  15. data/lib/loga.rb +33 -0
  16. data/lib/loga/configuration.rb +96 -0
  17. data/lib/loga/event.rb +21 -0
  18. data/lib/loga/ext/rails/rack/logger3.rb +21 -0
  19. data/lib/loga/ext/rails/rack/logger4.rb +13 -0
  20. data/lib/loga/formatter.rb +104 -0
  21. data/lib/loga/parameter_filter.rb +65 -0
  22. data/lib/loga/rack/logger.rb +102 -0
  23. data/lib/loga/rack/request.rb +77 -0
  24. data/lib/loga/rack/request_id.rb +44 -0
  25. data/lib/loga/railtie.rb +139 -0
  26. data/lib/loga/tagged_logging.rb +76 -0
  27. data/lib/loga/utilities.rb +7 -0
  28. data/lib/loga/version.rb +3 -0
  29. data/loga.gemspec +31 -0
  30. data/spec/fixtures/README.md +8 -0
  31. data/spec/fixtures/rails32/Rakefile +7 -0
  32. data/spec/fixtures/rails32/app/controllers/application_controller.rb +28 -0
  33. data/spec/fixtures/rails32/app/helpers/application_helper.rb +2 -0
  34. data/spec/fixtures/rails32/app/views/layouts/application.html.erb +14 -0
  35. data/spec/fixtures/rails32/app/views/user.html.erb +1 -0
  36. data/spec/fixtures/rails32/config.ru +4 -0
  37. data/spec/fixtures/rails32/config/application.rb +71 -0
  38. data/spec/fixtures/rails32/config/boot.rb +6 -0
  39. data/spec/fixtures/rails32/config/environment.rb +5 -0
  40. data/spec/fixtures/rails32/config/environments/development.rb +26 -0
  41. data/spec/fixtures/rails32/config/environments/production.rb +50 -0
  42. data/spec/fixtures/rails32/config/environments/test.rb +35 -0
  43. data/spec/fixtures/rails32/config/initializers/backtrace_silencers.rb +7 -0
  44. data/spec/fixtures/rails32/config/initializers/inflections.rb +15 -0
  45. data/spec/fixtures/rails32/config/initializers/mime_types.rb +5 -0
  46. data/spec/fixtures/rails32/config/initializers/secret_token.rb +7 -0
  47. data/spec/fixtures/rails32/config/initializers/session_store.rb +8 -0
  48. data/spec/fixtures/rails32/config/initializers/wrap_parameters.rb +10 -0
  49. data/spec/fixtures/rails32/config/locales/en.yml +5 -0
  50. data/spec/fixtures/rails32/config/routes.rb +64 -0
  51. data/spec/fixtures/rails32/public/404.html +26 -0
  52. data/spec/fixtures/rails32/public/422.html +26 -0
  53. data/spec/fixtures/rails32/public/500.html +25 -0
  54. data/spec/fixtures/rails32/public/favicon.ico +0 -0
  55. data/spec/fixtures/rails32/public/index.html +241 -0
  56. data/spec/fixtures/rails32/public/robots.txt +5 -0
  57. data/spec/fixtures/rails32/script/rails +6 -0
  58. data/spec/fixtures/rails40/Rakefile +6 -0
  59. data/spec/fixtures/rails40/app/controllers/application_controller.rb +30 -0
  60. data/spec/fixtures/rails40/app/helpers/application_helper.rb +2 -0
  61. data/spec/fixtures/rails40/app/views/layouts/application.html.erb +14 -0
  62. data/spec/fixtures/rails40/app/views/user.html.erb +1 -0
  63. data/spec/fixtures/rails40/bin/bundle +3 -0
  64. data/spec/fixtures/rails40/bin/rails +4 -0
  65. data/spec/fixtures/rails40/bin/rake +4 -0
  66. data/spec/fixtures/rails40/config.ru +4 -0
  67. data/spec/fixtures/rails40/config/application.rb +37 -0
  68. data/spec/fixtures/rails40/config/boot.rb +4 -0
  69. data/spec/fixtures/rails40/config/environment.rb +5 -0
  70. data/spec/fixtures/rails40/config/environments/development.rb +24 -0
  71. data/spec/fixtures/rails40/config/environments/production.rb +65 -0
  72. data/spec/fixtures/rails40/config/environments/test.rb +39 -0
  73. data/spec/fixtures/rails40/config/initializers/backtrace_silencers.rb +7 -0
  74. data/spec/fixtures/rails40/config/initializers/filter_parameter_logging.rb +4 -0
  75. data/spec/fixtures/rails40/config/initializers/inflections.rb +16 -0
  76. data/spec/fixtures/rails40/config/initializers/mime_types.rb +5 -0
  77. data/spec/fixtures/rails40/config/initializers/secret_token.rb +12 -0
  78. data/spec/fixtures/rails40/config/initializers/session_store.rb +3 -0
  79. data/spec/fixtures/rails40/config/initializers/wrap_parameters.rb +9 -0
  80. data/spec/fixtures/rails40/config/locales/en.yml +23 -0
  81. data/spec/fixtures/rails40/config/routes.rb +62 -0
  82. data/spec/fixtures/rails40/public/404.html +58 -0
  83. data/spec/fixtures/rails40/public/422.html +58 -0
  84. data/spec/fixtures/rails40/public/500.html +57 -0
  85. data/spec/fixtures/rails40/public/favicon.ico +0 -0
  86. data/spec/fixtures/rails40/public/robots.txt +5 -0
  87. data/spec/integration/rails/railtie_spec.rb +64 -0
  88. data/spec/integration/rails/request_spec.rb +42 -0
  89. data/spec/integration/sinatra_spec.rb +54 -0
  90. data/spec/spec_helper.rb +39 -0
  91. data/spec/support/helpers.rb +16 -0
  92. data/spec/support/request_spec.rb +183 -0
  93. data/spec/support/timecop_shared.rb +7 -0
  94. data/spec/unit/loga/configuration_spec.rb +123 -0
  95. data/spec/unit/loga/event_spec.rb +20 -0
  96. data/spec/unit/loga/formatter_spec.rb +186 -0
  97. data/spec/unit/loga/parameter_filter_spec.rb +76 -0
  98. data/spec/unit/loga/rack/logger_spec.rb +114 -0
  99. data/spec/unit/loga/rack/request_spec.rb +70 -0
  100. data/spec/unit/loga/utilities_spec.rb +16 -0
  101. data/spec/unit/loga_spec.rb +41 -0
  102. metadata +357 -0
@@ -0,0 +1,58 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The change you wanted was rejected (422)</title>
5
+ <style>
6
+ body {
7
+ background-color: #EFEFEF;
8
+ color: #2E2F30;
9
+ text-align: center;
10
+ font-family: arial, sans-serif;
11
+ }
12
+
13
+ div.dialog {
14
+ width: 25em;
15
+ margin: 4em auto 0 auto;
16
+ border: 1px solid #CCC;
17
+ border-right-color: #999;
18
+ border-left-color: #999;
19
+ border-bottom-color: #BBB;
20
+ border-top: #B00100 solid 4px;
21
+ border-top-left-radius: 9px;
22
+ border-top-right-radius: 9px;
23
+ background-color: white;
24
+ padding: 7px 4em 0 4em;
25
+ }
26
+
27
+ h1 {
28
+ font-size: 100%;
29
+ color: #730E15;
30
+ line-height: 1.5em;
31
+ }
32
+
33
+ body > p {
34
+ width: 33em;
35
+ margin: 0 auto 1em;
36
+ padding: 1em 0;
37
+ background-color: #F7F7F7;
38
+ border: 1px solid #CCC;
39
+ border-right-color: #999;
40
+ border-bottom-color: #999;
41
+ border-bottom-left-radius: 4px;
42
+ border-bottom-right-radius: 4px;
43
+ border-top-color: #DADADA;
44
+ color: #666;
45
+ box-shadow:0 3px 8px rgba(50, 50, 50, 0.17);
46
+ }
47
+ </style>
48
+ </head>
49
+
50
+ <body>
51
+ <!-- This file lives in public/422.html -->
52
+ <div class="dialog">
53
+ <h1>The change you wanted was rejected.</h1>
54
+ <p>Maybe you tried to change something you didn't have access to.</p>
55
+ </div>
56
+ <p>If you are the application owner check the logs for more information.</p>
57
+ </body>
58
+ </html>
@@ -0,0 +1,57 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>We're sorry, but something went wrong (500)</title>
5
+ <style>
6
+ body {
7
+ background-color: #EFEFEF;
8
+ color: #2E2F30;
9
+ text-align: center;
10
+ font-family: arial, sans-serif;
11
+ }
12
+
13
+ div.dialog {
14
+ width: 25em;
15
+ margin: 4em auto 0 auto;
16
+ border: 1px solid #CCC;
17
+ border-right-color: #999;
18
+ border-left-color: #999;
19
+ border-bottom-color: #BBB;
20
+ border-top: #B00100 solid 4px;
21
+ border-top-left-radius: 9px;
22
+ border-top-right-radius: 9px;
23
+ background-color: white;
24
+ padding: 7px 4em 0 4em;
25
+ }
26
+
27
+ h1 {
28
+ font-size: 100%;
29
+ color: #730E15;
30
+ line-height: 1.5em;
31
+ }
32
+
33
+ body > p {
34
+ width: 33em;
35
+ margin: 0 auto 1em;
36
+ padding: 1em 0;
37
+ background-color: #F7F7F7;
38
+ border: 1px solid #CCC;
39
+ border-right-color: #999;
40
+ border-bottom-color: #999;
41
+ border-bottom-left-radius: 4px;
42
+ border-bottom-right-radius: 4px;
43
+ border-top-color: #DADADA;
44
+ color: #666;
45
+ box-shadow:0 3px 8px rgba(50, 50, 50, 0.17);
46
+ }
47
+ </style>
48
+ </head>
49
+
50
+ <body>
51
+ <!-- This file lives in public/500.html -->
52
+ <div class="dialog">
53
+ <h1>We're sorry, but something went wrong.</h1>
54
+ </div>
55
+ <p>If you are the application owner check the logs for more information.</p>
56
+ </body>
57
+ </html>
@@ -0,0 +1,5 @@
1
+ # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2
+ #
3
+ # To ban all spiders from the entire site uncomment the next two lines:
4
+ # User-agent: *
5
+ # Disallow: /
@@ -0,0 +1,64 @@
1
+ require 'ostruct'
2
+
3
+ RSpec.describe Loga::Railtie do
4
+ let(:app) { Rails.application }
5
+ let(:middlewares) { app.middleware.middlewares }
6
+ let(:initializers) { described_class.initializers }
7
+
8
+ describe 'loga_initialize_logger' do
9
+ let(:initializer) { initializers.find { |i| i.name == :loga_initialize_logger } }
10
+
11
+ let(:app) { OpenStruct.new(config: config) }
12
+ let(:config) { OpenStruct.new(loga: loga, log_level: :info) }
13
+
14
+ before { initializer.run(app) }
15
+
16
+ context 'when loga is disabled' do
17
+ let(:loga) { Loga::Configuration.new.tap { |c| c.enabled = false } }
18
+
19
+ it 'is not initialized' do
20
+ expect(config.logger).to be_nil
21
+ end
22
+ end
23
+
24
+ context 'when loga is enabled' do
25
+ let(:loga) { Loga::Configuration.new }
26
+
27
+ it 'initializes the logger' do
28
+ expect(config.logger).to be_a(Logger)
29
+ end
30
+ end
31
+ end
32
+
33
+ it 'inserts Loga::Rack::Logger middleware after Rails::Rack::Logger' do
34
+ expect(middlewares.index(Loga::Rack::Logger))
35
+ .to eq(middlewares.index(Rails::Rack::Logger) + 1)
36
+ end
37
+
38
+ it 'disables colorized logging' do
39
+ expect(app.config.colorize_logging).to eq(false)
40
+ end
41
+
42
+ describe 'instrumentation' do
43
+ let(:listeners) do
44
+ ActiveSupport::Notifications.notifier.listeners_for(notification)
45
+ end
46
+ let(:subscribers) do
47
+ listeners.map { |l| l.instance_variable_get(:@delegate).class }
48
+ end
49
+
50
+ context 'ActionView' do
51
+ [
52
+ 'render_collection.action_view',
53
+ 'render_partial.action_view',
54
+ 'render_template.action_view',
55
+ ].each do |notification|
56
+ let(:notification) { notification }
57
+
58
+ it 'removes ActionView::LogSubscriber' do
59
+ expect(subscribers).to_not include(ActionView::LogSubscriber)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Integration with Rails', timecop: true do
4
+ let(:app) { Rails.application }
5
+
6
+ let(:json_entries) do
7
+ [].tap do |entries|
8
+ STREAM.tap do |s|
9
+ s.rewind
10
+ s.read.split("\n").each do |line|
11
+ entries << JSON.parse(line)
12
+ end
13
+ s.close
14
+ s.reopen
15
+ end
16
+ end
17
+ end
18
+
19
+ let(:json) { json_entries.last }
20
+ let(:json_response) { JSON.parse(last_response.body) }
21
+
22
+ include_examples 'request logger'
23
+
24
+ it 'preserves rails parameters' do
25
+ get '/show'
26
+ expect(json_response).to eq('action' => 'show', 'controller' => 'application')
27
+ end
28
+
29
+ context 'when a template is rendered' do
30
+ let(:action_view_notifications) do
31
+ json_entries.select { |e| e.to_json =~ /Rendered/ }
32
+ end
33
+
34
+ before { put '/users/5' }
35
+
36
+ specify { expect(last_response.status).to eq(200) }
37
+
38
+ it 'silences ActionView::LogSubscriber' do
39
+ expect(action_view_notifications).to be_empty
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Rack request logger with Sinatra', timecop: true do
4
+ let(:io) { StringIO.new }
5
+ before do
6
+ Loga.reset
7
+ Loga.configure do |config|
8
+ config.service_name = 'hello_world_app'
9
+ config.service_version = '1.0'
10
+ config.filter_parameters = [:password]
11
+ config.device = io
12
+ end
13
+ Loga.initialize!
14
+ end
15
+ let(:json) do
16
+ io.rewind
17
+ JSON.parse(io.read)
18
+ end
19
+
20
+ let(:app) do
21
+ Class.new(Sinatra::Base) do
22
+ # Disable show_exceptions and rely on user defined exception handlers
23
+ # (e.i. the error blocks)
24
+ set :show_exceptions, false
25
+
26
+ use Loga::Rack::RequestId
27
+ use Loga::Rack::Logger, Loga.logger, [:uuid]
28
+
29
+ error do
30
+ status 500
31
+ body 'Ooops'
32
+ end
33
+
34
+ get '/ok' do
35
+ 'Hello Sinatra'
36
+ end
37
+
38
+ get '/error' do
39
+ nil.name
40
+ end
41
+
42
+ post '/users' do
43
+ content_type :json
44
+ params.to_json
45
+ end
46
+
47
+ get '/new' do
48
+ redirect '/ok'
49
+ end
50
+ end
51
+ end
52
+
53
+ include_examples 'request logger'
54
+ end
@@ -0,0 +1,39 @@
1
+ require 'codeclimate-test-reporter'
2
+ require 'pry'
3
+ require 'support/helpers'
4
+ require 'support/timecop_shared'
5
+ require 'support/request_spec'
6
+ require 'rack/test'
7
+
8
+ CodeClimate::TestReporter.start if ENV.fetch('CODECLIMATE_REPO_TOKEN', nil)
9
+
10
+ class Socket
11
+ def self.gethostname
12
+ 'bird.example.com'
13
+ end
14
+ end
15
+
16
+ case ENV['BUNDLE_GEMFILE']
17
+ when /rails/
18
+ rspec_pattern = 'integration/rails/**/*_spec.rb'
19
+ /(?<appraisal>rails\d{2})\.gemfile/ =~ ENV['BUNDLE_GEMFILE']
20
+ require 'rails'
21
+ require File.expand_path("../fixtures/#{appraisal}/config/environment.rb", __FILE__)
22
+ when /sinatra/
23
+ rspec_pattern = 'integration/sinatra_spec.rb'
24
+ require 'json'
25
+ require 'sinatra'
26
+ require 'loga'
27
+ when /unit/
28
+ rspec_pattern = 'unit/**/*_spec.rb'
29
+ require 'loga'
30
+ else
31
+ fail 'BUNDLE_GEMFILE is unknown. Ensure the appraisal is present in Appraisals'
32
+ end
33
+
34
+ RSpec.configure do |config|
35
+ config.include Helpers
36
+ config.include Rack::Test::Methods
37
+
38
+ config.pattern = rspec_pattern
39
+ end
@@ -0,0 +1,16 @@
1
+ require 'bigdecimal'
2
+
3
+ module Helpers
4
+ # Time used when testing timestamp
5
+ def time_anchor
6
+ Time.new(2015, 12, 15, 9, 30, 5.123, '+06:00')
7
+ end
8
+
9
+ def time_anchor_unix
10
+ BigDecimal.new('1450150205.123')
11
+ end
12
+
13
+ def hostname_anchor
14
+ 'bird.example.com'
15
+ end
16
+ end
@@ -0,0 +1,183 @@
1
+ RSpec.shared_examples 'request logger' do
2
+ context 'get request' do
3
+ it 'logs the request' do
4
+ get '/ok',
5
+ { username: 'yoshi' },
6
+ 'HTTP_USER_AGENT' => 'Chrome', 'HTTP_X_REQUEST_ID' => '471a34dc'
7
+
8
+ expect(json).to match(
9
+ 'version' => '1.1',
10
+ 'host' => 'bird.example.com',
11
+ 'short_message' => 'GET /ok?username=yoshi 200 in 0ms',
12
+ 'timestamp' => 1_450_150_205.123,
13
+ 'level' => 6,
14
+ '_type' => 'request',
15
+ '_service.name' => 'hello_world_app',
16
+ '_service.version' => '1.0',
17
+ '_request.method' => 'GET',
18
+ '_request.path' => '/ok',
19
+ '_request.params' => { 'username' => 'yoshi' },
20
+ '_request.request_ip' => '127.0.0.1',
21
+ '_request.user_agent' => 'Chrome',
22
+ '_request.status' => 200,
23
+ '_request.request_id' => '471a34dc',
24
+ '_request.duration' => 0,
25
+ '_tags' => ['471a34dc'],
26
+ )
27
+ end
28
+ end
29
+
30
+ context 'post request' do
31
+ let(:json_response) { JSON.parse(last_response.body) }
32
+
33
+ it 'logs the request' do
34
+ post '/users?username=yoshi',
35
+ { email: 'hello@world.com' },
36
+ 'HTTP_USER_AGENT' => 'Chrome', 'HTTP_X_REQUEST_ID' => '471a34dc'
37
+
38
+ expect(json).to match(
39
+ 'version' => '1.1',
40
+ 'host' => 'bird.example.com',
41
+ 'short_message' => 'POST /users?username=yoshi 200 in 0ms',
42
+ 'timestamp' => 1_450_150_205.123,
43
+ 'level' => 6,
44
+ '_type' => 'request',
45
+ '_service.name' => 'hello_world_app',
46
+ '_service.version' => '1.0',
47
+ '_request.method' => 'POST',
48
+ '_request.path' => '/users',
49
+ '_request.params' => { 'username' => 'yoshi', 'email' => 'hello@world.com' },
50
+ '_request.request_ip' => '127.0.0.1',
51
+ '_request.user_agent' => 'Chrome',
52
+ '_request.status' => 200,
53
+ '_request.request_id' => '471a34dc',
54
+ '_request.duration' => 0,
55
+ '_tags' => ['471a34dc'],
56
+ )
57
+ end
58
+
59
+ it 'preseves request parameters' do
60
+ post '/users?username=yoshi', email: 'hello@world.com'
61
+ expect(json_response).to include('email' => 'hello@world.com', 'username' => 'yoshi')
62
+ end
63
+ end
64
+
65
+ context 'request with redirect' do
66
+ it 'specifies the original path' do
67
+ get '/new', {}, 'HTTP_USER_AGENT' => 'Chrome', 'HTTP_X_REQUEST_ID' => '471a34dc'
68
+
69
+ expect(json).to match(
70
+ 'version' => '1.1',
71
+ 'host' => 'bird.example.com',
72
+ 'short_message' => 'GET /new 302 in 0ms',
73
+ 'timestamp' => 1_450_150_205.123,
74
+ 'level' => 6,
75
+ '_type' => 'request',
76
+ '_service.name' => 'hello_world_app',
77
+ '_service.version' => '1.0',
78
+ '_request.method' => 'GET',
79
+ '_request.path' => '/new',
80
+ '_request.params' => {},
81
+ '_request.request_ip' => '127.0.0.1',
82
+ '_request.user_agent' => 'Chrome',
83
+ '_request.status' => 302,
84
+ '_request.request_id' => '471a34dc',
85
+ '_request.duration' => 0,
86
+ '_tags' => ['471a34dc'],
87
+ )
88
+ end
89
+ end
90
+
91
+ context 'when the request raises an exception' do
92
+ it 'logs the request with the exception' do
93
+ get '/error',
94
+ { username: 'yoshi' },
95
+ 'HTTP_USER_AGENT' => 'Chrome', 'HTTP_X_REQUEST_ID' => '471a34dc'
96
+
97
+ expect(json).to match(
98
+ 'version' => '1.1',
99
+ 'host' => 'bird.example.com',
100
+ 'short_message' => 'GET /error?username=yoshi 500 in 0ms',
101
+ 'timestamp' => 1_450_150_205.123,
102
+ 'level' => 3,
103
+ '_type' => 'request',
104
+ '_service.name' => 'hello_world_app',
105
+ '_service.version' => '1.0',
106
+ '_request.method' => 'GET',
107
+ '_request.path' => '/error',
108
+ '_request.params' => { 'username' => 'yoshi' },
109
+ '_request.request_ip' => '127.0.0.1',
110
+ '_request.user_agent' => 'Chrome',
111
+ '_request.status' => 500,
112
+ '_request.request_id' => '471a34dc',
113
+ '_request.duration' => 0,
114
+ '_exception.klass' => 'NoMethodError',
115
+ '_exception.message' => "undefined method `name' for nil:NilClass",
116
+ '_exception.backtrace' => be_a(String),
117
+ '_tags' => ['471a34dc'],
118
+ )
119
+ end
120
+ end
121
+
122
+ describe 'when request causes 404' do
123
+ it 'does not log the framework exception' do
124
+ get '/not_found', {}, 'HTTP_X_REQUEST_ID' => '471a34dc'
125
+
126
+ expect(json).to match(
127
+ 'version' => '1.1',
128
+ 'host' => 'bird.example.com',
129
+ 'short_message' => 'GET /not_found 404 in 0ms',
130
+ 'timestamp' => 1_450_150_205.123,
131
+ 'level' => 6,
132
+ '_type' => 'request',
133
+ '_service.name' => 'hello_world_app',
134
+ '_service.version' => '1.0',
135
+ '_request.method' => 'GET',
136
+ '_request.path' => '/not_found',
137
+ '_request.params' => {},
138
+ '_request.request_ip' => '127.0.0.1',
139
+ '_request.user_agent' => nil,
140
+ '_request.status' => 404,
141
+ '_request.request_id' => '471a34dc',
142
+ '_request.duration' => 0,
143
+ '_tags' => ['471a34dc'],
144
+ )
145
+ end
146
+ end
147
+
148
+ describe 'when the request includes a filtered parameter' do
149
+ before { get '/ok', params }
150
+
151
+ context 'when params is shallow' do
152
+ let(:params) { { password: 'password123' } }
153
+
154
+ it 'filters the parameter from the params hash' do
155
+ expect(json).to include(
156
+ '_request.params' => { 'password' => '[FILTERED]' },
157
+ )
158
+ end
159
+
160
+ it 'filters the parameter from the message' do
161
+ expect(json).to include(
162
+ 'short_message' => 'GET /ok?password=[FILTERED] 200 in 0ms',
163
+ )
164
+ end
165
+ end
166
+
167
+ context 'when params is nested' do
168
+ let(:params) { { users: [password: 'password123'] } }
169
+
170
+ it 'filters the parameter from the params hash' do
171
+ expect(json).to include(
172
+ '_request.params' => { 'users' => ['password' => '[FILTERED]'] },
173
+ )
174
+ end
175
+
176
+ it 'filters the parameter from the message' do
177
+ expect(json).to include(
178
+ 'short_message' => 'GET /ok?users[][password]=[FILTERED] 200 in 0ms',
179
+ )
180
+ end
181
+ end
182
+ end
183
+ end