Skip to content

Commit 3c682b5

Browse files
Merge pull request #376 from code0-tech/#353-open-telementry
added open telementry
2 parents 9322e3d + 2481fb1 commit 3c682b5

19 files changed

Lines changed: 964 additions & 309 deletions

Cargo.lock

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

Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ edition = "2024"
77
tokio = { version = "1.44.1", features = ["rt-multi-thread", "signal"] }
88
futures = "0.3.31"
99
log = "0.4.26"
10-
env_logger = "0.11.8"
10+
opentelemetry = { version = "0.32.0", features = ["metrics"] }
11+
tracing = { version = "0.1.41", features = ["log"] }
1112
prost = "0.14.1"
1213
tonic = "0.14.1"
1314
tucana = { version = "0.0.75", features = ["all"] }
14-
code0-flow = { version = "0.0.38", features = ["flow_health"] }
15+
code0-flow = { version = "0.0.40", features = ["flow_config", "flow_health", "flow_telemetry"] }
1516
serde_json = "1.0.140"
1617
async-nats = "0.49.0"
1718
tonic-health = "0.14.1"

aquila.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,17 @@ environment: development
66
# Valid values: static, dynamic.
77
mode: static
88

9-
# Default env_logger filter. RUST_LOG takes precedence when it is set.
9+
# Default tracing filter. RUST_LOG takes precedence when it is set.
1010
log_level: debug
1111

12+
# OTLP export for logs, traces, and the deliberately small metric set.
13+
opentelemetry:
14+
enabled: false
15+
service_name: aquila
16+
logs_endpoint:
17+
metrics_endpoint:
18+
traces_endpoint:
19+
1220
# NATS server and JetStream key-value bucket used for flow storage.
1321
nats:
1422
url: nats://localhost:4222

src/configuration/config.rs

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::{fmt, path::Path};
22

3+
use code0_flow::flow_telemetry::OpenTelemetry;
34
use config::{Config as ConfigLoader, ConfigError, File};
45
use serde::{Deserialize, Serialize};
56

