use crate::cli_state::journeys::{
APPLICATION_EVENT_OCKAM_GIT_HASH, APPLICATION_EVENT_OCKAM_HOME, APPLICATION_EVENT_OCKAM_VERSION,
};
use crate::Version;
use chrono::{DateTime, Datelike, Utc};
use gethostname::gethostname;
use opentelemetry::trace::{SpanId, TraceId};
use opentelemetry::Key;
use opentelemetry_sdk::trace::{IdGenerator, RandomIdGenerator};
use sha2::{Digest, Sha256};
use std::collections::HashMap;
use std::fmt::Write;
use std::process::Command;
pub fn default_attributes<'a>() -> HashMap<&'a Key, String> {
let mut attributes = HashMap::new();
let ockam_home = std::env::var("OCKAM_HOME").unwrap_or("OCKAM_HOME not set".to_string());
attributes.insert(APPLICATION_EVENT_OCKAM_HOME, ockam_home);
attributes.insert(
APPLICATION_EVENT_OCKAM_VERSION,
Version::crate_version().to_string(),
);
attributes.insert(
APPLICATION_EVENT_OCKAM_GIT_HASH,
Version::git_hash().to_string(),
);
attributes
}
pub(crate) fn make_host_trace_id(now: DateTime<Utc>) -> TraceId {
let machine = adjust(make_host(), 25, '1');
let now = now_as_string(now);
trace_id_from_hex(format!("1{machine}{now}").as_str())
}
pub(crate) fn make_project_trace_id(project_id: &str, now: DateTime<Utc>) -> TraceId {
let project_id_trace_id = adjust(project_id.to_string().replace('-', ""), 25, '1');
trace_id_from_hex(format!("1{}{}", project_id_trace_id, now_as_string(now)).as_str())
}
pub(crate) fn make_journey_span_id(trace_id: TraceId) -> SpanId {
let trace_id = trace_id.to_string();
let length = trace_id.len();
match SpanId::from_hex(&trace_id[length - 16..length]) {
Ok(span_id) => span_id,
_ => {
let random_id_generator = RandomIdGenerator::default();
random_id_generator.new_span_id()
}
}
}
pub(crate) fn make_host() -> String {
let host = match (get_mac_address(), get_ip_address()) {
(Some(mac_address), Some(ip_address)) => format!(
"{}{}",
mac_address.replace(':', ""),
ip_address.replace('.', "")
),
_ => gethostname().to_string_lossy().to_string(),
};
convert_to_hex(&hash(host))
}
fn adjust(s: String, desired_size: usize, filler: char) -> String {
let mut result = s;
let current_size = result.len();
if current_size < desired_size {
result.extend(std::iter::repeat(filler).take(desired_size - current_size));
};
result[0..desired_size].to_string()
}
fn convert_to_hex(s: &str) -> String {
let is_hex = s.chars().all(|c| c.is_ascii_hexdigit());
if is_hex {
s.to_string()
} else {
s.bytes().fold(String::new(), |mut output, b| {
let _ = write!(output, "{b:02x}");
output
})
}
}
fn hash(s: String) -> String {
let mut hasher = Sha256::new();
hasher.update(s.as_bytes());
format!("{:x}", hasher.finalize())
}
fn trace_id_from_hex(trace_id: &str) -> TraceId {
match TraceId::from_hex(trace_id) {
Ok(trace_id) => trace_id,
Err(_) => {
let random_id_generator = RandomIdGenerator::default();
random_id_generator.new_trace_id()
}
}
}
fn now_as_string(now: DateTime<Utc>) -> String {
let year = now.year() - 2000;
let month = now.month();
let today = now.day();
let day = if today < 5 { 1 } else { (today / 5) * 5 };
format!("{:02}{:02}{:02}", year, month, day)
}
fn get_mac_address() -> Option<String> {
let output = Command::new("ifconfig").output().ok()?;
let output_str = String::from_utf8_lossy(&output.stdout);
let mut result = None;
for line in output_str.lines() {
if line.contains("ether") {
let split = line.split(' ').collect::<Vec<_>>();
let mac_address = split.get(1)?;
result = Some(mac_address.to_string());
break;
}
}
result
}
fn get_ip_address() -> Option<String> {
let output = Command::new("ifconfig").output().ok()?;
let output_str = String::from_utf8_lossy(&output.stdout);
let mut result = None;
for line in output_str.lines() {
if line.contains("inet ") && !line.contains("127.0.0.1") {
let split = line.split(' ').collect::<Vec<_>>();
let ip_address = split.get(1)?;
result = Some(ip_address.to_string());
break;
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::TimeZone;
#[test]
fn test_make_host_trace_id_ends_with_the_current_date() {
let trace_id = make_host_trace_id(datetime("2024-02-22T12:00:00Z")).to_string();
let length = trace_id.len();
assert_eq!(
&trace_id[length - 6..length],
"240220",
"the current host is {} = mac {:?} / ip {:?} / hostname {:?}",
make_host(),
get_mac_address(),
get_ip_address(),
gethostname().to_string_lossy().to_string()
);
}
#[test]
fn test_make_host_journey_span_id_is_end_of_trace_id() {
let trace_id = make_host_trace_id(datetime("2024-02-22T12:00:00Z"));
let span_id = make_journey_span_id(trace_id);
assert!(trace_id.to_string().ends_with(span_id.to_string().as_str()));
}
#[test]
fn test_make_project_trace_id_contains_part_of_the_project_id_and_current_date() {
let trace_id = make_project_trace_id(
"8a12dc0e-d48b-4da1-925d-cda822505348",
datetime("2024-02-22T12:00:00Z"),
)
.to_string();
assert_eq!(
trace_id, "18a12dc0ed48b4da1925dcda82240220",
"the trace id {trace_id} is incorrect"
);
}
#[test]
fn test_make_project_span_id_is_end_of_trace_id() {
let trace_id = make_project_trace_id(
"8a12dc0e-d48b-4da1-925d-cda822505348",
datetime("2024-02-22T12:00:00Z"),
);
let span_id = make_journey_span_id(trace_id);
assert!(trace_id.to_string().ends_with(span_id.to_string().as_str()));
}
#[test]
fn test_now_as_string() {
assert_eq!(now_as_string(datetime("2024-02-01T12:00:00Z")), "240201");
assert_eq!(now_as_string(datetime("2024-02-04T12:00:00Z")), "240201");
assert_eq!(now_as_string(datetime("2024-02-05T12:00:00Z")), "240205");
assert_eq!(now_as_string(datetime("2024-02-07T12:00:00Z")), "240205");
assert_eq!(now_as_string(datetime("2024-02-09T12:00:00Z")), "240205");
assert_eq!(now_as_string(datetime("2024-02-10T12:00:00Z")), "240210");
assert_eq!(now_as_string(datetime("2024-03-31T12:00:00Z")), "240330");
}
fn datetime(s: &str) -> DateTime<Utc> {
Utc.from_utc_datetime(&DateTime::parse_from_rfc3339(s).unwrap().naive_utc())
}
}