Class: AllStak::SessionTracker

Inherits:
Object
  • Object
show all
Defined in:
lib/allstak/session_tracker.rb

Overview

Server-mode “single session” release-health tracker.

Mirrors the AllStak Java SDK ‘SessionTracker` lifecycle + status model: on #start the SDK posts a `/ingest/v1/sessions/start` envelope with the process’s distinct session id, the resolved release, and SDK identity. On #end it posts ‘/ingest/v1/sessions/end` with the final status + total duration. ERRORED / CRASHED transitions are recorded in-memory only; only the terminal #end call performs network I/O for status, so per-error latency stays unaffected.

One instance per Client. Re-entrancy safe: once started a second #start is a no-op; once ended the tracker does not re-arm.

Sessions are NEVER sampled — they are always sent (when tracking is on and a release is resolvable). The whole tracker is fully fail-open: a network failure or any other error must never crash app boot or shutdown.

Constant Summary collapse

PATH_START =
"/ingest/v1/sessions/start".freeze
PATH_END =
"/ingest/v1/sessions/end".freeze
STATUS_OK =

Lifecycle status. Vocabulary matches the backend ‘/sessions/end` contract and Sentry’s release-health conventions:

ok       — ended normally, at most non-fatal logs.
errored  — at least one HANDLED error captured; process kept running.
crashed  — an UNHANDLED/fatal exception ended the process.
abnormal — ended without a normal flush (reserved).
"ok".freeze
STATUS_ERRORED =
"errored".freeze
STATUS_CRASHED =
"crashed".freeze
STATUS_ABNORMAL =
"abnormal".freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config, transport, logger = nil) ⇒ SessionTracker

Returns a new instance of SessionTracker.



38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/allstak/session_tracker.rb', line 38

def initialize(config, transport, logger = nil)
  @config = config
  @transport = transport
  @logger = logger
  @mutex = Mutex.new
  @session_id = nil
  @started_at = nil
  @status = STATUS_OK
  @error_count = 0
  @started = false
  @ended = false
end

Instance Attribute Details

#session_idObject (readonly)

Returns the value of attribute session_id.



36
37
38
# File 'lib/allstak/session_tracker.rb', line 36

def session_id
  @session_id
end

#started_atObject (readonly)

Returns the value of attribute started_at.



36
37
38
# File 'lib/allstak/session_tracker.rb', line 36

def started_at
  @started_at
end

Class Method Details

.test_runtime?Boolean

Detect a unit-test runtime so session tracking self-disables there, matching AllStak.register_runtime_release‘s own guard.

Returns:

  • (Boolean)


62
63
64
65
66
67
68
# File 'lib/allstak/session_tracker.rb', line 62

def self.test_runtime?
  return true if ENV["MT_TEST"]
  return true if ENV["RACK_ENV"] == "test" || ENV["RAILS_ENV"] == "test"
  return true if ENV["RUBYOPT"].to_s.include?("minitest")
  return true if $PROGRAM_NAME.to_s.include?("rspec")
  defined?(Minitest) ? true : false
end

Instance Method Details

#current_session_idObject

The active session id, or nil before start / after end. Attached to every error/event payload so the backend can mark the session errored/crashed.



106
107
108
# File 'lib/allstak/session_tracker.rb', line 106

def current_session_id
  @mutex.synchronize { (@started && !@ended) ? @session_id : nil }
end

#enabled?Boolean

Should this runtime track sessions at all? Off when the user opted out via ‘enable_auto_session_tracking = false`, and automatically off under a unit test runtime (mirrors the Java SDK’s test guard) so the suite never emits session traffic.

Returns:

  • (Boolean)


55
56
57
58
# File 'lib/allstak/session_tracker.rb', line 55

def enabled?
  return false unless @config.enable_auto_session_tracking
  !self.class.test_runtime?
end

#end(final_status = nil) ⇒ Object

Terminate the session and POST ‘/sessions/end` with durationMs + status. Idempotent. Best-effort with a short timeout; must not block or raise. `final_status` overrides the accumulated status when given.



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/allstak/session_tracker.rb', line 133

def end(final_status = nil)
  sid = nil
  status = nil
  duration = nil
  @mutex.synchronize do
    return if @ended || !@started
    @ended = true
    sid = @session_id
    status = final_status || @status
    duration = [now_ms - @started_at.to_i, 0].max
  end

  return unless enabled?
  return if transport_disabled?
  return if effective_release.to_s.empty?

  payload = {
    sessionId:  sid,
    durationMs: clamp_int(duration),
    status:     status
  }.compact

  send_sync(PATH_END, payload, "session end")
end

#record_crashObject

Record an UNHANDLED/fatal crash: terminal status (overrides errored). No I/O — the #end POST carries the status.



122
123
124
125
126
127
128
# File 'lib/allstak/session_tracker.rb', line 122

def record_crash
  @mutex.synchronize do
    next unless active_locked?
    @error_count += 1
    @status = STATUS_CRASHED
  end
end

#record_errorObject

Record a HANDLED error: bump status ok -> errored (never downgrades a terminal crash). No I/O.



112
113
114
115
116
117
118
# File 'lib/allstak/session_tracker.rb', line 112

def record_error
  @mutex.synchronize do
    next unless active_locked?
    @error_count += 1
    @status = STATUS_ERRORED if @status == STATUS_OK
  end
end

#startObject

Idempotent. Records sessionStart, sets in-memory status = “ok”, and POSTs ‘/sessions/start` on a daemon thread so SDK init never blocks on a network round-trip. No-op when tracking is disabled, the transport is disabled, or no release/sdkVersion can be resolved. Never raises.



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/allstak/session_tracker.rb', line 74

def start
  @mutex.synchronize do
    return self if @started
    @started = true
    @session_id = SecureRandom.uuid
    @started_at = now_ms
    @status = STATUS_OK
    @error_count = 0
  end

  return self unless enabled?
  return self if transport_disabled?

  release = effective_release
  return self if release.to_s.empty?

  payload = {
    sessionId:   @session_id,
    release:     release,
    environment: @config.environment,
    userId:      current_user_id,
    sdkName:     @config.sdk_name,
    sdkVersion:  @config.sdk_version,
    platform:    @config.platform
  }.compact

  send_async(PATH_START, payload, "session start")
  self
end