closeyourit-ruby 0.3.4 → 0.4.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: fce4d4eb3a92009bf70daeb903091ec30b4274d926b52d5b8df44644a808c35e
4
- data.tar.gz: be83377677c8567c700d8ba5d6a47aa1664474db87aa750ab8a61dbed85fadb2
3
+ metadata.gz: f8d2868773842804dc2c90f163ea4766ea748c59edf8adaa956f257827391e8c
4
+ data.tar.gz: 7fe183c1a6c64226f341b09c7da4cd37bf58bb75409b9afc20c223efc55ffe1f
5
5
  SHA512:
6
- metadata.gz: 4533f36e714d2c8ac1706a8bdce8652beed02384f0ce09f5965679c0d76a5e098b5ed006530bc88bbc4c92ed982ed373bd9dd5d5db04cf015770949deebe3e00
7
- data.tar.gz: 3500e6f71ea42ba8bb900d78dabde73941913e5b03459ab0f2e570b27c26b6637df27a786d5ea1eb6e63b7ffcb8c8b84d07b6299d5553d5711ea3700543449dd
6
+ metadata.gz: fc0267a4f038cd2ab3f6e0af119cacbdb368cdce3f4d07d2a01ea1b8b5f2d2c4f4753fed6d5dcc0b71c5826868c7557eab614d7b0f37f953fd586aff01147c1b
7
+ data.tar.gz: 0b6e57599d21d07d4d2552331aa14d17bed514c7ed0733881d4a7ebd850051ca1271137fac7147f1e2d12c74c69e6e5e265017b256c36abf06ffd6342b458350
data/README.md CHANGED
@@ -26,12 +26,12 @@ Caratteristiche:
26
26
 
27
27
  ## Installazione
28
28
 
29
- Uso interno → install via git o path (no RubyGems):
29
+ Pubblicata su [RubyGems](https://rubygems.org/gems/closeyourit-ruby):
30
30
 
31
31
  ```ruby
32
32
  # Gemfile del progetto da monitorare
33
- gem "closeyourit-ruby", git: "https://github.com/bussolabs/closeyourit-ruby"
34
- # in sviluppo locale:
33
+ gem "closeyourit-ruby"
34
+ # in sviluppo locale, dal checkout:
35
35
  # gem "closeyourit-ruby", path: "../closeyourit-ruby"
36
36
  ```
37
37
 
@@ -20,7 +20,8 @@ module CloseYourIt
20
20
  :slow_query_threshold_ms, :slow_method_threshold_ms,
21
21
  :send_pii, :obfuscate_sql, :send_server_name,
22
22
  :capture_query_bindings, :capture_method_arguments,
23
- :capture_request, :request_header_allowlist,
23
+ :capture_request, :request_header_allowlist, :context_lines,
24
+ :capture_request_body,
24
25
  :breadcrumbs_enabled, :max_breadcrumbs, :sample_rate,
25
26
  :capture_handled_errors, :report_active_job_errors,
26
27
  :logs_enabled, :logs_sample_rate, :logs_batch_size, :logs_flush_interval,
@@ -55,6 +56,14 @@ module CloseYourIt
55
56
  @capture_request = true
56
57
  @request_header_allowlist = DEFAULT_REQUEST_HEADER_ALLOWLIST.dup
57
58
 
59
+ # Righe di sorgente attorno a ogni frame dello stacktrace (pre/context/post). 0 = disattivo.
60
+ @context_lines = 3
61
+
62
+ # Params del body della richiesta nell'evento (`request.data`), estratti LAZY solo quando
63
+ # l'errore accade, sanitizzati e scrubbati (denylist + filter_parameters). Il backend
64
+ # ri-scruba difensivamente. Upload → placeholder, cap 64 KB.
65
+ @capture_request_body = true
66
+
58
67
  # Breadcrumbs: cronologia (query offuscate, eventi custom) allegata all'errore.
59
68
  @breadcrumbs_enabled = true
60
69
  @max_breadcrumbs = 100
@@ -4,6 +4,7 @@ require "securerandom"
4
4
  require "socket"
5
5
  require_relative "../event"
6
6
  require_relative "../scrubber"
7
+ require_relative "../line_cache"
7
8
 
8
9
  module CloseYourIt
9
10
  # Trasforma un'eccezione Ruby nel **payload evento Sentry** che il backend CloseYourIt ingerisce
@@ -72,21 +73,35 @@ module CloseYourIt
72
73
  }
73
74
  end
74
75
 
75
- # Sentry-style: frame più recente per ultimo; chiavi filename/function/lineno/in_app/abs_path.
76
+ # Sentry-style: frame più recente per ultimo; chiavi filename/function/lineno/in_app/abs_path
77
+ # + snippet di sorgente (pre_context/context_line/post_context) quando il file è leggibile.
76
78
  def frames(locations)
77
79
  return [] if locations.nil?
78
80
 
81
+ context_lines = @configuration.context_lines.to_i
79
82
  locations.reverse.map do |loc|
