Skip to content

Commit

Permalink
Add a bridge for supertd event logger
Browse files Browse the repository at this point in the history
Summary: Create a bridge between raw scuba client and Logger based backends for scuba! {} macro. This allows to turn on the switch through a JK.

Reviewed By: rjbailey

Differential Revision: D67654057

fbshipit-source-id: 55931d75109dcd18d141034e0feddf6acd23b9e6
  • Loading branch information
Xiang Gao authored and facebook-github-bot committed Jan 7, 2025
1 parent 8ed1a79 commit 0c29954
Show file tree
Hide file tree
Showing 3 changed files with 290 additions and 16 deletions.
1 change: 1 addition & 0 deletions td_util/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub mod qe;
pub mod schedules;
pub mod string;
pub mod supertd_events;
pub mod supertd_events_logger;
pub mod tracing;
pub mod workflow_error;
pub mod xplat;
Expand Down
49 changes: 33 additions & 16 deletions td_util/src/supertd_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ use scuba::ScubaSampleBuilder;
pub use serde_json;
pub use tracing;

use crate::knobs::check_boolean_knob;

const SCUBA_DATASET: &str = "supertd_events";
pub const USE_LOGGER_KNOB: &str = "ci_efficiency/citadel:use_supertd_events_logger";

static BUILDER: OnceLock<ScubaSampleBuilder> = OnceLock::new();

