tinymonrb 0.1.0 → 0.2.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: c33d3e807a92d0f19e975e8d384e2a6a9e78ed22e62c4f2fdf5a5c2e3ff953c7
4
- data.tar.gz: 667a99ab8a3b6d3e08780c56e99ccbadbf0c748cd3c2524dfd8ecc78e8911b6b
3
+ metadata.gz: 2fb61a62ef1e259893c449ab46dbbbe6ba67f300a45551cb093e4598adeac193
4
+ data.tar.gz: 12f762661bba46acbbce593f7558e5dbc38ed1a2b181c9ed14e334bacc47a3de
5
5
  SHA512:
6
- metadata.gz: '085fdbe62bc6c876ee0c15d40de22f7645141064d7f12cd9035789d2684bfcf3f19f7c1eeb0e0a830311df6eaaa219d3018997b0a0fb96576147a63fe0cd455d'
7
- data.tar.gz: 13543ba9734ac10fc3a9ccda8b9c456d7d39e57314449f7583ab4dc48572c9d40051d0bd8f4cfe62d3660f74b23b3dcef5834fac20a464c709685eefd2f9af78
6
+ metadata.gz: 53ef44902a951ef0f01c463f94d8ebe841b35718b9a40ffe071e8ed559e34c939a6012e05e7a7b78aab0fcda7704a35aed814ffd982283a22a61633335ff97e4
7
+ data.tar.gz: cf09570386509420ff6952856d6366aa15d304699fc6b8d4692a877acb74f59d49933ca4d5d5154a7db24632c857f09272828324ad8dae40c8fb893b2f2ed9e7
@@ -2,37 +2,41 @@
2
2
 
3
3
  require_relative "event_builder"
4
4
  require_relative "scope"
5
+ require_relative "scrub"
5
6
  require_relative "transport"
6
7
 
7
8
  module Tinymon
8
9
  class Client
9
10
  DEFAULT_ENDPOINT = "https://console.tinymon.dev/api/ingest"
10
11
 
11
- def initialize(dsn:, endpoint: nil, environment: nil, release: nil, sample_rate: 1.0, before_send: nil)
12
+ def initialize(
13
+ dsn:,
14
+ endpoint: nil,
15
+ environment: nil,
16
+ release: nil,
17
+ sample_rate: 1.0,
18
+ before_send: nil,
19
+ # Privacy controls — defaults are privacy-preserving.
20
+ scrub_url_query: true,
21
+ scrub_patterns: nil,
22
+ send_default_pii: false
23
+ )
12
24
  @dsn = dsn
13
25
  @endpoint = endpoint || DEFAULT_ENDPOINT
14
26
  @environment = environment
15
27
  @release = release
16
28
  @sample_rate = sample_rate
17
29
  @before_send = before_send
30
+ @scrub_url_query = scrub_url_query
31
+ @scrub_patterns = scrub_patterns
32
+ @send_default_pii = send_default_pii
18
33
  @transport = Transport.new(@endpoint, dsn)
19
34
  end
20
35
 
21
36
  def capture_exception(exception)
22
37
  return if rand > @sample_rate
23
- snap = SCOPE.snapshot
24
- event = EventBuilder.build(
25
- exception,
26
- release: @release,
27
- environment: @environment,
28
- user: snap[:user],
29
- tags: snap[:tags],
30
- breadcrumbs: snap[:breadcrumbs],
31
- )
32
- if @before_send
33
- event = @before_send.call(event)
34
- return if event.nil?
35
- end
38
+ event = _prepare(exception)
39
+ return if event.nil?
36
40
  @transport.enqueue(event)
37
41
  rescue StandardError
38
42
  # SWALLOW. The SDK must never throw into the host app.
@@ -40,21 +44,36 @@ module Tinymon
40
44
  end
41
45
 
42
46
  def capture_message(message, level: "info")
43
- synthetic = StandardError.new(message)
47
+ event = _prepare(StandardError.new(message))
48
+ return if event.nil?
49
+ event["level"] = level
50
+ event["exception"]["type"] = "Message"
51
+ @transport.enqueue(event)
52
+ rescue StandardError
53
+ nil
54
+ end
55
+
56
+ private
57
+
58
+ def _prepare(exception)
44
59
  snap = SCOPE.snapshot
45
60
  event = EventBuilder.build(
46
- synthetic,
61
+ exception,
47
62
  release: @release,
48
63
  environment: @environment,
49
64
  user: snap[:user],
50
65
  tags: snap[:tags],
51
66
  breadcrumbs: snap[:breadcrumbs],
52
67
  )
