miniapm 1.1.0 → 1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d9e86e9f297b739658918cd083749603dc9b051f9155a3468f59dde33b8ce908
4
- data.tar.gz: 55d5fb852f0c5cfc244e3344a4e97a1de2adffa2e4c2f19fb0c01617ec639d17
3
+ metadata.gz: c449015f1ca45ec37a611645ce5e220f33eb59cfcc2e1d5d33a703ee5acd37b7
4
+ data.tar.gz: 415f6d83da9ef99c12b104ac5c23265585c47758176894734622903ed327ad60
5
5
  SHA512:
6
- metadata.gz: afc6919e18594a95b783024a82ae597a8f199ce51427e846ca937224257642443a0ebdcfb6974cd55764d8179b4d4dd298846614b0d9b24817b90307caac0eea
7
- data.tar.gz: 74ab8984fba2107643fd85106e655917836b670f869ff5caae4a272b021176e71b77d35efe9291c17dd29a101c1c34d0cbc867179bacd87cdb3472da3aaae609
6
+ metadata.gz: 6de6e509faec967f0da90e233823b6ecf325a5f4e3b6d3d248569906f9310fcd07718cb3af3795bf9b454315b1f3c131fcf66031d12c6f8135c4a478fa08ae3d
7
+ data.tar.gz: 2a82ad3cab7be447ed2be3531eb862c0803ba1757bd34a7fd0f2d2874fd4d8e23e16a90f8a974460fa79232bde8327e5d91048f0410f8208f3214743dd1f63b5
@@ -138,7 +138,7 @@ module MiniAPM
138
138
  class InstrumentationConfig
