log_sanity 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/Gemfile +15 -0
  4. data/Gemfile.lock +141 -0
  5. data/LICENSE +20 -0
  6. data/README.md +180 -0
  7. data/Rakefile +34 -0
  8. data/lib/log_sanity.rb +36 -0
  9. data/lib/log_sanity/extensions/action_controller_helper.rb +12 -0
  10. data/lib/log_sanity/extensions/active_support_subscriber.rb +25 -0
  11. data/lib/log_sanity/formatter.rb +45 -0
  12. data/lib/log_sanity/log_subscribers/action_controller.rb +56 -0
  13. data/lib/log_sanity/log_subscribers/action_dispatch.rb +63 -0
  14. data/lib/log_sanity/log_subscribers/action_mailer.rb +27 -0
  15. data/lib/log_sanity/log_subscribers/active_job.rb +69 -0
  16. data/lib/log_sanity/log_subscribers/base.rb +10 -0
  17. data/lib/log_sanity/middleware/request_logger.rb +69 -0
  18. data/lib/log_sanity/middleware/routing_error_catcher.rb +30 -0
  19. data/lib/log_sanity/railtie.rb +50 -0
  20. data/lib/log_sanity/version.rb +3 -0
  21. data/lib/tasks/log_sanity_tasks.rake +4 -0
  22. data/log_sanity.gemspec +21 -0
  23. data/test/dummy/README.rdoc +28 -0
  24. data/test/dummy/Rakefile +6 -0
  25. data/test/dummy/app/assets/config/manifest.js +1 -0
  26. data/test/dummy/app/assets/images/.keep +0 -0
  27. data/test/dummy/app/assets/javascripts/application.js +13 -0
  28. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  29. data/test/dummy/app/controllers/application_controller.rb +5 -0
  30. data/test/dummy/app/controllers/concerns/.keep +0 -0
  31. data/test/dummy/app/helpers/application_helper.rb +2 -0
  32. data/test/dummy/app/mailers/.keep +0 -0
  33. data/test/dummy/app/models/.keep +0 -0
  34. data/test/dummy/app/models/concerns/.keep +0 -0
  35. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  36. data/test/dummy/bin/bundle +3 -0
  37. data/test/dummy/bin/rails +4 -0
  38. data/test/dummy/bin/rake +4 -0
  39. data/test/dummy/bin/setup +29 -0
  40. data/test/dummy/config.ru +4 -0
  41. data/test/dummy/config/application.rb +29 -0
  42. data/test/dummy/config/boot.rb +5 -0
  43. data/test/dummy/config/environment.rb +5 -0
  44. data/test/dummy/config/environments/development.rb +38 -0
  45. data/test/dummy/config/environments/production.rb +76 -0
  46. data/test/dummy/config/environments/test.rb +42 -0
  47. data/test/dummy/config/initializers/assets.rb +11 -0
  48. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  49. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  50. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  51. data/test/dummy/config/initializers/inflections.rb +16 -0
  52. data/test/dummy/config/initializers/mime_types.rb +4 -0
  53. data/test/dummy/config/initializers/session_store.rb +3 -0
  54. data/test/dummy/config/initializers/wrap_parameters.rb +9 -0
  55. data/test/dummy/config/locales/en.yml +23 -0
  56. data/test/dummy/config/routes.rb +56 -0
  57. data/test/dummy/config/secrets.yml +22 -0
  58. data/test/dummy/lib/assets/.keep +0 -0
  59. data/test/dummy/log/.keep +0 -0
  60. data/test/dummy/public/404.html +67 -0
  61. data/test/dummy/public/422.html +67 -0
  62. data/test/dummy/public/500.html +66 -0
  63. data/test/dummy/public/favicon.ico +0 -0
  64. data/test/log_sanity_test.rb +7 -0
  65. data/test/test_helper.rb +19 -0
  66. metadata +171 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: cf587be669e717c152dbdcc863726d68fc0a0de5cb56e07f53f61aa707059626
