Skip to content

Commit a38f051

Browse files
Merge pull request #254 from code0-tech/#228-add-open-telemetry
add open telemetry
2 parents 486f9a9 + b6ed5d9 commit a38f051

13 files changed

Lines changed: 779 additions & 14 deletions

File tree

Cargo.lock

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

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ code0-flow = { version = "0.0.40" }
1212
tucana = { version = "0.0.75" }
1313
tokio = { version = "1.44.1", features = ["rt-multi-thread", "signal"] }
1414
log = "0.4.27"
15+
opentelemetry = { version = "0.32.0", features = ["metrics"] }
1516
futures-lite = "2.6.0"
1617
rand = "0.10.0"
1718
base64 = "0.22.1"

crates/taurus/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ version.workspace = true
44
edition.workspace = true
55

66
[dependencies]
7-
code0-flow = { workspace = true, features = ["flow_service", "flow_health", "flow_config"] }
7+
code0-flow = { workspace = true, features = ["flow_service", "flow_health", "flow_config", "flow_telemetry"] }
88
tucana = { workspace = true }
99
tokio = { workspace = true }
1010
log = { workspace = true }
11+
opentelemetry = { workspace = true }
1112
futures-lite ={ workspace = true }
1213
rand = { workspace = true }
1314
base64 = { workspace = true }
14-
env_logger = { workspace = true }
1515
async-nats = { workspace = true }
1616
prost = { workspace = true }
1717
tonic-health = { workspace = true }

crates/taurus/src/app/mod.rs

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
mod worker;
22

3+
use code0_flow::flow_config::environment::Environment;
34
use code0_flow::flow_config::load_env_file;
4-
use code0_flow::flow_config::mode::Mode::DYNAMIC;
5+
use code0_flow::flow_config::mode::Mode::{DYNAMIC, STATIC};
56
use code0_flow::flow_definition::Reader;
67
use code0_flow::flow_service::FlowUpdateService;
78
use std::sync::Arc;
@@ -18,12 +19,14 @@ use tucana::shared::module_status::StatusVariant;
1819
use crate::client::runtime_execution::TaurusRuntimeExecutionService;
1920
use crate::client::runtime_status::TaurusRuntimeStatusService;
2021
use crate::config::Config;
22+
use crate::telemetry::{self, TelemetrySettings, errors};
2123

2224
pub async fn run() {
23-
init_logging();
2425
load_env_file();
2526

2627
let config = Config::new();
28+
let telemetry = init_telemetry(&config);
29+
install_panic_logging();
2730
let engine = ExecutionEngine::new();
2831
let client = connect_nats(&config).await;
2932

@@ -42,6 +45,7 @@ pub async fn run() {
4245
nats_remote,
4346
runtime_emitter,
4447
runtime_execution_service,
48+
mode_label(&config).to_string(),
4549
);
4650

4751
wait_for_shutdown(&mut worker_task, &mut health_task).await;
@@ -51,17 +55,72 @@ pub async fn run() {
5155
&& !err.is_cancelled()
5256
{
5357
log::warn!("Runtime status heartbeat task ended unexpectedly: {}", err);
58+
errors::record(
59+
"task",
60+
"runtime_status_heartbeat.task",
61+
&err,
62+
"mode=dynamic",
63+
);
5464
}
5565
}
5666
update_stopped_status(runtime_status_service.as_ref()).await;
5767

5868
log::info!("Taurus shutdown complete");
69+
telemetry.shutdown();
5970
}
6071

61-
fn init_logging() {
62-
env_logger::Builder::from_default_env()
63-
.filter_level(log::LevelFilter::Debug)
64-
.init();
72+
fn init_telemetry(config: &Config) -> telemetry::Telemetry {
73+
telemetry::Telemetry::initialize(
74+
&config.opentelemetry,
75+
TelemetrySettings {
76+
environment: environment_label(&config.environment),
77+
default_log_level: "debug",
78+
service_version: env!("CARGO_PKG_VERSION"),
79+
instrumentation_name: env!("CARGO_PKG_NAME"),
80+
initialize_metrics: Some(telemetry::metrics::initialize),
81+
},
82+
)
83+
.unwrap_or_else(|error| panic!("failed to initialize telemetry: {error}"))
84+
}
85+
86+
fn install_panic_logging() {
87+
std::panic::set_hook(Box::new(move |panic_info| {
88+
let message = if let Some(message) = panic_info.payload().downcast_ref::<&str>() {
89+
*message
90+
} else if let Some(message) = panic_info.payload().downcast_ref::<String>() {
91+
message.as_str()
92+
} else {
93+
"<non-string panic payload>"
94+
};
95+
96+
let location = panic_info
97+
.location()
98+
.map(|location| {
99+
format!(
100+
"{}:{}:{}",
101+
location.file(),
102+
location.line(),
103+
location.column()
104+
)
105+
})
106+
.unwrap_or_else(|| "unknown".into());
107+
errors::panic(message, &location);
108+
}));
109+
}
110+
111+
fn environment_label(environment: &Environment) -> &'static str {
112+
match environment {
113+
Environment::Development => "development",
114+
Environment::Staging => "staging",
115+
Environment::Production => "production",
116+
}
117+
}
118+
119+
fn mode_label(config: &Config) -> &'static str {
120+
match config.mode {
121+
STATIC => "static",
122+
DYNAMIC => "dynamic",
123+
}
65124
}
66125

