logtail-rails 0.2.15 → 0.3.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.
- checksums.yaml +4 -4
- data/.github/actions/cleanup-sources/action.yml +33 -0
- data/.github/actions/cleanup-sources/run.sh +51 -0
- data/.github/actions/create-source/action.yml +46 -0
- data/.github/actions/create-source/run.sh +40 -0
- data/.github/actions/verify-logs/action.yml +47 -0
- data/.github/actions/verify-logs/run.sh +104 -0
- data/.github/workflows/e2e.yml +275 -0
- data/.github/workflows/main.yml +6 -20
- data/.github/workflows/notify_linear_failure.yml +153 -0
- data/README.md +1 -1
- data/e2e/example.sh +66 -0
- data/e2e/expected.json +109 -0
- data/e2e/installed.rb +20 -0
- data/e2e/plan.sh +112 -0
- data/example-project/Gemfile +2 -4
- data/example-project/README.md +54 -11
- data/example-project/app/controllers/example_controller.rb +14 -7
- data/example-project/app/views/example/index.html.erb +3 -1
- data/example-project/config/application.rb +5 -4
- data/example-project/config/routes.rb +1 -0
- 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 +15 -0
- metadata +13 -2
- data/gemfiles/rails-edge.gemfile +0 -13
data/example-project/README.md
CHANGED
|
@@ -9,23 +9,23 @@ You can download the example project from GitHub directly or you can clone it to
|
|
|
9
9
|
bundle install
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
This will install all dependencies listed in the `Gemfile.
|
|
12
|
+
This will install all dependencies listed in the `Gemfile`. The example runs on Rails 7.1 and newer.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
`config/application.rb` reads your source token and ingesting host from the environment. You can find them by going to [Sources](https://telemetry.betterstack.com/team/0/sources) -> Configure in Better Stack.
|
|
15
15
|
|
|
16
16
|
```ruby
|
|
17
17
|
config.logger = Logtail::Logger.create_default_logger(
|
|
18
|
-
"
|
|
19
|
-
ingesting_host: "
|
|
18
|
+
ENV["BETTER_STACK_SOURCE_TOKEN"],
|
|
19
|
+
ingesting_host: ENV["BETTER_STACK_INGESTING_HOST"],
|
|
20
20
|
)
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
## Run the example project
|
|
24
24
|
|
|
25
|
-
To run the example application, run the following command:
|
|
25
|
+
To run the example application, run the following command with your source token and ingesting host:
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
rails server
|
|
28
|
+
BETTER_STACK_SOURCE_TOKEN=<source_token> BETTER_STACK_INGESTING_HOST=<ingesting_host> bin/rails server
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
This will open a local server [127.0.0.1:3000.](http://127.0.0.1:3000/) On the main page, click the "Let's go!" button to generate test logs.
|
|
@@ -37,7 +37,9 @@ All done!
|
|
|
37
37
|
Log into your Logtail account to check your logs.
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
This will create a
|
|
40
|
+
This will create a log for each log level, one with tags and one with additional structured data, and on Rails 8.1 and newer two events reported with `Rails.event`, next to the request, controller and template logs of the Rails integration. Click "Raise an exception" on the main page to see how an exception raised in a controller action is logged. You can review these logs in Better Stack.
|
|
41
|
+
|
|
42
|
+
Stop the server with Ctrl-C. The logger sends what it still holds before the server exits.
|
|
41
43
|
|
|
42
44
|
# Logging
|
|
43
45
|
|
|
@@ -185,12 +187,53 @@ This will generate the following JSON output:
|
|
|
185
187
|
}
|
|
186
188
|
```
|
|
187
189
|
|
|
188
|
-
We will automatically add the information about the current user to each log if you're using Ruby on Rails
|
|
190
|
+
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.
|
|
191
|
+
|
|
192
|
+
### Context for every log line of a request
|
|
193
|
+
|
|
194
|
+
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.
|
|
195
|
+
|
|
196
|
+
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:
|
|
197
|
+
|
|
198
|
+
```ruby
|
|
199
|
+
# config/initializers/logtail.rb
|
|
200
|
+
Logtail::Integrations::Rack::UserContext.custom_user_hash = lambda do |rack_env|
|
|
201
|
+
user_id = rack_env["rack.session"]["user_id"]
|
|
202
|
+
user = User.find_by(id: user_id) if user_id
|
|
203
|
+
user && { id: user.id, email: user.email }
|
|
204
|
+
end
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
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"]`.
|
|
189
208
|
|
|
190
|
-
|
|
209
|
+
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:
|
|
191
210
|
|
|
192
211
|
```ruby
|
|
193
|
-
|
|
212
|
+
# config/initializers/logtail.rb
|
|
213
|
+
class LogtailTenantContext
|
|
214
|
+
def initialize(app)
|
|
215
|
+
@app = app
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def call(env)
|
|
219
|
+
tenant = env["HTTP_X_TENANT"]
|
|
220
|
+
return @app.call(env) unless tenant
|
|
221
|
+
|
|
222
|
+
Logtail.with_context(tenant: { name: tenant }) { @app.call(env) }
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
Rails.application.config.middleware.insert_before Logtail::Integrations::Rack::HTTPEvents, LogtailTenantContext
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
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`.
|
|
230
|
+
|
|
231
|
+
### Context from a controller
|
|
232
|
+
|
|
233
|
+
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:
|
|
234
|
+
|
|
235
|
+
```ruby
|
|
236
|
+
class ApplicationController < ActionController::Base # or ActionController::API
|
|
194
237
|
around_action :with_logtail_context
|
|
195
238
|
|
|
196
239
|
private
|
|
@@ -202,7 +245,7 @@ class ApplicationController < ActionController::Base
|
|
|
202
245
|
yield
|
|
203
246
|
end
|
|
204
247
|
end
|
|
205
|
-
|
|
248
|
+
|
|
206
249
|
def user_context
|
|
207
250
|
Logtail::Contexts::User.new(
|
|
208
251
|
id: current_user.id,
|
|
@@ -38,13 +38,20 @@ class ExampleController < ApplicationController
|
|
|
38
38
|
)
|
|
39
39
|
|
|
40
40
|
# Since Rails 8.1, you can also use the Rails.event to send events with structured data to Better Stack.
|
|
41
|
-
Rails.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
Rails.event.
|
|
41
|
+
if Rails.respond_to?(:event)
|
|
42
|
+
Rails.event.notify("My first event", user_id: 123, email: "user@example.com")
|
|
43
|
+
|
|
44
|
+
# You can add context to all events
|
|
45
|
+
Rails.event.set_context(request_id: "abc123", shop_id: 456)
|
|
46
|
+
# And tags specific events
|
|
47
|
+
Rails.event.tagged("api") do
|
|
48
|
+
Rails.event.notify("My tagged event with additional context", user_id: 123, email: "user@example.com")
|
|
49
|
+
end
|
|
48
50
|
end
|
|
49
51
|
end
|
|
52
|
+
|
|
53
|
+
# Exceptions raised in an action are sent to Better Stack too, with their class, message and backtrace
|
|
54
|
+
def raise_error
|
|
55
|
+
raise ArgumentError, "Something went wrong, this action raised an exception!"
|
|
56
|
+
end
|
|
50
57
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
<h1>Hello from Logtail</h1>
|
|
2
2
|
<p>Click the button below to send some example logs</p>
|
|
3
|
-
<%= button_to "Let's go!", send_logs_path, method: :get %>
|
|
3
|
+
<%= button_to "Let's go!", send_logs_path, method: :get %>
|
|
4
|
+
<p>Or raise an exception in a controller action</p>
|
|
5
|
+
<%= button_to "Raise an exception", raise_error_path, method: :get %>
|
|
@@ -8,8 +8,8 @@ Bundler.require(*Rails.groups)
|
|
|
8
8
|
|
|
9
9
|
module ExampleProject
|
|
10
10
|
class Application < Rails::Application
|
|
11
|
-
# Initialize configuration defaults
|
|
12
|
-
config.load_defaults
|
|
11
|
+
# Initialize configuration defaults of Rails 7.1, the oldest Rails this example runs on.
|
|
12
|
+
config.load_defaults 7.1
|
|
13
13
|
|
|
14
14
|
# Configuration for the application, engines, and railties goes here.
|
|
15
15
|
#
|
|
@@ -19,9 +19,10 @@ module ExampleProject
|
|
|
19
19
|
# config.time_zone = "Central Time (US & Canada)"
|
|
20
20
|
# config.eager_load_paths << Rails.root.join("extras")
|
|
21
21
|
|
|
22
|
+
# Send the logs to your Better Stack source, its token and ingesting host are read from the environment
|
|
22
23
|
config.logger = Logtail::Logger.create_default_logger(
|
|
23
|
-
"
|
|
24
|
-
ingesting_host: "
|
|
24
|
+
ENV["BETTER_STACK_SOURCE_TOKEN"],
|
|
25
|
+
ingesting_host: ENV["BETTER_STACK_INGESTING_HOST"],
|
|
25
26
|
)
|
|
26
27
|
end
|
|
27
28
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
Rails.application.routes.draw do
|
|
2
2
|
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
|
|
3
3
|
get '/send_logs', to: 'example#send_logs', as: 'send_logs'
|
|
4
|
+
get '/raise_error', to: 'example#raise_error', as: 'raise_error'
|
|
4
5
|
|
|
5
6
|
# Defines the root path route ("/")
|
|
6
7
|
root 'example#index'
|
|
@@ -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
|
@@ -2,6 +2,10 @@ require "logtail-rails/overrides"
|
|
|
2
2
|
|
|
3
3
|
require "logtail"
|
|
4
4
|
|
|
5
|
+
# Rails' own files rely on Active Support being loaded first, and on Rails main (8.2.0.alpha) rails/railtie
|
|
6
|
+
# fails without it: "uninitialized constant ActiveSupport::DescendantsTracker::WeakSet". It comes after logtail,
|
|
7
|
+
# which loads Ruby's logger: older Active Support releases need that with concurrent-ruby 1.3.5 or newer.
|
|
8
|
+
require "active_support"
|
|
5
9
|
require "rails/railtie"
|
|
6
10
|
require "active_record"
|
|
7
11
|
require "rack"
|
|
@@ -50,6 +54,7 @@ module Logtail
|
|
|
50
54
|
ActionView.integrate!
|
|
51
55
|
ActiveRecord.integrate!
|
|
52
56
|
RackLogger.integrate!
|
|
57
|
+
filter_parameters_in_urls(::Rails.application.config.filter_parameters)
|
|
53
58
|
end
|
|
54
59
|
|
|
55
60
|
def self.enabled=(value)
|
|
@@ -71,6 +76,16 @@ module Logtail
|
|
|
71
76
|
@middlewares ||= [Logtail::Integrations::Rack::HTTPContext, SessionContext, Logtail::Integrations::Rack::UserContext,
|
|
72
77
|
Logtail::Integrations::Rack::HTTPEvents, Logtail::Integrations::Rails::ErrorEvent].select(&:enabled?)
|
|
73
78
|
end
|
|
79
|
+
|
|
80
|
+
# Filters the app's filter_parameters from the query strings and the Referer and Location
|
|
81
|
+
# URLs that Rack::HTTPEvents logs, unless the app customised its query_string_filters.
|
|
82
|
+
# Procs are left out: they rewrite values, while query string filters match names.
|
|
83
|
+
def self.filter_parameters_in_urls(filter_parameters)
|
|
84
|
+
http_events = Logtail::Integrations::Rack::HTTPEvents
|
|
85
|
+
return if http_events.query_string_filters != http_events::DEFAULT_QUERY_STRING_FILTERS
|
|
86
|
+
|
|
87
|
+
http_events.query_string_filters = filter_parameters.reject { |filter| filter.is_a?(Proc) }
|
|
88
|
+
end
|
|
74
89
|
end
|
|
75
90
|
end
|
|
76
91
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: logtail-rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Better Stack
|
|
@@ -237,8 +237,16 @@ executables: []
|
|
|
237
237
|
extensions: []
|
|
238
238
|
extra_rdoc_files: []
|
|
239
239
|
files:
|
|
240
|
+
- ".github/actions/cleanup-sources/action.yml"
|
|
241
|
+
- ".github/actions/cleanup-sources/run.sh"
|
|
242
|
+
- ".github/actions/create-source/action.yml"
|
|
243
|
+
- ".github/actions/create-source/run.sh"
|
|
244
|
+
- ".github/actions/verify-logs/action.yml"
|
|
245
|
+
- ".github/actions/verify-logs/run.sh"
|
|
246
|
+
- ".github/workflows/e2e.yml"
|
|
240
247
|
- ".github/workflows/main.yml"
|
|
241
248
|
- ".github/workflows/no-closing-keywords.yml"
|
|
249
|
+
- ".github/workflows/notify_linear_failure.yml"
|
|
242
250
|
- ".github/workflows/release.yml"
|
|
243
251
|
- ".gitignore"
|
|
244
252
|
- ".rspec"
|
|
@@ -246,6 +254,10 @@ files:
|
|
|
246
254
|
- LICENSE.md
|
|
247
255
|
- README.md
|
|
248
256
|
- Rakefile
|
|
257
|
+
- e2e/example.sh
|
|
258
|
+
- e2e/expected.json
|
|
259
|
+
- e2e/installed.rb
|
|
260
|
+
- e2e/plan.sh
|
|
249
261
|
- example-project/.gitattributes
|
|
250
262
|
- example-project/.gitignore
|
|
251
263
|
- example-project/Gemfile
|
|
@@ -336,7 +348,6 @@ files:
|
|
|
336
348
|
- gemfiles/rails-7.2.gemfile
|
|
337
349
|
- gemfiles/rails-8.0.gemfile
|
|
338
350
|
- gemfiles/rails-8.1.gemfile
|
|
339
|
-
- gemfiles/rails-edge.gemfile
|
|
340
351
|
- lib/logtail-rails.rb
|
|
341
352
|
- lib/logtail-rails/action_controller.rb
|
|
342
353
|
- lib/logtail-rails/action_controller/log_subscriber.rb
|
data/gemfiles/rails-edge.gemfile
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
source 'https://rubygems.org'
|
|
2
|
-
|
|
3
|
-
gem 'rails', github: 'rails/rails', branch: 'main'
|
|
4
|
-
gem 'sidekiq', '>= 7.3.0', require: false if RUBY_VERSION >= '2.7.0'
|
|
5
|
-
|
|
6
|
-
gem 'logtail'
|
|
7
|
-
gem 'logtail-rack'
|
|
8
|
-
|
|
9
|
-
if RUBY_PLATFORM == "java"
|
|
10
|
-
gem 'mime-types', '2.6.2'
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
gemspec :path => '../'
|