53
- event["level"] = level
54
- event["exception"]["type"] = "Message"
55
- @transport.enqueue(event)
56
- rescue StandardError
57
- nil
68
+ # Default scrub BEFORE before_send.
69
+ Scrub.scrub_event(event, scrub_url_query: @scrub_url_query, scrub_patterns: @scrub_patterns)
70
+ # PII gate on the wire — server only attaches IP if true.
71
+ event["send_default_pii"] = !!@send_default_pii
72
+ if @before_send
73
+ event = @before_send.call(event)
74
+ return nil if event.nil?
75
+ end
76
+ event
58
77
  end
59
78
  end
60
79
  end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Default PII scrub. Identical semantics to the JS SDK and the server.
4
+ # Runs BEFORE before_send so user hooks can both relax and tighten the defaults.
5
+ module Tinymon
6
+ module Scrub
7
+ SENSITIVE_TAG_KEY = /password|token|secret|auth|card|cvv|ssn/i.freeze
8
+
9
+ DEFAULT_PATTERNS = [
10
+ /\b[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}\b/, # email
11
+ /\b(?:\d[ \-]?){12,15}\d\b/, # card-like
12
+ /\b[Bb]earer\s+[A-Za-z0-9._\-]+/, # bearer
13
+ /\bAuthorization\s*:\s*\S+/ # auth header
14
+ ].freeze
15
+
16
+ module_function
17
+
18
+ def scrub_string(s, patterns)
19
+ out = s.to_s.dup
20
+ patterns.each { |re| out.gsub!(re, "[redacted]") }
21
+ out
22
+ end
23
+
24
+ def strip_url_query(url)
25
+ return url if url.nil? || url.empty?
26
+ i = url.index(/[?#]/)
27
+ i.nil? ? url : url[0...i]
28
+ end
29
+
30
+ # Mutate the event hash in place. Returns it for chaining.
31
+ def scrub_event(event, scrub_url_query: true, scrub_patterns: nil)
32
+ patterns = DEFAULT_PATTERNS + Array(scrub_patterns)
33
+
34
+ if scrub_url_query && (req = event[:request] || event["request"])
35
+ url_key = req.key?(:url) ? :url : "url"
36
+ req[url_key] = strip_url_query(req[url_key]) if req[url_key]
37
+ end
38
+
39
+ if (exc = event[:exception] || event["exception"])
40
+ val_key = exc.key?(:value) ? :value : "value"
41
+ exc[val_key] = scrub_string(exc[val_key], patterns) if exc[val_key]
42
+ end
43
+
44
+ Array(event[:breadcrumbs] || event["breadcrumbs"]).each do |b|
45
+ msg_key = b.key?(:message) ? :message : "message"
46
+ b[msg_key] = scrub_string(b[msg_key], patterns) if b[msg_key]
47
+ end
48
+
49
+ tags = event[:tags] || event["tags"]
50
+ if tags
51
+ cleaned = {}
52
+ tags.each { |k, v| cleaned[k] = SENSITIVE_TAG_KEY.match?(k.to_s) ? "[redacted]" : v }
53
+ if event.key?(:tags)
54
+ event[:tags] = cleaned
55
+ else
56
+ event["tags"] = cleaned
57
+ end
58
+ end
59
+
60
+ event
61
+ end
62
+ end
63
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tinymon
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  SDK_NAME = "tinymon.ruby"
6
6
  end
data/lib/tinymonrb.rb CHANGED
@@ -23,7 +23,17 @@ module Tinymon
23
23
  @client = nil
24
24
 
25
25
  class << self
26
- def init(dsn:, endpoint: nil, environment: nil, release: nil, sample_rate: 1.0, before_send: nil)
26
+ def init(
27
+ dsn:,
28
+ endpoint: nil,
29
+ environment: nil,
30
+ release: nil,
31
+ sample_rate: 1.0,
32
+ before_send: nil,
33
+ scrub_url_query: true,
34
+ scrub_patterns: nil,
35
+ send_default_pii: false
36
+ )
27
37
  @client = Client.new(
28
38
  dsn: dsn,
29
39
  endpoint: endpoint,
@@ -31,6 +41,9 @@ module Tinymon
31
41
  release: release,
32
42
  sample_rate: sample_rate,
33
43
  before_send: before_send,
44
+ scrub_url_query: scrub_url_query,
45
+ scrub_patterns: scrub_patterns,
46
+ send_default_pii: send_default_pii,
34
47
  )
35
48
  Integrations.install(@client)
36
49
  @client
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tinymonrb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - tinymon
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-06-06 00:00:00.000000000 Z
11
+ date: 2026-06-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json_schemer
@@ -49,6 +49,7 @@ files:
49
49
  - lib/tinymon/event_builder.rb
50
50
  - lib/tinymon/integrations.rb
51
51
  - lib/tinymon/scope.rb
52
+ - lib/tinymon/scrub.rb
52
53
  - lib/tinymon/stacktrace.rb
53
54
  - lib/tinymon/transport.rb
54
55
  - lib/tinymon/version.rb