Skip to content

feat: OpenTelemetry integration #96

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,866 changes: 1,720 additions & 146 deletions Cargo.lock

Large diffs are not rendered by default.

37 changes: 33 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,17 +73,43 @@ serde = { version = "1.0.204", features = ["derive"] }
toml = "0.8.14"
paste = "1.0.15"
serde_json = "1.0.120"
libbpf-rs = { version = "0.24.6", optional = true, default-features = false }
libbpf-rs = { version = "0.25.0-beta.1", optional = true, default-features = false }
# libbpf-sys exists here because we want to control its features
libbpf-sys = { version = "1", optional = true, default-features = false }
libseccomp = "0.4.0"
weak-table = { version = "0.3.2", default-features = false, features = ["ahash"] }
weak-table = { version = "0.3.2", default-features = false, features = [
"ahash",
] }
rand = "0.9"
hashbrown = "0.15.2"
indexset = "0.12"
chrono = "0.4.40"
nutype = { version = "0.6.1", features = ["serde"] }
humantime = "2.2.0"
opentelemetry = { version = "0.29.1", features = ["futures"] }
opentelemetry_sdk = "0.29.0"
url = { version = "2.5.4", features = ["serde"] }
mini-moka = "0.10.3"
# This is a transitive dependency.
# We just want to set its features
zstd = { version = "*" }

[dependencies.opentelemetry-otlp]
version = "0.29.0"
default-features = false
package = "tracexec-opentelemetry-otlp"
features = [
"trace", # We are not using metrics and logs
# GRPC, use tonic and zstd compression with system trust roots
"grpc-tonic",
"zstd-tonic",
"tls-roots",
# HTTP, use reqwest and rustls with system trust roots
"http-proto",
"reqwest-blocking-client",
# "reqwest-client", # https://github.com/open-telemetry/opentelemetry-rust/issues/2673
"reqwest-rustls",
]
# tui-prompts = { version = "0.3.11", path = "../../contrib/tui-prompts" }
# tui-popup = { version = "0.3.0", path = "../../contrib/tui-popup" }

Expand All @@ -99,7 +125,7 @@ libbpf-cargo = { version = "0.24.6", default-features = false }


[features]
default = ["recommended", "vendored-libbpf"]
default = ["recommended", "vendored-libbpf", "dynamic-libzstd"]
recommended = ["ebpf"]
ebpf = ["dep:libbpf-rs", "dep:libbpf-sys"]
# The ebpf-debug feature is not meant for end users.
Expand All @@ -109,10 +135,13 @@ ebpf = ["dep:libbpf-rs", "dep:libbpf-sys"]
# Either cargo doesn't rebuild and run build.rs on feature flag change,
# or some logic is wrong in build.rs
ebpf-debug = ["ebpf"]
ebpf-no-rcu-kfuncs = ["ebpf"] # Avoid using rcu helpers. Necessary for kernel version < 6.2
ebpf-no-rcu-kfuncs = [
"ebpf",
] # Avoid using rcu helpers. Necessary for kernel version < 6.2
static = ["libbpf-sys/static"]
vendored = ["libbpf-sys/vendored", "vendored-libbpf"]
vendored-libbpf = ["libbpf-sys/vendored-libbpf", "libbpf-cargo/default"]
dynamic-libzstd = ["zstd/pkg-config"]

[profile.dev]
opt-level = 1
Expand Down
44 changes: 44 additions & 0 deletions config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,47 @@

# Decode errno of exec failure
# decode_errno = true

#
# Config for OpenTelemetry
#
[otlp]

# Enable the OpenTelemetry exporter
# enable = false

# Protocol for the OpenTelemetry exporter
# Currently supported protocols are http and grpc
# protocol = "http"

# Endpoint of the OpenTelemetry collector (http)
# TLS is supported and the system trust roots are honored.
# http.endpoint = "http://127.0.0.1:4318"

# Endpoint of the OpenTelemetry collector (grpc)
# TLS is supported and the system trust roots are honored.
# grpc.endpoint = "http://127.0.0.1:4317"

# Service name to use
# service_name = "tracexec"

# Set the possible span end point
# span_end_point = "exec"

# Export values of environment variables
# export.env = true

# Export the environment variable diff
# export.env_diff = false

# Export information about file descriptors
# export.fd = true

