railswatch_gem 0.4.1 → 0.4.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e813b247ee297eccab4b8fc1e2240142bdd0dfe29a49c95bb6e39e2e7ff38ecd
4
- data.tar.gz: fb805d469b067b639d1e2860f68d0ca51cd2360b544ccce7f9a4b868984fde08
3
+ metadata.gz: 7104283a38fc40386d74cd268ea296724e592374b7a57438387da67f778bd30b
4
+ data.tar.gz: 8965ca361f832aa434723f86d54a2159c2102a12436918ab8577680c2add5ccf
5
5
  SHA512:
6
- metadata.gz: 66833fbfc3fc437c766c07686167d7224c190876d0d9d4f93431db6fb9a11e6d795ea1fb5f73eeec62c44b9c719fb790c033ce800b12fc2ee2bda9024e138ff9
7
- data.tar.gz: 1f4e3dac70f8e457c73f944fd63426d09e908c41537719a85f50dad8a86e777065899066342939f8aa853e3069a87d5a45bbfe6ea40306ad3fbf641605a78928
6
+ metadata.gz: e98665bebe25182bbd7295f4d77f0c9d088f114f11b60dd56de0159fc354ac9a56ffc7167b84b6009fc5fde9e19e18d95583ef9471d146cc6031b5ceb537ac29
7
+ data.tar.gz: a45c68ded7600b4d769b4ed2e538a5de46e11ef72ac207ff725915164fef74ab6b8966fa4a479078d47f0e3cf8aba403cb6823b277023e51ba6d2e829ea37c9c
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailswatchGem
4
+ # OTel SpanProcessor that captures application-level stack traces on database spans.
5
+ #
6
+ # Unlike ActiveSupport::Notifications subscribers (which fire AFTER a span closes),
7
+ # SpanProcessor#on_start runs while the span is still recording, so the attribute
8
+ # lands on the correct span (PG / ActiveRecord) rather than its parent.
9
+ class QuerySourceProcessor < OpenTelemetry::SDK::Trace::SpanProcessor
10
+ MAX_FRAMES = 10
11
+
12
+ DB_SCOPES = %w[
13
+ OpenTelemetry::Instrumentation::PG
14
+ OpenTelemetry::Instrumentation::ActiveRecord
15
+ OpenTelemetry::Instrumentation::Mysql2
16
+ OpenTelemetry::Instrumentation::Trilogy
17
+ ].freeze
18
+
19
+ def initialize(app_root)
20
+ @app_root = app_root.to_s
21
+ @app_prefix = "#{@app_root}/"
22
+ end
23
+
24
+ def on_start(span, _parent_context)
25
+ return unless RailswatchGem.config.track_query_source
26
+ return unless span.recording?
27
+
28
+ scope_name = span.instrumentation_scope&.name.to_s
29
+ return unless DB_SCOPES.include?(scope_name)
30
+
31
+ frames = caller_locations(0, 50)
32
+ .select { |f| f.path.start_with?(@app_root) && !f.path.include?("/vendor/") }
33
+ .first(MAX_FRAMES)
34
+ .map { |f| "#{f.path.delete_prefix(@app_prefix)}:#{f.lineno} in `#{f.label}`" }
35
+
36
+ span.set_attribute("code.stacktrace", frames.join("\n")) if frames.any?
37
+ rescue StandardError
38
+ # Never break the app for instrumentation
39
+ end
40
+
41
+ def on_finish(_span); end
42
+ def shutdown(timeout: nil) = OpenTelemetry::SDK::Trace::Export::SUCCESS
43
+ def force_flush(timeout: nil) = OpenTelemetry::SDK::Trace::Export::SUCCESS
44
+ end
45
+ end
46
+
@@ -2,8 +2,6 @@
2
2
 
3
3
  module RailswatchGem
4
4
  class Railtie < ::Rails::Railtie
5
- MAX_STACKTRACE_FRAMES = 10
6
-
7
5
  config.after_initialize do
8
6
  if RailswatchGem.config.api_key.present?
9
7
  RailswatchGem.start!
@@ -39,28 +37,5 @@ module RailswatchGem
39
37
  end
40
38
  end
41
39
  end
42
-
43
- initializer "railswatch.query_source_location" do
44
- ActiveSupport.on_load(:active_record) do
45
- app_root = Rails.root.to_s
46
-
47
- ActiveSupport::Notifications.subscribe("sql.active_record") do |*, payload|
48
- next unless RailswatchGem.config.enabled
49
- next unless RailswatchGem.config.track_query_source
50
-
51
- span = OpenTelemetry::Trace.current_span
52
- next unless span&.recording?
53
-
54
- frames = caller_locations(0, 40)
55
- .select { |f| f.path.start_with?(app_root) && !f.path.include?("/vendor/") }
56
- .first(MAX_STACKTRACE_FRAMES)
57
- .map { |f| "#{f.path.delete_prefix(app_root + '/')}:#{f.lineno} in `#{f.label}`" }
58
-
59
- span.set_attribute("code.stacktrace", frames.join("\n")) if frames.any?
60
- rescue => e
61
- Rails.logger.debug { "[Railswatch] Query source capture skipped: #{e.message}" } if defined?(Rails.logger)
62
- end
63
- end
64
- end
65
40
  end
66
41
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailswatchGem
4
- VERSION = "0.4.1"
4
+ VERSION = "0.4.2"
5
5
  end
@@ -3,6 +3,7 @@
3
3
  require "railswatch_gem/version"
4
4
  require "railswatch_gem/configuration"
5
5
  require "railswatch_gem/helpers"
6
+ require "railswatch_gem/query_source_processor"
6
7
  require "railswatch_gem/instrumentation/ai_helper"
7
8
  require "railswatch_gem/instrumentation/openai"
8
9
  require "railswatch_gem/instrumentation/anthropic"
@@ -32,9 +33,14 @@ module RailswatchGem
32
33
  ENV['OTEL_EXPORTER_OTLP_PROTOCOL'] = 'http/json'
33
34
  ENV['OTEL_EXPORTER_OTLP_COMPRESSION'] = 'gzip'
34
35
 
36
+ app_root = defined?(::Rails) ? ::Rails.root.to_s : Dir.pwd
37
+
35
38
  OpenTelemetry::SDK.configure do |c|
36
39
  c.service_name = config.service_name
37
40
 
41
+ # Capture application stack traces on DB spans (runs on_start before export)
42
+ c.add_span_processor(QuerySourceProcessor.new(app_root)) if config.track_query_source
43
+
38
44
  c.add_span_processor(
39
45
  OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
40
46
  OpenTelemetry::Exporter::OTLP::Exporter.new(
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: railswatch_gem
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tyler Hammett
@@ -79,6 +79,7 @@ files:
79
79
  - lib/railswatch_gem/instrumentation/ai_helper.rb
80
80
  - lib/railswatch_gem/instrumentation/anthropic.rb
81
81
  - lib/railswatch_gem/instrumentation/openai.rb
82
+ - lib/railswatch_gem/query_source_processor.rb
82
83
  - lib/railswatch_gem/railtie.rb
83
84
  - lib/railswatch_gem/version.rb
84
85
  homepage: https://railswatch.com