@@ -14,6 +15,9 @@ pub struct Config {
1415
pub environment: Environment,
1516
pub mode: Mode,
1617
pub log_level: String,
18+
#[serde(alias = "telemetry")]
19+
#[serde(default = "default_opentelemetry")]
20+
pub opentelemetry: OpenTelemetry,
1721
pub nats: Nats,
1822
pub static_config: StaticConfig,
1923
pub dynamic_config: DynamicConfig,
@@ -78,6 +82,7 @@ impl Default for Config {
7882
environment: Environment::Development,
7983
mode: Mode::Static,
8084
log_level: "debug".into(),
85+
opentelemetry: default_opentelemetry(),
8186
nats: Nats::default(),
8287
static_config: StaticConfig::default(),
8388
dynamic_config: DynamicConfig::default(),
@@ -87,6 +92,13 @@ impl Default for Config {
8792
}
8893
}
8994

95+
fn default_opentelemetry() -> OpenTelemetry {
96+
OpenTelemetry {
97+
service_name: env!("CARGO_PKG_NAME").into(),
98+
..OpenTelemetry::default()
99+
}
100+
}
101+
90102
impl Default for Nats {
91103
fn default() -> Self {
92104
Self {
@@ -175,6 +187,28 @@ impl fmt::Display for Config {
175187
writeln!(formatter, " Environment: {}", self.environment)?;
176188
writeln!(formatter, " Mode: {}", self.mode)?;
177189
writeln!(formatter, " Log level: {}", self.log_level)?;
190+
writeln!(formatter, " OpenTelemetry")?;
191+
writeln!(formatter, " Enabled: {}", self.opentelemetry.enabled)?;
192+
writeln!(
193+
formatter,
194+
" Service: {}",
195+
self.opentelemetry.service_name
196+
)?;
197+
writeln!(
198+
formatter,
199+
" Logs: {}",
200+
display_optional_url(&self.opentelemetry.logs_endpoint)
201+
)?;
202+
writeln!(
203+
formatter,
204+
" Metrics: {}",
205+
display_optional_url(&self.opentelemetry.metrics_endpoint)
206+
)?;
207+
writeln!(
208+
formatter,
209+
" Traces: {}",
210+
display_optional_url(&self.opentelemetry.traces_endpoint)
211+
)?;
178212
writeln!(formatter, " NATS")?;
179213
writeln!(formatter, " URL: {}", self.nats.url)?;
180214
writeln!(formatter, " Bucket: {}", self.nats.bucket)?;
@@ -222,11 +256,21 @@ impl fmt::Display for Config {
222256
}
223257
}
224258

259+
fn display_optional_url(url: &Option<String>) -> &str {
260+
url.as_deref()
261+
.filter(|value| !value.trim().is_empty())
262+
.unwrap_or("<disabled>")
263+
}
264+
225265
#[cfg(test)]
226266
mod tests {
227267
use std::sync::Mutex;
228268

229-
use super::Config;
269+
use config::Config as ConfigLoader;
270+
271+
use code0_flow::flow_telemetry::OpenTelemetry;
272+
273+
use super::{Config, default_opentelemetry};
230274

231275
static ENV_LOCK: Mutex<()> = Mutex::new(());
232276

@@ -275,4 +319,39 @@ mod tests {
275319
assert!(!output.contains("super-secret"));
276320
assert!(!output.contains("Config {"));
277321
}
322+
323+
#[test]
324+
fn opentelemetry_endpoints_are_enabled_by_presence() {
325+
let config: OpenTelemetry = ConfigLoader::builder()
326+
.add_source(
327+
ConfigLoader::try_from(&default_opentelemetry())
328+
.expect("default telemetry config should serialize"),
329+
)
330+
.set_override("enabled", true)
331+
.expect("enabled override should apply")
332+
.set_override("service_name", "sagittarius")
333+
.expect("service name override should apply")
334+
.set_override("logs_endpoint", "")
335+
.expect("logs override should apply")
336+
.set_override("metrics_endpoint", " ")
337+
.expect("metrics override should apply")
338+
.set_override("traces_endpoint", "http://localhost:4317")
339+
.expect("traces override should apply")
340+
.build()
341+
.expect("telemetry config should build")
342+
.try_deserialize()
343+
.expect("telemetry config should deserialize");
344+
345+
assert!(config.enabled);
346+
assert_eq!(config.service_name, "sagittarius");
347+
assert_eq!(config.logs_endpoint(), None);
348+
assert_eq!(config.metrics_endpoint(), None);
349+
assert_eq!(config.traces_endpoint(), Some("http://localhost:4317"));
350+
assert!(config.has_enabled_exporter());
351+
}
352+
353+
#[test]
354+
fn opentelemetry_default_service_name_is_aquila() {
355+
assert_eq!(Config::default().opentelemetry.service_name, "aquila");
356+
}
278357
}

src/configuration/service.rs

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ impl From<SerializableServiceConfiguration> for ServiceConfiguration {
8888
fn from(value: SerializableServiceConfiguration) -> Self {
8989
Self {
9090
actions: value.actions.into_iter().map(Into::into).collect(),
91-
runtimes: value.runtimes.into_iter().map(Into::into).collect(),
91+
runtimes: value.runtimes.into_iter().collect(),
9292
}
9393
}
9494
}
@@ -116,25 +116,17 @@ impl ServiceConfiguration {
116116
None => return false,
117117
};
118118

119-
match self
120-
.runtimes
119+
self.runtimes
121120
.iter()
122121
.find(|x| &x.token == token && x.identifier == name)
123-
{
124-
Some(_) => true,
125-
None => false,
126-
}
122+
.is_some()
127123
}
128124

129125
pub fn has_action(&self, token: &String, action_name: &String) -> bool {
130-
match self
131-
.actions
126+
self.actions
132127
.iter()
133128
.find(|x| &x.token == token && &x.service_name == action_name)
134-
{
135-
Some(_) => true,
136-
None => false,
137-
}
129+
.is_some()
138130
}
139131

140132
pub fn get_action_configuration(
@@ -185,9 +177,9 @@ impl ServiceConfiguration {
185177
"Couldn't parse service configuration file, Reason: {}. Starting with empty service configuration",
186178
error
187179
);
188-
return ServiceConfiguration::default();
180+
ServiceConfiguration::default()
189181
}
190-
};
182+
}
191183
}
192184
}
193185