Expand Down Expand Up @@ -69,17 +72,22 @@ pub enum Step {
/// Panics if `SUPERTD_SCUBA_LOGFILE` is set and the log file cannot be opened
/// for writing.
pub fn init(fb: fbinit::FacebookInit) -> ScubaClientGuard {
let mut builder = match std::env::var_os("SUPERTD_SCUBA_LOGFILE") {
None => ScubaSampleBuilder::new(fb, SCUBA_DATASET),
Some(path) => ScubaSampleBuilder::with_discard()
.with_log_file(path)
.unwrap(),
};
builder.add_common_server_data();
add_sandcastle_columns(&mut builder);
if BUILDER.set(builder).is_err() {
tracing::error!("supertd_events Scuba client initialized twice");
if should_use_logger() {
crate::supertd_events_logger::init(fb);
} else {
let mut builder = match std::env::var_os("SUPERTD_SCUBA_LOGFILE") {
None => ScubaSampleBuilder::new(fb, SCUBA_DATASET),
Some(path) => ScubaSampleBuilder::with_discard()
.with_log_file(path)
.unwrap(),
};
builder.add_common_server_data();
add_sandcastle_columns(&mut builder);
if BUILDER.set(builder).is_err() {
tracing::error!("supertd_events Scuba client initialized twice");
}
}

ScubaClientGuard(())
}

Expand Down Expand Up @@ -114,12 +122,16 @@ pub fn init(fb: fbinit::FacebookInit) -> ScubaClientGuard {
#[macro_export]
macro_rules! scuba {
( event: $event:ident $(, $key:ident : $value:expr)* $(,)? ) => {
let mut builder = $crate::supertd_events::sample_builder();
builder.add("event", format!("{:?}", &$crate::supertd_events::Event::$event));
$($crate::scuba! { @SET_FIELD(builder, $key, $value) })*
if let Err(e) = builder.try_log() {
$crate::supertd_events::tracing::error!(
"Failed to log to supertd_events Scuba: {:?}", e);
if $crate::supertd_events::should_use_logger() {
$crate::scuba_logger! {event: $event $(, $key : $value)*};
} else {
let mut builder = $crate::supertd_events::sample_builder();
builder.add("event", format!("{:?}", &$crate::supertd_events::Event::$event));
$($crate::scuba! { @SET_FIELD(builder, $key, $value) })*
if let Err(e) = builder.try_log() {
$crate::supertd_events::tracing::error!(
"Failed to log to supertd_events Scuba: {:?}", e);
}
}
};
( $($key:ident : $value:expr),* $(,)? ) => {
Expand Down Expand Up @@ -172,6 +184,11 @@ pub fn sample_builder() -> ScubaSampleBuilder {
.unwrap_or_else(ScubaSampleBuilder::with_discard)
}

#[doc(hidden)]
pub fn should_use_logger() -> bool {
check_boolean_knob(USE_LOGGER_KNOB)
}

fn add_sandcastle_columns(sample: &mut ScubaSampleBuilder) {
let Some(nexus_path) = std::env::var_os("SANDCASTLE_NEXUS") else {
return;
Expand Down
256 changes: 256 additions & 0 deletions td_util/src/supertd_events_logger.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under both the MIT license found in the
* LICENSE-MIT file in the root directory of this source tree and the Apache
* License, Version 2.0 found in the LICENSE-APACHE file in the root directory
* of this source tree.
*/

//! Simple interface for logging to the `supertd_events` dataset.
#[cfg(target_os = "linux")]
pub use linux::*;
#[cfg(not(target_os = "linux"))]
pub use non_linux::*;

#[cfg(not(target_os = "linux"))]
mod non_linux {
pub fn init(_fb: fbinit::FacebookInit) {}

#[macro_export]
macro_rules! scuba_logger {
( event: $event:ident $(, $key:ident : $value:expr)* $(,)? ) => {};
}
}

#[cfg(target_os = "linux")]
mod linux {
use std::env::var;
use std::path::Path;
use std::sync::OnceLock;

use build_info::BuildInfo;
use supertd_events_rust_logger::SupertdEventsLogEntry;
use supertd_events_rust_logger::SupertdEventsLogger;

static LOG_ENTRY: OnceLock<SupertdEventsLogEntry> = OnceLock::new();
static FB_INIT: OnceLock<fbinit::FacebookInit> = OnceLock::new();

/// Initialize the Scuba client for the `supertd_events` dataset.
///
/// Returns a guard that flushes the Scuba client when dropped.
///
/// Expects `tracing` to be initialized.
pub fn init(fb: fbinit::FacebookInit) {
if FB_INIT.set(fb).is_err() {
tracing::error!("supertd_events client initialized twice");
}
let mut log_entry = SupertdEventsLogEntry::default();
add_common_server_data(&mut log_entry);
add_sandcastle_columns(&mut log_entry);
if LOG_ENTRY.set(log_entry).is_err() {
tracing::error!("supertd_events Scuba client initialized twice");
}
}

/// Log a sample to the `supertd_events` dataset.
///
/// The `event` column should be a distinct string for each source location
/// logging an event.
///
/// The `data` column contains JSON-encoded data specific to that event (so that
/// we do not inflate the number of columns in the Scuba table with properties
/// populated by only one event). Use this data in derived columns or queries
/// using `JSON_EXTRACT`.
///
/// If [`init`] has not been invoked, the sample will not be logged.
///
/// # Examples
///
/// ```
/// # let f = || (10, 2);
/// let t = std::time::Instant::now();
/// let (foos_run, bars_launched) = f();
/// td_util::scuba!(
/// event: BTD_SUCCESS,
/// duration: t.elapsed(),
/// data: json!({
/// "arbitrary": ["JSON", "object"],
/// "foos_run": foos_run,
/// "bars_launched": bars_launched,
/// })
/// );
/// ```
#[macro_export]
macro_rules! scuba_logger {
( event: $event:ident $(, $key:ident : $value:expr)* $(,)? ) => {
let mut builder = $crate::supertd_events_logger::log_entry();
builder.set_event(format!("{:?}", &$crate::supertd_events::Event::$event));
$($crate::scuba_logger! { @SET_FIELD(builder, $key, $value) })*
$crate::supertd_events_logger::log(&builder);
};
( $($key:ident : $value:expr),* $(,)? ) => {
compile_error!("`event` must be the first field in the `scuba!` macro");
};
( @SET_FIELD ( $builder:ident, event, $value:expr ) ) => {
compile_error!("duplicate `event` field in `scuba!` macro");
};
( @SET_FIELD ( $builder:ident, data, $value:expr ) ) => {{
use $crate::supertd_events::serde_json::json;
match $crate::supertd_events::serde_json::to_string(&$value) {
Ok(json) => {
$builder.set_data(json);
}
Err(e) => {
$crate::supertd_events::tracing::error!(
"Failed to serialize `data` column in `scuba!` macro: {:?}", e);
}
}
}};
( @SET_FIELD ( $builder:ident, duration, $value:expr ) ) => {
$builder.set_duration_ms(::std::time::Duration::as_millis(&$value) as i64);
};
( @SET_FIELD ( $builder:ident, duration_ms, $value:expr ) ) => {
compile_error!("unrecognized column name in `scuba!` macro: duration_ms (use `duration` instead)");
};
( @SET_FIELD ( $builder:ident, $key:ident, $value:expr ) ) => {
compile_error!(concat!("unrecognized column name in `scuba!` macro: ", stringify!($key)));
};
}

/// Get the log_entry for the `supertd_events` dataset.
///
/// Please use the [`scuba!`] macro instead of this function, since it provides
/// additional type safety (e.g., prevents typos in column names). This function
/// is exposed only for internal use by the macro.
#[doc(hidden)]
pub fn log_entry() -> SupertdEventsLogEntry {
LOG_ENTRY.get().cloned().unwrap_or_default()
}

#[doc(hidden)]
pub fn log(log_entry: &SupertdEventsLogEntry) {
if let Some(&fb) = FB_INIT.get() {
if let Err(e) = SupertdEventsLogger::from_entry(fb, log_entry).log() {
tracing::error!("Failed to flush supertd_events Scuba: {:?}", e);
}
}
}

fn add_common_server_data(log_entry: &mut SupertdEventsLogEntry) {
if let Ok(who) = fbwhoami::FbWhoAmI::get() {
if let Some(hostname) = who.name.as_deref() {
log_entry.set_server_hostname(hostname.to_owned());
}
if let Some(region) = who.region.as_deref() {
log_entry.set_region(region.to_owned());
}
if let Some(dc) = who.datacenter.as_deref() {
log_entry.set_datacenter(dc.to_owned());
}
if let Some(dc_prefix) = who.region_datacenter_prefix.as_deref() {
log_entry.set_region_datacenter_prefix(dc_prefix.to_owned());
}
}

if let Ok(smc_tier) = var("SMC_TIERS") {
log_entry.set_server_tier(smc_tier);
}

if let Ok(tw_task_id) = var("TW_TASK_ID") {
log_entry.set_tw_task_id(tw_task_id);
}

if let Ok(tw_canary_id) = var("TW_CANARY_ID") {
log_entry.set_tw_canary_id(tw_canary_id);
}

if let (Ok(tw_cluster), Ok(tw_user), Ok(tw_name)) = (
var("TW_JOB_CLUSTER"),
var("TW_JOB_USER"),
var("TW_JOB_NAME"),
) {
log_entry.set_tw_handle(format!("{}/{}/{}", tw_cluster, tw_user, tw_name));
};

if let (Ok(tw_cluster), Ok(tw_user), Ok(tw_name), Ok(tw_task_id)) = (
var("TW_JOB_CLUSTER"),
var("TW_JOB_USER"),
var("TW_JOB_NAME"),
var("TW_TASK_ID"),
) {
log_entry.set_tw_task_handle(format!(
"{}/{}/{}/{}",
tw_cluster, tw_user, tw_name, tw_task_id
));
};

#[cfg(target_os = "linux")]
{
log_entry.set_build_revision(BuildInfo::get_revision().to_owned());
log_entry.set_build_rule(BuildInfo::get_rule().to_owned());
}
}

fn apply_verifiable(var: &str, variables_path: &Path, f: impl FnOnce(String)) {
if let Ok(value) = std::fs::read_to_string(variables_path.join(var)) {
f(value);
} else if let Ok(value) = std::env::var(var) {
f(value);
}
}

fn add_sandcastle_columns(log_entry: &mut SupertdEventsLogEntry) {
let Some(nexus_path) = std::env::var_os("SANDCASTLE_NEXUS") else {
return;
};
let nexus_path = std::path::Path::new(&nexus_path);
if !nexus_path.exists() {
return;
}
let variables_path = nexus_path.join("variables");
apply_verifiable("SANDCASTLE_ALIAS_NAME", &variables_path, |value| {
log_entry.set_sandcastle_alias_name(value);
});
apply_verifiable("SANDCASTLE_ALIAS", &variables_path, |value| {
log_entry.set_sandcastle_alias(value);
});
apply_verifiable("SANDCASTLE_COMMAND_NAME", &variables_path, |value| {
log_entry.set_sandcastle_command_name(value);
});
apply_verifiable("SANDCASTLE_INSTANCE_ID", &variables_path, |value| {
log_entry.set_sandcastle_instance_id(value);
});
apply_verifiable("SANDCASTLE_IS_DRY_RUN", &variables_path, |value| {
log_entry.set_sandcastle_is_dry_run(value);
});
apply_verifiable("SANDCASTLE_JOB_OWNER", &variables_path, |value| {
log_entry.set_sandcastle_job_owner(value);
});
apply_verifiable("SANDCASTLE_NONCE", &variables_path, |value| {
log_entry.set_sandcastle_nonce(value);
});
apply_verifiable("SANDCASTLE_PHABRICATOR_DIFF_ID", &variables_path, |value| {
log_entry.set_sandcastle_phabricator_diff_id(value);
});
apply_verifiable("SANDCASTLE_SCHEDULE_TYPE", &variables_path, |value| {
log_entry.set_sandcastle_schedule_type(value);
});
apply_verifiable("SANDCASTLE_TYPE", &variables_path, |value| {
log_entry.set_sandcastle_type(value);
});
apply_verifiable("SANDCASTLE_URL", &variables_path, |value| {
log_entry.set_sandcastle_url(value);
});
apply_verifiable("SKYCASTLE_ACTION_ID", &variables_path, |value| {
log_entry.set_skycastle_action_id(value);
});
apply_verifiable("SKYCASTLE_JOB_ID", &variables_path, |value| {
log_entry.set_skycastle_job_id(value);
});
apply_verifiable("SKYCASTLE_WORKFLOW_RUN_ID", &variables_path, |value| {
log_entry.set_skycastle_workflow_run_id(value);
});
}
}

0 comments on commit 0c29954

Please sign in to comment.