koderift-rails 1.3.4
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 +7 -0
- data/CHANGELOG.md +82 -0
- data/LICENSE +21 -0
- data/README.md +54 -0
- data/lib/koderift/rails/configuration.rb +75 -0
- data/lib/koderift/rails/controller.rb +73 -0
- data/lib/koderift/rails/instrumentation.rb +202 -0
- data/lib/koderift/rails/lograge_config.rb +70 -0
- data/lib/koderift/rails/railtie.rb +54 -0
- data/lib/koderift/rails/rum_helper.rb +34 -0
- data/lib/koderift/rails/sidekiq_middleware.rb +79 -0
- data/lib/koderift/rails/version.rb +7 -0
- data/lib/koderift/rails.rb +27 -0
- data/lib/koderift-rails.rb +3 -0
- metadata +146 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 8e49d9d7f56f87dde732a6465a3ced955d8960b692408f6818cf686e30965c8c
|
|
4
|
+
data.tar.gz: 299cb7491b8211baa559aebbca51b7759929891caefe5048d562791431759202
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: e01be29d2c18193b54c04d93ae80ce6bca85d58b8c3c57e92227d5e706c4e033d8d13cb103c285a61d90a19e14431f80eec95e2ace8ed38fe2bbb8f92cd9fabf
|
|
7
|
+
data.tar.gz: b620b59cbe817e063ef7f862be7ed1fc0a36390b6465b4f52b20d59afe51f34de53a47dc72371f7ba14a0079c579a3946c16dbfa1bb3d7cd316c7ad381afe53f
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `koderift-rails` are documented here.
|
|
4
|
+
|
|
5
|
+
This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
Releases before 1.3.4 were installed from git and were never published to
|
|
7
|
+
RubyGems; they are listed for continuity.
|
|
8
|
+
|
|
9
|
+
## [1.3.4] - 2026-08-27
|
|
10
|
+
|
|
11
|
+
First release published to RubyGems.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- An external HTTP call could be recorded twice on a single request, inflating
|
|
15
|
+
the external-call count and the time attributed to it.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- Packaging only: added a LICENSE file, gem metadata (source, changelog and
|
|
19
|
+
issue links, MFA required for publishing) and this changelog. No change to
|
|
20
|
+
what the gem captures or emits.
|
|
21
|
+
|
|
22
|
+
## [1.3.3] - 2026-05-25
|
|
23
|
+
### Added
|
|
24
|
+
- The current Rails environment is emitted with each request, so traces can be
|
|
25
|
+
filtered by environment rather than inferring it from the host.
|
|
26
|
+
|
|
27
|
+
## [1.3.2] - 2026-05-25
|
|
28
|
+
### Changed
|
|
29
|
+
- Improved internal logging.
|
|
30
|
+
|
|
31
|
+
## [1.3.1] - 2026-05-25
|
|
32
|
+
### Added
|
|
33
|
+
- External HTTP calls made from Sidekiq jobs are tracked, not just those made
|
|
34
|
+
during a web request.
|
|
35
|
+
|
|
36
|
+
## [1.3.0] - 2026-05-25
|
|
37
|
+
### Changed
|
|
38
|
+
- Removed the per-request limits on captured spans; the full set is emitted and
|
|
39
|
+
trimmed downstream by the ingest agent.
|
|
40
|
+
|
|
41
|
+
## [1.2.2] - 2026-05-25
|
|
42
|
+
### Fixed
|
|
43
|
+
- Maintenance release.
|
|
44
|
+
|
|
45
|
+
## [1.2.1] - 2026-05-24
|
|
46
|
+
### Fixed
|
|
47
|
+
- Searchkick instrumentation issue reported by a client.
|
|
48
|
+
|
|
49
|
+
## [1.2.0] - 2026-05-24
|
|
50
|
+
### Added
|
|
51
|
+
- Search query instrumentation (`search.searchkick` and
|
|
52
|
+
`multi_search.searchkick`).
|
|
53
|
+
|
|
54
|
+
## [1.0.6] - 2026-05-24
|
|
55
|
+
### Added
|
|
56
|
+
- RUM token support.
|
|
57
|
+
|
|
58
|
+
## [1.0.5] - 2026-05-24
|
|
59
|
+
### Added
|
|
60
|
+
- Browser RUM instrumentation.
|
|
61
|
+
|
|
62
|
+
## [1.0.4] - 2026-05-24
|
|
63
|
+
### Added
|
|
64
|
+
- Distributed tracing: a trace id is propagated across services via the
|
|
65
|
+
`X-Koderift-Trace-ID` header.
|
|
66
|
+
|
|
67
|
+
## [1.0.3] - 2026-05-23
|
|
68
|
+
### Added
|
|
69
|
+
- External HTTP call tracking via a `Net::HTTP` patch.
|
|
70
|
+
|
|
71
|
+
## [1.0.2] - 2026-05-22
|
|
72
|
+
### Fixed
|
|
73
|
+
- Maintenance release.
|
|
74
|
+
|
|
75
|
+
## [1.0.1] - 2025-12-10
|
|
76
|
+
### Fixed
|
|
77
|
+
- Load-order issue when the gem initialised before Lograge.
|
|
78
|
+
|
|
79
|
+
## [1.0.0] - 2025-08-11
|
|
80
|
+
### Added
|
|
81
|
+
- Initial release: request instrumentation, partial and SQL span capture,
|
|
82
|
+
breadcrumbs, and automatic Lograge configuration.
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 Koderift
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# koderift-rails
|
|
2
|
+
|
|
3
|
+
Rails instrumentation gem for [Koderift](https://koderift.com) log analytics.
|
|
4
|
+
|
|
5
|
+
Automatically captures request context, performance data, and error
|
|
6
|
+
information — no manual `ApplicationController` changes needed.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
Requires Ruby >= 2.7 and Rails 7.1-8.x.
|
|
11
|
+
|
|
12
|
+
Add to your `Gemfile`:
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
gem 'koderift-rails', '~> 1.3'
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Run `bundle install`. Lograge is a dependency of this gem and is installed
|
|
19
|
+
with it - you do not need to add it yourself.
|
|
20
|
+
|
|
21
|
+
## Configuration
|
|
22
|
+
|
|
23
|
+
Create `config/initializers/koderift.rb`:
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
Koderift::Rails.configure do |config|
|
|
27
|
+
config.project_token = ENV['KODERIFT_PROJECT_TOKEN']
|
|
28
|
+
|
|
29
|
+
# Optional: customise how the current user is resolved
|
|
30
|
+
# config.current_user = ->(controller) { controller.current_user }
|
|
31
|
+
end
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Set `KODERIFT_PROJECT_TOKEN` in your environment to the token from your
|
|
35
|
+
project's Log Analytics settings page in Koderift.
|
|
36
|
+
|
|
37
|
+
## What it captures
|
|
38
|
+
|
|
39
|
+
* Controller, action, path, method, status, duration
|
|
40
|
+
* DB runtime, view runtime, allocations
|
|
41
|
+
* Current user ID
|
|
42
|
+
* Request ID, IP address, user agent, referer
|
|
43
|
+
* Request params (filtered — passwords, tokens etc removed)
|
|
44
|
+
* Top 5 slowest partials with render times
|
|
45
|
+
* Top 5 slowest SQL queries
|
|
46
|
+
* Last 20 breadcrumbs (ordered partial renders + SQL queries)
|
|
47
|
+
* Exception class, message, and cause chain
|
|
48
|
+
* Stack traces (requires `keep_original_rails_log: true`)
|
|
49
|
+
|
|
50
|
+
## Requirements
|
|
51
|
+
|
|
52
|
+
* Ruby >= 2.7
|
|
53
|
+
* Rails >= 6.1
|
|
54
|
+
* lograge >= 0.11
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Koderift
|
|
4
|
+
module Rails
|
|
5
|
+
class Configuration
|
|
6
|
+
# Required: identifies which project this app reports to.
|
|
7
|
+
# Set from an environment variable in the initializer.
|
|
8
|
+
attr_accessor :project_token
|
|
9
|
+
|
|
10
|
+
# Lambda called with the controller instance to get the current user.
|
|
11
|
+
# Default tries common patterns. Override if your app is different.
|
|
12
|
+
# Example: ->(controller) { controller.current_user }
|
|
13
|
+
attr_accessor :current_user
|
|
14
|
+
|
|
15
|
+
# Parameters to filter from captured request params.
|
|
16
|
+
attr_accessor :filter_params
|
|
17
|
+
|
|
18
|
+
# Maximum number of breadcrumbs to capture per request.
|
|
19
|
+
attr_accessor :max_breadcrumbs
|
|
20
|
+
|
|
21
|
+
# Hosts to exclude from external call tracking (e.g. internal services,
|
|
22
|
+
# localhost, known noise). Always excludes 127.0.0.1 and localhost.
|
|
23
|
+
attr_accessor :external_call_ignore_hosts
|
|
24
|
+
|
|
25
|
+
# Whether to enable the instrumentation. Set to false to disable
|
|
26
|
+
# without removing the gem.
|
|
27
|
+
attr_accessor :enabled
|
|
28
|
+
|
|
29
|
+
# Whether to enable Browser RUM. Set to false for API-only apps.
|
|
30
|
+
# Defaults to true — the meta tag is harmless if rum.js is not loaded.
|
|
31
|
+
attr_accessor :rum_enabled
|
|
32
|
+
|
|
33
|
+
# Fraction of sessions to capture (0.0–1.0). Default 0.1 (10%).
|
|
34
|
+
# Passed to rum.js so sampling happens client-side.
|
|
35
|
+
attr_accessor :rum_sampling_rate
|
|
36
|
+
|
|
37
|
+
# Threshold in milliseconds above which a Sidekiq job is marked 'slow'
|
|
38
|
+
# in the emitted log payload. Default 30_000 (30 seconds).
|
|
39
|
+
attr_accessor :slow_job_threshold_ms
|
|
40
|
+
|
|
41
|
+
def initialize
|
|
42
|
+
@project_token = nil
|
|
43
|
+
@current_user = default_current_user
|
|
44
|
+
@filter_params = %w[
|
|
45
|
+
password password_confirmation
|
|
46
|
+
token secret authenticity_token
|
|
47
|
+
credit_card cvv ssn
|
|
48
|
+
]
|
|
49
|
+
@max_breadcrumbs = 20
|
|
50
|
+
@external_call_ignore_hosts = []
|
|
51
|
+
@enabled = true
|
|
52
|
+
@rum_enabled = true
|
|
53
|
+
@rum_sampling_rate = 0.1
|
|
54
|
+
@slow_job_threshold_ms = 30_000
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def valid?
|
|
58
|
+
project_token.present?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def default_current_user
|
|
64
|
+
lambda do |controller|
|
|
65
|
+
# Try common patterns — override in initializer if different
|
|
66
|
+
if controller.respond_to?(:current_user, true)
|
|
67
|
+
controller.send(:current_user)
|
|
68
|
+
end
|
|
69
|
+
rescue StandardError
|
|
70
|
+
nil
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support/concern'
|
|
4
|
+
require 'securerandom'
|
|
5
|
+
require 'koderift/rails/instrumentation'
|
|
6
|
+
|
|
7
|
+
module Koderift
|
|
8
|
+
module Rails
|
|
9
|
+
module Controller
|
|
10
|
+
extend ActiveSupport::Concern
|
|
11
|
+
|
|
12
|
+
included do
|
|
13
|
+
around_action :koderift_instrument_request
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def koderift_instrument_request
|
|
19
|
+
trace_id = request.headers['X-Koderift-Trace-ID'].presence ||
|
|
20
|
+
SecureRandom.uuid
|
|
21
|
+
|
|
22
|
+
Thread.current[:koderift_trace_id] = trace_id
|
|
23
|
+
request.env['koderift.trace_id'] = trace_id
|
|
24
|
+
|
|
25
|
+
result = Koderift::Rails::Instrumentation.capture do
|
|
26
|
+
yield
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
request.env['koderift.spans'] = result[:spans]
|
|
30
|
+
request.env['koderift.query_count'] = result[:query_count]
|
|
31
|
+
request.env['koderift.partial_count'] = result[:partial_count]
|
|
32
|
+
request.env['koderift.search_count'] = result[:search_count]
|
|
33
|
+
request.env['koderift.breadcrumbs'] = result[:breadcrumbs]
|
|
34
|
+
ensure
|
|
35
|
+
Thread.current[:koderift_trace_id] = nil
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def append_info_to_payload(payload)
|
|
39
|
+
super
|
|
40
|
+
config = Koderift::Rails.configuration
|
|
41
|
+
|
|
42
|
+
user = begin
|
|
43
|
+
config.current_user.call(self)
|
|
44
|
+
rescue StandardError
|
|
45
|
+
nil
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
filtered_params = request.filtered_parameters
|
|
49
|
+
.except('controller', 'action', 'format',
|
|
50
|
+
'utf8', '_method')
|
|
51
|
+
.reject do |k, _|
|
|
52
|
+
config.filter_params.any? do |f|
|
|
53
|
+
k.to_s.match?(/#{Regexp.escape(f)}/i)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
payload[:koderift_user_id] = user&.id
|
|
58
|
+
payload[:koderift_host] = request.host
|
|
59
|
+
payload[:koderift_ip] = request.remote_ip
|
|
60
|
+
payload[:koderift_user_agent] = request.user_agent
|
|
61
|
+
payload[:koderift_request_id] = request.request_id
|
|
62
|
+
payload[:koderift_trace_id] = request.env['koderift.trace_id']
|
|
63
|
+
payload[:koderift_referer] = request.referer
|
|
64
|
+
payload[:koderift_params] = filtered_params.to_h
|
|
65
|
+
payload[:koderift_spans] = request.env['koderift.spans']
|
|
66
|
+
payload[:koderift_query_count] = request.env['koderift.query_count']
|
|
67
|
+
payload[:koderift_partial_count] = request.env['koderift.partial_count']
|
|
68
|
+
payload[:koderift_search_count] = request.env['koderift.search_count']
|
|
69
|
+
payload[:koderift_breadcrumbs] = request.env['koderift.breadcrumbs']
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'time'
|
|
4
|
+
require 'active_support/core_ext/string/filters'
|
|
5
|
+
|
|
6
|
+
module Koderift
|
|
7
|
+
module Rails
|
|
8
|
+
module Instrumentation
|
|
9
|
+
# Subscribes to Rails notifications during a request to capture
|
|
10
|
+
# partial render times, SQL query times, and breadcrumbs.
|
|
11
|
+
# Returns a hash of captured data to be merged into the log payload.
|
|
12
|
+
def self.capture
|
|
13
|
+
partials = []
|
|
14
|
+
queries = []
|
|
15
|
+
search_queries = []
|
|
16
|
+
breadcrumbs = []
|
|
17
|
+
config = Koderift::Rails.configuration
|
|
18
|
+
|
|
19
|
+
partial_sub = ActiveSupport::Notifications.subscribe('render_partial.action_view') do |*args|
|
|
20
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
21
|
+
payload = event.payload
|
|
22
|
+
duration = (payload[:duration] || event.duration).round
|
|
23
|
+
identifier = payload[:identifier].to_s
|
|
24
|
+
.sub(rails_root_prefix, '')
|
|
25
|
+
.sub(%r{\A/app/views/}, '')
|
|
26
|
+
|
|
27
|
+
partials << { partial: identifier, duration_ms: duration }
|
|
28
|
+
breadcrumbs << {
|
|
29
|
+
type: 'render',
|
|
30
|
+
message: identifier,
|
|
31
|
+
duration_ms: duration,
|
|
32
|
+
timestamp: Time.now.utc.iso8601
|
|
33
|
+
}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
query_sub = ActiveSupport::Notifications.subscribe('sql.active_record') do |*args|
|
|
37
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
38
|
+
payload = event.payload
|
|
39
|
+
next if payload[:name].to_s.match?(/\A(CACHE|SCHEMA)/)
|
|
40
|
+
|
|
41
|
+
duration = (payload[:duration] || event.duration).round
|
|
42
|
+
sql = payload[:sql].to_s.squish.truncate(300)
|
|
43
|
+
|
|
44
|
+
queries << { sql: sql, duration_ms: duration }
|
|
45
|
+
breadcrumbs << {
|
|
46
|
+
type: 'sql',
|
|
47
|
+
message: sql,
|
|
48
|
+
duration_ms: duration,
|
|
49
|
+
timestamp: Time.now.utc.iso8601
|
|
50
|
+
}
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Single-model search: fired by Model.search(...)
|
|
54
|
+
# payload: { name: "Admin10 Search", query: {...} }
|
|
55
|
+
search_sub = ActiveSupport::Notifications.subscribe('search.searchkick') do |*args|
|
|
56
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
57
|
+
payload = event.payload
|
|
58
|
+
duration = (payload[:duration] || event.duration).round
|
|
59
|
+
|
|
60
|
+
index = payload[:name].to_s.sub(/\s*Search\z/i, '').strip
|
|
61
|
+
index = 'unknown' if index.blank?
|
|
62
|
+
|
|
63
|
+
search_queries << { index: index, duration_ms: duration }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Multi search: fired by Searchkick.multi_search([...])
|
|
67
|
+
# payload: { name: "Multi Search", body: "..." }
|
|
68
|
+
# Duration covers the entire batch — record as a single entry.
|
|
69
|
+
multi_search_sub = ActiveSupport::Notifications.subscribe('multi_search.searchkick') do |*args|
|
|
70
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
71
|
+
duration = event.duration.round
|
|
72
|
+
|
|
73
|
+
search_queries << { index: 'multi_search', duration_ms: duration }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
external_calls = []
|
|
77
|
+
Thread.current[:koderift_external_calls] = external_calls
|
|
78
|
+
Thread.current[:koderift_external_config] = config
|
|
79
|
+
|
|
80
|
+
begin
|
|
81
|
+
yield
|
|
82
|
+
ensure
|
|
83
|
+
ActiveSupport::Notifications.unsubscribe(partial_sub)
|
|
84
|
+
ActiveSupport::Notifications.unsubscribe(query_sub)
|
|
85
|
+
ActiveSupport::Notifications.unsubscribe(search_sub)
|
|
86
|
+
ActiveSupport::Notifications.unsubscribe(multi_search_sub)
|
|
87
|
+
Thread.current[:koderift_external_calls] = nil
|
|
88
|
+
Thread.current[:koderift_external_config] = nil
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
spans = []
|
|
92
|
+
|
|
93
|
+
partials.each_with_index do |p, i|
|
|
94
|
+
spans << { type: 'partial', name: p[:partial], duration_ms: p[:duration_ms], sequence: i }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
queries.each_with_index do |q, i|
|
|
98
|
+
spans << { type: 'sql', name: q[:sql], duration_ms: q[:duration_ms], sequence: i }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
search_queries.each_with_index do |s, i|
|
|
102
|
+
spans << { type: 'search', name: s[:index], duration_ms: s[:duration_ms], sequence: i }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
external_calls.each_with_index do |c, i|
|
|
106
|
+
spans << {
|
|
107
|
+
type: 'http',
|
|
108
|
+
name: "#{c[:host]}#{c[:endpoint]}",
|
|
109
|
+
duration_ms: c[:duration_ms],
|
|
110
|
+
detail: { method: c[:method], status: c[:status], trace_id: c[:trace_id] }.compact.to_json,
|
|
111
|
+
sequence: i
|
|
112
|
+
}
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
{
|
|
116
|
+
spans: spans,
|
|
117
|
+
query_count: queries.size,
|
|
118
|
+
partial_count: partials.size,
|
|
119
|
+
search_count: search_queries.size,
|
|
120
|
+
breadcrumbs: breadcrumbs.last(config.max_breadcrumbs)
|
|
121
|
+
}
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def self.rails_root_prefix
|
|
125
|
+
defined?(::Rails) && ::Rails.respond_to?(:root) && ::Rails.root ? ::Rails.root.to_s : ''
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Installed once when the gem loads (see the prepend at the bottom of this
|
|
130
|
+
# file). Writes to the thread-local external_calls array set by
|
|
131
|
+
# Instrumentation.capture; outside of a capture block it is a pass-through.
|
|
132
|
+
#
|
|
133
|
+
# CONTRACT: exactly one entry per LOGICAL outbound call. Net::HTTP#request
|
|
134
|
+
# re-enters itself whenever it is called on a session that is not started:
|
|
135
|
+
#
|
|
136
|
+
# def request(req, body = nil, &block)
|
|
137
|
+
# unless started?
|
|
138
|
+
# start { req['connection'] ||= 'close'; return request(req, body, &block) }
|
|
139
|
+
# end
|
|
140
|
+
#
|
|
141
|
+
# (net-http-0.9.1 lib/net/http.rb:2399-2405). Both frames run this prepended
|
|
142
|
+
# patch, so one call was recorded twice. That branch also forces
|
|
143
|
+
# `Connection: close` and start's ensure closes the socket, so an affected
|
|
144
|
+
# call site double-counts on EVERY call, not intermittently.
|
|
145
|
+
module NetHttpPatch
|
|
146
|
+
def request(req, *args, &block)
|
|
147
|
+
calls = Thread.current[:koderift_external_calls]
|
|
148
|
+
config = Thread.current[:koderift_external_config]
|
|
149
|
+
trace_id = Thread.current[:koderift_trace_id]
|
|
150
|
+
|
|
151
|
+
req['X-Koderift-Trace-ID'] = trace_id if trace_id
|
|
152
|
+
|
|
153
|
+
# Nothing below this line may run before the pass-through return: the
|
|
154
|
+
# spec stand-in defines only #request, and the header injection must
|
|
155
|
+
# still happen when instrumentation is not capturing.
|
|
156
|
+
return super(req, *args, &block) unless calls && config
|
|
157
|
+
|
|
158
|
+
# The stdlib recursion re-invokes with the SAME req object, so request
|
|
159
|
+
# identity separates "my own re-entry" from a genuinely different call
|
|
160
|
+
# issued while this one is in flight. Do NOT use `started?` (it keeps
|
|
161
|
+
# the inner frame, which cannot see connection setup, and the stand-in
|
|
162
|
+
# does not define it) and do NOT use an object ivar (one Net::HTTP
|
|
163
|
+
# shared across threads would silently lose most of its spans).
|
|
164
|
+
in_flight = (Thread.current[:koderift_http_in_flight] ||= [])
|
|
165
|
+
return super(req, *args, &block) if in_flight.any? { |r| r.equal?(req) }
|
|
166
|
+
|
|
167
|
+
in_flight << req
|
|
168
|
+
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
169
|
+
begin
|
|
170
|
+
response = super(req, *args, &block)
|
|
171
|
+
ensure
|
|
172
|
+
# Strictly LIFO: the inner frame returns above without pushing, and
|
|
173
|
+
# push/pop are paired in one begin/ensure. The ensure also covers a
|
|
174
|
+
# raise (Net::OpenTimeout, ECONNREFUSED, a raising response block) --
|
|
175
|
+
# without it one failure would silence every later call on this thread.
|
|
176
|
+
in_flight.pop
|
|
177
|
+
end
|
|
178
|
+
duration = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).round
|
|
179
|
+
|
|
180
|
+
host = address.to_s
|
|
181
|
+
ignored = ['127.0.0.1', 'localhost', '[::1]'].include?(host) ||
|
|
182
|
+
config.external_call_ignore_hosts.any? { |h| host.include?(h) }
|
|
183
|
+
|
|
184
|
+
unless ignored
|
|
185
|
+
raw_path = req.path.to_s.split('?').first.to_s
|
|
186
|
+
segments = raw_path.split('/').reject(&:empty?).first(2)
|
|
187
|
+
endpoint = segments.empty? ? '/' : '/' + segments.join('/')
|
|
188
|
+
|
|
189
|
+
calls << {
|
|
190
|
+
host: host, endpoint: endpoint, method: req.method.to_s.upcase,
|
|
191
|
+
status: response.code.to_i, duration_ms: duration, trace_id: trace_id
|
|
192
|
+
}
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
response
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
require 'net/http'
|
|
202
|
+
Net::HTTP.prepend(Koderift::Rails::NetHttpPatch)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require 'lograge'
|
|
5
|
+
rescue LoadError
|
|
6
|
+
# lograge not available — instrumentation will be skipped
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
module Koderift
|
|
10
|
+
module Rails
|
|
11
|
+
module LogrageConfig
|
|
12
|
+
MAX_FRAMES = 50
|
|
13
|
+
LEADING_FRAMES = 10
|
|
14
|
+
|
|
15
|
+
def self.apply(app)
|
|
16
|
+
return unless defined?(Lograge)
|
|
17
|
+
|
|
18
|
+
app.config.lograge.enabled = true
|
|
19
|
+
app.config.lograge.keep_original_rails_log = true
|
|
20
|
+
app.config.lograge.formatter = Lograge::Formatters::Json.new
|
|
21
|
+
|
|
22
|
+
app.config.lograge.custom_options = lambda do |event|
|
|
23
|
+
exceptions = event.payload[:exception_object]
|
|
24
|
+
|
|
25
|
+
{
|
|
26
|
+
user_id: event.payload[:koderift_user_id],
|
|
27
|
+
host: event.payload[:koderift_host],
|
|
28
|
+
db: event.payload[:db_runtime]&.round(2),
|
|
29
|
+
exception: event.payload[:exception]&.join(': '),
|
|
30
|
+
request_id: event.payload[:koderift_request_id],
|
|
31
|
+
trace_id: event.payload[:koderift_trace_id],
|
|
32
|
+
ip: event.payload[:koderift_ip],
|
|
33
|
+
user_agent: event.payload[:koderift_user_agent],
|
|
34
|
+
allocations: event.payload[:allocations],
|
|
35
|
+
error_cause: exceptions&.cause&.message,
|
|
36
|
+
stack_trace: format_backtrace(exceptions),
|
|
37
|
+
referer: event.payload[:koderift_referer],
|
|
38
|
+
breadcrumbs: event.payload[:koderift_breadcrumbs],
|
|
39
|
+
params: event.payload[:koderift_params],
|
|
40
|
+
spans: event.payload[:koderift_spans],
|
|
41
|
+
query_count: event.payload[:koderift_query_count],
|
|
42
|
+
partial_count: event.payload[:koderift_partial_count],
|
|
43
|
+
search_count: event.payload[:koderift_search_count]
|
|
44
|
+
}.compact
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.format_backtrace(exception)
|
|
49
|
+
return nil unless exception&.backtrace&.any?
|
|
50
|
+
|
|
51
|
+
backtrace = exception.backtrace
|
|
52
|
+
|
|
53
|
+
leading = backtrace.first(LEADING_FRAMES)
|
|
54
|
+
|
|
55
|
+
rest = backtrace[LEADING_FRAMES..]&.select { |l| l.include?('/app/') } || []
|
|
56
|
+
|
|
57
|
+
frames = (leading + rest).first(MAX_FRAMES)
|
|
58
|
+
|
|
59
|
+
if frames.size < MAX_FRAMES
|
|
60
|
+
non_app_rest = backtrace[LEADING_FRAMES..]&.reject { |l| l.include?('/app/') } || []
|
|
61
|
+
frames = (frames + non_app_rest).first(MAX_FRAMES)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
frames.join("\n").presence
|
|
65
|
+
rescue StandardError
|
|
66
|
+
nil
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails/railtie'
|
|
4
|
+
|
|
5
|
+
module Koderift
|
|
6
|
+
module Rails
|
|
7
|
+
class Railtie < ::Rails::Railtie
|
|
8
|
+
# Configure Lograge after the app initializers have run
|
|
9
|
+
# (so the user's initializer sets up Koderift.configure first)
|
|
10
|
+
initializer 'koderift.configure_lograge', after: :load_config_initializers do |app|
|
|
11
|
+
next unless Koderift::Rails.configuration.enabled
|
|
12
|
+
|
|
13
|
+
require 'koderift/rails/lograge_config'
|
|
14
|
+
Koderift::Rails::LogrageConfig.apply(app)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Include the controller concern into ActionController::Base
|
|
18
|
+
initializer 'koderift.include_controller', after: :load_config_initializers do
|
|
19
|
+
next unless Koderift::Rails.configuration.enabled
|
|
20
|
+
|
|
21
|
+
ActiveSupport.on_load(:action_controller_base) do
|
|
22
|
+
require 'koderift/rails/controller'
|
|
23
|
+
include Koderift::Rails::Controller
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Make koderift_rum_meta_tag available in all views
|
|
28
|
+
initializer 'koderift.helpers', after: :load_config_initializers do
|
|
29
|
+
next unless Koderift::Rails.configuration.enabled
|
|
30
|
+
|
|
31
|
+
ActiveSupport.on_load(:action_view) do
|
|
32
|
+
require 'koderift/rails/rum_helper'
|
|
33
|
+
include Koderift::Rails::RumHelper
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Register Sidekiq server middleware lazily when Sidekiq is present.
|
|
38
|
+
initializer 'koderift.sidekiq_middleware', after: :load_config_initializers do
|
|
39
|
+
next unless Koderift::Rails.configuration.enabled
|
|
40
|
+
|
|
41
|
+
ActiveSupport.on_load(:after_initialize) do
|
|
42
|
+
if defined?(Sidekiq) && Sidekiq.respond_to?(:configure_server)
|
|
43
|
+
require 'koderift/rails/sidekiq_middleware'
|
|
44
|
+
Sidekiq.configure_server do |sidekiq_config|
|
|
45
|
+
sidekiq_config.server_middleware do |chain|
|
|
46
|
+
chain.add Koderift::Rails::SidekiqMiddleware
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Koderift
|
|
4
|
+
module Rails
|
|
5
|
+
module RumHelper
|
|
6
|
+
# Renders the meta tag that rum.js reads for initialisation.
|
|
7
|
+
# Place once inside <head> in your application layout.
|
|
8
|
+
#
|
|
9
|
+
# Example:
|
|
10
|
+
# <%= koderift_rum_meta_tag %>
|
|
11
|
+
#
|
|
12
|
+
# Renders:
|
|
13
|
+
# <meta name="koderift-rum-token"
|
|
14
|
+
# content="abc123"
|
|
15
|
+
# data-rum-sampling-rate="0.1"
|
|
16
|
+
# data-environment="production">
|
|
17
|
+
#
|
|
18
|
+
# Returns an empty string when RUM is disabled or no token is configured.
|
|
19
|
+
def koderift_rum_meta_tag
|
|
20
|
+
config = Koderift::Rails.configuration
|
|
21
|
+
return ''.html_safe unless config.rum_enabled && config.project_token.present?
|
|
22
|
+
|
|
23
|
+
tag.meta(
|
|
24
|
+
name: 'koderift-rum-token',
|
|
25
|
+
content: config.project_token,
|
|
26
|
+
data: {
|
|
27
|
+
rum_sampling_rate: config.rum_sampling_rate.to_f,
|
|
28
|
+
environment: (defined?(::Rails) && ::Rails.respond_to?(:env) ? ::Rails.env.to_s : 'production')
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'securerandom'
|
|
5
|
+
require 'koderift/rails/instrumentation'
|
|
6
|
+
|
|
7
|
+
module Koderift
|
|
8
|
+
module Rails
|
|
9
|
+
# Sidekiq server middleware that wraps each job in Instrumentation.capture
|
|
10
|
+
# and writes a single JSON line to Rails.logger. The koderift-agent picks
|
|
11
|
+
# this up via the existing Rails log tailer — the "job_class" key
|
|
12
|
+
# distinguishes it from Lograge request lines.
|
|
13
|
+
class SidekiqMiddleware
|
|
14
|
+
def call(worker, job, queue)
|
|
15
|
+
config = Koderift::Rails.configuration
|
|
16
|
+
return yield unless config.enabled && config.project_token.present?
|
|
17
|
+
|
|
18
|
+
trace_id = job['koderift_trace_id'].to_s
|
|
19
|
+
trace_id = SecureRandom.uuid if trace_id.empty?
|
|
20
|
+
Thread.current[:koderift_trace_id] = trace_id
|
|
21
|
+
|
|
22
|
+
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
23
|
+
|
|
24
|
+
begin
|
|
25
|
+
result = Instrumentation.capture { yield }
|
|
26
|
+
duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).round
|
|
27
|
+
emit_job_log(worker, job, queue, result, duration_ms, trace_id, error: nil)
|
|
28
|
+
result
|
|
29
|
+
rescue StandardError => e
|
|
30
|
+
duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).round
|
|
31
|
+
empty_result = { spans: [], query_count: 0, partial_count: 0,
|
|
32
|
+
search_count: 0, breadcrumbs: [] }
|
|
33
|
+
emit_job_log(worker, job, queue, empty_result, duration_ms, trace_id, error: e)
|
|
34
|
+
raise
|
|
35
|
+
ensure
|
|
36
|
+
Thread.current[:koderift_trace_id] = nil
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def emit_job_log(_worker, job, queue, result, duration_ms, trace_id, error:)
|
|
43
|
+
config = Koderift::Rails.configuration
|
|
44
|
+
|
|
45
|
+
level = if error
|
|
46
|
+
'error'
|
|
47
|
+
elsif duration_ms >= config.slow_job_threshold_ms
|
|
48
|
+
'slow'
|
|
49
|
+
else
|
|
50
|
+
'info'
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
payload = {
|
|
54
|
+
job_class: job['class'].to_s,
|
|
55
|
+
jid: job['jid'].to_s,
|
|
56
|
+
queue: queue.to_s,
|
|
57
|
+
status: error ? 500 : 200,
|
|
58
|
+
duration: duration_ms,
|
|
59
|
+
level: level,
|
|
60
|
+
trace_id: trace_id,
|
|
61
|
+
spans: result[:spans],
|
|
62
|
+
query_count: result[:query_count],
|
|
63
|
+
search_count: result[:search_count],
|
|
64
|
+
breadcrumbs: result[:breadcrumbs]
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if error
|
|
68
|
+
payload[:exception] = "#{error.class}: #{error.message}"
|
|
69
|
+
bt = error.backtrace&.first(20)&.join("\n")
|
|
70
|
+
payload[:stack_trace] = bt if bt && !bt.empty?
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
::Rails.logger.info(payload.compact.to_json)
|
|
74
|
+
rescue StandardError => e
|
|
75
|
+
::Rails.logger.warn("[KoderiftSidekiqMiddleware] emit failed: #{e.message}")
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'koderift/rails/version'
|
|
4
|
+
require 'koderift/rails/configuration'
|
|
5
|
+
require 'koderift/rails/instrumentation'
|
|
6
|
+
require 'koderift/rails/lograge_config'
|
|
7
|
+
require 'koderift/rails/controller'
|
|
8
|
+
require 'koderift/rails/rum_helper'
|
|
9
|
+
require 'koderift/rails/railtie' if defined?(::Rails::Railtie)
|
|
10
|
+
|
|
11
|
+
module Koderift
|
|
12
|
+
module Rails
|
|
13
|
+
class << self
|
|
14
|
+
def configuration
|
|
15
|
+
@configuration ||= Configuration.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def configure
|
|
19
|
+
yield configuration
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def reset!
|
|
23
|
+
@configuration = Configuration.new
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: koderift-rails
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.3.4
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Koderift
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: railties
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '7.1'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '9'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '7.1'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '9'
|
|
32
|
+
- !ruby/object:Gem::Dependency
|
|
33
|
+
name: lograge
|
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - ">="
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '0.11'
|
|
39
|
+
- - "<"
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: '2'
|
|
42
|
+
type: :runtime
|
|
43
|
+
prerelease: false
|
|
44
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - ">="
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '0.11'
|
|
49
|
+
- - "<"
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: '2'
|
|
52
|
+
- !ruby/object:Gem::Dependency
|
|
53
|
+
name: rspec
|
|
54
|
+
requirement: !ruby/object:Gem::Requirement
|
|
55
|
+
requirements:
|
|
56
|
+
- - "~>"
|
|
57
|
+
- !ruby/object:Gem::Version
|
|
58
|
+
version: '3.12'
|
|
59
|
+
type: :development
|
|
60
|
+
prerelease: false
|
|
61
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
62
|
+
requirements:
|
|
63
|
+
- - "~>"
|
|
64
|
+
- !ruby/object:Gem::Version
|
|
65
|
+
version: '3.12'
|
|
66
|
+
- !ruby/object:Gem::Dependency
|
|
67
|
+
name: rspec-rails
|
|
68
|
+
requirement: !ruby/object:Gem::Requirement
|
|
69
|
+
requirements:
|
|
70
|
+
- - "~>"
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: '7.1'
|
|
73
|
+
type: :development
|
|
74
|
+
prerelease: false
|
|
75
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
76
|
+
requirements:
|
|
77
|
+
- - "~>"
|
|
78
|
+
- !ruby/object:Gem::Version
|
|
79
|
+
version: '7.1'
|
|
80
|
+
- !ruby/object:Gem::Dependency
|
|
81
|
+
name: rails
|
|
82
|
+
requirement: !ruby/object:Gem::Requirement
|
|
83
|
+
requirements:
|
|
84
|
+
- - ">="
|
|
85
|
+
- !ruby/object:Gem::Version
|
|
86
|
+
version: '7.1'
|
|
87
|
+
- - "<"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '9'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - ">="
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '7.1'
|
|
97
|
+
- - "<"
|
|
98
|
+
- !ruby/object:Gem::Version
|
|
99
|
+
version: '9'
|
|
100
|
+
description: Automatically captures request context, performance data, and error information
|
|
101
|
+
for Koderift log analytics.
|
|
102
|
+
email:
|
|
103
|
+
- support@koderift.com
|
|
104
|
+
executables: []
|
|
105
|
+
extensions: []
|
|
106
|
+
extra_rdoc_files: []
|
|
107
|
+
files:
|
|
108
|
+
- CHANGELOG.md
|
|
109
|
+
- LICENSE
|
|
110
|
+
- README.md
|
|
111
|
+
- lib/koderift-rails.rb
|
|
112
|
+
- lib/koderift/rails.rb
|
|
113
|
+
- lib/koderift/rails/configuration.rb
|
|
114
|
+
- lib/koderift/rails/controller.rb
|
|
115
|
+
- lib/koderift/rails/instrumentation.rb
|
|
116
|
+
- lib/koderift/rails/lograge_config.rb
|
|
117
|
+
- lib/koderift/rails/railtie.rb
|
|
118
|
+
- lib/koderift/rails/rum_helper.rb
|
|
119
|
+
- lib/koderift/rails/sidekiq_middleware.rb
|
|
120
|
+
- lib/koderift/rails/version.rb
|
|
121
|
+
homepage: https://github.com/koderift/koderift-rails
|
|
122
|
+
licenses:
|
|
123
|
+
- MIT
|
|
124
|
+
metadata:
|
|
125
|
+
source_code_uri: https://github.com/koderift/koderift-rails
|
|
126
|
+
changelog_uri: https://github.com/koderift/koderift-rails/blob/main/CHANGELOG.md
|
|
127
|
+
bug_tracker_uri: https://github.com/koderift/koderift-rails/issues
|
|
128
|
+
rubygems_mfa_required: 'true'
|
|
129
|
+
rdoc_options: []
|
|
130
|
+
require_paths:
|
|
131
|
+
- lib
|
|
132
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
133
|
+
requirements:
|
|
134
|
+
- - ">="
|
|
135
|
+
- !ruby/object:Gem::Version
|
|
136
|
+
version: '2.7'
|
|
137
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
138
|
+
requirements:
|
|
139
|
+
- - ">="
|
|
140
|
+
- !ruby/object:Gem::Version
|
|
141
|
+
version: '0'
|
|
142
|
+
requirements: []
|
|
143
|
+
rubygems_version: 3.6.9
|
|
144
|
+
specification_version: 4
|
|
145
|
+
summary: Rails instrumentation for Koderift log analytics
|
|
146
|
+
test_files: []
|