Module: ZnyxSdk::Telemetry

Defined in:
lib/znyx_sdk/telemetry.rb

Overview

Anonymous install telemetry for the ZNYX Ruby SDK.

Sends a single fire-and-forget ping when a client is first constructed, then at most one "heartbeat" ping per 24h. Non-sensitive metadata only: install_id (random UUID, persisted to ~/.znyx/sdk-state.json), SDK version, source ("ruby-sdk"), OS / arch / Ruby version, run_count.

No PII, no request content, no tenant data. Opt out with ZNYX_TELEMETRY=false (on by default). Disclosed once to stderr on the first run. Every operation is best-effort — telemetry must never slow down, block, or break the calling application. See TELEMETRY.md.

Constant Summary collapse

ENDPOINT =

Telemetry endpoint. Defaults to the ZNYX production receiver so anonymous install telemetry is on out of the box (opt-out, fully transparent). Override with ZNYX_TELEMETRY_URL (or ZNYX_HEARTBEAT_URL), or opt out with ZNYX_TELEMETRY=false.

(
  (ENV["ZNYX_TELEMETRY_URL"] || "").empty? ? nil : ENV["ZNYX_TELEMETRY_URL"]
) || (
  (ENV["ZNYX_HEARTBEAT_URL"] || "").empty? ? nil : ENV["ZNYX_HEARTBEAT_URL"]
) || "https://cp.znyx.ai/v1/install-telemetry"
STATE_FILE =
File.join(Dir.home, ".znyx", "sdk-state.json")
HEARTBEAT_INTERVAL =

seconds (24h)

86_400
SOURCE =
"ruby-sdk"
DISCLOSURE =
"[znyx-sdk] Anonymous usage telemetry is on (install id, SDK version, OS - " \
"no PII, no request content). Opt out with ZNYX_TELEMETRY=false."

Class Method Summary collapse

Class Method Details

.maybe_send_install_pingObject

Send an anonymous install ping, throttled to once per 24h. Fire-and-forget.



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
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
103
104
105
# File 'lib/znyx_sdk/telemetry.rb', line 46

def maybe_send_install_ping
  return if @attempted

  @attempted = true

  return if ENDPOINT.to_s.empty? || !enabled?

  state = load_state
  last_ping = state["last_ping_at"]

  if last_ping.nil?
    event_type = "first_run"
  else
    begin
      elapsed = Time.now - Time.iso8601(last_ping)
    rescue StandardError
      elapsed = HEARTBEAT_INTERVAL # unparseable -> treat as due
    end
    return if elapsed < HEARTBEAT_INTERVAL

    event_type = "heartbeat"
  end

  install_id = state["install_id"] || SecureRandom.uuid
  run_count  = (state["run_count"] || 0).to_i + 1
  now_iso    = Time.now.utc.iso8601

  state.merge!(
    "install_id"    => install_id,
    "first_seen_at" => state["first_seen_at"] || now_iso,
    "last_ping_at"  => now_iso,
    "run_count"     => run_count
  )
  save_state(state)

  if event_type == "first_run"
    begin
      warn DISCLOSURE
    rescue StandardError
      # ignore
    end
  end

  payload = {
    install_id:   install_id,
    version:      version,
    event_type:   event_type,
    source:       SOURCE,
    os:           RbConfig::CONFIG["host_os"],
    os_version:   host_os_version,
    arch:         RbConfig::CONFIG["host_cpu"],
    ruby_version: RUBY_VERSION,
    run_count:    run_count,
    timestamp:    now_iso
  }

  Thread.new { post(payload) }
rescue StandardError
  # Telemetry must never break client construction.
end