Skip to content

Commit 92cf6c3

Browse files
authored
feat(telemetry): add optional application label via env var (#1992)
Read `COCOINDEX_APPLICATION_FOR_TRACKING` once at telemetry init. When set to a non-empty value, every emitted event payload includes an additional `application` field so end applications can self-identify in aggregate telemetry. When unset or empty, the field is omitted.
1 parent 3865921 commit 92cf6c3

1 file changed

Lines changed: 40 additions & 0 deletions

File tree

rust/core/src/telemetry/mod.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use crate::engine::runtime::{get_runtime, is_runtime_shutdown};
1818
const SCARF_BASE: &str = "https://cocoindex.gateway.scarf.sh";
1919
const REQUEST_TIMEOUT: Duration = Duration::from_secs(5);
2020
const DISABLE_ENV: &str = "COCOINDEX_DISABLE_USAGE_TRACKING";
21+
const APPLICATION_ENV: &str = "COCOINDEX_APPLICATION_FOR_TRACKING";
2122

2223
static TELEMETRY: OnceLock<TelemetryContext> = OnceLock::new();
2324

@@ -26,13 +27,16 @@ struct TelemetryContext {
2627
base_url: String,
2728
platform: String,
2829
lang: String,
30+
application: Option<String>,
2931
}
3032

3133
#[derive(Serialize)]
3234
struct EventPayload<'a> {
3335
event: &'a str,
3436
platform: &'a str,
3537
lang: &'a str,
38+
#[serde(skip_serializing_if = "Option::is_none")]
39+
application: Option<&'a str>,
3640
}
3741

3842
/// Initialize telemetry. No-op in debug builds, no-op if
@@ -80,9 +84,16 @@ fn build_context(package_id: String, lang: String) -> Option<TelemetryContext> {
8084
base_url: format!("{SCARF_BASE}/{package_id}"),
8185
platform: current_platform(),
8286
lang,
87+
application: read_application_env(),
8388
})
8489
}
8590

91+
fn read_application_env() -> Option<String> {
92+
std::env::var(APPLICATION_ENV)
93+
.ok()
94+
.filter(|v| !v.is_empty())
95+
}
96+
8697
fn current_platform() -> String {
8798
format!("{}-{}", std::env::consts::ARCH, std::env::consts::OS)
8899
}
@@ -103,6 +114,7 @@ async fn send_event(ctx: &TelemetryContext, event: &'static str) {
103114
event,
104115
platform: &ctx.platform,
105116
lang: &ctx.lang,
117+
application: ctx.application.as_deref(),
106118
};
107119
let result = ctx
108120
.client
@@ -183,11 +195,21 @@ mod tests {
183195
}
184196

185197
fn make_test_ctx(base_url: String, lang: String, timeout: Duration) -> TelemetryContext {
198+
make_test_ctx_with_application(base_url, lang, timeout, None)
199+
}
200+
201+
fn make_test_ctx_with_application(
202+
base_url: String,
203+
lang: String,
204+
timeout: Duration,
205+
application: Option<String>,
206+
) -> TelemetryContext {
186207
TelemetryContext {
187208
client: reqwest::Client::builder().timeout(timeout).build().unwrap(),
188209
base_url,
189210
platform: current_platform(),
190211
lang,
212+
application,
191213
}
192214
}
193215

@@ -210,6 +232,24 @@ mod tests {
210232
assert_eq!(body["event"], "app_create");
211233
assert_eq!(body["lang"], "python3.11");
212234
assert_eq!(body["platform"], current_platform());
235+
assert!(body.get("application").is_none());
236+
}
237+
238+
#[tokio::test]
239+
async fn send_event_includes_application_when_set() {
240+
let (addr, recorded) = spawn_mock_server(StatusCode::OK).await;
241+
let ctx = make_test_ctx_with_application(
242+
format!("http://{addr}/python-1.0.0a1"),
243+
"python3.11".to_string(),
244+
Duration::from_secs(5),
245+
Some("my-app".to_string()),
246+
);
247+
248+
send_event(&ctx, "app_create").await;
249+
250+
let recs = recorded.lock().unwrap().clone();
251+
assert_eq!(recs.len(), 1);
252+
assert_eq!(recs[0].body["application"], "my-app");
213253
}
214254

215255
#[tokio::test]

0 commit comments

Comments
 (0)