diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..b3ea37a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "pred" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +axum = { version = "0.6.20", features = ["macros", "headers"] } +reqwest = "0.11" +tokio = { version = "1", features = ["full"] } +select = "0.5" +dotenvy = "~0" +cfg-if = "~1" + +# -- Tracing +tracing-subscriber = { version = "0.3.16", features = ["env-filter", "json", "std"], optional = true } +tracing = { version = "~0", features = ["log", "log-always"] } +opentelemetry = {version ="0.20.0", features = ["rt-tokio"], optional = true } +opentelemetry-otlp = { version = "0.13.0", optional = true } +tracing-opentelemetry = { version = "0.21.0", optional = true } +tracing-log = { version = "~0", optional = true, features = ["env_logger"] } +tracing-bunyan-formatter = { version = "0.3.9", optional = true } + +# Misc +thiserror = "~1" +log = "~0" + +[features] +default = ["tracing", "bunyan"] +tracing = ["tracing-log", "tracing-subscriber", "tracing-opentelemetry", "opentelemetry", "opentelemetry-otlp"] +bunyan = ["tracing-bunyan-formatter"] \ No newline at end of file diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..1c526ee --- /dev/null +++ b/src/error.rs @@ -0,0 +1,29 @@ +use opentelemetry::trace::TraceError; + +pub type Result = core::result::Result; + +#[derive(Debug)] +pub enum Error { + TraceErr(String), + SetGlobalDefaultErr(String) +} + +impl From for Error { + fn from(_: tracing::subscriber::SetGlobalDefaultError) -> Self { + Error::SetGlobalDefaultErr("Set global err".to_string()) + } +} + +impl From for Error { + fn from(_: TraceError) -> Self { + Error::TraceErr("Trace err".to_string()) + } +} +impl core::fmt::Display for Error { + fn fmt( + &self, + fmt: &mut core::fmt::Formatter, + ) -> core::result::Result<(), core::fmt::Error> { + write!(fmt, "{self:?}") + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..6cee131 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,47 @@ + +// use crate::observability; + +mod observability; +mod error; +use axum::{response::Html, routing::get, Router}; +use tracing::debug; + + +#[tokio::main] +async fn main() -> error::Result<()> { + + // Set up a tracing subscriber with JSON formatting + tracing_log::LogTracer::builder() + .ignore_crate("sqlx") + .with_max_level(log::LevelFilter::Info) + .init() + .expect("could not initialize log tracer"); + + match observability::configure_observability().await { + Ok(_) => { + tracing::debug!("tracing configured"); + } + Err(err) => { + tracing::error!("error configuring tracing: {}", err); + return Err(err); + } + }; + + // build our application with a route + let app = Router::new().route("/", get(handler)); + + // run it + debug!("listening on 0.0.0.0:3000"); + + axum::Server::bind(&"0.0.0.0:3000".parse().unwrap()) + .serve(app.into_make_service()) + .await + .expect("server failed"); + + Ok(()) +} + +#[tracing::instrument] +async fn handler() -> Html<&'static str> { + Html("

Hello, World!

") +} diff --git a/src/mod.rs b/src/mod.rs new file mode 100644 index 0000000..294b766 --- /dev/null +++ b/src/mod.rs @@ -0,0 +1 @@ +pub mod observability; \ No newline at end of file diff --git a/src/observability.rs b/src/observability.rs new file mode 100755 index 0000000..f167672 --- /dev/null +++ b/src/observability.rs @@ -0,0 +1,68 @@ +use opentelemetry_otlp::WithExportConfig; +use tracing_bunyan_formatter::{BunyanFormattingLayer, JsonStorageLayer}; +use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt; +use crate::error::Result; +use tracing::debug; + +const OTEL_EXPORTER_OTLP_ENDPOINT_ENV_VAR: &str = "OTEL_EXPORTER_OTLP_ENDPOINT"; +const OTEL_EXPORTER_OTLP_ENDPOINT_DEFAULT: &str = "http://localhost:4317"; + +const OBSERVABILITY_SERVICE_NAME_ENV_VAR: &str = "OBSERVABILITY_SERVICE_NAME"; +const OBSERVABILITY_SERVICE_NAME_DEFAULT: &str = "pred"; + +#[tracing::instrument] +pub async fn configure_observability() -> Result<()> { + debug!("{:<12} - configure_observability", "fn"); + + let otel_exporter_endpoint = + dotenvy::var(OTEL_EXPORTER_OTLP_ENDPOINT_ENV_VAR).unwrap_or_else(|_| { + tracing::warn!( + "{} Env var not set, using default", + OTEL_EXPORTER_OTLP_ENDPOINT_DEFAULT + ); + OTEL_EXPORTER_OTLP_ENDPOINT_DEFAULT.to_string() + }); + + let observability_service_name = dotenvy::var(OBSERVABILITY_SERVICE_NAME_ENV_VAR) + .unwrap_or_else(|_| OBSERVABILITY_SERVICE_NAME_DEFAULT.to_string()); + + let tracer = opentelemetry_otlp::new_pipeline() + .tracing() + .with_exporter( + opentelemetry_otlp::new_exporter() + .tonic() + .with_endpoint(otel_exporter_endpoint), + ) + .with_trace_config(opentelemetry::sdk::trace::config().with_resource( + opentelemetry::sdk::Resource::new(vec![opentelemetry::KeyValue::new( + "service.name", + observability_service_name.clone(), + )]), + )) + .install_batch(opentelemetry::runtime::Tokio)?; + + // Create a tracing layer with the configured tracer + let telemetry_layer = tracing_opentelemetry::layer().with_tracer(tracer); + + let filter = tracing_subscriber::EnvFilter::from_default_env(); + + cfg_if::cfg_if! { + if #[cfg(feature="bunyan")] { + // Create a new formatting layer to print bunyan formatted logs to stdout, pipe into bunyan to view + let formatting_layer = BunyanFormattingLayer::new(observability_service_name, std::io::stdout); + let subscriber = tracing_subscriber::Registry::default() + .with(filter) + .with(telemetry_layer) + .with(JsonStorageLayer) + .with(formatting_layer); + } else { + let subscriber = tracing_subscriber::Registry::default() + .with_filter(filter) + .with_writer(std::io::stdout) + .with(telemetry_layer); + } + } + + Ok(tracing::subscriber::set_global_default(subscriber)?) +} +