tinymonrb 0.3.0 → 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 +4 -4
- data/lib/tinymon/client.rb +11 -3
- data/lib/tinymon/transport.rb +99 -34
- data/lib/tinymon/version.rb +1 -1
- data/lib/tinymon.rb +5 -62
- data/lib/tinymonrb.rb +6 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 721bdc7f477b8281a78914201aadf4367fe02206248735b91dc6c564459c4050
|
|
4
|
+
data.tar.gz: 5b119d9cd4b65d8cef7648b779be6a84f93ffca1c78c7fd0560e63529ca31560
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 911fac08481f040b354966c3baf7d14db96d9a5deab26b86bd077b432c430eca3dcda7c9dc84e0fc600efd344c4c8354c31137651e3b22cada233e976b142a47
|
|
7
|
+
data.tar.gz: b3cf2caffb694ba16700b9f69d758d2f025d87bae3a4c1849bc10d0899118905a096082b79a29aa87f23b786b69ed48f82fb7231c419c3b2d02bf40e8ef032a2
|
data/lib/tinymon/client.rb
CHANGED
|
@@ -37,7 +37,7 @@ module Tinymon
|
|
|
37
37
|
return if rand > @sample_rate
|
|
38
38
|
event = _prepare(exception)
|
|
39
39
|
return if event.nil?
|
|
40
|
-
@transport.
|
|
40
|
+
@transport.send_event(event)
|
|
41
41
|
rescue StandardError
|
|
42
42
|
# SWALLOW. The SDK must never throw into the host app.
|
|
43
43
|
nil
|
|
@@ -48,7 +48,7 @@ module Tinymon
|
|
|
48
48
|
return if event.nil?
|
|
49
49
|
event["level"] = level
|
|
50
50
|
event["exception"]["type"] = "Message"
|
|
51
|
-
@transport.
|
|
51
|
+
@transport.send_event(event)
|
|
52
52
|
rescue StandardError
|
|
53
53
|
nil
|
|
54
54
|
end
|
|
@@ -60,7 +60,15 @@ module Tinymon
|
|
|
60
60
|
return if rand > @sample_rate
|
|
61
61
|
event = _prepare(exception, ctx: { request: request, user: user, tags: tags })
|
|
62
62
|
return if event.nil?
|
|
63
|
-
@transport.
|
|
63
|
+
@transport.send_event(event)
|
|
64
|
+
rescue StandardError
|
|
65
|
+
nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Block until queued events are delivered (or timeout). Call before a
|
|
69
|
+
# short-lived process exits so events aren't dropped.
|
|
70
|
+
def flush(timeout = 2.0)
|
|
71
|
+
@transport.flush(timeout)
|
|
64
72
|
rescue StandardError
|
|
65
73
|
nil
|
|
66
74
|
end
|
data/lib/tinymon/transport.rb
CHANGED
|
@@ -5,46 +5,55 @@ require "net/http"
|
|
|
5
5
|
require "uri"
|
|
6
6
|
|
|
7
7
|
module Tinymon
|
|
8
|
-
#
|
|
9
|
-
#
|
|
8
|
+
# HTTP transport. stdlib-only. Sends each event immediately on a background
|
|
9
|
+
# worker thread; events are only queued when the network fails, and that
|
|
10
|
+
# retry queue is drained with exponential backoff. This matches how
|
|
11
|
+
# production error SDKs behave — the error lands in the dashboard now, not
|
|
12
|
+
# on a 5-second timer. at_exit drains anything still pending on shutdown.
|
|
10
13
|
class Transport
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
MAX_QUEUE = 30 # cap on retry buffer (dead-network memory guard)
|
|
15
|
+
REQUEST_TIMEOUT = 5 # seconds
|
|
16
|
+
BACKOFF_START = 1.0 # seconds
|
|
17
|
+
BACKOFF_MAX = 30.0 # seconds
|
|
15
18
|
|
|
16
19
|
def initialize(endpoint, dsn)
|
|
17
20
|
@endpoint = endpoint
|
|
18
21
|
@dsn = dsn
|
|
19
|
-
@
|
|
22
|
+
@pending = [] # fresh events — sent immediately
|
|
23
|
+
@retry = [] # failed events — retried with backoff
|
|
20
24
|
@mutex = Mutex.new
|
|
25
|
+
@cond = ConditionVariable.new
|
|
26
|
+
@backoff = BACKOFF_START
|
|
21
27
|
@stop = false
|
|
22
28
|
@thread = Thread.new { run_loop }
|
|
23
29
|
@thread.name = "tinymon-transport" if @thread.respond_to?(:name=)
|
|
24
30
|
at_exit { shutdown }
|
|
25
31
|
end
|
|
26
32
|
|
|
27
|
-
|
|
28
|
-
|
|
33
|
+
# Queue for immediate send on the worker thread (non-blocking).
|
|
34
|
+
def send_event(event)
|
|
29
35
|
@mutex.synchronize do
|
|
30
|
-
@
|
|
31
|
-
@
|
|
32
|
-
should_flush = @queue.length >= MAX_BATCH
|
|
36
|
+
@pending.push(event)
|
|
37
|
+
@cond.signal
|
|
33
38
|
end
|
|
34
|
-
flush if should_flush
|
|
35
39
|
end
|
|
36
40
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
# Block until pending + retry events are sent, or timeout elapses.
|
|
42
|
+
def flush(timeout = 2.0)
|
|
43
|
+
deadline = monotonic + timeout
|
|
44
|
+
loop do
|
|
45
|
+
empty = @mutex.synchronize { @pending.empty? && @retry.empty? }
|
|
46
|
+
return if empty
|
|
47
|
+
break if monotonic >= deadline
|
|
48
|
+
@mutex.synchronize { @cond.signal }
|
|
49
|
+
sleep(0.02)
|
|
41
50
|
end
|
|
42
|
-
return if batch.nil? || batch.empty?
|
|
43
|
-
batch.each { |event| send_one(event) }
|
|
44
51
|
end
|
|
45
52
|
|
|
46
53
|
private
|
|
47
54
|
|
|
55
|
+
# Returns true on success OR an unrecoverable 4xx (retrying a client bug
|
|
56
|
+
# won't help); false on 5xx / network failure.
|
|
48
57
|
def send_one(event)
|
|
49
58
|
uri = URI.parse(@endpoint)
|
|
50
59
|
http = Net::HTTP.new(uri.host, uri.port)
|
|
@@ -57,33 +66,89 @@ module Tinymon
|
|
|
57
66
|
})
|
|
58
67
|
req.body = JSON.dump(event)
|
|
59
68
|
begin
|
|
60
|
-
http.request(req)
|
|
69
|
+
res = http.request(req)
|
|
70
|
+
code = res.code.to_i
|
|
71
|
+
return true if code >= 200 && code < 300
|
|
72
|
+
return true if code >= 400 && code < 500 # client bug — drop, don't retry
|
|
73
|
+
false
|
|
61
74
|
rescue StandardError
|
|
62
|
-
|
|
63
|
-
@mutex.synchronize do
|
|
64
|
-
@queue.unshift(event) if @queue.length < MAX_QUEUE
|
|
65
|
-
end
|
|
75
|
+
false
|
|
66
76
|
end
|
|
67
77
|
end
|
|
68
78
|
|
|
69
79
|
def run_loop
|
|
70
80
|
until @stop
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
81
|
+
event = next_pending
|
|
82
|
+
if event
|
|
83
|
+
queue_retry(event) unless send_one(event)
|
|
84
|
+
next
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
next if drain_retry
|
|
88
|
+
|
|
89
|
+
# Idle — wait for new work or the next retry window.
|
|
90
|
+
@mutex.synchronize do
|
|
91
|
+
wait = @retry.empty? ? nil : @backoff
|
|
92
|
+
@cond.wait(@mutex, wait) unless @stop
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def next_pending
|
|
98
|
+
@mutex.synchronize { @pending.shift }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def queue_retry(event)
|
|
102
|
+
@mutex.synchronize do
|
|
103
|
+
@retry.shift while @retry.length >= MAX_QUEUE
|
|
104
|
+
@retry.push(event)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def drain_retry
|
|
109
|
+
batch = @mutex.synchronize do
|
|
110
|
+
next nil if @retry.empty?
|
|
111
|
+
b = @retry.dup
|
|
112
|
+
@retry.clear
|
|
113
|
+
b
|
|
114
|
+
end
|
|
115
|
+
return false if batch.nil?
|
|
116
|
+
|
|
117
|
+
any_failed = false
|
|
118
|
+
batch.each do |event|
|
|
119
|
+
unless send_one(event)
|
|
120
|
+
any_failed = true
|
|
121
|
+
queue_retry(event)
|
|
76
122
|
end
|
|
77
123
|
end
|
|
124
|
+
if any_failed
|
|
125
|
+
@backoff = [@backoff * 2, BACKOFF_MAX].min
|
|
126
|
+
sleep(@backoff)
|
|
127
|
+
else
|
|
128
|
+
@backoff = BACKOFF_START
|
|
129
|
+
end
|
|
130
|
+
true
|
|
78
131
|
end
|
|
79
132
|
|
|
80
133
|
def shutdown
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
134
|
+
# Best-effort: deliver anything still pending before exit.
|
|
135
|
+
deadline = monotonic + 2.0
|
|
136
|
+
while monotonic < deadline
|
|
137
|
+
event = next_pending
|
|
138
|
+
event ||= @mutex.synchronize { @retry.shift }
|
|
139
|
+
break if event.nil?
|
|
140
|
+
send_one(event)
|
|
141
|
+
end
|
|
142
|
+
@mutex.synchronize do
|
|
143
|
+
@stop = true
|
|
144
|
+
@cond.broadcast
|
|
86
145
|
end
|
|
146
|
+
rescue StandardError
|
|
147
|
+
nil
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def monotonic
|
|
151
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
87
152
|
end
|
|
88
153
|
end
|
|
89
154
|
end
|
data/lib/tinymon/version.rb
CHANGED
data/lib/tinymon.rb
CHANGED
|
@@ -1,64 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
require_relative "
|
|
8
|
-
require_relative "tinymon/client"
|
|
9
|
-
require_relative "tinymon/integrations"
|
|
10
|
-
|
|
11
|
-
# Public API:
|
|
12
|
-
#
|
|
13
|
-
# require "tinymon"
|
|
14
|
-
# Tinymon.init(dsn: "tm_pub_xxx", environment: "production", release: "1.0.0")
|
|
15
|
-
#
|
|
16
|
-
# begin
|
|
17
|
-
# risky_thing
|
|
18
|
-
# rescue => e
|
|
19
|
-
# Tinymon.capture_exception(e)
|
|
20
|
-
# end
|
|
21
|
-
#
|
|
22
|
-
module Tinymon
|
|
23
|
-
@client = nil
|
|
24
|
-
|
|
25
|
-
class << self
|
|
26
|
-
def init(dsn:, endpoint: nil, environment: nil, release: nil, sample_rate: 1.0, before_send: nil)
|
|
27
|
-
@client = Client.new(
|
|
28
|
-
dsn: dsn,
|
|
29
|
-
endpoint: endpoint,
|
|
30
|
-
environment: environment,
|
|
31
|
-
release: release,
|
|
32
|
-
sample_rate: sample_rate,
|
|
33
|
-
before_send: before_send,
|
|
34
|
-
)
|
|
35
|
-
Integrations.install(@client)
|
|
36
|
-
@client
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def capture_exception(exception)
|
|
40
|
-
@client&.capture_exception(exception)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def capture_message(message, level: "info")
|
|
44
|
-
@client&.capture_message(message, level: level)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def set_user(user)
|
|
48
|
-
SCOPE.set_user(user)
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def set_tag(key, value)
|
|
52
|
-
SCOPE.set_tag(key, value)
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def add_breadcrumb(crumb)
|
|
56
|
-
SCOPE.add_breadcrumb(crumb)
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
# Exposed for the cross-language contract test in test/test_contract.rb.
|
|
60
|
-
def _build_event(exception, **kwargs)
|
|
61
|
-
EventBuilder.build(exception, **kwargs)
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
end
|
|
3
|
+
# Back-compat shim. The gem is published as `tinymonrb`, so the canonical
|
|
4
|
+
# entry point is `require "tinymonrb"`. This file only exists so that older
|
|
5
|
+
# code doing `require "tinymon"` keeps working — it loads the full SDK
|
|
6
|
+
# (context capture, Rack middleware, Rails railtie) rather than a partial copy.
|
|
7
|
+
require_relative "tinymonrb"
|
data/lib/tinymonrb.rb
CHANGED
|
@@ -66,6 +66,12 @@ module Tinymon
|
|
|
66
66
|
@client&.capture_message(message, level: level)
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
+
# Block until queued events are delivered (or timeout). Call before a
|
|
70
|
+
# short-lived process exits so events aren't dropped.
|
|
71
|
+
def flush(timeout = 2.0)
|
|
72
|
+
@client&.flush(timeout)
|
|
73
|
+
end
|
|
74
|
+
|
|
69
75
|
def set_user(user)
|
|
70
76
|
SCOPE.set_user(user)
|
|
71
77
|
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.4.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-11 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: json_schemer
|