67126
async fn connect_nats(config: &Config) -> async_nats::Client {
@@ -71,6 +130,7 @@ async fn connect_nats(config: &Config) -> async_nats::Client {
71130
client
72131
}
73132
Err(err) => {
133+
errors::record("transport", "nats.connect", &err, "component=nats");
74134
panic!("Failed to connect to NATS server: {}", err);
75135
}
76136
}
@@ -86,6 +146,12 @@ fn spawn_health_task(config: &Config) -> Option<JoinHandle<()>> {
86146
Ok(address) => address,
87147
Err(err) => {
88148
log::error!("Failed to parse gRPC address: {:?}", err);
149+
errors::record(
150+
"configuration",
151+
"health.address.parse",
152+
&err,
153+
"service=health",
154+
);
89155
return None;
90156
}
91157
};
@@ -98,6 +164,7 @@ fn spawn_health_task(config: &Config) -> Option<JoinHandle<()>> {
98164
.await
99165
{
100166
log::error!("Health server error: {:?}", err);
167+
errors::record("server", "health.serve", &err, "service=health");
101168
} else {
102169
log::info!("Health server stopped gracefully");
103170
}
@@ -196,6 +263,12 @@ fn read_module_status_identifiers(definition_path: &str) -> Vec<String> {
196263
"Failed to read module definitions for runtime status: {:?}",
197264
err
198265
);
266+
errors::record_message(
267+
"configuration",
268+
"definitions.read_modules",
269+
format!("{err:?}"),
270+
format!("path={definition_path}"),
271+
);
199272
Vec::new()
200273
}
201274
}

crates/taurus/src/app/worker.rs

Lines changed: 108 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use std::time::Instant;
2-
31
use futures_lite::StreamExt;
42
use prost::Message;
53
use taurus_core::runtime::engine::{EmitType, ExecutionEngine, ExecutionId, RespondEmitter};
@@ -14,13 +12,15 @@ use tucana::shared::execution_result;
1412
use tucana::shared::{ExecutionFlow, ExecutionResult, NodeExecutionResult, Value};
1513

1614
use crate::client::runtime_execution::TaurusRuntimeExecutionService;
15+
use crate::telemetry::{errors, metrics};
1716