4
+ data.tar.gz: 75fd9003f9812d20f5c73476368cff9388d9875e5a67865c4156ea948c62bf53
5
+ SHA512:
6
+ metadata.gz: fcd3e322168ebd60856cccba40ac63d90152192323b25679c8c6dbca3f05e5b6d3a66ace7025ac817cdb21b008cf56aa04bf66a26dab7e9b579c48948066e030
7
+ data.tar.gz: 0b7c2309c55819217d2424541d38da82b8138b0432e7b022b78f9e19476688c929cc5dfde4bdbd25114c674e2054c2fb665834d240544c7eab353b516a1ef376
@@ -0,0 +1,8 @@
1
+ .bundle/
2
+ log/*.log
3
+ pkg/
4
+ test/dummy/db/*.sqlite3
5
+ test/dummy/db/*.sqlite3-journal
6
+ test/dummy/log/*.log
7
+ test/dummy/tmp/
8
+ test/dummy/.sass-cache
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Declare your gem's dependencies in log_sanity.gemspec.
4
+ # Bundler will treat runtime dependencies like base dependencies, and
5
+ # development dependencies will be added by default to the :development group.
6
+ gemspec
7
+
8
+ # Declare any dependencies that are still in development here instead of in
9
+ # your gemspec. These might include edge Rails or gems from your path or
10
+ # Git. Remember to move these dependencies to your gemspec before releasing
11
+ # your gem to rubygems.org.
12
+
13
+ # To use a debugger
14
+ # gem 'byebug', group: [:development, :test]
15
+
@@ -0,0 +1,141 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ log_sanity (0.2.5)
5
+ rails (>= 5, < 6.1)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ actioncable (6.0.2.1)
11
+ actionpack (= 6.0.2.1)
12
+ nio4r (~> 2.0)
13
+ websocket-driver (>= 0.6.1)
14
+ actionmailbox (6.0.2.1)
15
+ actionpack (= 6.0.2.1)
16
+ activejob (= 6.0.2.1)
17
+ activerecord (= 6.0.2.1)
18
+ activestorage (= 6.0.2.1)
19
+ activesupport (= 6.0.2.1)
20
+ mail (>= 2.7.1)
21
+ actionmailer (6.0.2.1)
22
+ actionpack (= 6.0.2.1)
23
+ actionview (= 6.0.2.1)
24
+ activejob (= 6.0.2.1)
25
+ mail (~> 2.5, >= 2.5.4)
26
+ rails-dom-testing (~> 2.0)
27
+ actionpack (6.0.2.1)
28
+ actionview (= 6.0.2.1)
29
+ activesupport (= 6.0.2.1)
30
+ rack (~> 2.0, >= 2.0.8)
31
+ rack-test (>= 0.6.3)
32
+ rails-dom-testing (~> 2.0)
33
+ rails-html-sanitizer (~> 1.0, >= 1.2.0)
34
+ actiontext (6.0.2.1)
35
+ actionpack (= 6.0.2.1)
36
+ activerecord (= 6.0.2.1)
37
+ activestorage (= 6.0.2.1)
38
+ activesupport (= 6.0.2.1)
39
+ nokogiri (>= 1.8.5)
40
+ actionview (6.0.2.1)
41
+ activesupport (= 6.0.2.1)
42
+ builder (~> 3.1)
43
+ erubi (~> 1.4)
44
+ rails-dom-testing (~> 2.0)
45
+ rails-html-sanitizer (~> 1.1, >= 1.2.0)
46
+ activejob (6.0.2.1)
47
+ activesupport (= 6.0.2.1)
48
+ globalid (>= 0.3.6)
49
+ activemodel (6.0.2.1)
50
+ activesupport (= 6.0.2.1)
51
+ activerecord (6.0.2.1)
52
+ activemodel (= 6.0.2.1)
53
+ activesupport (= 6.0.2.1)
54
+ activestorage (6.0.2.1)
55
+ actionpack (= 6.0.2.1)
56
+ activejob (= 6.0.2.1)
57
+ activerecord (= 6.0.2.1)
58
+ marcel (~> 0.3.1)
59
+ activesupport (6.0.2.1)
60
+ concurrent-ruby (~> 1.0, >= 1.0.2)
61
+ i18n (>= 0.7, < 2)
62
+ minitest (~> 5.1)
63
+ tzinfo (~> 1.1)
64
+ zeitwerk (~> 2.2)
65
+ builder (3.2.4)
66
+ concurrent-ruby (1.1.6)
67
+ crass (1.0.6)
68
+ erubi (1.9.0)
69
+ globalid (0.4.2)
70
+ activesupport (>= 4.2.0)
71
+ i18n (1.8.2)
72
+ concurrent-ruby (~> 1.0)
73
+ loofah (2.4.0)
74
+ crass (~> 1.0.2)
75
+ nokogiri (>= 1.5.9)
76
+ mail (2.7.1)
77
+ mini_mime (>= 0.1.1)
78
+ marcel (0.3.3)
79
+ mimemagic (~> 0.3.2)
80
+ method_source (0.9.2)
81
+ mimemagic (0.3.4)
82
+ mini_mime (1.0.2)
83
+ mini_portile2 (2.4.0)
84
+ minitest (5.14.0)
85
+ nio4r (2.5.2)
86
+ nokogiri (1.10.8)
87
+ mini_portile2 (~> 2.4.0)
88
+ rack (2.2.2)
89
+ rack-test (1.1.0)
90
+ rack (>= 1.0, < 3)
91
+ rails (6.0.2.1)
92
+ actioncable (= 6.0.2.1)
93
+ actionmailbox (= 6.0.2.1)
94
+ actionmailer (= 6.0.2.1)
95
+ actionpack (= 6.0.2.1)
96
+ actiontext (= 6.0.2.1)
97
+ actionview (= 6.0.2.1)
98
+ activejob (= 6.0.2.1)
99
+ activemodel (= 6.0.2.1)
100
+ activerecord (= 6.0.2.1)
101
+ activestorage (= 6.0.2.1)
102
+ activesupport (= 6.0.2.1)
103
+ bundler (>= 1.3.0)
104
+ railties (= 6.0.2.1)
105
+ sprockets-rails (>= 2.0.0)
106
+ rails-dom-testing (2.0.3)
107
+ activesupport (>= 4.2.0)
108
+ nokogiri (>= 1.6)
109
+ rails-html-sanitizer (1.3.0)
110
+ loofah (~> 2.3)
111
+ railties (6.0.2.1)
112
+ actionpack (= 6.0.2.1)
113
+ activesupport (= 6.0.2.1)
114
+ method_source
115
+ rake (>= 0.8.7)
116
+ thor (>= 0.20.3, < 2.0)
117
+ rake (13.0.1)
118
+ sprockets (4.0.0)
119
+ concurrent-ruby (~> 1.0)
120
+ rack (> 1, < 3)
121
+ sprockets-rails (3.2.1)
122
+ actionpack (>= 4.0)
123
+ activesupport (>= 4.0)
124
+ sprockets (>= 3.0.0)
125
+ thor (1.0.1)
126
+ thread_safe (0.3.6)
127
+ tzinfo (1.2.6)
128
+ thread_safe (~> 0.1)
129
+ websocket-driver (0.7.1)
130
+ websocket-extensions (>= 0.1.0)
131
+ websocket-extensions (0.1.4)
132
+ zeitwerk (2.2.2)
133
+
134
+ PLATFORMS
135
+ ruby
136
+
137
+ DEPENDENCIES
138
+ log_sanity!
139
+
140
+ BUNDLED WITH
141
+ 1.17.3
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2017-2020 thomas morgan
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,180 @@
1
+ # LogSanity
2
+
3
+ LogSanity is another attempt at taming Rails logging and making logs more viable in production.
4
+
5
+ It's quite opinionated (partly intentional, partly a byproduct of being a young project) and yet strives for sane defaults and to play nicely within the natural order of Rails.
6
+
7
+ At its core, it aggregates various logging variables and then outputs one JSON-formatted, primary request log line. It's quite easy to add extra variables to this output, great for appending info about the authenticated user, active account, etc.
8
+
9
+ However, just because the primary output is a single line doesn't make it a one-line logging system. Taking inspiration from elsewhere[^1], the intent is for each conceptual event to have its own log entry. The default request line is just an http(s) request event. If the user takes other actions and those should be logged, do so--and give them their own entry. To this end, a small number of default Rails events are logged individually, such as sending an email, some ActiveJob processing, and a few others.
10
+
11
+ Example output: (Multi-line and extra whitespace added for readability; normally all one line.)
12
+
13
+ ```
14
+ { "at" : "2017-01-18T00:27:50.947Z",
15
+ "event" : "https_get",
16
+ "ip" : "127.0.0.1",
17
+ "rq" : "ec337729-dbf3-4c6f-86b9-8d1a06c2277e",
18
+ "route" : "ArticlesController#index",
19
+ "format" : "html",
20
+ "params" : {"id":"123456"},
21
+ "duration" : {"total":20, "views":9, "activerecord":1},
22
+ "status" : 200
23
+ }
24
+ ```
25
+
26
+
27
+
28
+ ### Installation
29
+
30
+ Install the usual way:
31
+ ```
32
+ gem 'log_sanity'
33
+ ```
34
+
35
+ By default, LogSanity does not enable itself. To do so, in `config/environments/production.rb` add:
36
+ ```
37
+ config.logsanity.enabled = true
38
+ config.log_level = :info
39
+ ```
40
+
41
+ You can go less verbose than `:info` (say, `:warn`), but more verbose (ie: `:debug`) is not recommended.
42
+
43
+
44
+ ### Usage
45
+
46
+ Basic usage may require nothing more than enable LogSanity as outlined above. Some common configuration settings include silencing logging for certain paths (like health checks) or adding information about the currently authenticated user.
47
+
48
+ ##### Adding attributes
49
+
50
+ The most common way to add attributes is via controllers. Helper methods are provided for this. For example, to log the current user's ID, you might add the following to `application_controller.rb`:
51
+
52
+ ```
53
+ after_filter do
54
+ if current_user
55
+ log_field 'user', current_user.id
56
+ end
57
+ end
58
+ ```
59
+
60
+ The syntax is simply `log_field(key, value)`. Since the output is JSON, `value` can even be a hash or array:
61
+ ```
62
+ log_field 'user', {id: current_user.id, name: current_user.name}
63
+ ```
64
+
65
+ You can log multiple fields by calling `log_field` multiple times.
66
+
67
+ If you must, you can get to the full fields hash:
68
+ ```
69
+ LogSanity.fields['user'] ||= {}
70
+ LogSanity.fields['user']['id'] = current_user.id
71
+ ```
72
+
73
+ It's also possible to add attributes via Rails existing `log_tags` facility, which is documented more below.
74
+
75
+
76
+ ##### Logging complete entries
77
+
78
+ To log a complete event (a complete log entry), try something like:
79
+ ```
80
+ logger.info 'event'=>'user_signup', 'source'=>'ppc', 'campaign'=>'awesomeness', 'rq'=>request.uuid, 'user'=>current_user.id
81
+ ```
82
+
83
+ If you pass in a hash to any `logger` method, it automatically becomes JSON output. The timestamp will be automatically added.
84
+
85
+
86
+
87
+ ### Configuration options
88
+
89
+ While the above cover most of it, there are a handful of other potentially useful settings.
90
+
91
+ ##### Silence logging for certain paths
92
+
93
+ This is particularly useful if you have any kind of health check path, as there's no need to fill up log files with that stuff.
94
+
95
+ ```
96
+ config.logsanity.silence_paths += ["/health", %r{^/healthcheck}]
97
+ ```
98
+
99
+ Both exact path matches (Strings) and Regex's are supported.
100
+
101
+
102
+ ##### Logging of strings
103
+
104
+ By default, strings are logged as-is and not stuck inside a JSON object. If you prefer the opposite:
105
+
106
+ ```
107
+ config.logsanity.json_strings = true
108
+ ```
109
+
110
+ With `json_strings` as `false` (default), `logger.info "This is fantastic!"` would normally just output:
111
+ ```
112
+ This is fantastic!
113
+ ```
114
+ However, if `true`, you'll see:
115
+ ```
116
+ {"at":"2017-01-18T00:27:50.947Z","message":"This is fantastic!"}
117
+ ```
118
+
119
+
120
+ ##### String formatting
121
+
122
+ When LogSanity initializes, it replaces the Rails log formatter with its own, but saves the old one for outputting strings (assuming `json_strings` is `false`). This means you can still configure the formatting of those. For example, to use Logger's default formatting (instead of Rails' default):
123
+
124
+ ```
125
+ config.log_formatter = ::Logger::Formatter.new
126
+ ```
127
+
128
+
129
+ ##### Tagged logging
130
+
131
+ As noted above, you can either add extra attributes directly or via `log_tags`. We discussed direct already. Now let's take a look at tagged logging, which is a bit different in a JSON world.
132
+
133
+ ```
134
+ config.log_tags = [ :subdomain ]
135
+ ```
136
+
137
+ Just like Rails' support for text-style logs, you may use symbols (which call the named method on the `request` object), strings (logged literally), and Procs (which are passed `request` as a parameter).
138
+
139
+ LogSanity takes these and adds them to the default request log entry (but _not_ other log entries). If a tagged method (via symbol or Proc) returns a hash, it's merged directly into the output. Otherwise the return value is used as a string and given a key in the form `tag#` where # is automatically calculated.
140
+
141
+
142
+ ### Additional notes
143
+
144
+ LogSanity is intended for production use at log_level info. At level debug, some logs are simply turned off. Others may continue to output as normal strings (such as ActiveRecord).
145
+
146
+ There is no need to use ActiveSupport::TaggedLogging with your logger. Just set the logger directly (if not using the default):
147
+ ```
148
+ config.logger = ActiveSupport::Logger.new(STDOUT)
149
+ ```
150
+
151
+ All default output includes the :uuid/:request_id using the key "rq". There is no need to add \[:uuid] to `config.log_tags`.
152
+
153
+ `ActionController::RoutingError` exceptions are always silenced and turned into a simple 404 log entry.
154
+
155
+ The request path is not included, as it mostly just duplicates `route` and `params`. If you need it, you could add it using `config.log_tags = [:filtered_path]`. Alternatively, consider adding X-Request-Id to your `nginx` (or other webserver) logs and correlating to those logs instead.
156
+
157
+ The `total` duration may be longer than you're used to seeing. By default, Rails only counts the time inside the application controller. In contrast, LogSanity also includes all the middleware between itself and the application controller. While this isn't the entire picture, it is closer to the actual real time elapsed.
158
+
159
+
160
+ ### What does it do behind the scenes?
161
+
162
+ In short:
163
+ * Removes all default Rails logging, replacing it with its own
164
+ * Replaces Rails::Rack::Logger middleware with its own
165
+ * Adds middleware to intercept routing errors
166
+ * Replaces the current logger's formatter
167
+
168
+
169
+ ### Final notes
170
+
171
+ There are still some things that could be handled better (such as multi-line strings in json_strings mode).
172
+
173
+ Pull requests are welcomed and encouraged. The only goal is to avoid making things unnecessarily complex.
174
+
175
+ Tested on Rails 5.2 and up. Anything older is untested. Small patches for older compatibility will be considered.
176
+
177
+ License: MIT
178
+
179
+
180
+ [^1]: Parts [one](https://medium.com/@jlsuttles/structured-logging-part-1-what-s-the-big-deal-b7c6011e2504), [two](https://medium.com/@jlsuttles/structured-logging-part-2-usage-7754db10b6c), and [three](https://medium.com/@jlsuttles/structured-logging-part-3-practical-application-for-ruby-b5023f29f0af).
@@ -0,0 +1,34 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'LogSanity'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+
18
+
19
+
20
+
21
+
22
+ Bundler::GemHelper.install_tasks
23
+
24
+ require 'rake/testtask'
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'lib'
28
+ t.libs << 'test'
29
+ t.pattern = 'test/**/*_test.rb'
30
+ t.verbose = false
31
+ end
32
+
33
+
34
+ task default: :test
@@ -0,0 +1,36 @@
1
+ %w( formatter
2
+ railtie
3
+ log_subscribers/base
4
+ log_subscribers/action_controller
5
+ log_subscribers/action_dispatch
6
+ log_subscribers/action_mailer
7
+ log_subscribers/active_job
8
+ middleware/request_logger
9
+ middleware/routing_error_catcher
10
+ extensions/action_controller_helper
11
+ extensions/active_support_subscriber
12
+ ).each do |fn|
13
+ require_relative "log_sanity/#{fn}"
14
+ end
15
+
16
+ ActionController::Base.include LogSanity::Extensions::ActionControllerHelper
17
+ ActionController::API.include LogSanity::Extensions::ActionControllerHelper if defined?(ActionController::API)
18
+ if Rails.version < '6'
19
+ ActiveSupport::Subscriber.include LogSanity::Extensions::ActiveSupportSubscriber
20
+ end
21
+
22
+ module LogSanity
23
+ module_function
24
+
25
+ def fields
26
+ Thread.current[:logsanity_fields] || reset_fields
27
+ end
28
+
29
+ def reset_fields
30
+ Thread.current[:logsanity_fields] = {}.with_indifferent_access
31
+ end
32
+
33
+ def log(key, val)
34
+ fields[key.to_s] = val
35
+ end
36
+ end