+33
-7
Cargo.lock
+33
-7
Cargo.lock
···
2726
2726
"indexmap 2.9.0",
2727
2727
"ipnet",
2728
2728
"metrics",
2729
-
"metrics-util",
2729
+
"metrics-util 0.19.0",
2730
2730
"quanta",
2731
2731
"thiserror 1.0.69",
2732
2732
"tokio",
···
2735
2735
2736
2736
[[package]]
2737
2737
name = "metrics-exporter-prometheus"
2738
-
version = "0.17.1"
2738
+
version = "0.17.2"
2739
2739
source = "registry+https://github.com/rust-lang/crates.io-index"
2740
-
checksum = "989903b4c7abfa6827a8d1128ef42faf83f8969d429797c5431f236f2cae8b8b"
2740
+
checksum = "2b166dea96003ee2531cf14833efedced545751d800f03535801d833313f8c15"
2741
2741
dependencies = [
2742
2742
"base64 0.22.1",
2743
2743
"http-body-util",
···
2747
2747
"indexmap 2.9.0",
2748
2748
"ipnet",
2749
2749
"metrics",
2750
-
"metrics-util",
2750
+
"metrics-util 0.20.0",
2751
2751
"quanta",
2752
2752
"thiserror 2.0.12",
2753
2753
"tokio",
···
2782
2782
"metrics",
2783
2783
"quanta",
2784
2784
"rand 0.8.5",
2785
-
"rand_xoshiro",
2785
+
"rand_xoshiro 0.6.0",
2786
+
"sketches-ddsketch",
2787
+
]
2788
+
2789
+
[[package]]
2790
+
name = "metrics-util"
2791
+
version = "0.20.0"
2792
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2793
+
checksum = "fe8db7a05415d0f919ffb905afa37784f71901c9a773188876984b4f769ab986"
2794
+
dependencies = [
2795
+
"crossbeam-epoch",
2796
+
"crossbeam-utils",
2797
+
"hashbrown 0.15.2",
2798
+
"metrics",
2799
+
"quanta",
2800
+
"rand 0.9.1",
2801
+
"rand_xoshiro 0.7.0",
2786
2802
"sketches-ddsketch",
2787
2803
]
2788
2804
···
3399
3415
]
3400
3416
3401
3417
[[package]]
3418
+
name = "rand_xoshiro"
3419
+
version = "0.7.0"
3420
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3421
+
checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41"
3422
+
dependencies = [
3423
+
"rand_core 0.9.3",
3424
+
]
3425
+
3426
+
[[package]]
3402
3427
name = "ratelimit"
3403
3428
version = "0.10.0"
3404
3429
source = "registry+https://github.com/rust-lang/crates.io-index"
···
4162
4187
"links",
4163
4188
"log",
4164
4189
"metrics",
4165
-
"metrics-exporter-prometheus 0.17.1",
4190
+
"metrics-exporter-prometheus 0.17.2",
4166
4191
"rand 0.9.1",
4167
4192
"schemars",
4168
4193
"semver",
···
4752
4777
"log",
4753
4778
"lsm-tree",
4754
4779
"metrics",
4755
-
"metrics-exporter-prometheus 0.17.1",
4780
+
"metrics-exporter-prometheus 0.17.2",
4756
4781
"schemars",
4757
4782
"semver",
4758
4783
"serde",
···
5063
5088
"handlebars",
5064
5089
"hickory-resolver",
5065
5090
"metrics",
5091
+
"metrics-exporter-prometheus 0.17.2",
5066
5092
"rand 0.9.1",
5067
5093
"reqwest",
5068
5094
"serde",
+1
who-am-i/Cargo.toml
+1
who-am-i/Cargo.toml
···
17
17
handlebars = { version = "6.3.2", features = ["dir_source"] }
18
18
hickory-resolver = "0.25.2"
19
19
metrics = "0.24.2"
20
+
metrics-exporter-prometheus = { version = "0.17.2", features = ["http-listener"] }
20
21
rand = "0.9.1"
21
22
reqwest = { version = "0.12.22", features = ["native-tls-vendored"] }
22
23
serde = { version = "1.0.219", features = ["derive"] }
+4
-1
who-am-i/src/expiring_task_map.rs
+4
-1
who-am-i/src/expiring_task_map.rs
···
49
49
.run_until_cancelled(sleep(expiration))
50
50
.await
51
51
.is_some()
52
-
// is Some if the (sleep) task completed first
53
52
{
53
+
// is Some if the (sleep) task completed first
54
54
map.remove(&k);
55
55
cancel.cancel();
56
+
metrics::counter!("whoami_task_map_completions", "result" => "expired")
57
+
.increment(1);
56
58
}
57
59
});
58
60
···
60
62
}
61
63
62
64
pub fn take(&self, key: &str) -> Option<JoinHandle<T>> {
65
+
metrics::counter!("whoami_task_map_completions", "result" => "retrieved").increment(1);
63
66
// when the _guard drops, the token gets cancelled for us
64
67
self.0.map.remove(key).map(|(_, (_guard, handle))| handle)
65
68
}
+21
-1
who-am-i/src/main.rs
+21
-1
who-am-i/src/main.rs
···
1
1
use clap::{ArgAction, Parser};
2
+
use metrics_exporter_prometheus::PrometheusBuilder;
2
3
use tokio_util::sync::CancellationToken;
3
4
use who_am_i::serve;
4
5
···
35
36
let args = Args::parse();
36
37
37
38
if args.allowed_hosts.is_empty() {
38
-
panic!("at least one --one-click host must be set");
39
+
panic!("at least one --allowed-host host must be set");
39
40
}
40
41
41
42
println!("starting with allowed_hosts hosts:");
42
43
for host in &args.allowed_hosts {
43
44
println!(" - {host}");
44
45
}
46
+
47
+
if let Err(e) = install_metrics_server() {
48
+
eprintln!("failed to install metrics server: {e:?}");
49
+
};
45
50
46
51
serve(shutdown, args.app_secret, args.allowed_hosts, args.dev).await;
47
52
}
53
+
54
+
fn install_metrics_server() -> Result<(), metrics_exporter_prometheus::BuildError> {
55
+
println!("installing metrics server...");
56
+
let host = [0, 0, 0, 0];
57
+
let port = 8765;
58
+
PrometheusBuilder::new()
59
+
.set_enable_unit_suffix(false)
60
+
.with_http_listener((host, port))
61
+
.install()?;
62
+
println!(
63
+
"metrics server installed! listening on http://{}.{}.{}.{}:{port}",
64
+
host[0], host[1], host[2], host[3]
65
+
);
66
+
Ok(())
67
+
}
+10
-7
who-am-i/src/oauth.rs
+10
-7
who-am-i/src/oauth.rs
···
198
198
&self,
199
199
query: &str,
200
200
) -> core::result::Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> {
201
-
Ok(self
202
-
.0
203
-
.txt_lookup(query)
204
-
.await?
205
-
.iter()
206
-
.map(|txt| txt.to_string())
207
-
.collect())
201
+
match self.0.txt_lookup(query).await {
202
+
Ok(r) => {
203
+
metrics::counter!("whoami_resolve_dns_txt", "success" => "true").increment(1);
204
+
Ok(r.iter().map(|r| r.to_string()).collect())
205
+
}
206
+
Err(e) => {
207
+
metrics::counter!("whoami_resolve_dns_txt", "success" => "false").increment(1);
208
+
Err(e.into())
209
+
}
210
+
}
208
211
}
209
212
}
+25
-6
who-am-i/src/server.rs
+25
-6
who-am-i/src/server.rs
···
172
172
headers: HeaderMap,
173
173
) -> impl IntoResponse {
174
174
let err = |reason, check_frame| {
175
+
metrics::counter!("whoami_auth_prompt", "ok" => "false", "reason" => reason).increment(1);
175
176
let info = json!({ "reason": reason, "check_frame": check_frame });
176
177
let html = RenderHtml("prompt-error", engine.clone(), info);
177
178
(StatusCode::BAD_REQUEST, html).into_response()
···
222
223
shutdown.child_token(),
223
224
);
224
225
226
+
metrics::counter!("whoami_auth_prompt", "ok" => "true", "known" => "true").increment(1);
225
227
let info = json!({
226
228
"did": did,
227
229
"fetch_key": fetch_key,
228
230
"parent_host": parent_host,
229
231
"parent_origin": parent_origin,
230
232
});
231
-
232
233
(frame_headers, jar, RenderHtml("prompt", engine, info)).into_response()
233
234
} else {
235
+
metrics::counter!("whoami_auth_prompt", "ok" => "true", "known" => "false").increment(1);
234
236
let info = json!({
235
237
"parent_host": parent_host,
236
238
"parent_origin": parent_origin,
···
250
252
}): State<AppState>,
251
253
Query(params): Query<UserInfoParams>,
252
254
) -> impl IntoResponse {
253
-
let err = |status, reason| (status, Json(json!({ "reason": reason }))).into_response();
255
+
let err = |status, reason: &str| {
256
+
metrics::counter!("whoami_user_info", "found" => "false", "reason" => reason.to_string())
257
+
.increment(1);
258
+
(status, Json(json!({ "reason": reason }))).into_response()
259
+
};
254
260
255
261
let Some(task_handle) = resolve_handles.take(¶ms.fetch_key) else {
256
262
return err(StatusCode::NOT_FOUND, "fetch key does not exist or expired");
···
279
285
StatusCode::INTERNAL_SERVER_ERROR,
280
286
&format!("handle appears invalid: {reason}"),
281
287
),
282
-
Ok(Ok(handle)) => Json(json!({ "handle": handle })).into_response(),
288
+
Ok(Ok(handle)) => {
289
+
metrics::counter!("whoami_user_info", "found" => "true").increment(1);
290
+
Json(json!({ "handle": handle })).into_response()
291
+
}
283
292
}
284
293
}
285
294
···
299
308
use atrium_identity::Error as IdError;
300
309
use atrium_oauth::Error as OAuthError;
301
310
302
-
let err = |code, reason| {
311
+
let err = |code, reason: &str| {
312
+
metrics::counter!("whoami_auth_start", "ok" => "false", "reason" => reason.to_string())
313
+
.increment(1);
303
314
let info = json!({
304
315
"result": "fail",
305
316
"reason": reason,
···
308
319
};
309
320
310
321
match oauth.begin(¶ms.handle).await {
311
-
Ok(auth_url) => (jar, Redirect::to(&auth_url)).into_response(),
312
322
Err(OAuthError::Identity(
313
323
IdError::NotFound | IdError::HttpStatus(StatusCode::NOT_FOUND),
314
324
)) => err(StatusCode::NOT_FOUND, "handle not found"),
···
316
326
Err(e) => {
317
327
eprintln!("begin auth failed: {e:?}");
318
328
err(StatusCode::INTERNAL_SERVER_ERROR, "unknown")
329
+
}
330
+
Ok(auth_url) => {
331
+
metrics::counter!("whoami_auth_start", "ok" => "true").increment(1);
332
+
(jar, Redirect::to(&auth_url)).into_response()
319
333
}
320
334
}
321
335
}
···
331
345
Query(params): Query<OAuthCallbackParams>,
332
346
jar: SignedCookieJar,
333
347
) -> Response {
334
-
let err = |code, result, reason| {
348
+
let err = |code, result, reason: &str| {
349
+
metrics::counter!("whoami_auth_complete", "ok" => "false", "reason" => reason.to_string())
350
+
.increment(1);
335
351
let info = json!({
336
352
"result": result,
337
353
"reason": reason,
···
378
394
},
379
395
shutdown.child_token(),
380
396
);
397
+
398
+
metrics::counter!("whoami_auth_complete", "ok" => "true").increment(1);
381
399
let info = json!({
382
400
"did": did,
383
401
"fetch_key": fetch_key,
···
386
404
}
387
405
388
406
async fn disconnect(jar: SignedCookieJar) -> impl IntoResponse {
407
+
metrics::counter!("whoami_disconnect").increment(1);
389
408
let jar = jar.remove(DID_COOKIE_KEY);
390
409
(jar, Json(json!({ "ok": true })))
391
410
}