@boshyxd/rosentry 0.1.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.
@@ -0,0 +1,120 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local Queue
3
+ do
4
+ Queue = setmetatable({}, {
5
+ __tostring = function()
6
+ return "Queue"
7
+ end,
8
+ })
9
+ Queue.__index = Queue
10
+ function Queue.new(...)
11
+ local self = setmetatable({}, Queue)
12
+ return self:constructor(...) or self
13
+ end
14
+ function Queue:constructor(config, transport)
15
+ self._config = config
16
+ self._transport = transport
17
+ self._queue = {}
18
+ self._lastFlush = tick()
19
+ self._flushLoopRunning = false
20
+ end
21
+ function Queue:_debug(...)
22
+ local args = { ... }
23
+ if self._config:get("debug") then
24
+ print("[RoSentry:Queue]", unpack(args))
25
+ end
26
+ end
27
+ function Queue:_shouldSample()
28
+ local sampleRate = self._config:get("sampleRate")
29
+ return math.random() <= sampleRate
30
+ end
31
+ function Queue:push(errorData)
32
+ if not self._config:isEnabled() then
33
+ return false
34
+ end
35
+ if not self:_shouldSample() then
36
+ self:_debug("Event dropped due to sampling")
37
+ return false
38
+ end
39
+ local beforeSend = self._config:get("beforeSend")
40
+ if beforeSend then
41
+ local result = beforeSend(errorData)
42
+ if result == nil then
43
+ self:_debug("Event dropped by beforeSend hook")
44
+ return false
45
+ end
46
+ errorData = result
47
+ end
48
+ local __queue = self._queue
49
+ local _errorData = errorData
50
+ table.insert(__queue, _errorData)
51
+ self:_debug("Event queued, queue size:", #self._queue)
52
+ local maxQueueSize = self._config:get("maxQueueSize")
53
+ local flushInterval = self._config:get("flushInterval")
54
+ if #self._queue >= maxQueueSize or (tick() - self._lastFlush) >= flushInterval then
55
+ self:flush()
56
+ end
57
+ return true
58
+ end
59
+ function Queue:flush()
60
+ if #self._queue == 0 then
61
+ return nil
62
+ end
63
+ local maxQueueSize = self._config:get("maxQueueSize")
64
+ local errors = {}
65
+ local count = math.min(#self._queue, maxQueueSize)
66
+ do
67
+ local i = 0
68
+ local _shouldIncrement = false
69
+ while true do
70
+ if _shouldIncrement then
71
+ i += 1
72
+ else
73
+ _shouldIncrement = true
74
+ end
75
+ if not (i < count) then
76
+ break
77
+ end
78
+ local item = table.remove(self._queue, 1)
79
+ if item ~= nil then
80
+ table.insert(errors, item)
81
+ end
82
+ end
83
+ end
84
+ self:_debug("Flushing", #errors, "errors")
85
+ self._transport:sendAsync({
86
+ errors = errors,
87
+ })
88
+ self._lastFlush = tick()
89
+ end
90
+ function Queue:startFlushLoop()
91
+ if self._flushLoopRunning then
92
+ return nil
93
+ end
94
+ self._flushLoopRunning = true
95
+ task.spawn(function()
96
+ while self._flushLoopRunning do
97
+ local flushInterval = self._config:get("flushInterval")
98
+ task.wait(flushInterval)
99
+ if #self._queue > 0 then
100
+ self:flush()
101
+ end
102
+ end
103
+ end)
104
+ self:_debug("Flush loop started")
105
+ end
106
+ function Queue:stopFlushLoop()
107
+ self._flushLoopRunning = false
108
+ self:_debug("Flush loop stopped")
109
+ end
110
+ function Queue:getSize()
111
+ return #self._queue
112
+ end
113
+ function Queue:clear()
114
+ self._queue = {}
115
+ self:_debug("Queue cleared")
116
+ end
117
+ end
118
+ return {
119
+ Queue = Queue,
120
+ }
@@ -0,0 +1,25 @@
1
+ import { Breadcrumb, UserContext } from "../types";
2
+ import { Config } from "./Config";
3
+ export declare class Scope {
4
+ private _config;
5
+ private _user?;
6
+ private _breadcrumbs;
7
+ private _tags;
8
+ private _extras;
9
+ constructor(config: Config);
10
+ private _debug;
11
+ setUser(userId: number, playerName?: string, data?: Map<string, unknown>): void;
12
+ getUser(): UserContext | undefined;
13
+ clearUser(): void;
14
+ addBreadcrumb(category: string, message: string, data?: Map<string, unknown>): void;
15
+ getBreadcrumbs(): Breadcrumb[];
16
+ clearBreadcrumbs(): void;
17
+ setTag(key: string, value: string): void;
18
+ getTags(): Map<string, string>;
19
+ removeTag(key: string): void;
20
+ setExtra(key: string, value: unknown): void;
21
+ getExtras(): Map<string, unknown>;
22
+ removeExtra(key: string): void;
23
+ clear(): void;
24
+ clone(): Scope;
25
+ }
@@ -0,0 +1,155 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local Scope
3
+ do
4
+ Scope = setmetatable({}, {
5
+ __tostring = function()
6
+ return "Scope"
7
+ end,
8
+ })
9
+ Scope.__index = Scope
10
+ function Scope.new(...)
11
+ local self = setmetatable({}, Scope)
12
+ return self:constructor(...) or self
13
+ end
14
+ function Scope:constructor(config)
15
+ self._config = config
16
+ self._breadcrumbs = {}
17
+ self._tags = {}
18
+ self._extras = {}
19
+ end
20
+ function Scope:_debug(...)
21
+ local args = { ... }
22
+ if self._config:get("debug") then
23
+ print("[RoSentry:Scope]", unpack(args))
24
+ end
25
+ end
26
+ function Scope:setUser(userId, playerName, data)
27
+ self._user = {
28
+ id = userId,
29
+ name = playerName,
30
+ data = data or {},
31
+ }
32
+ self:addBreadcrumb("user", "User set", {
33
+ userId = userId,
34
+ playerName = playerName,
35
+ })
36
+ local _self = self
37
+ local _exp = userId
38
+ local _condition = playerName
39
+ if _condition == nil then
40
+ _condition = "(no name)"
41
+ end
42
+ _self:_debug("User set:", _exp, _condition)
43
+ end
44
+ function Scope:getUser()
45
+ return self._user
46
+ end
47
+ function Scope:clearUser()
48
+ self._user = nil
49
+ self:_debug("User cleared")
50
+ end
51
+ function Scope:addBreadcrumb(category, message, data)
52
+ local maxBreadcrumbs = self._config:get("maxBreadcrumbs")
53
+ local breadcrumb = {
54
+ category = category,
55
+ message = message,
56
+ data = data,
57
+ timestamp = os.time(),
58
+ }
59
+ local _exp = self._breadcrumbs
60
+ table.insert(_exp, breadcrumb)
61
+ while #self._breadcrumbs > maxBreadcrumbs do
62
+ table.remove(self._breadcrumbs, 1)
63
+ end
64
+ self:_debug("Breadcrumb added:", category, message)
65
+ end
66
+ function Scope:getBreadcrumbs()
67
+ return self._breadcrumbs
68
+ end
69
+ function Scope:clearBreadcrumbs()
70
+ self._breadcrumbs = {}
71
+ self:_debug("Breadcrumbs cleared")
72
+ end
73
+ function Scope:setTag(key, value)
74
+ local __tags = self._tags
75
+ local _key = key
76
+ local _value = value
77
+ __tags[_key] = _value
78
+ self:_debug("Tag set:", key, value)
79
+ end
80
+ function Scope:getTags()
81
+ return self._tags
82
+ end
83
+ function Scope:removeTag(key)
84
+ local __tags = self._tags
85
+ local _key = key
86
+ __tags[_key] = nil
87
+ end
88
+ function Scope:setExtra(key, value)
89
+ local __extras = self._extras
90
+ local _key = key
91
+ local _value = value
92
+ __extras[_key] = _value
93
+ self:_debug("Extra set:", key)
94
+ end
95
+ function Scope:getExtras()
96
+ return self._extras
97
+ end
98
+ function Scope:removeExtra(key)
99
+ local __extras = self._extras
100
+ local _key = key
101
+ __extras[_key] = nil
102
+ end
103
+ function Scope:clear()
104
+ self._user = nil
105
+ self._breadcrumbs = {}
106
+ self._tags = {}
107
+ self._extras = {}
108
+ self:_debug("Scope cleared")
109
+ end
110
+ function Scope:clone()
111
+ local cloned = Scope.new(self._config)
112
+ if self._user then
113
+ local _object = table.clone(self._user)
114
+ setmetatable(_object, nil)
115
+ cloned._user = _object
116
+ end
117
+ local _array = {}
118
+ local _length = #_array
119
+ local _array_1 = self._breadcrumbs
120
+ table.move(_array_1, 1, #_array_1, _length + 1, _array)
121
+ cloned._breadcrumbs = _array
122
+ local tagsCopy = {}
123
+ local _exp = self._tags
124
+ -- ▼ ReadonlyMap.forEach ▼
125
+ local _callback = function(v, k)
126
+ local _k = k
127
+ local _v = v
128
+ tagsCopy[_k] = _v
129
+ return tagsCopy
130
+ end
131
+ for _k, _v in _exp do
132
+ _callback(_v, _k, _exp)
133
+ end
134
+ -- ▲ ReadonlyMap.forEach ▲
135
+ cloned._tags = tagsCopy
136
+ local extrasCopy = {}
137
+ local _exp_1 = self._extras
138
+ -- ▼ ReadonlyMap.forEach ▼
139
+ local _callback_1 = function(v, k)
140
+ local _k = k
141
+ local _v = v
142
+ extrasCopy[_k] = _v
143
+ return extrasCopy
144
+ end
145
+ for _k, _v in _exp_1 do
146
+ _callback_1(_v, _k, _exp_1)
147
+ end
148
+ -- ▲ ReadonlyMap.forEach ▲
149
+ cloned._extras = extrasCopy
150
+ return cloned
151
+ end
152
+ end
153
+ return {
154
+ Scope = Scope,
155
+ }
@@ -0,0 +1,21 @@
1
+ import { Trace, TraceOptions, TraceStatus } from "../types";
2
+ import { Config } from "./Config";
3
+ import { Scope } from "./Scope";
4
+ import { Queue } from "./Queue";
5
+ export declare class TraceManager {
6
+ private _config;
7
+ private _scope;
8
+ private _queue;
9
+ private _activeTraces;
10
+ constructor(config: Config, scope: Scope, queue: Queue);
11
+ private _debug;
12
+ private _cleanupExpired;
13
+ getActiveCount(): number;
14
+ startTrace(traceName: string, options?: TraceOptions): Trace | undefined;
15
+ private _createTraceInterface;
16
+ private _logToTrace;
17
+ private _finishTrace;
18
+ getTrace(traceId: string): Trace | undefined;
19
+ endAllTraces(status?: TraceStatus): void;
20
+ setScope(scope: Scope): void;
21
+ }
@@ -0,0 +1,299 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local HttpService = TS.import(script, TS.getModule(script, "@rbxts", "services")).HttpService
4
+ local _util = TS.import(script, script.Parent, "util")
5
+ local getJobId = _util.getJobId
6
+ local getPlaceId = _util.getPlaceId
7
+ local getGameVersion = _util.getGameVersion
8
+ local isServer = _util.isServer
9
+ local getTimestamp = _util.getTimestamp
10
+ local DEFAULT_MAX_EVENTS = 100
11
+ local DEFAULT_EXPIRES_IN = 3600
12
+ local MAX_ACTIVE_TRACES = 50
13
+ local function isoFromUnixSeconds(seconds)
14
+ return DateTime.fromUnixTimestamp(seconds):ToIsoDate()
15
+ end
16
+ local TraceManager
17
+ do
18
+ TraceManager = setmetatable({}, {
19
+ __tostring = function()
20
+ return "TraceManager"
21
+ end,
22
+ })
23
+ TraceManager.__index = TraceManager
24
+ function TraceManager.new(...)
25
+ local self = setmetatable({}, TraceManager)
26
+ return self:constructor(...) or self
27
+ end
28
+ function TraceManager:constructor(config, scope, queue)
29
+ self._config = config
30
+ self._scope = scope
31
+ self._queue = queue
32
+ self._activeTraces = {}
33
+ end
34
+ function TraceManager:_debug(...)
35
+ local args = { ... }
36
+ if self._config:get("debug") then
37
+ print("[RoSentry:Trace]", unpack(args))
38
+ end
39
+ end
40
+ function TraceManager:_cleanupExpired()
41
+ local now = os.time()
42
+ local expired = {}
43
+ local _exp = self._activeTraces
44
+ -- ▼ ReadonlyMap.forEach ▼
45
+ local _callback = function(trace, id)
46
+ if now >= trace.expiresAt then
47
+ self:_debug("Trace expired:", trace.name, id)
48
+ local _id = id
49
+ table.insert(expired, _id)
50
+ end
51
+ end
52
+ for _k, _v in _exp do
53
+ _callback(_v, _k, _exp)
54
+ end
55
+ -- ▲ ReadonlyMap.forEach ▲
56
+ for _, id in expired do
57
+ self._activeTraces[id] = nil
58
+ end
59
+ end
60
+ function TraceManager:getActiveCount()
61
+ self:_cleanupExpired()
62
+ local count = 0
63
+ local _exp = self._activeTraces
64
+ -- ▼ ReadonlyMap.forEach ▼
65
+ local _callback = function()
66
+ count += 1
67
+ end
68
+ for _k, _v in _exp do
69
+ _callback(_v, _k, _exp)
70
+ end
71
+ -- ▲ ReadonlyMap.forEach ▲
72
+ return count
73
+ end
74
+ function TraceManager:startTrace(traceName, options)
75
+ if not self._config:isEnabled() then
76
+ return nil
77
+ end
78
+ self:_cleanupExpired()
79
+ if self:getActiveCount() >= MAX_ACTIVE_TRACES then
80
+ warn(`[RoSentry] Max active traces reached ({MAX_ACTIVE_TRACES}). Cannot start new trace.`)
81
+ return nil
82
+ end
83
+ local opts = options or {}
84
+ local now = os.time()
85
+ local _condition = opts.maxEvents
86
+ if _condition == nil then
87
+ _condition = DEFAULT_MAX_EVENTS
88
+ end
89
+ local maxEvents = _condition
90
+ local _condition_1 = opts.expiresIn
91
+ if _condition_1 == nil then
92
+ _condition_1 = DEFAULT_EXPIRES_IN
93
+ end
94
+ local expiresIn = _condition_1
95
+ maxEvents = math.min(maxEvents, 500)
96
+ expiresIn = math.min(expiresIn, 7200)
97
+ local traceId = HttpService:GenerateGUID(false)
98
+ local instance = {
99
+ id = traceId,
100
+ name = traceName,
101
+ status = "active",
102
+ eventCount = 0,
103
+ maxEvents = maxEvents,
104
+ startedAt = now,
105
+ expiresAt = now + expiresIn,
106
+ tags = opts.tags or {},
107
+ metadata = opts.metadata or {},
108
+ }
109
+ self._activeTraces[traceId] = instance
110
+ self:_debug("Started trace:", traceName, traceId, "maxEvents:", maxEvents, "expiresIn:", expiresIn)
111
+ return self:_createTraceInterface(instance)
112
+ end
113
+ function TraceManager:_createTraceInterface(instance)
114
+ local manager = self
115
+ local traceId = instance.id
116
+ local trace
117
+ trace = {
118
+ id = traceId,
119
+ name = instance.name,
120
+ log = function(message, data)
121
+ trace.info(message, data)
122
+ end,
123
+ debug = function(message, data)
124
+ manager:_logToTrace(traceId, "debug", message, data)
125
+ end,
126
+ info = function(message, data)
127
+ manager:_logToTrace(traceId, "info", message, data)
128
+ end,
129
+ warn = function(message, data)
130
+ manager:_logToTrace(traceId, "warn", message, data)
131
+ end,
132
+ error = function(message, data)
133
+ manager:_logToTrace(traceId, "error", message, data)
134
+ end,
135
+ isActive = function()
136
+ local inst = manager._activeTraces[traceId]
137
+ if not inst then
138
+ return false
139
+ end
140
+ if inst.status ~= "active" then
141
+ return false
142
+ end
143
+ if os.time() >= inst.expiresAt then
144
+ return false
145
+ end
146
+ return true
147
+ end,
148
+ finish = function(status)
149
+ manager:_finishTrace(traceId, status or "completed")
150
+ end,
151
+ }
152
+ return trace
153
+ end
154
+ function TraceManager:_logToTrace(traceId, level, message, data)
155
+ local __activeTraces = self._activeTraces
156
+ local _traceId = traceId
157
+ local instance = __activeTraces[_traceId]
158
+ if not instance then
159
+ self:_debug("Trace not found:", traceId)
160
+ return nil
161
+ end
162
+ if instance.status ~= "active" then
163
+ self:_debug("Trace not active:", traceId, instance.status)
164
+ return nil
165
+ end
166
+ if os.time() >= instance.expiresAt then
167
+ self:_debug("Trace expired:", traceId)
168
+ self:_finishTrace(traceId, "timeout")
169
+ return nil
170
+ end
171
+ if instance.eventCount >= instance.maxEvents then
172
+ self:_debug("Trace event limit reached:", traceId, instance.eventCount)
173
+ self:_finishTrace(traceId, "completed")
174
+ return nil
175
+ end
176
+ instance.eventCount += 1
177
+ local user = self._scope:getUser()
178
+ local errorData = {
179
+ message = message,
180
+ level = level,
181
+ stack_trace = nil,
182
+ user_id = if user then user.id else nil,
183
+ player_name = if user then user.name else nil,
184
+ trace_id = traceId,
185
+ job_id = getJobId(),
186
+ place_id = getPlaceId(),
187
+ game_version = getGameVersion(),
188
+ environment = self._config:get("environment"),
189
+ context = {
190
+ userData = if user then user.data else nil,
191
+ custom = data,
192
+ breadcrumbs = nil,
193
+ isServer = isServer(),
194
+ script = nil,
195
+ trace = {
196
+ id = traceId,
197
+ name = instance.name,
198
+ status = instance.status,
199
+ eventCount = instance.eventCount,
200
+ maxEvents = instance.maxEvents,
201
+ startedAt = isoFromUnixSeconds(instance.startedAt),
202
+ expiresAt = isoFromUnixSeconds(instance.expiresAt),
203
+ metadata = instance.metadata,
204
+ },
205
+ },
206
+ tags = instance.tags,
207
+ timestamp = getTimestamp(),
208
+ }
209
+ self._queue:push(errorData)
210
+ self:_debug("Logged to trace:", traceId, level, message, `({instance.eventCount}/{instance.maxEvents})`)
211
+ end
212
+ function TraceManager:_finishTrace(traceId, status)
213
+ local __activeTraces = self._activeTraces
214
+ local _traceId = traceId
215
+ local instance = __activeTraces[_traceId]
216
+ if not instance then
217
+ return nil
218
+ end
219
+ instance.status = status
220
+ self:_debug("Finished trace:", instance.name, traceId, "status:", status, "events:", instance.eventCount)
221
+ local user = self._scope:getUser()
222
+ local finishEvent = {
223
+ message = "trace:finish",
224
+ level = "debug",
225
+ stack_trace = nil,
226
+ user_id = if user then user.id else nil,
227
+ player_name = if user then user.name else nil,
228
+ trace_id = traceId,
229
+ job_id = getJobId(),
230
+ place_id = getPlaceId(),
231
+ game_version = getGameVersion(),
232
+ environment = self._config:get("environment"),
233
+ context = {
234
+ userData = if user then user.data else nil,
235
+ custom = {
236
+ __rosentry = {
237
+ kind = "trace_finish",
238
+ status = status,
239
+ },
240
+ },
241
+ breadcrumbs = nil,
242
+ isServer = isServer(),
243
+ script = nil,
244
+ trace = {
245
+ id = traceId,
246
+ name = instance.name,
247
+ status = status,
248
+ eventCount = instance.eventCount,
249
+ maxEvents = instance.maxEvents,
250
+ startedAt = isoFromUnixSeconds(instance.startedAt),
251
+ expiresAt = isoFromUnixSeconds(instance.expiresAt),
252
+ endedAt = getTimestamp(),
253
+ metadata = instance.metadata,
254
+ },
255
+ },
256
+ tags = instance.tags,
257
+ timestamp = getTimestamp(),
258
+ }
259
+ self._queue:push(finishEvent)
260
+ local __activeTraces_1 = self._activeTraces
261
+ local _traceId_1 = traceId
262
+ __activeTraces_1[_traceId_1] = nil
263
+ end
264
+ function TraceManager:getTrace(traceId)
265
+ local __activeTraces = self._activeTraces
266
+ local _traceId = traceId
267
+ local instance = __activeTraces[_traceId]
268
+ if not instance then
269
+ return nil
270
+ end
271
+ if instance.status ~= "active" or os.time() >= instance.expiresAt then
272
+ local __activeTraces_1 = self._activeTraces
273
+ local _traceId_1 = traceId
274
+ __activeTraces_1[_traceId_1] = nil
275
+ return nil
276
+ end
277
+ return self:_createTraceInterface(instance)
278
+ end
279
+ function TraceManager:endAllTraces(status)
280
+ local finalStatus = status or "completed"
281
+ local _exp = self._activeTraces
282
+ -- ▼ ReadonlyMap.forEach ▼
283
+ local _callback = function(instance, id)
284
+ self:_debug("Ending trace:", instance.name, id)
285
+ instance.status = finalStatus
286
+ end
287
+ for _k, _v in _exp do
288
+ _callback(_v, _k, _exp)
289
+ end
290
+ -- ▲ ReadonlyMap.forEach ▲
291
+ table.clear(self._activeTraces)
292
+ end
293
+ function TraceManager:setScope(scope)
294
+ self._scope = scope
295
+ end
296
+ end
297
+ return {
298
+ TraceManager = TraceManager,
299
+ }
@@ -0,0 +1,13 @@
1
+ import { Config } from "./Config";
2
+ export declare class Transport {
3
+ private _config;
4
+ private _remoteEvent?;
5
+ constructor(config: Config);
6
+ private _debug;
7
+ private _getRemoteEvent;
8
+ setupServerRelay(): void;
9
+ private _sendHttp;
10
+ private _sendRemote;
11
+ send(payload: Record<string, unknown>): boolean;
12
+ sendAsync(payload: Record<string, unknown>): void;
13
+ }