logtail-rails 0.2.15 → 0.3.0
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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/example-project/README.md +45 -4
- data/lib/logtail-rails/error_event.rb +5 -3
- data/lib/logtail-rails/logger.rb +15 -3
- data/lib/logtail-rails/railtie.rb +25 -0
- data/lib/logtail-rails/version.rb +1 -1
- data/lib/logtail-rails.rb +11 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2c5828f2141c9b70b0f47591b7dcf8855a648c0297477637fd7029cfd5e39a8d
|
|
4
|
+
data.tar.gz: 0a88a29844cdbbc76a20a827f0575fc3f3aa4befe32dde404315bb55a733cfa2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9238c31e5e7945f99fdc3acae8a7006f6f0568e3e2269469a0498da0d5010718d2a3dc884bfd64cc471be0c7a54283e32ff327f8fa1dd11d70b76d1baf84dc2c
|
|
7
|
+
data.tar.gz: 76f5185abcdc9efafbe564d65a543cb924dcfb75326686645f346a7e30201b053cad5380701f8abe3d0ed01db85f0a54b631522dca449bf4aba2164967f6d840
|
data/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Experience SQL-compatible structured log management based on ClickHouse. [Learn
|
|
|
14
14
|
|
|
15
15
|
[Getting started ⇗](https://betterstack.com/docs/logs/ruby-and-rails/#logging-from-rails)
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
[Example project](example-project/README.md): logging, structured data and [adding context to every log line of a request](example-project/README.md#context).
|
|
18
18
|
|
|
19
19
|
## Need help?
|
|
20
20
|
Please let us know at [hello@betterstack.com](mailto:hello@betterstack.com). We're happy to help!
|
data/example-project/README.md
CHANGED
|
@@ -185,12 +185,53 @@ This will generate the following JSON output:
|
|
|
185
185
|
}
|
|
186
186
|
```
|
|
187
187
|
|
|
188
|
-
We will automatically add the information about the current user to each log if you're using Ruby on Rails
|
|
188
|
+
We will automatically add the information about the current user to each log if you're using Ruby on Rails with Devise (or any other Warden-based authentication) or with Clearance.
|
|
189
189
|
|
|
190
|
-
|
|
190
|
+
### Context for every log line of a request
|
|
191
|
+
|
|
192
|
+
The request and response lines (`Started GET "/" for 127.0.0.1` and `Completed 200 OK in 12.3ms`) are logged by a Rack middleware, before the request reaches your controller. Context set from inside a controller is therefore attached to the logs from the controller action on, but not to these two lines. To attach context to every log line of a request, set it in a Rack middleware instead.
|
|
193
|
+
|
|
194
|
+
The user context from Devise or Clearance is set this way, by the `Logtail::Integrations::Rack::UserContext` middleware. If you authenticate differently, give it a lambda that finds the user in the Rack environment and returns a hash, or `nil` when nobody is signed in:
|
|
191
195
|
|
|
192
196
|
```ruby
|
|
193
|
-
|
|
197
|
+
# config/initializers/logtail.rb
|
|
198
|
+
Logtail::Integrations::Rack::UserContext.custom_user_hash = lambda do |rack_env|
|
|
199
|
+
user_id = rack_env["rack.session"]["user_id"]
|
|
200
|
+
user = User.find_by(id: user_id) if user_id
|
|
201
|
+
user && { id: user.id, email: user.email }
|
|
202
|
+
end
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
The hash is logged as `context.user` on every log line of the request, including the request and response lines. The lambda runs on every request, so keep it cheap. It can read anything from the Rack environment, for example an API token from `rack_env["HTTP_AUTHORIZATION"]`.
|
|
206
|
+
|
|
207
|
+
For any other per-request context (a tenant, an API client, a GraphQL operation name), write your own middleware that wraps the request in `Logtail.with_context` and insert it before the middleware that logs the request and response lines:
|
|
208
|
+
|
|
209
|
+
```ruby
|
|
210
|
+
# config/initializers/logtail.rb
|
|
211
|
+
class LogtailTenantContext
|
|
212
|
+
def initialize(app)
|
|
213
|
+
@app = app
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def call(env)
|
|
217
|
+
tenant = env["HTTP_X_TENANT"]
|
|
218
|
+
return @app.call(env) unless tenant
|
|
219
|
+
|
|
220
|
+
Logtail.with_context(tenant: { name: tenant }) { @app.call(env) }
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
Rails.application.config.middleware.insert_before Logtail::Integrations::Rack::HTTPEvents, LogtailTenantContext
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Inserting it before `Logtail::Integrations::Rack::HTTPEvents` also places it after your authentication middleware (such as Warden), so the session and the signed-in user are already available in `env`.
|
|
228
|
+
|
|
229
|
+
### Context from a controller
|
|
230
|
+
|
|
231
|
+
If the user is only known once your controller runs (for example, you authenticate from a token in a `before_action`), you can still set the context with Rails' `around_action`. It covers the logs from the controller action and everything it calls, but not the request and response lines, which are logged before the action runs. A simple implementation could look like this:
|
|
232
|
+
|
|
233
|
+
```ruby
|
|
234
|
+
class ApplicationController < ActionController::Base # or ActionController::API
|
|
194
235
|
around_action :with_logtail_context
|
|
195
236
|
|
|
196
237
|
private
|
|
@@ -202,7 +243,7 @@ class ApplicationController < ActionController::Base
|
|
|
202
243
|
yield
|
|
203
244
|
end
|
|
204
245
|
end
|
|
205
|
-
|
|
246
|
+
|
|
206
247
|
def user_context
|
|
207
248
|
Logtail::Contexts::User.new(
|
|
208
249
|
id: current_user.id,
|
|
@@ -18,9 +18,11 @@ module Logtail
|
|
|
18
18
|
# We determine this when the app loads to avoid the overhead on a per request basis.
|
|
19
19
|
EXCEPTION_WRAPPER_TAKES_CLEANER = defined?(::ActionDispatch::ExceptionWrapper) &&
|
|
20
20
|
!::ActionDispatch::ExceptionWrapper.instance_methods.include?(:env)
|
|
21
|
-
# config.action_dispatch.log_rescued_responses (Rails 7.0+)
|
|
22
|
-
#
|
|
21
|
+
# config.action_dispatch.log_rescued_responses (Rails 7.0+) and
|
|
22
|
+
# config.action_dispatch.debug_exception_log_level (Rails 7.1+), as ActionDispatch::DebugExceptions
|
|
23
|
+
# reads them. This gem silences its logging and logs the exception here instead.
|
|
23
24
|
LOG_RESCUED_RESPONSES_KEY = "action_dispatch.log_rescued_responses".freeze
|
|
25
|
+
DEBUG_EXCEPTION_LOG_LEVEL_KEY = "action_dispatch.debug_exception_log_level".freeze
|
|
24
26
|
|
|
25
27
|
def call(env)
|
|
26
28
|
begin
|
|
@@ -38,7 +40,7 @@ module Logtail
|
|
|
38
40
|
def log_exception(env, exception)
|
|
39
41
|
return if !log_exception?(env, exception)
|
|
40
42
|
|
|
41
|
-
Config.instance.logger.
|
|
43
|
+
Config.instance.logger.add(env[DEBUG_EXCEPTION_LOG_LEVEL_KEY] || ::Logger::FATAL) do
|
|
42
44
|
backtrace = extract_backtrace(env, exception)
|
|
43
45
|
Events::Error.new(
|
|
44
46
|
name: exception.class.name,
|
data/lib/logtail-rails/logger.rb
CHANGED
|
@@ -65,10 +65,15 @@ module Logtail
|
|
|
65
65
|
options[:ingesting_port] ||= options[:telemetry_port] if options[:telemetry_port].present?
|
|
66
66
|
options[:ingesting_scheme] ||= options[:telemetry_scheme] if options[:telemetry_scheme].present?
|
|
67
67
|
|
|
68
|
-
if ENV['LOGTAIL_SKIP_LOGS'].
|
|
69
|
-
io_device = Logtail::LogDevices::HTTP.new(source_token, options)
|
|
70
|
-
else
|
|
68
|
+
if ENV['LOGTAIL_SKIP_LOGS'].present? || Rails.env.test?
|
|
71
69
|
io_device = STDOUT
|
|
70
|
+
elsif source_token.blank?
|
|
71
|
+
# Don't break booting the app without the token, e.g. for `assets:precompile` in a Docker build
|
|
72
|
+
@blank_source_token = true
|
|
73
|
+
io_device = STDOUT
|
|
74
|
+
else
|
|
75
|
+
io_device = Logtail::LogDevices::HTTP.new(source_token, options)
|
|
76
|
+
@better_stack_logger_created = true
|
|
72
77
|
end
|
|
73
78
|
|
|
74
79
|
logger = self.create_logger(io_device)
|
|
@@ -87,5 +92,12 @@ module Logtail
|
|
|
87
92
|
|
|
88
93
|
logger
|
|
89
94
|
end
|
|
95
|
+
|
|
96
|
+
class << self
|
|
97
|
+
# What {create_default_logger} did, Logtail::Frameworks::Rails::Railtie warns about it once the app has booted
|
|
98
|
+
#
|
|
99
|
+
# @private
|
|
100
|
+
attr_reader :blank_source_token, :better_stack_logger_created
|
|
101
|
+
end
|
|
90
102
|
end
|
|
91
103
|
end
|
|
@@ -43,6 +43,31 @@ module Logtail
|
|
|
43
43
|
config.app_middleware.use middleware_class
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
|
+
|
|
47
|
+
# Warns about problems with the logger create_default_logger created, unless the integration is turned off.
|
|
48
|
+
# Registered here, so that it runs after the app's own after_initialize blocks, which may still broadcast to it.
|
|
49
|
+
initializer(:logtail_logger_check, after: :load_config_initializers) do |app|
|
|
50
|
+
app.config.after_initialize do
|
|
51
|
+
next unless Integrations::Rails.enabled?
|
|
52
|
+
|
|
53
|
+
if Logtail::Logger.blank_source_token
|
|
54
|
+
Kernel.warn("Logtail: the source token passed to Logtail::Logger.create_default_logger is blank, logging to STDOUT instead of sending logs to Better Stack.")
|
|
55
|
+
next
|
|
56
|
+
end
|
|
57
|
+
next unless Logtail::Logger.better_stack_logger_created
|
|
58
|
+
|
|
59
|
+
# A `config.logger = ...` line after config/application.rb, like the one generated in
|
|
60
|
+
# config/environments/production.rb, replaces the Better Stack logger without any error
|
|
61
|
+
loggers = ::Rails.logger.respond_to?(:broadcasts) ? ::Rails.logger.broadcasts : [::Rails.logger]
|
|
62
|
+
next if loggers.any? { |logger| logger.is_a?(Logtail::Logger) }
|
|
63
|
+
|
|
64
|
+
# Before Rails 7.1, broadcasting extends the logger with an anonymous module, which hides the target
|
|
65
|
+
extended_modules = ::Rails.logger.singleton_class.included_modules - ::Rails.logger.class.included_modules
|
|
66
|
+
next if extended_modules.any? { |mod| mod.name.nil? }
|
|
67
|
+
|
|
68
|
+
Kernel.warn("Logtail: Rails.logger isn't the Better Stack logger created by Logtail::Logger.create_default_logger, and doesn't broadcast to it, so your logs don't reach Better Stack. Most likely config.logger is set again later, e.g. in config/environments/production.rb.")
|
|
69
|
+
end
|
|
70
|
+
end
|
|
46
71
|
end
|
|
47
72
|
end
|
|
48
73
|
end
|
data/lib/logtail-rails.rb
CHANGED
|
@@ -50,6 +50,7 @@ module Logtail
|
|
|
50
50
|
ActionView.integrate!
|
|
51
51
|
ActiveRecord.integrate!
|
|
52
52
|
RackLogger.integrate!
|
|
53
|
+
filter_parameters_in_urls(::Rails.application.config.filter_parameters)
|
|
53
54
|
end
|
|
54
55
|
|
|
55
56
|
def self.enabled=(value)
|
|
@@ -71,6 +72,16 @@ module Logtail
|
|
|
71
72
|
@middlewares ||= [Logtail::Integrations::Rack::HTTPContext, SessionContext, Logtail::Integrations::Rack::UserContext,
|
|
72
73
|
Logtail::Integrations::Rack::HTTPEvents, Logtail::Integrations::Rails::ErrorEvent].select(&:enabled?)
|
|
73
74
|
end
|
|
75
|
+
|
|
76
|
+
# Filters the app's filter_parameters from the query strings and the Referer and Location
|
|
77
|
+
# URLs that Rack::HTTPEvents logs, unless the app customised its query_string_filters.
|
|
78
|
+
# Procs are left out: they rewrite values, while query string filters match names.
|
|
79
|
+
def self.filter_parameters_in_urls(filter_parameters)
|
|
80
|
+
http_events = Logtail::Integrations::Rack::HTTPEvents
|
|
81
|
+
return if http_events.query_string_filters != http_events::DEFAULT_QUERY_STRING_FILTERS
|
|
82
|
+
|
|
83
|
+
http_events.query_string_filters = filter_parameters.reject { |filter| filter.is_a?(Proc) }
|
|
84
|
+
end
|
|
74
85
|
end
|
|
75
86
|
end
|
|
76
87
|
end
|