80
- {
83
+ frame = {
81
84
  "filename" => loc.path,
82
85
  "abs_path" => loc.path,
83
86
  "function" => loc.label,
84
87
  "lineno" => loc.lineno,
85
88
  "in_app" => in_app?(loc.path)
86
89
  }
90
+ add_context!(frame, loc.path, loc.lineno, context_lines) if context_lines.positive?
91
+ frame
87
92
  end
88
93
  end
89
94
 
95
+ def add_context!(frame, path, lineno, count)
96
+ lines = LineCache.lines(path)
97
+ return if lines.nil? || lineno.nil? || lineno < 1 || lineno > lines.size
98
+
99
+ index = lineno - 1
100
+ frame["pre_context"] = lines[[ index - count, 0 ].max...index]
101
+ frame["context_line"] = lines[index]
102
+ frame["post_context"] = lines[index + 1, count] || []
103
+ end
104
+
90
105
  def in_app?(path)
91
106
  return false if path.nil?
92
107
 
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CloseYourIt
4
+ # Cache in-process delle righe dei file sorgente, per le context lines dei frame
5
+ # (pre_context/context_line/post_context). Bounded: oltre MAX_FILES si svuota per intero —
6
+ # semplice e sufficiente (i file di un'app in errore sono pochi e ricorrenti). Thread-safe.
7
+ module LineCache
8
+ MAX_FILES = 200
9
+
10
+ @cache = {}
11
+ @mutex = Mutex.new
12
+
13
+ class << self
14
+ # Righe del file (chomp-ate), oppure nil se non leggibile o path sintetico ("(eval)", "(irb)").
15
+ def lines(path)
16
+ return nil if path.nil? || path.empty? || path.start_with?("(")
17
+
18
+ @mutex.synchronize do
19
+ return @cache[path] if @cache.key?(path)
20
+
21
+ @cache.clear if @cache.size >= MAX_FILES
22
+ @cache[path] = read_lines(path)
23
+ end
24
+ end
25
+
26
+ def clear
27
+ @mutex.synchronize { @cache.clear }
28
+ end
29
+
30
+ private
31
+
32
+ def read_lines(path)
33
+ return nil unless File.readable?(path)
34
+
35
+ File.readlines(path, chomp: true)
36
+ rescue StandardError
37
+ nil
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "uri"
5
+ require_relative "../scrubber"
6
+
7
+ module CloseYourIt
8
+ module Rails
9
+ # Estrae i params del body della richiesta (`request.data`) al momento dell'EVENTO — mai
10
+ # eagerly a ogni richiesta (zero overhead sul percorso felice). Preferisce i params già
11
+ # parsati da Rails/Rack presenti in env; ripiega sulla rilettura di rack.input (con rewind)
12
+ # solo per JSON/form ≤ MAX_BODY_BYTES. Output sanitizzato (upload → "[FILE: …]", oggetti →
13
+ # "[OBJECT: …]", stringhe troncate) e scrubbato con la stessa denylist del resto del client.
14
+ module RequestBody
15
+ MAX_BODY_BYTES = 65_536
16
+ MAX_DEPTH = 8
17
+ MAX_STRING = 1024
18
+
19
+ FORM_TYPE = "application/x-www-form-urlencoded"
20
+ JSON_TYPE = "application/json"
21
+
22
+ class << self
23
+ def extract(env)
24
+ params = parsed_params(env) || raw_params(env)
25
+ return nil if params.nil? || params.empty?
26
+
27
+ Scrubber.new(CloseYourIt.configuration).filter_params(sanitize(params, 0))
28
+ rescue StandardError
29
+ nil
30
+ end
31
+
32
+ private
33
+
34
+ # Params già parsati a monte (ActionDispatch o Rack): nessuna rilettura del body.
35
+ def parsed_params(env)
36
+ env["action_dispatch.request.request_parameters"] || env["rack.request.form_hash"]
37
+ end
38
+
39
+ def raw_params(env)
40
+ raw = read_body(env)
41
+ return nil if raw.nil? || raw.empty?
42
+
43
+ case env["CONTENT_TYPE"].to_s.split(";").first.to_s.strip.downcase
44
+ when JSON_TYPE
45
+ value = JSON.parse(raw)
46
+ value.is_a?(Hash) ? value : { "_json" => value }
47
+ when FORM_TYPE
48
+ # Fallback flat (stdlib): il percorso reale con nesting passa dai params già parsati.
49
+ URI.decode_www_form(raw).to_h
50
+ end
51
+ rescue JSON::ParserError, ArgumentError
52
+ nil
53
+ end
54
+
55
+ def read_body(env)
56
+ input = env["rack.input"]
57
+ return nil if input.nil?
58
+
59
+ length = env["CONTENT_LENGTH"].to_i
60
+ return nil if length <= 0 || length > MAX_BODY_BYTES
61
+
62
+ input.rewind if input.respond_to?(:rewind)
63
+ raw = input.read(MAX_BODY_BYTES)
64
+ input.rewind if input.respond_to?(:rewind)
65
+ raw
66
+ end
67
+
68
+ def sanitize(value, depth)
69
+ return "[TRUNCATED]" if depth > MAX_DEPTH
70
+
71
+ case value
72
+ when Hash then value.each_with_object({}) { |(key, val), acc| acc[key.to_s] = sanitize(val, depth + 1) }
73
+ when Array then value.map { |item| sanitize(item, depth + 1) }
74
+ when String then value.size > MAX_STRING ? "#{value[0, MAX_STRING]}…" : value
75
+ when Numeric, true, false, nil then value
76
+ else
77
+ if value.respond_to?(:original_filename)
78
+ "[FILE: #{value.original_filename}]"
79
+ else
80
+ "[OBJECT: #{value.class.name}]"
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -20,7 +20,11 @@ module CloseYourIt
20
20
  if enabled?
21
21
  # trace_id sempre (correlazione log↔errori), anche con capture_request OFF.
22
22
  CloseYourIt::Scope.current.trace_id = trace_id_for(env)
23
- CloseYourIt::Scope.current.request = build_request(env) if CloseYourIt.configuration.capture_request
23
+ if CloseYourIt.configuration.capture_request
24
+ CloseYourIt::Scope.current.request = build_request(env)
25
+ # Riferimento all'env per l'estrazione LAZY del body (request.data) a evento costruito.
26
+ CloseYourIt::Scope.current.rack_env = env
27
+ end
24
28
  end
25
29
  @app.call(env)
26
30
  ensure
@@ -35,7 +35,7 @@ module CloseYourIt
35
35
  end
36
36
  end
37
37
 
38
- attr_accessor :request, :trace_id
38
+ attr_accessor :request, :trace_id, :rack_env
39
39
  attr_reader :user, :tags, :extra, :contexts, :breadcrumbs
40
40
 
41
41
  def initialize
@@ -78,6 +78,7 @@ module CloseYourIt
78
78
  @extra = {}
79
79
  @contexts = {}
80
80
  @request = nil
81
+ @rack_env = nil
81
82
  @trace_id = nil
82
83
  @breadcrumbs = BreadcrumbBuffer.new(CloseYourIt.configuration.max_breadcrumbs)
83
84
  @performance_profile = nil
@@ -93,7 +94,7 @@ module CloseYourIt
93
94
  "tags" => scrub(presence(@tags)),
94
95
  "extra" => scrub(presence(@extra)),
95
96
  "contexts" => scrub(presence(@contexts)),
96
- "request" => @request,
97
+ "request" => request_payload,
97
98
  "breadcrumbs" => breadcrumbs_payload
98
99
  }.reject { |_key, value| value.nil? }
