Skip to content

Commit 16534a4

Browse files
authored
Move usage_reporting under telemetry.hive and reuse options (#668)
**Before** ```yaml usage_reporting: enabled: true access_token: "your-hive-token" # Duplicated target_id: "org/project/target" # Duplicated # Client identification was tied only to usage reporting client_name_header: "x-client-name" client_version_header: "x-client-version" sample_rate: "50%" exclude: ["IntrospectionQuery"] telemetry: hive: token: "your-hive-token" # Duplicated target: "org/project/target" # Duplicated tracing: enabled: true ``` **After** ```yaml telemetry: # Client identification is now global for all telemetry (traces, usage, etc.) client_identification: name_header: "x-client-name" version_header: "x-client-version" hive: # Credentials are defined once and shared by both usage and tracing token: "your-hive-token" target: "org/project/target" usage_reporting: enabled: true sample_rate: "50%" exclude: ["IntrospectionQuery"] tracing: enabled: true ```
1 parent bbe3598 commit 16534a4

File tree

16 files changed

+348
-206
lines changed

16 files changed

+348
-206
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
config: minor
3+
router: minor
4+
internal: minor
5+
---
6+
7+
# Unified Hive Telemetry Configuration
8+
9+
Refactored the configuration structure to unify Hive-specific telemetry (tracing and usage reporting) and centralize client identification.
10+
11+
- **Unified Hive Config**: Moved `usage_reporting` under `telemetry.hive.usage`. Usage reporting now shares the `token` and `target` configuration with Hive tracing, eliminating redundant settings.
12+
- **Centralized Client Identification**: Introduced `telemetry.client_identification` to define client name and version headers once. These are now propagated to both OpenTelemetry spans and Hive usage reports.
13+
- **Enhanced Expression Support**: Both Hive token and target ID now support VRL expressions for usage reporting, matching the existing behavior of tracing.
14+
15+
### Breaking Changes:
16+
17+
The top-level `usage_reporting` block has been moved.
18+
19+
**Before:**
20+
```yaml
21+
usage_reporting:
22+
enabled: true
23+
access_token: "..."
24+
target_id: "..."
25+
client_name_header: "..."
26+
client_version_header: "..."
27+
```
28+
29+
**After:**
30+
```yaml
31+
telemetry:
32+
client_identification:
33+
name_header: "..."
34+
version_header: "..."
35+
hive:
36+
token: "..."
37+
target: "..."
38+
usage:
39+
enabled: true
40+
```

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bin/router/src/lib.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use crate::{
1919
probes::{health_check_handler, readiness_check_handler},
2020
},
2121
jwt::JwtAuthRuntime,
22-
pipeline::{graphql_request_handler, usage_reporting::init_hive_user_agent},
22+
pipeline::{graphql_request_handler, usage_reporting::init_hive_usage_agent},
2323
telemetry::HeaderExtractor,
2424
};
2525

@@ -129,12 +129,11 @@ pub async fn configure_app_from_config(
129129
false => None,
130130
};
131131

