tinymonrb 0.1.0 → 0.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 +4 -4
- data/lib/tinymon/client.rb +56 -23
- data/lib/tinymon/event_builder.rb +11 -2
- data/lib/tinymon/rack.rb +56 -0
- data/lib/tinymon/railtie.rb +13 -0
- data/lib/tinymon/scrub.rb +63 -0
- data/lib/tinymon/version.rb +1 -1
- data/lib/tinymonrb.rb +23 -1
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8df6c08b32ccae44e241b41fbd2a98f2033043c37ce6292a2f529869838cb8aa
|
|
4
|
+
data.tar.gz: 8e924831169fbbbbd9c409c49c10a71428eef4dbd48e9b01345f606230e75eb5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 27265f4b71e845ffba1f87f3679f6979e7ec5a0175cdbc3399130a2140a8896178554ce72504159f1469674e288ef6a959f0dc84c293ff20a8a4f6b89cf55345
|
|
7
|
+
data.tar.gz: 0ff34d5f4fa892fb1dc00076ba17746836fd5d2205a4b804ca3cf16ec333e81fb307cdcabbbf0a1b778e1ae8cd8a8ff8392e1affaae381def405c72953af09d1
|
data/lib/tinymon/client.rb
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
24
|
-
|
|
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,50 @@ module Tinymon
|
|
|
40
44
|
end
|
|
41
45
|
|
|
42
46
|
def capture_message(message, level: "info")
|
|
43
|
-
|
|
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
|
+
# Per-event context capture. Use from Rack middleware; do NOT use
|
|
57
|
+
# Tinymon.set_user/set_tag for per-request data — they race under
|
|
58
|
+
# concurrency.
|
|
59
|
+
def capture_exception_with_context(exception, request: nil, user: nil, tags: nil)
|
|
60
|
+
return if rand > @sample_rate
|
|
61
|
+
event = _prepare(exception, ctx: { request: request, user: user, tags: tags })
|
|
62
|
+
return if event.nil?
|
|
63
|
+
@transport.enqueue(event)
|
|
64
|
+
rescue StandardError
|
|
65
|
+
nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def _prepare(exception, ctx: nil)
|
|
44
71
|
snap = SCOPE.snapshot
|
|
72
|
+
ctx ||= {}
|
|
45
73
|
event = EventBuilder.build(
|
|
46
|
-
|
|
74
|
+
exception,
|
|
47
75
|
release: @release,
|
|
48
76
|
environment: @environment,
|
|
49
|
-
user: snap[:user],
|
|
50
|
-
tags: snap[:tags],
|
|
77
|
+
user: ctx[:user] || snap[:user],
|
|
78
|
+
tags: ctx[:tags] || snap[:tags],
|
|
51
79
|
breadcrumbs: snap[:breadcrumbs],
|
|
80
|
+
request: ctx[:request],
|
|
52
81
|
)
|
|
53
|
-
|
|
54
|
-
event
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
82
|
+
# Default scrub BEFORE before_send.
|
|
83
|
+
Scrub.scrub_event(event, scrub_url_query: @scrub_url_query, scrub_patterns: @scrub_patterns)
|
|
84
|
+
# PII gate on the wire — server only attaches IP if true.
|
|
85
|
+
event["send_default_pii"] = !!@send_default_pii
|
|
86
|
+
if @before_send
|
|
87
|
+
event = @before_send.call(event)
|
|
88
|
+
return nil if event.nil?
|
|
89
|
+
end
|
|
90
|
+
event
|
|
58
91
|
end
|
|
59
92
|
end
|
|
60
93
|
end
|
|
@@ -12,7 +12,7 @@ module Tinymon
|
|
|
12
12
|
|
|
13
13
|
SENSITIVE = /password|token|secret|auth|card|cvv|ssn/i.freeze
|
|
14
14
|
|
|
15
|
-
def build(exception, release: nil, environment: nil, user: nil, tags: nil, breadcrumbs: nil, url: nil)
|
|
15
|
+
def build(exception, release: nil, environment: nil, user: nil, tags: nil, breadcrumbs: nil, url: nil, request: nil)
|
|
16
16
|
event = {
|
|
17
17
|
"event_id" => SecureRandom.uuid,
|
|
18
18
|
"timestamp" => Time.now.to_f,
|
|
@@ -30,7 +30,16 @@ module Tinymon
|
|
|
30
30
|
}
|
|
31
31
|
event["release"] = release if release
|
|
32
32
|
event["environment"] = environment if environment
|
|
33
|
-
|
|
33
|
+
if request && (request[:method] || request["method"] || request[:url] || request["url"])
|
|
34
|
+
req = {}
|
|
35
|
+
m = request[:method] || request["method"]
|
|
36
|
+
u = request[:url] || request["url"]
|
|
37
|
+
req["method"] = m if m
|
|
38
|
+
req["url"] = u if u
|
|
39
|
+
event["request"] = req
|
|
40
|
+
elsif url
|
|
41
|
+
event["request"] = { "url" => url }
|
|
42
|
+
end
|
|
34
43
|
event
|
|
35
44
|
end
|
|
36
45
|
|
data/lib/tinymon/rack.rb
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Rack middleware. Insert into ANY Rack app (Rails, Sinatra, Hanami,
|
|
4
|
+
# plain Rack) to capture in-request exceptions. Captures with per-event
|
|
5
|
+
# request context (method + URL) and RE-RAISES so the framework's own
|
|
6
|
+
# error handling still fires.
|
|
7
|
+
#
|
|
8
|
+
# Manual insert:
|
|
9
|
+
# require "tinymonrb"
|
|
10
|
+
# use Tinymon::Rack # Sinatra / plain Rack
|
|
11
|
+
# # Rails:
|
|
12
|
+
# config.middleware.use Tinymon::Rack
|
|
13
|
+
#
|
|
14
|
+
# Or use the Railtie for auto-insert in Rails apps (loaded automatically
|
|
15
|
+
# when Rails is detected at runtime).
|
|
16
|
+
|
|
17
|
+
module Tinymon
|
|
18
|
+
class Rack
|
|
19
|
+
def initialize(app)
|
|
20
|
+
@app = app
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def call(env)
|
|
24
|
+
@app.call(env)
|
|
25
|
+
rescue StandardError, ScriptError => e
|
|
26
|
+
capture(e, env)
|
|
27
|
+
raise
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def capture(exc, env)
|
|
33
|
+
mod = ::Tinymon
|
|
34
|
+
client = mod.instance_variable_get(:@client)
|
|
35
|
+
return unless client
|
|
36
|
+
|
|
37
|
+
url = build_url(env)
|
|
38
|
+
method = env["REQUEST_METHOD"]
|
|
39
|
+
client.capture_exception_with_context(
|
|
40
|
+
exc,
|
|
41
|
+
request: { method: method, url: url }
|
|
42
|
+
)
|
|
43
|
+
rescue StandardError
|
|
44
|
+
# never let the SDK break the request
|
|
45
|
+
nil
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def build_url(env)
|
|
49
|
+
scheme = env["rack.url_scheme"] || "http"
|
|
50
|
+
host = env["HTTP_HOST"] || env["SERVER_NAME"] || ""
|
|
51
|
+
path = env["REQUEST_URI"] ||
|
|
52
|
+
"#{env['SCRIPT_NAME']}#{env['PATH_INFO']}#{env['QUERY_STRING'] && !env['QUERY_STRING'].empty? ? "?#{env['QUERY_STRING']}" : ''}"
|
|
53
|
+
host.empty? ? path : "#{scheme}://#{host}#{path}"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Auto-insert Tinymon::Rack into the Rails middleware stack. Loaded only
|
|
4
|
+
# when Rails is present at require time — no hard Rails dependency.
|
|
5
|
+
require_relative "rack"
|
|
6
|
+
|
|
7
|
+
module Tinymon
|
|
8
|
+
class Railtie < ::Rails::Railtie
|
|
9
|
+
initializer "tinymon.middleware" do |app|
|
|
10
|
+
app.middleware.use Tinymon::Rack
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
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
|
data/lib/tinymon/version.rb
CHANGED
data/lib/tinymonrb.rb
CHANGED
|
@@ -7,6 +7,9 @@ require_relative "tinymon/scope"
|
|
|
7
7
|
require_relative "tinymon/transport"
|
|
8
8
|
require_relative "tinymon/client"
|
|
9
9
|
require_relative "tinymon/integrations"
|
|
10
|
+
require_relative "tinymon/rack"
|
|
11
|
+
# Rails auto-insert — only loaded when Rails is present at require time.
|
|
12
|
+
require_relative "tinymon/railtie" if defined?(::Rails::Railtie)
|
|
10
13
|
|
|
11
14
|
# Public API:
|
|
12
15
|
#
|
|
@@ -23,7 +26,17 @@ module Tinymon
|
|
|
23
26
|
@client = nil
|
|
24
27
|
|
|
25
28
|
class << self
|
|
26
|
-
def init(
|
|
29
|
+
def init(
|
|
30
|
+
dsn:,
|
|
31
|
+
endpoint: nil,
|
|
32
|
+
environment: nil,
|
|
33
|
+
release: nil,
|
|
34
|
+
sample_rate: 1.0,
|
|
35
|
+
before_send: nil,
|
|
36
|
+
scrub_url_query: true,
|
|
37
|
+
scrub_patterns: nil,
|
|
38
|
+
send_default_pii: false
|
|
39
|
+
)
|
|
27
40
|
@client = Client.new(
|
|
28
41
|
dsn: dsn,
|
|
29
42
|
endpoint: endpoint,
|
|
@@ -31,6 +44,9 @@ module Tinymon
|
|
|
31
44
|
release: release,
|
|
32
45
|
sample_rate: sample_rate,
|
|
33
46
|
before_send: before_send,
|
|
47
|
+
scrub_url_query: scrub_url_query,
|
|
48
|
+
scrub_patterns: scrub_patterns,
|
|
49
|
+
send_default_pii: send_default_pii,
|
|
34
50
|
)
|
|
35
51
|
Integrations.install(@client)
|
|
36
52
|
@client
|
|
@@ -40,6 +56,12 @@ module Tinymon
|
|
|
40
56
|
@client&.capture_exception(exception)
|
|
41
57
|
end
|
|
42
58
|
|
|
59
|
+
# Per-event context capture. Use from Rack middleware; do NOT use
|
|
60
|
+
# set_user/set_tag for per-request data (they race under concurrency).
|
|
61
|
+
def capture_exception_with_context(exception, request: nil, user: nil, tags: nil)
|
|
62
|
+
@client&.capture_exception_with_context(exception, request: request, user: user, tags: tags)
|
|
63
|
+
end
|
|
64
|
+
|
|
43
65
|
def capture_message(message, level: "info")
|
|
44
66
|
@client&.capture_message(message, level: level)
|
|
45
67
|
end
|
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.
|
|
4
|
+
version: 0.3.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-
|
|
11
|
+
date: 2026-06-10 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: json_schemer
|
|
@@ -48,7 +48,10 @@ files:
|
|
|
48
48
|
- lib/tinymon/client.rb
|
|
49
49
|
- lib/tinymon/event_builder.rb
|
|
50
50
|
- lib/tinymon/integrations.rb
|
|
51
|
+
- lib/tinymon/rack.rb
|
|
52
|
+
- lib/tinymon/railtie.rb
|
|
51
53
|
- lib/tinymon/scope.rb
|
|
54
|
+
- lib/tinymon/scrub.rb
|
|
52
55
|
- lib/tinymon/stacktrace.rb
|
|
53
56
|
- lib/tinymon/transport.rb
|
|
54
57
|
- lib/tinymon/version.rb
|