Skip to content

Commit fbf38f2

Browse files
Dynamic Logs/Metrics (#1735)
* add config * cli * add * add metric metadata to logs * lint * uncomment * lint * lint * abstract * dynamic-configure * add * add explanation * add error handling --------- Co-authored-by: Jonathan Widjaja <[email protected]>
1 parent 92ea702 commit fbf38f2

File tree

6 files changed

+96
-27
lines changed

6 files changed

+96
-27
lines changed

apps/framework-cli/src/cli.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,7 @@ async fn top_command_handler(
324324
let (metrics, rx) = Metrics::new(TelemetryMetadata {
325325
anonymous_telemetry_enabled: settings.telemetry.enabled,
326326
machine_id: settings.telemetry.machine_id.clone(),
327+
metric_labels: settings.metric.labels.clone(),
327328
is_moose_developer: settings.telemetry.is_moose_developer,
328329
is_production: project_arc.is_production,
329330
project_name: project_arc.name().to_string(),
@@ -495,6 +496,7 @@ async fn top_command_handler(
495496
let (metrics, rx) = Metrics::new(TelemetryMetadata {
496497
anonymous_telemetry_enabled: settings.telemetry.enabled,
497498
machine_id: settings.telemetry.machine_id.clone(),
499+
metric_labels: settings.metric.labels.clone(),
498500
is_moose_developer: settings.telemetry.is_moose_developer,
499501
is_production: project_arc.is_production,
500502
project_name: project_arc.name().to_string(),

apps/framework-cli/src/cli/logger.rs

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,8 @@
3131
//! ```
3232
//!
3333
34+
use log::{error, warn};
3435
use log::{info, LevelFilter, Metadata, Record};
35-
use std::time::{Duration, SystemTime};
36-
3736
use opentelemetry::logs::Logger;
3837
use opentelemetry::KeyValue;
3938
use opentelemetry_appender_log::OpenTelemetryLogBridge;
@@ -44,8 +43,13 @@ use opentelemetry_sdk::logs::LoggerProvider;
4443
use opentelemetry_sdk::Resource;
4544
use opentelemetry_semantic_conventions::resource::SERVICE_NAME;
4645
use serde::Deserialize;
46+
use serde_json::Value;
47+
use std::env;
48+
49+
use std::time::{Duration, SystemTime};
4750

4851
use crate::utilities::constants::{CONTEXT, CTX_SESSION_ID};
52+
use crate::utilities::decode_object;
4953

5054
use super::settings::user_directory;
5155

@@ -171,18 +175,18 @@ pub fn setup_logging(settings: &LoggerSettings, machine_id: &str) -> Result<(),
171175
})
172176
} else {
173177
fern::Dispatch::new().format(move |out, message, record| {
178+
let log_json = serde_json::json!({
179+
"timestamp": chrono::Utc::now().to_rfc3339(),
180+
"severity": record.level().to_string(),
181+
"session_id": &session_id,
182+
"target": record.target(),
183+
"message": message,
184+
});
185+
174186
out.finish(format_args!(
175187
"{}",
176-
serde_json::to_string(&serde_json::json!(
177-
{
178-
"timestamp": chrono::Utc::now().to_rfc3339(),
179-
"severity": record.level(),
180-
"session_id": &session_id,
181-
"target": record.target(),
182-
"message": message,
183-
}
184-
))
185-
.expect("formatting `serde_json::Value` with string keys never fails")
188+
serde_json::to_string(&log_json)
189+
.expect("formatting `serde_json::Value` with string keys never fails")
186190
))
187191
})
188192
};
@@ -215,12 +219,30 @@ pub fn setup_logging(settings: &LoggerSettings, machine_id: &str) -> Result<(),
215219
.build_log_exporter()
216220
.unwrap();
217221

222+
let mut resource_attributes = vec![
223+
KeyValue::new(SERVICE_NAME, "moose-cli"),
224+
KeyValue::new("session_id", session_id.as_str()),
225+
KeyValue::new("machine_id", String::from(machine_id)),
226+
];
227+
let metric_labels = decode_object::decode_base64_to_json(
228+
// We are reading from the environment variables because we metrics and logs are sharing
229+
// the same fields to append to the JSON
230+
env::var("MOOSE_METRIC__LABELS").unwrap().as_str(),
231+
);
232+
match metric_labels {
233+
Ok(Value::Object(labels)) => {
234+
for (key, value) in labels {
235+
if let Some(value_str) = value.as_str() {
236+
resource_attributes.push(KeyValue::new(key, value_str.to_string()));
237+
}
238+
}
239+
}
240+
Err(e) => error!("Error decoding MOOSE_METRIC_LABELS: {}", e),
241+
_ => warn!("Unexpected value for MOOSE_METRIC_LABELS"),
242+
}
243+
218244
let logger_provider = LoggerProvider::builder()
219-
.with_config(Config::default().with_resource(Resource::new(vec![
220-
KeyValue::new(SERVICE_NAME, "moose-cli"),
221-
KeyValue::new("session_id", session_id.as_str()),
222-
KeyValue::new("machine_id", String::from(machine_id)),
223-
])))
245+
.with_config(Config::default().with_resource(Resource::new(resource_attributes)))
224246
.with_batch_exporter(otel_exporter, opentelemetry_sdk::runtime::Tokio)
225247
.build();
226248

apps/framework-cli/src/cli/settings.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@ use uuid::Uuid;
88

99
use super::display::{Message, MessageType};
1010
use super::logger::LoggerSettings;
11-
use crate::utilities::constants::{
12-
CLI_CONFIG_FILE, CLI_USER_DIRECTORY, ENVIRONMENT_VARIABLE_PREFIX,
13-
};
11+
use crate::utilities::constants::{CLI_CONFIG_FILE, CLI_USER_DIRECTORY};
1412

1513
/// # Config
1614
/// Module to handle reading the config file from the user's home directory and configuring the CLI
@@ -21,11 +19,17 @@ use crate::utilities::constants::{
2119
/// - add a config file validation and error handling
2220
///
2321
22+
const ENVIRONMENT_VARIABLE_PREFIX: &str = "MOOSE";
23+
24+
#[derive(Deserialize, Debug, Default)]
25+
pub struct MetricLabels {
26+
pub labels: Option<String>,
27+
}
28+
2429
#[derive(Deserialize, Debug)]
2530
pub struct Telemetry {
2631
pub machine_id: String,
2732
pub enabled: bool,
28-
2933
#[serde(default)]
3034
pub is_moose_developer: bool,
3135
}
@@ -53,6 +57,9 @@ pub struct Settings {
5357
#[serde(default)]
5458
pub telemetry: Telemetry,
5559

60+
#[serde(default)]
61+
pub metric: MetricLabels,
62+
5663
#[serde(default)]
5764
pub features: Features,
5865
}

apps/framework-cli/src/metrics.rs

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,23 @@ use prometheus_client::{
77
registry::Registry,
88
};
99
use serde_json::json;
10+
use serde_json::Value;
11+
use std::env;
1012
use std::sync::Arc;
1113
use std::{path::PathBuf, time::Duration};
1214
use tokio::time;
1315

1416
use chrono::Utc;
1517

1618
use crate::utilities::constants::{self, CONTEXT, CTX_SESSION_ID};
17-
18-
const ANONYMOUS_METRICS_URL: &str = "https://moosefood.514.dev/ingest/MooseSessionTelemetry/0.6";
19-
const ANONMOUS_METRICS_REPORTING_INTERVAL: Duration = Duration::from_secs(10);
19+
use crate::utilities::decode_object;
20+
const DEFAULT_ANONYMOUS_METRICS_URL: &str =
21+
"https://moosefood.514.dev/ingest/MooseSessionTelemetry/0.6";
22+
lazy_static::lazy_static! {
23+
static ref ANONYMOUS_METRICS_URL: String = env::var("MOOSE_METRICS_DEST")
24+
.unwrap_or_else(|_| DEFAULT_ANONYMOUS_METRICS_URL.to_string());
25+
}
26+
const ANONYMOUS_METRICS_REPORTING_INTERVAL: Duration = Duration::from_secs(10);
2027
pub const TOTAL_LATENCY: &str = "moose_total_latency";
2128
pub const LATENCY: &str = "moose_latency";
2229
pub const INGESTED_BYTES: &str = "moose_ingested_bytes";
@@ -93,6 +100,7 @@ pub struct TelemetryMetadata {
93100
pub anonymous_telemetry_enabled: bool,
94101
pub machine_id: String,
95102
pub is_moose_developer: bool,
103+
pub metric_labels: Option<String>,
96104
pub is_production: bool,
97105
pub project_name: String,
98106
}
@@ -438,6 +446,17 @@ impl Metrics {
438446

439447
let cloned_metadata = self.telemetry_metadata.clone();
440448

449+
let metric_labels = match cloned_metadata.metric_labels {
450+
Some(labels) => match decode_object::decode_base64_to_json(labels.as_str()) {
451+
Ok(decoded) => decoded,
452+
Err(e) => {
453+
log::warn!("Failed to decode metric labels: {:?}", e);
454+
serde_json::Value::Null
455+
}
456+
},
457+
None => serde_json::Value::Null,
458+
};
459+
441460
if self.telemetry_metadata.anonymous_telemetry_enabled {
442461
tokio::spawn(async move {
443462
let client = reqwest::Client::new();
@@ -458,7 +477,7 @@ impl Metrics {
458477
};
459478

460479
loop {
461-
let _ = time::sleep(ANONMOUS_METRICS_REPORTING_INTERVAL).await;
480+
let _ = time::sleep(ANONYMOUS_METRICS_REPORTING_INTERVAL).await;
462481

463482
let session_duration_in_sec = Utc::now()
464483
.signed_duration_since(session_start)
@@ -480,7 +499,7 @@ impl Metrics {
480499
0
481500
};
482501

483-
let telemetry_payload = json!({
502+
let mut telemetry_payload = json!({
484503
"timestamp": Utc::now(),
485504
"machineId": cloned_metadata.machine_id.clone(),
486505
"sequenceId": CONTEXT.get(CTX_SESSION_ID).unwrap(),
@@ -503,8 +522,15 @@ impl Metrics {
503522
"ip": ip,
504523
});
505524

525+
// Merge metric_labels into telemetry_payload
526+
if let Some(payload_obj) = telemetry_payload.as_object_mut() {
527+
if let Value::Object(labels_obj) = metric_labels.clone() {
528+
payload_obj.extend(labels_obj);
529+
}
530+
}
531+
506532
let _ = client
507-
.post(ANONYMOUS_METRICS_URL)
533+
.post(ANONYMOUS_METRICS_URL.as_str())
508534
.json(&telemetry_payload)
509535
.send()
510536
.await;

apps/framework-cli/src/utilities.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::path::Path;
22

33
pub mod capture;
44
pub mod constants;
5+
pub mod decode_object;
56
pub mod docker;
67
pub mod git;
78
pub mod package_managers;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
use base64::{engine::general_purpose, Engine as _};
2+
use serde_json::Value;
3+
4+
pub fn decode_base64_to_json(encoded: &str) -> Result<Value, String> {
5+
general_purpose::STANDARD
6+
.decode(encoded)
7+
.map_err(|e| format!("Invalid base64 encoding: {}", e))
8+
.and_then(|json_str| {
9+
serde_json::from_slice::<Value>(&json_str).map_err(|e| format!("Invalid JSON: {}", e))
10+
})
11+
}

0 commit comments

Comments
 (0)