mirror of
https://github.com/block/goose.git
synced 2026-07-17 12:56:20 +02:00
first run at unique safe fingerprinting
This commit is contained in:
Generated
+2
-1
@@ -2025,7 +2025,7 @@ dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users 0.5.2",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2659,6 +2659,7 @@ dependencies = [
|
||||
"etcetera",
|
||||
"fs2",
|
||||
"futures",
|
||||
"hex",
|
||||
"ignore",
|
||||
"include_dir",
|
||||
"indexmap 2.12.0",
|
||||
|
||||
@@ -111,6 +111,7 @@ shellexpand = "3.1.1"
|
||||
indexmap = "2.12.0"
|
||||
ignore = "0.4.25"
|
||||
which = "8.0.0"
|
||||
hex = "0.4.3"
|
||||
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
//! Privacy-respecting client fingerprinting for telemetry.
|
||||
//!
|
||||
//! Generates a stable, hashed client ID plus OS/arch info for OTLP resource attributes.
|
||||
//! The raw UUID is stored locally; only the SHA-256 hash is sent.
|
||||
|
||||
use opentelemetry::KeyValue;
|
||||
use sha2::{Digest, Sha256};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::config::Config;
|
||||
|
||||
const CLIENT_ID_CONFIG_KEY: &str = "goose_client_id";
|
||||
|
||||
/// Get or create the client ID, returning its SHA-256 hash.
|
||||
pub fn get_client_id_hash() -> String {
|
||||
let config = Config::global();
|
||||
|
||||
let client_id: String = match config.get_param(CLIENT_ID_CONFIG_KEY) {
|
||||
Ok(id) => id,
|
||||
Err(_) => {
|
||||
let new_id = Uuid::new_v4().to_string();
|
||||
let _ = config.set_param(CLIENT_ID_CONFIG_KEY, &new_id);
|
||||
new_id
|
||||
}
|
||||
};
|
||||
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(client_id.as_bytes());
|
||||
hex::encode(hasher.finalize())
|
||||
}
|
||||
|
||||
pub fn get_os_type() -> &'static str {
|
||||
#[cfg(target_os = "macos")]
|
||||
return "macos";
|
||||
#[cfg(target_os = "linux")]
|
||||
return "linux";
|
||||
#[cfg(target_os = "windows")]
|
||||
return "windows";
|
||||
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
pub fn get_host_arch() -> &'static str {
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
return "x86_64";
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
return "aarch64";
|
||||
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
/// Create OTLP resource attributes for client fingerprinting.
|
||||
pub fn create_fingerprint_attributes() -> Vec<KeyValue> {
|
||||
vec![
|
||||
KeyValue::new("client.id", get_client_id_hash()),
|
||||
KeyValue::new("os.type", get_os_type()),
|
||||
KeyValue::new("host.arch", get_host_arch()),
|
||||
]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_client_id_hash_is_stable() {
|
||||
let hash1 = get_client_id_hash();
|
||||
let hash2 = get_client_id_hash();
|
||||
assert_eq!(hash1, hash2);
|
||||
assert_eq!(hash1.len(), 64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_os_and_arch() {
|
||||
assert!(["macos", "linux", "windows", "unknown"].contains(&get_os_type()));
|
||||
assert!(["x86_64", "aarch64", "unknown"].contains(&get_host_arch()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fingerprint_attributes() {
|
||||
let attrs = create_fingerprint_attributes();
|
||||
assert_eq!(attrs.len(), 3);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
pub mod client_fingerprint;
|
||||
pub mod langfuse_layer;
|
||||
mod observation_layer;
|
||||
pub mod otlp_layer;
|
||||
pub mod rate_limiter;
|
||||
|
||||
pub use client_fingerprint::{
|
||||
create_fingerprint_attributes, get_client_id_hash, get_host_arch, get_os_type,
|
||||
};
|
||||
pub use langfuse_layer::{create_langfuse_observer, LangfuseBatchManager};
|
||||
pub use observation_layer::{
|
||||
flatten_metadata, map_level, BatchManager, ObservationLayer, SpanData, SpanTracker,
|
||||
|
||||
@@ -10,6 +10,8 @@ use tracing::{Level, Metadata};
|
||||
use tracing_opentelemetry::{MetricsLayer, OpenTelemetryLayer};
|
||||
use tracing_subscriber::filter::FilterFn;
|
||||
|
||||
use super::client_fingerprint::create_fingerprint_attributes;
|
||||
|
||||
pub type OtlpTracingLayer =
|
||||
OpenTelemetryLayer<tracing_subscriber::Registry, opentelemetry_sdk::trace::Tracer>;
|
||||
pub type OtlpMetricsLayer = MetricsLayer<tracing_subscriber::Registry>;
|
||||
@@ -55,12 +57,25 @@ impl OtlpConfig {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_otlp_tracing(config: &OtlpConfig) -> OtlpResult<()> {
|
||||
let resource = Resource::new(vec![
|
||||
/// Create an OpenTelemetry Resource with service info and client fingerprint.
|
||||
///
|
||||
/// Includes:
|
||||
/// - service.name, service.version, service.namespace (standard OTLP attributes)
|
||||
/// - client.id (hashed, privacy-safe unique identifier)
|
||||
/// - os.type (macos, linux, windows)
|
||||
/// - host.arch (x86_64, aarch64)
|
||||
fn create_resource() -> Resource {
|
||||
let mut attributes = vec![
|
||||
KeyValue::new("service.name", "goose"),
|
||||
KeyValue::new("service.version", env!("CARGO_PKG_VERSION")),
|
||||
KeyValue::new("service.namespace", "goose"),
|
||||
]);
|
||||
];
|
||||
attributes.extend(create_fingerprint_attributes());
|
||||
Resource::new(attributes)
|
||||
}
|
||||
|
||||
pub fn init_otlp_tracing(config: &OtlpConfig) -> OtlpResult<()> {
|
||||
let resource = create_resource();
|
||||
|
||||
let exporter = opentelemetry_otlp::SpanExporter::builder()
|
||||
.with_http()
|
||||
@@ -81,11 +96,7 @@ pub fn init_otlp_tracing(config: &OtlpConfig) -> OtlpResult<()> {
|
||||
}
|
||||
|
||||
pub fn init_otlp_metrics(config: &OtlpConfig) -> OtlpResult<()> {
|
||||
let resource = Resource::new(vec![
|
||||
KeyValue::new("service.name", "goose"),
|
||||
KeyValue::new("service.version", env!("CARGO_PKG_VERSION")),
|
||||
KeyValue::new("service.namespace", "goose"),
|
||||
]);
|
||||
let resource = create_resource();
|
||||
|
||||
let exporter = opentelemetry_otlp::MetricExporter::builder()
|
||||
.with_http()
|
||||
@@ -109,12 +120,7 @@ pub fn init_otlp_metrics(config: &OtlpConfig) -> OtlpResult<()> {
|
||||
|
||||
pub fn create_otlp_tracing_layer() -> OtlpResult<OtlpTracingLayer> {
|
||||
let config = OtlpConfig::from_config().ok_or("OTEL_EXPORTER_OTLP_ENDPOINT not configured")?;
|
||||
|
||||
let resource = Resource::new(vec![
|
||||
KeyValue::new("service.name", "goose"),
|
||||
KeyValue::new("service.version", env!("CARGO_PKG_VERSION")),
|
||||
KeyValue::new("service.namespace", "goose"),
|
||||
]);
|
||||
let resource = create_resource();
|
||||
|
||||
let exporter = opentelemetry_otlp::SpanExporter::builder()
|
||||
.with_http()
|
||||
@@ -138,12 +144,7 @@ pub fn create_otlp_tracing_layer() -> OtlpResult<OtlpTracingLayer> {
|
||||
|
||||
pub fn create_otlp_metrics_layer() -> OtlpResult<OtlpMetricsLayer> {
|
||||
let config = OtlpConfig::from_config().ok_or("OTEL_EXPORTER_OTLP_ENDPOINT not configured")?;
|
||||
|
||||
let resource = Resource::new(vec![
|
||||
KeyValue::new("service.name", "goose"),
|
||||
KeyValue::new("service.version", env!("CARGO_PKG_VERSION")),
|
||||
KeyValue::new("service.namespace", "goose"),
|
||||
]);
|
||||
let resource = create_resource();
|
||||
|
||||
let exporter = opentelemetry_otlp::MetricExporter::builder()
|
||||
.with_http()
|
||||
@@ -167,12 +168,7 @@ pub fn create_otlp_metrics_layer() -> OtlpResult<OtlpMetricsLayer> {
|
||||
|
||||
pub fn create_otlp_logs_layer() -> OtlpResult<OpenTelemetryTracingBridge<LoggerProvider, Logger>> {
|
||||
let config = OtlpConfig::from_config().ok_or("OTEL_EXPORTER_OTLP_ENDPOINT not configured")?;
|
||||
|
||||
let resource = Resource::new(vec![
|
||||
KeyValue::new("service.name", "goose"),
|
||||
KeyValue::new("service.version", env!("CARGO_PKG_VERSION")),
|
||||
KeyValue::new("service.namespace", "goose"),
|
||||
]);
|
||||
let resource = create_resource();
|
||||
|
||||
let exporter = opentelemetry_otlp::LogExporter::builder()
|
||||
.with_http()
|
||||
|
||||
Reference in New Issue
Block a user