# Export the file descriptor diff
# export.fd_diff = false

# Export the commandline
# export.cmdline = false

# Export the arguments
# export.argv = true
53 changes: 47 additions & 6 deletions src/bpf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::{
sync::{Arc, LazyLock, RwLock, atomic::Ordering},
};

use crate::{cache::ArcStr, tracer::TracerBuilder};
use crate::{cache::ArcStr, handle_tracer_errors, tracer::TracerBuilder};
use color_eyre::{Section, eyre::eyre};
use enumflags2::BitFlag;
use nix::unistd::User;
Expand Down Expand Up @@ -63,11 +63,12 @@ pub async fn main(
} => {
let modifier_args = modifier_args.processed();
let baseline = Arc::new(BaselineInfo::new()?);
let output = Cli::get_output(output, color)?;
let output = Cli::get_output(output, color, true)?;
let printer = Printer::new(
PrinterArgs::from_cli(&log_args, &modifier_args),
baseline.clone(),
);
let (tracer_tx, mut tracer_rx) = mpsc::unbounded_channel();
let tracer = TracerBuilder::new()
.mode(TracerMode::Log {
foreground: log_args.foreground(),
Expand All @@ -77,10 +78,38 @@ pub async fn main(
.baseline(baseline)
.user(user)
.modifier(modifier_args)
.tracer_tx(tracer_tx)
.build_ebpf();
let running_tracer = tracer.spawn(&cmd, obj, Some(output))?;
running_tracer.run_until_exit();
Ok(())
let tracer_thread = spawn_blocking(move || {
let running_tracer = tracer.spawn(&cmd, obj, Some(output))?;
running_tracer.run_until_exit();
Ok::<(), color_eyre::Report>(())
});
let mut errors = Vec::new();
loop {
match tracer_rx.recv().await {
Some(TracerMessage::Event(TracerEvent {
details: TracerEventDetails::TraceeExit { exit_code, .. },
..
})) => {
tracing::debug!("Waiting for tracer thread to exit");
tracer_thread.await??;
handle_tracer_errors(&errors);
process::exit(exit_code);
}
Some(TracerMessage::Error(e)) => {
errors.push(e);
}
// channel closed abnormally.
None | Some(TracerMessage::FatalError(_)) => {
tracing::debug!("Waiting for tracer thread to exit");
tracer_thread.await??;
handle_tracer_errors(&errors);
process::exit(1);
}
_ => (),
}
}
}
EbpfCommand::Tui {
cmd,
Expand Down Expand Up @@ -182,7 +211,7 @@ pub async fn main(
} => {
let modifier_args = modifier_args.processed();
let baseline = Arc::new(BaselineInfo::new()?);
let mut output = Cli::get_output(output, color)?;
let mut output = Cli::get_output(output, color, false)?;
let log_args = LogModeArgs {
show_cmdline: false,
show_argv: true,
Expand Down Expand Up @@ -213,7 +242,9 @@ pub async fn main(
let tracer_thread = spawn_blocking(move || {
running_tracer.run_until_exit();
});
let mut errors = Vec::new();
match format {
ExportFormat::OpenTelemetry => todo!(),
ExportFormat::Json => {
let mut json = export::Json {
meta: JsonMetaData::new(baseline.as_ref().to_owned()),
Expand All @@ -230,6 +261,7 @@ pub async fn main(
serialize_json_to_output(&mut output, &json, pretty)?;
output.write_all(b"\n")?;
output.flush()?;
handle_tracer_errors(&errors);
process::exit(exit_code);
}
Some(TracerMessage::Event(TracerEvent {
Expand All @@ -238,10 +270,14 @@ pub async fn main(
})) => {
json.events.push(JsonExecEvent::new(id, *exec));
}
Some(TracerMessage::Error(e)) => {
errors.push(e);
}
// channel closed abnormally.
None | Some(TracerMessage::FatalError(_)) => {
tracing::debug!("Waiting for tracer thread to exit");
tracer_thread.await?;
handle_tracer_errors(&errors);
process::exit(1);
}
_ => (),
Expand All @@ -262,6 +298,7 @@ pub async fn main(
})) => {
tracing::debug!("Waiting for tracer thread to exit");
tracer_thread.await?;
handle_tracer_errors(&errors);
process::exit(exit_code);
}
Some(TracerMessage::Event(TracerEvent {
Expand All @@ -273,10 +310,14 @@ pub async fn main(
output.write_all(b"\n")?;
output.flush()?;
}
Some(TracerMessage::Error(e)) => {
errors.push(e);
}
// channel closed abnormally.
None | Some(TracerMessage::FatalError(_)) => {
tracing::debug!("Waiting for tracer thread to exit");
tracer_thread.await?;
handle_tracer_errors(&errors);
process::exit(1);
}
_ => (),
Expand Down
8 changes: 4 additions & 4 deletions src/bpf/tracer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,8 @@ impl EbpfTracer {
if self.filter.intersects(TracerEventDetailsKind::Exec) {
let id = TracerEvent::allocate_id();
let parent_tracker = tracker.parent_tracker_mut(pid).unwrap();
let parent = parent_tracker.update_last_exec(id, event.ret == 0);
let event = TracerEventDetails::Exec(Box::new(ExecEvent {
let (parent, parent_ctx) = parent_tracker.update_last_exec(id, event.ret == 0);
let exec = Box::new(ExecEvent {
timestamp: exec_data.timestamp,
pid,
cwd: exec_data.cwd.clone(),
Expand All @@ -232,8 +232,8 @@ impl EbpfTracer {
result: event.ret,
fdinfo: exec_data.fdinfo.clone(),
parent,
}))
.into_event_with_id(id);
});
let event = TracerEventDetails::Exec(exec).into_event_with_id(id);
if follow_forks {
tracker.associate_events(pid, [event.id])
} else {
Expand Down
4 changes: 4 additions & 0 deletions src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ impl ArcStr {
pub fn as_str(&self) -> &str {
&self.0
}

pub fn clone_inner(&self) -> Arc<str> {
self.0.clone()
}
}

impl Deref for ArcStr {
Expand Down
23 changes: 15 additions & 8 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::{
path::PathBuf,
};

use args::{DebuggerArgs, PtraceArgs, TuiModeArgs};
use args::{DebuggerArgs, OpenTelemetryArgs, PtraceArgs, TuiModeArgs};
use clap::{CommandFactory, Parser, Subcommand};
use config::Config;
use options::ExportFormat;
Expand Down Expand Up @@ -67,6 +67,8 @@ pub enum CliCommand {
ptrace_args: PtraceArgs,
#[clap(flatten)]
tracer_event_args: TracerEventArgs,
#[clap(flatten)]
otel_args: OpenTelemetryArgs,
#[clap(
short,
long,
Expand All @@ -85,6 +87,8 @@ pub enum CliCommand {
#[clap(flatten)]
tracer_event_args: TracerEventArgs,
#[clap(flatten)]
otel_args: OpenTelemetryArgs,
#[clap(flatten)]
tui_args: TuiModeArgs,
#[clap(flatten)]
debugger_args: DebuggerArgs,
Expand All @@ -101,16 +105,14 @@ pub enum CliCommand {
#[clap(flatten)]
modifier_args: ModifierArgs,
#[clap(flatten)]
otel_args: OpenTelemetryArgs,
#[clap(flatten)]
ptrace_args: PtraceArgs,
#[clap(short = 'F', long, help = "the format for exported exec events")]
format: ExportFormat,
#[clap(short, long, help = "prettify the output if supported")]
pretty: bool,
#[clap(
short,
long,
help = "Output, stderr by default. A single hyphen '-' represents stdout."
)]
#[clap(short, long, help = "Output, stdout by default.")]
output: Option<PathBuf>,
#[clap(
long,
Expand Down Expand Up @@ -203,9 +205,14 @@ pub enum EbpfCommand {
}

impl Cli {
pub fn get_output(path: Option<PathBuf>, color: Color) -> std::io::Result<Box<PrinterOut>> {
pub fn get_output(
path: Option<PathBuf>,
color: Color,
default_stderr: bool,
) -> std::io::Result<Box<PrinterOut>> {
Ok(match path {
None => Box::new(stderr()),
None if default_stderr => Box::new(stderr()),
None => Box::new(stdout()),
Some(ref x) if x.as_os_str() == "-" => Box::new(stdout()),
Some(path) => {
let file = std::fs::OpenOptions::new()
Expand Down
Loading
Loading