99
100
  end
@@ -109,6 +110,24 @@ module CloseYourIt
109
110
  Scrubber.new(CloseYourIt.configuration).filter_params(hash)
110
111
  end
111
112
 
113
+ # Request context + body params (`request.data`) estratti LAZY qui — cioè solo quando un
114
+ # evento viene davvero costruito, mai sul percorso felice della richiesta.
115
+ def request_payload
116
+ return nil if @request.nil?
117
+
118
+ data = request_body_data
119
+ data ? @request.merge("data" => data) : @request
120
+ end
121
+
122
+ def request_body_data
123
+ return nil unless CloseYourIt.configuration.capture_request_body
124
+ return nil if @rack_env.nil?
125
+
126
+ Rails::RequestBody.extract(@rack_env)
127
+ rescue StandardError
128
+ nil
129
+ end
130
+
112
131
  # `user.id` sempre; email/ip_address/username solo con `send_pii` (il backend li strippa
113
132
  # comunque — difesa in profondità).
114
133
  def serialize_user
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CloseYourIt
4
- VERSION = "0.3.4"
4
+ VERSION = "0.4.0"
5
5
  end
@@ -28,6 +28,7 @@ require_relative "closeyourit/monitor"
28
28
  require_relative "closeyourit/client"
29
29
  require_relative "closeyourit/rails/capture_exceptions"
30
30
  require_relative "closeyourit/rails/request_context"
31
+ require_relative "closeyourit/rails/request_body"
31
32
  require_relative "closeyourit/rails/log_broadcast"
32
33
  require_relative "closeyourit/rails/net_http_patch"
33
34
  require_relative "closeyourit/rails/active_job_extension"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: closeyourit-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.4
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alessio Bussolari
@@ -47,6 +47,7 @@ files:
47
47
  - lib/closeyourit/events/slow_method_event.rb
48
48
  - lib/closeyourit/events/slow_query_event.rb
49
49
  - lib/closeyourit/instrumenter.rb
50
+ - lib/closeyourit/line_cache.rb
50
51
  - lib/closeyourit/log_buffer.rb
51
52
  - lib/closeyourit/log_device.rb
52
53
  - lib/closeyourit/monitor.rb
@@ -59,6 +60,7 @@ files:
59
60
  - lib/closeyourit/rails/net_http_patch.rb
60
61
  - lib/closeyourit/rails/query_source.rb
61
62
  - lib/closeyourit/rails/railtie.rb
63
+ - lib/closeyourit/rails/request_body.rb
62
64
  - lib/closeyourit/rails/request_context.rb
63
65
  - lib/closeyourit/scope.rb
64
66
  - lib/closeyourit/scrubber.rb