src/main.rs

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ pub mod flow;
99
pub mod sagittarius;
1010
pub mod server;
1111
pub mod startup;
12+
pub mod telemetry;
1213

1314
const CONFIG_PATH_ENV: &str = "AQUILA_CONFIG_PATH";
1415
const SERVICE_CONFIG_PATH_ENV: &str = "AQUILA_SERVICE_CONFIG_PATH";
@@ -26,12 +27,30 @@ async fn main() {
2627
.as_ref()
2728
.map(|config| config.log_level.as_str())
2829
.unwrap_or("debug");
29-
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(log_level)).init();
30+
let telemetry_config = config_result
31+
.as_ref()
32+
.map(|config| config.opentelemetry.clone())
33+
.unwrap_or_default();
34+
let environment = config_result
35+
.as_ref()
36+
.map(|config| config.environment.to_string())
37+
.unwrap_or_else(|_| "unknown".into());
38+
let telemetry = telemetry::Telemetry::initialize(
39+
&telemetry_config,
40+
telemetry::TelemetrySettings {
41+
environment: &environment,
42+
default_log_level: log_level,
43+
service_version: env!("CARGO_PKG_VERSION"),
44+
instrumentation_name: env!("CARGO_PKG_NAME"),
45+
initialize_metrics: Some(telemetry::metrics::initialize),
46+
},
47+
)
48+
.unwrap_or_else(|error| panic!("failed to initialize telemetry: {error}"));
3049
install_panic_logging();
3150

3251
let config = config_result
3352
.unwrap_or_else(|error| panic!("failed to load Aquila configuration: {error}"));
34-
log::info!("Starting Aquila");
53+
log::info!("Starting Aquila runtime gateway");
3554

3655
let app_readiness = AppReadiness::new();
3756
let service_config = std::env::var_os(SERVICE_CONFIG_PATH_ENV)
@@ -40,6 +59,7 @@ async fn main() {
4059
log::debug!("{config}");
4160

4261
startup::run(config, app_readiness, service_config).await;
62+
telemetry.shutdown();
4363
}
4464

4565
fn install_panic_logging() {
@@ -52,15 +72,17 @@ fn install_panic_logging() {
5272
"<non-string panic payload>"
5373
};
5474

55-
match panic_info.location() {
56-
Some(location) => log::error!(
57-
"Process panic message={} file={} line={} column={}",
58-
message,
59-
location.file(),
60-
location.line(),
61-
location.column()
62-
),
63-
None => log::error!("Process panic message={} location=unknown", message),
64-
}
75+
let location = panic_info
76+
.location()
77+
.map(|location| {
78+
format!(
79+
"{}:{}:{}",
80+
location.file(),
81+
location.line(),
82+
location.column()
83+
)
84+
})
85+
.unwrap_or_else(|| "unknown".into());
86+
telemetry::errors::panic(message, &location);
6587
}));
6688
}

src/sagittarius/flow_service_client_impl.rs

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use crate::{authorization::authorization::get_authorization_metadata, flow::get_flow_identifier};
1+
use crate::{
2+
authorization::authorization::get_authorization_metadata, flow::get_flow_identifier,
3+
telemetry::metrics,
4+
};
25
use futures::{StreamExt, TryStreamExt};
36
use prost::Message;
47
use std::{path::Path, sync::Arc};
@@ -181,8 +184,10 @@ impl SagittariusFlowClient {
181184
}
182185