139
139
  DEFAULTS = {
140
140
  rails: { enabled: true },
141
- activerecord: { enabled: true, log_sql: false },
141
+ activerecord: { enabled: true, log_sql: true },
142
142
  activejob: { enabled: true },
143
143
  sidekiq: { enabled: true },
144
144
  cache: { enabled: true },
@@ -7,7 +7,10 @@ module MiniAPM
7
7
  class ErrorEvent
8
8
  attr_reader :exception_class, :message, :backtrace, :fingerprint
9
9
  attr_reader :request_id, :user_id, :params, :timestamp
10
- attr_reader :context
10
+ attr_reader :context, :source_context
11
+
12
+ # Number of lines to include before and after the error line
13
+ CONTEXT_LINES = 5
11
14
 
12
15
  def initialize(
13
16
  exception_class:,
@@ -29,6 +32,7 @@ module MiniAPM
29
32
  @params = filter_params(params)
30
33
  @timestamp = timestamp || Time.now.utc
31
34
  @context = context
35
+ @source_context = extract_source_context
32
36
  end
33
37
 
34
38
  def self.from_exception(exception, context = {})
@@ -44,7 +48,7 @@ module MiniAPM
44
48
  end
45
49
 
46
50
  def to_h
47
- {
51
+ hash = {
48
52
  exception_class: @exception_class,
49
53
  message: @message,
50
54
  backtrace: @backtrace,
@@ -53,7 +57,9 @@ module MiniAPM
53
57
  user_id: @user_id,
54
58
  params: @params,
55
59
  timestamp: @timestamp.iso8601
56
- }.compact
60
+ }
61
+ hash[:source_context] = @source_context if @source_context
62
+ hash.compact
57
63
  end
58
64
 
59
65
  private
@@ -126,5 +132,60 @@ module MiniAPM
126
132
  string = string.to_s
127
133
  string.length > max_length ? string[0, max_length] + "..." : string
128
134
  end
135
+
136
+ def extract_source_context
137
+ # Find first application backtrace line (not gem/stdlib)
138
+ app_line = @backtrace.find do |line|
139
+ !line.include?("/gems/") &&
140
+ !line.include?("/ruby/") &&
141
+ !line.include?("/vendor/") &&
142
+ !line.include?("/bundle/") &&
143
+ !line.start_with?("<")
144
+ end
145
+
146
+ return nil unless app_line
147
+
148
+ # Parse file:line from backtrace line
149
+ # Format: "/path/to/file.rb:123:in `method_name'" or "/path/to/file.rb:123"
150
+ match = app_line.match(/\A(.+?):(\d+)/)
151
+ return nil unless match
152
+
153
+ file_path = match[1]
154
+ lineno = match[2].to_i
155
+
156
+ # Handle relative paths (e.g., "app/controllers/...") by prepending Rails.root
157
+ unless file_path.start_with?("/")
158
+ if defined?(Rails) && Rails.respond_to?(:root) && Rails.root
159
+ file_path = File.join(Rails.root.to_s, file_path)
160
+ else
161
+ return nil
162
+ end
163
+ end
164
+
165
+ return nil unless File.exist?(file_path) && File.readable?(file_path)
166
+
167
+ begin
168
+ lines = File.readlines(file_path)
169
+ return nil if lineno < 1 || lineno > lines.length
170
+
171
+ # Calculate context range (0-indexed)
172
+ start_line = [lineno - CONTEXT_LINES - 1, 0].max
173
+ end_line = [lineno + CONTEXT_LINES - 1, lines.length - 1].min
174
+
175
+ pre_context = lines[start_line...lineno - 1].map(&:chomp)
176
+ context_line = lines[lineno - 1]&.chomp || ""
177
+ post_context = lines[lineno..end_line].map(&:chomp)
178
+
179
+ {
180
+ file: file_path,
181
+ lineno: lineno,
182
+ pre_context: pre_context,
183
+ context_line: context_line,
184
+ post_context: post_context
185
+ }
186
+ rescue StandardError
187
+ nil
188
+ end
189
+ end
129
190
  end
130
191
  end
@@ -43,7 +43,7 @@ module MiniAPM
43
43
 
44
44
  attributes["db.sql.table"] = table if table
45
45
 
46
- # Optionally log SQL (configurable, defaults to off)
46
+ # Log SQL (configurable, defaults to on)
47
47
  if MiniAPM.configuration.instrumentations.options(:activerecord)[:log_sql]
48
48
  attributes["db.statement"] = truncate_sql(sql)
49
49
  end
@@ -4,32 +4,88 @@ module MiniAPM
4
4
  module Instrumentations
5
5
  module Rails
6
6
  class Controller < Base
7
+ # Subscriber class for view rendering that tracks span context properly
8
+ class ViewSubscriber
9
+ def initialize(type)
10
+ @type = type
11
+ @spans = {}
12
+ end
13
+
14
+ def start(name, id, payload)
15
+ return unless MiniAPM.enabled?
16
+ return unless Context.current_trace
17
+
18
+ template = payload[:identifier] || payload[:virtual_path] || "unknown"
19
+
20
+ # Clean up template path
21
+ if defined?(::Rails.root) && ::Rails.root
22
+ template = template.sub(::Rails.root.to_s + "/", "")
23
+ end
24
+
25
+ template_name = File.basename(template)
26
+
27
+ attributes = {
28
+ "rails.template" => template,
29
+ "rails.template.type" => @type
30
+ }
31
+
32
+ attributes["rails.layout"] = payload[:layout] if payload[:layout]
33
+ attributes["rails.collection.count"] = payload[:count] if payload[:count]
34
+
35
+ span = Span.new(
36
+ name: "#{@type} #{template_name}",
37
+ category: :view,
38
+ trace_id: Context.current_trace_id,
39
+ parent_span_id: Context.current_span&.span_id,
40
+ attributes: attributes
41
+ )
42
+
43
+ # Store span by event ID and push to context
44
+ @spans[id] = span
45
+ Context.push_span(span)
46
+ end
47
+
48
+ def finish(name, id, payload)
49
+ span = @spans.delete(id)
50
+ return unless span
51
+
52
+ # Pop from context and finish
53
+ Context.pop_span
54
+ span.finish
55
+ MiniAPM.record_span(span)
56
+ end
57
+ end
58
+
7
59
  class << self
8
60
  def install!
9
61
  return if installed?
10
62
  mark_installed!
11
63
 
12
- # Subscribe to controller processing
64
+ # Subscribe to controller processing (still uses standard subscribe)
13
65
  subscribe("process_action.action_controller") do |event|
14
66
  handle_process_action(event)
15
67
  end
16
68
 
17
- # Subscribe to view rendering
18
- subscribe("render_template.action_view") do |event|
19
- handle_render_template(event)
20
- end
69
+ # Subscribe to view rendering with monotonic subscribers for proper nesting
70
+ ActiveSupport::Notifications.monotonic_subscribe(
71
+ "render_template.action_view",
72
+ ViewSubscriber.new("render_template")
73
+ )
21
74
 
22
- subscribe("render_partial.action_view") do |event|
23
- handle_render_partial(event)
24
- end
75
+ ActiveSupport::Notifications.monotonic_subscribe(
76
+ "render_partial.action_view",
77
+ ViewSubscriber.new("render_partial")
78
+ )
25
79
 
26
- subscribe("render_collection.action_view") do |event|
27
- handle_render_collection(event)
28
- end
80
+ ActiveSupport::Notifications.monotonic_subscribe(
81
+ "render_collection.action_view",
82
+ ViewSubscriber.new("render_collection")
83
+ )
29
84
 
30
- subscribe("render_layout.action_view") do |event|
31
- handle_render_layout(event)
32
- end
85
+ ActiveSupport::Notifications.monotonic_subscribe(
86
+ "render_layout.action_view",
87
+ ViewSubscriber.new("render_layout")
88
+ )
33
89
  end
34
90
 
35
91
  private
@@ -64,60 +120,16 @@ module MiniAPM
64
120
  # Record exception if present
65
121
  if payload[:exception_object]
66
122
  span.record_exception(payload[:exception_object])
67
- end
68
- end
69
-
70
- def handle_render_template(event)
71
- record_view_span("render_template", event)
72
- end
73
-
74
- def handle_render_partial(event)
75
- record_view_span("render_partial", event)
76
- end
77
-
78
- def handle_render_collection(event)
79
- record_view_span("render_collection", event)
80
- end
81
123
 
82
- def handle_render_layout(event)
83
- record_view_span("render_layout", event)
84
- end
85
-
86
- def record_view_span(type, event)
87
- return unless MiniAPM.enabled?
88
- return unless Context.current_trace
89
-
90
- payload = event.payload
91
- template = payload[:identifier] || payload[:virtual_path] || "unknown"
92
-
93
- # Clean up template path
94
- if defined?(::Rails.root) && ::Rails.root
95
- template = template.sub(::Rails.root.to_s + "/", "")
124
+ # Also send to dedicated errors endpoint (with source context)
125
+ MiniAPM.record_error(
126
+ payload[:exception_object],
127
+ context: {
128
+ request_id: payload[:request]&.request_id,
129
+ params: payload[:params]
130
+ }
131
+ )
96
132
  end
97
-
98
- template_name = File.basename(template)
99
-
100
- attributes = {
101
- "rails.template" => template,
102
- "rails.template.type" => type
103
- }
104
-
105
- if payload[:layout]
106
- attributes["rails.layout"] = payload[:layout]
107
- end
108
-
109
- if payload[:count]
110
- attributes["rails.collection.count"] = payload[:count]
111
- end
112
-
113
- span = create_span_from_event(
114
- event,
115
- name: "#{type} #{template_name}",
116
- category: :view,
117
- attributes: attributes
118
- )
119
-
120
- record_span(span)
121
133
  end
122
134
  end
123
135
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MiniAPM
4
- VERSION = "1.1.0"
4
+ VERSION = "1.3.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: miniapm
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Hasinski