1817
pub fn spawn_worker(
1918
client: async_nats::Client,
2019
engine: ExecutionEngine,
2120
nats_remote: NATSRemoteRuntime,
2221
runtime_emitter: NATSRespondEmitter,
2322
mut runtime_execution_service: Option<TaurusRuntimeExecutionService>,
23+
flow_type: String,
2424
) -> JoinHandle<()> {
2525
tokio::spawn(async move {
2626
let mut execution_subscription = match client
@@ -33,6 +33,12 @@ pub fn spawn_worker(
3333
}
3434
Err(err) => {
3535
log::error!("Failed to subscribe to 'execution.*': {:?}", err);
36+
errors::record(
37+
"transport",
38+
"nats.subscribe",
39+
&err,
40+
"subject=execution.* queue=taurus",
41+
);
3642
return;
3743
}
3844
};
@@ -50,6 +56,7 @@ pub fn spawn_worker(
5056
&nats_remote,
5157
&runtime_emitter,
5258
runtime_execution_service.as_mut(),
59+
flow_type.as_str(),
5360
).await;
5461
}
5562
None => {
@@ -71,6 +78,7 @@ async fn process_execution_message(
7178
nats_remote: &NATSRemoteRuntime,
7279
runtime_emitter: &NATSRespondEmitter,
7380
mut runtime_execution_service: Option<&mut TaurusRuntimeExecutionService>,
81+
flow_type: &str,
7482
) {
7583
let requested_execution_id = parse_execution_id_from_subject(&message.subject, "execution")
7684
.unwrap_or_else(|| {
@@ -91,6 +99,16 @@ async fn process_execution_message(
9199
err,
92100
&message.payload
93101
);
102+
errors::record(
103+
"serialization",
104+
"execution_flow.decode",
105+
&err,
106+
format!(
107+
"subject={} payload_bytes={}",
108+
message.subject,
109+
message.payload.len()
110+
),
111+
);
94112
if let Some(execution_service) = runtime_execution_service.as_mut() {
95113
execution_service
96114
.update_runtime_execution(build_decode_error_result(requested_execution_id))
@@ -101,6 +119,7 @@ async fn process_execution_message(
101119
};
102120

103121
let flow_id = flow.flow_id;
122+
let function_identifiers = function_identifiers_by_node_id(&flow);
104123
// Taurus app forwards all lifecycle events to emitter.
105124
// Direct request/reply responses remain disabled; delivery is emitter-only.
106125
let respond_emitter = |execution_id, emit_type: EmitType, value: Value| {
@@ -112,6 +131,8 @@ async fn process_execution_message(
112131
engine,
113132
Some(nats_remote),
114133
Some(&respond_emitter),
134+
flow_type,
135+
function_identifiers,
115136
)
116137
.await;
117138
log::debug!(
@@ -153,9 +174,12 @@ async fn execute_flow(
153174
engine: &ExecutionEngine,
154175
remote: Option<&dyn RemoteRuntime>,
155176
respond_emitter: Option<&dyn RespondEmitter>,
177+
flow_type: &str,
178+
function_identifiers: std::collections::HashMap<i64, String>,
156179
) -> FlowRunResult {
157180
let started_at = now_unix_micros();
158181
let flow_id = flow.flow_id;
182+
let project_id = flow.project_id;
159183
let input = flow.input_value.clone();
160184
let report = engine
161185
.execute_flow_with_execution_id_report_async(
@@ -167,6 +191,16 @@ async fn execute_flow(
167191
)
168192
.await;
169193
let finished_at = now_unix_micros();
194+
record_flow_metrics(
195+
flow_id,
196+
project_id,
197+
flow_type,
198+
started_at,
199+
finished_at,
200+
&report.signal,
201+
&report.node_execution_results,
202+
&function_identifiers,
203+
);
170204

171205
FlowRunResult {
172206
execution_id,
@@ -179,6 +213,67 @@ async fn execute_flow(
179213
}
180214
}
181215

216+
fn function_identifiers_by_node_id(flow: &ExecutionFlow) -> std::collections::HashMap<i64, String> {
217+
flow.node_functions
218+
.iter()
219+
.filter_map(|function| {
220+
function
221+
.database_id
222+
.map(|id| (id, function.runtime_function_id.clone()))
223+
})
224+
.collect()
225+
}
226+
227+
fn record_flow_metrics(
228+
flow_id: i64,
229+
project_id: i64,
230+
flow_type: &str,
231+
started_at: i64,
232+
finished_at: i64,
233+
signal: &Signal,
234+
node_execution_results: &[NodeExecutionResult],
235+
function_identifiers: &std::collections::HashMap<i64, String>,
236+
) {
237+
metrics::flow_execution(metrics::FlowExecution {
238+
flow_id,
239+
project_id,
240+
flow_type,
241+
outcome: signal_outcome(signal),
242+
duration_seconds: metrics::duration_seconds(started_at, finished_at),
243+
});
244+
245+
for result in node_execution_results {
246+
let node_id = metrics::result_node_id(result);
247+
let function_identifier = metrics::result_function_identifier(result)
248+
.map(ToOwned::to_owned)
249+
.or_else(|| node_id.and_then(|id| function_identifiers.get(&id).cloned()))
250+
.unwrap_or_else(|| "unknown".to_string());
251+
let (error_code, error_category) = metrics::node_result_error(result);
252+
253+
metrics::function_execution(metrics::FunctionExecution {
254+
flow_id,
255+
project_id,
256+
flow_type,
257+
function_identifier: function_identifier.as_str(),
258+
node_id,
259+
outcome: metrics::node_result_outcome(result),
260+
duration_seconds: metrics::duration_seconds(result.started_at, result.finished_at),
261+
error_code,
262+
error_category,
263+
});
264+
}
265+
}
266+
267+
fn signal_outcome(signal: &Signal) -> &'static str {
268+
match signal {
269+
Signal::Success(_) => "success",
270+
Signal::Failure(_) => "failure",
271+
Signal::Return(_) => "return",
272+
Signal::Respond(_) => "respond",
273+
Signal::Stop => "stop",
274+
}
275+
}
276+
182277
fn parse_execution_id_from_subject(
183278
subject: &async_nats::Subject,
184279
prefix: &str,
@@ -327,7 +422,17 @@ mod tests {
327422
let flow = execution_flow_from_fixture(fixture);
328423
let engine = ExecutionEngine::new();
329424

330-
let run_result = execute_flow(execution_id, flow, &engine, None, None).await;
425+
let function_identifiers = function_identifiers_by_node_id(&flow);
426+
let run_result = execute_flow(
427+
execution_id,
428+
flow,
429+
&engine,
430+
None,
431+
None,
432+
"test",
433+
function_identifiers,
434+
)
435+
.await;
331436

332437
println!(
333438
"started_at={} finished_at={} delta={}",

0 commit comments

Comments
 (0)