183186
if deleted_count == 0 {
187+
metrics::flow_operation("delete", "not_found", 1);
184188
log::warn!("Flow deletion matched no stored keys flow_id={}", id);
185189
} else {
190+
metrics::flow_operation("delete", "success", 1);
186191
log::info!(
187192
"Flow deleted successfully id={} deleted_keys={}",
188193
id,
@@ -192,16 +197,22 @@ impl SagittariusFlowClient {
192197
}
193198
Data::UpdatedFlow(flow) => {
194199
let key = get_flow_identifier(&flow);
195-
let flow_id = flow.flow_id.clone();
200+
let flow_id = flow.flow_id;
196201
let bytes = flow.encode_to_vec();
197202
match self.store.put(key.clone(), bytes.into()).await {
198-
Ok(_) => log::info!("Stored flow update flow_id={} key={}", flow_id, key),
199-
Err(err) => log::error!(
200-
"Failed to store flow update flow_id={} key={} error={:?}",
201-
flow_id,
202-
key,
203-
err
204-
),
203+
Ok(_) => {
204+
metrics::flow_operation("update", "success", 1);
205+
log::info!("Stored flow update flow_id={} key={}", flow_id, key)
206+
}
207+
Err(err) => {
208+
metrics::flow_operation("update", "failure", 1);
209+
log::error!(
210+
"Failed to store flow update flow_id={} key={} error={:?}",
211+
flow_id,
212+
key,
213+
err
214+
)
215+
}
205216
};
206217
}
207218
Data::Flows(flows) => {
@@ -256,6 +267,12 @@ impl SagittariusFlowClient {
256267
purged_count,
257268
stored_count
258269
);
270+
metrics::flow_operation("replace", "success", stored_count as u64);
271+
metrics::flow_operation(
272+
"replace",
273+
"failure",
274+
received_count.saturating_sub(stored_count) as u64,
275+
);
259276
}
260277
Data::ModuleConfigurations(action_configurations) => {
261278
let (project_count, config_count) = module_config_stats(&action_configurations);
@@ -290,13 +307,16 @@ impl SagittariusFlowClient {
290307

291308
let response = match self.client.update(request).await {
292309
Ok(res) => {
293-
log::info!("Successfully established a Stream (for Flows)");
310+
log::info!("Sagittarius flow synchronization stream established");
294311
self.sagittarius_ready.store(true, Ordering::SeqCst);
295312
res
296313
}
297314
Err(status) => {
298315
self.sagittarius_ready.store(false, Ordering::SeqCst);
299-
log::warn!("Failed to establish Flow stream: {:?}", status);
316+
log::warn!(
317+
"Sagittarius flow synchronization stream connection failed status={:?}",
318+
status
319+
);
300320
return Err(status);
301321
}
302322
};
@@ -310,15 +330,18 @@ impl SagittariusFlowClient {
310330
}
311331
Err(status) => {
312332
self.sagittarius_ready.store(false, Ordering::SeqCst);
313-
log::warn!("Flow stream error (will reconnect): {:?}", status);
333+
log::warn!(
334+
"Sagittarius flow synchronization stream failed; reconnecting status={:?}",
335+
status
336+
);
314337
return Err(status);
315338
}
316339
};
317340
}
318341

319342
// Stream ended without an explicit error
320343
self.sagittarius_ready.store(false, Ordering::SeqCst);
321-
log::warn!("Flow stream ended (server closed). Will reconnect.");
344+
log::warn!("Sagittarius closed the flow synchronization stream; reconnecting");
322345
Err(tonic::Status::unavailable("flow stream ended"))
323346
}
324347
}

0 commit comments

Comments
 (0)