132-
let hive_usage_agent = match router_config.usage_reporting.enabled {
133-
true => Some(init_hive_user_agent(
134-
bg_tasks_manager,
135-
&router_config.usage_reporting,
136-
)?),
137-
false => None,
132+
let hive_usage_agent = match router_config.telemetry.hive.as_ref() {
133+
Some(hive_config) if hive_config.usage_reporting.enabled => {
134+
Some(init_hive_usage_agent(bg_tasks_manager, hive_config)?)
135+
}
136+
_ => None,
138137
};
139138

140139
let router_config_arc = Arc::new(router_config);

bin/router/src/pipeline/mod.rs

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,26 @@ pub async fn execute_pipeline(
154154
let operation_span = GraphQLOperationSpan::new();
155155
async {
156156
let start = Instant::now();
157+
let client_name = req
158+
.headers()
159+
.get(
160+
&shared_state
161+
.router_config
162+
.telemetry
163+
.client_identification
164+
.name_header,
165+
)
166+
.and_then(|v| v.to_str().ok());
167+
let client_version = req
168+
.headers()
169+
.get(
170+
&shared_state
171+
.router_config
172+
.telemetry
173+
.client_identification
174+
.version_header,
175+
)
176+
.and_then(|v| v.to_str().ok());
157177
perform_csrf_prevention(req, &shared_state.router_config.csrf)?;
158178
let jwt_request_details = match &shared_state.jwt_auth_runtime {
159179
Some(jwt_auth_runtime) => match jwt_auth_runtime
@@ -179,6 +199,7 @@ pub async fn execute_pipeline(
179199

180200
operation_span.record_document(&parser_payload.minified_document);
181201
operation_span.record_operation_identity((&parser_payload).into());
202+
operation_span.record_client_identity(client_name, client_version);
182203
operation_span.record_hive_operation_hash(&parser_payload.hive_operation_hash);
183204

184205
validate_operation_with_cache(supergraph, schema_state, shared_state, &parser_payload)
@@ -254,19 +275,29 @@ pub async fn execute_pipeline(
254275
)
255276
.await?;
256277

257-
if shared_state.router_config.usage_reporting.enabled {
258-
if let Some(hive_usage_agent) = &shared_state.hive_usage_agent {
259-
usage_reporting::collect_usage_report(
260-
supergraph.supergraph_schema.clone(),
261-
start.elapsed(),
262-
req,
263-
&client_request_details,
264-
hive_usage_agent,
265-
&shared_state.router_config.usage_reporting,
266-
&execution_result,
267-
)
268-
.await;
269-
}
278+
if let Some(hive_usage_agent) = &shared_state.hive_usage_agent {
279+
usage_reporting::collect_usage_report(
280+
supergraph.supergraph_schema.clone(),
281+
start.elapsed(),
282+
client_name,
283+
client_version,
284+
&client_request_details,
285+
hive_usage_agent,
286+
shared_state
287+
.router_config
288+
.telemetry
289+
.hive
290+
.as_ref()
291+
.map(|c| &c.usage_reporting)
292+
.expect(
293+
// SAFETY: According to `configure_app_from_config` in `bin/router/src/lib.rs`,
294+
// the UsageAgent is only created when usage reporting is enabled.
295+
// Thus, this expect should never panic.
296+
"Expected Usage Reporting options to be present when Hive Usage Agent is initialized",
297+
),
298+
&execution_result,
299+
)
300+
.await;
270301
}
271302

272303
Ok(execution_result)

bin/router/src/pipeline/usage_reporting.rs

Lines changed: 44 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@ use async_trait::async_trait;
77
use graphql_tools::parser::schema::Document;
88
use hive_console_sdk::agent::usage_agent::{AgentError, UsageAgentExt};
99
use hive_console_sdk::agent::usage_agent::{ExecutionReport, UsageAgent};
10+
use hive_router_config::telemetry::hive::{
11+
is_slug_target_ref, is_uuid_target_ref, HiveTelemetryConfig,
12+
};
1013
use hive_router_config::usage_reporting::UsageReportingConfig;
14+
use hive_router_internal::telemetry::resolve_value_or_expression;
1115
use hive_router_plan_executor::execution::{
1216
client_request_details::ClientRequestDetails, plan::PlanExecutionOutput,
1317
};
14-
use ntex::web::HttpRequest;
18+
1519
use rand::Rng;
1620
use tokio_util::sync::CancellationToken;
1721

@@ -22,47 +26,71 @@ use crate::{
2226

2327
#[derive(Debug, thiserror::Error)]
2428
pub enum UsageReportingError {
25-
#[error("Usage Reporting - Access token is missing. Please provide it via 'HIVE_ACCESS_TOKEN' environment variable or under 'usage_reporting.access_token' in the configuration.")]
29+
#[error("Usage Reporting - Access token is missing. Please provide it via 'HIVE_ACCESS_TOKEN' environment variable or under 'telemetry.hive.token' in the configuration.")]
2630
MissingAccessToken,
2731
#[error("Usage Reporting - Failed to initialize usage agent: {0}")]
2832
AgentCreationError(#[from] AgentError),
33+
#[error("Usage Reporting - Configuration error: {0}")]
34+
ConfigurationError(String),
2935
}
3036

31-
pub fn init_hive_user_agent(
37+
pub fn init_hive_usage_agent(
3238
bg_tasks_manager: &mut BackgroundTasksManager,
33-
usage_config: &UsageReportingConfig,
39+
hive_config: &HiveTelemetryConfig,
3440
) -> Result<UsageAgent, UsageReportingError> {
41+
let usage_config = &hive_config.usage_reporting;
3542
let user_agent = format!("hive-router/{}", ROUTER_VERSION);
36-
let access_token = usage_config
37-
.access_token
38-
.as_deref()
39-
.ok_or(UsageReportingError::MissingAccessToken)?;
43+
let access_token = match &hive_config.token {
44+
Some(t) => resolve_value_or_expression(t, "Hive Telemetry token")
45+
.map_err(|e| UsageReportingError::ConfigurationError(e.to_string()))?,
46+
None => return Err(UsageReportingError::MissingAccessToken),
47+
};
48+
49+
let target = match &hive_config.target {
50+
Some(t) => Some(
51+
resolve_value_or_expression(t, "Hive Telemetry target")
52+
.map_err(|e| UsageReportingError::ConfigurationError(e.to_string()))?,
53+
),
54+
None => None,
55+
};
4056

41-
let mut agent = UsageAgent::builder()
57+
if let Some(target) = &target {
58+
if !is_uuid_target_ref(target) && !is_slug_target_ref(target) {
59+
return Err(UsageReportingError::ConfigurationError(format!(
60+
"Invalid Hive Telemetry target format: '{}'. It must be either in slug format '$organizationSlug/$projectSlug/$targetSlug' or UUID format 'a0f4c605-6541-4350-8cfe-b31f21a4bf80'",
61+
target
62+
)));
63+
}
64+
}
65+
66+
let mut agent_builder = UsageAgent::builder()
4267
.user_agent(user_agent)
4368
.endpoint(usage_config.endpoint.clone())
44-
.token(access_token.to_string())
69+
.token(access_token)
4570
.buffer_size(usage_config.buffer_size)
4671
.connect_timeout(usage_config.connect_timeout)
4772
.request_timeout(usage_config.request_timeout)
4873
.accept_invalid_certs(usage_config.accept_invalid_certs)
4974
.flush_interval(usage_config.flush_interval);
5075

51-
if let Some(target_id) = usage_config.target_id.as_ref() {
52-
agent = agent.target_id(target_id.clone());
76+
if let Some(target_id) = target {
77+
agent_builder = agent_builder.target_id(target_id);
5378
}
5479

55-
let agent = agent.build()?;
80+
let agent = agent_builder.build()?;
5681

5782
bg_tasks_manager.register_task(agent.clone());
5883
Ok(agent)
5984
}
6085

86+
// TODO: simplfy args
87+
#[allow(clippy::too_many_arguments)]
6188
#[inline]
6289
pub async fn collect_usage_report<'a>(
6390
schema: Arc<Document<'static, String>>,
6491
duration: Duration,
65-
req: &HttpRequest,
92+
client_name: Option<&str>,
93+
client_version: Option<&str>,
6694
client_request_details: &ClientRequestDetails<'a, 'a>,
6795
hive_usage_agent: &UsageAgent,
6896
usage_config: &UsageReportingConfig,
@@ -79,16 +107,14 @@ pub async fn collect_usage_report<'a>(
79107
{
80108
return;
81109
}
82-
let client_name = get_header_value(req, &usage_config.client_name_header);
83-
let client_version = get_header_value(req, &usage_config.client_version_header);
84110
let timestamp = SystemTime::now()
85111
.duration_since(UNIX_EPOCH)
86112
.unwrap()
87113
.as_millis() as u64;
88114
let execution_report = ExecutionReport {
89115
schema,
90-
client_name: client_name.map(|s| s.to_owned()),
91-
client_version: client_version.map(|s| s.to_owned()),
116+
client_name: client_name.map(|name| name.to_string()),
117+
client_version: client_version.map(|version| version.to_string()),
92118
timestamp,
93119
duration,
94120
ok: execution_result.error_count == 0,
@@ -106,11 +132,6 @@ pub async fn collect_usage_report<'a>(
106132
}
107133
}
108134

109-
#[inline]
110-
fn get_header_value<'req>(req: &'req HttpRequest, header_name: &str) -> Option<&'req str> {
111-
req.headers().get(header_name).and_then(|v| v.to_str().ok())
112-
}
113-
114135
#[async_trait]
115136
impl BackgroundTask for UsageAgent {
116137
fn id(&self) -> &str {

0 commit comments

Comments
 (0)