Skip to content
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

Updated dependencies #16

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
22 changes: 11 additions & 11 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,18 @@ resolver = "2"

[dependencies]
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
tracing-opentelemetry = "0.26.0"
opentelemetry = { version = "0.25.0", features = ["trace", "logs"] }
opentelemetry_sdk = { version = "0.25.0", features = ["metrics", "rt-tokio"] }
opentelemetry-otlp = { version = "0.25.0", features = ["trace", "logs"] }
opentelemetry-semantic-conventions = "0.25.0"
tracing-opentelemetry = "0.28"
opentelemetry = "0.27"
opentelemetry_sdk = { version = "0.27", features = ["metrics", "rt-tokio"] }
opentelemetry-otlp = { version = "0.27", features = ["trace", "logs"] }
opentelemetry-semantic-conventions = {version = "0.27", features=["semconv_experimental"]}
anyhow = "1.0"
thiserror = "1.0"
derive_builder = "0.20.0"
thiserror = "2.0"
derive_builder = "0.20"

[dev-dependencies]
tokio = { version = "1.38", features = ["rt","macros"] }
tokio = { version = "1.4", features = ["rt", "macros"] }
tracing = "0.1"
testcontainers = "0.22.0"
reqwest = { version = "0.12.3", features = ["blocking", "json"] }
serde_json = "1.0"
testcontainers = "0.22"
reqwest = { version = "0.12", features = ["blocking", "json"] }
serde_json = "1.0"
90 changes: 51 additions & 39 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,39 +1,39 @@
//! # OpenTelemetry Logging with Tokio Tracing
//!
//!
//! This crate provides a convienent way to initialize the OpenTelemetry logger
//! with otlp endpoint. It uses the [`opentelemetry`] and [`tracing`]
//! crates to provide structured, context-aware logging for Rust applications.
//!
//!
//! Simply add the following to your `Cargo.toml`:
//! ```toml
//! [dependencies]
//! tracing = "0.1"
//! otlp-logger = "0.4"
//! tokio = { version = "1.38", features = ["rt", "macros"] }
//! ```
//!
//!
//! Because this crate uses the batching function of the OpenTelemetry SDK, it is
//! required to use the `tokio` runtime. Due to this requirement, the [`tokio`] crate
//! must be added as a dependency in your `Cargo.toml` file.
//!
//!
//! In your code initialize the logger with:
//! ```rust
//! #[tokio::main]
//! async fn main() {
//! // Initialize the OpenTelemetry logger using environment variables
//! otlp_logger::init().await;
//! // ... your application code
//!
//! // and optionally call open telemetry logger shutdown to make sure all the
//!
//! // and optionally call open telemetry logger shutdown to make sure all the
//! // data is sent to the configured endpoint before the application exits
//! otlp_logger::shutdown();
//! }
//! ```
//!
//!
//! If the `OTEL_EXPORTER_OTLP_ENDPOINT` environment variable is set, the
//! OpenTelemetry logger will be used. Otherwise, the logger will default to
//! only stdout.
//!
//!
//! The OpenTelemetry logger can be configured with the following environment
//! variables:
//! - `OTEL_EXPORTER_OTLP_ENDPOINT`: The endpoint to send OTLP data to.
Expand All @@ -42,31 +42,31 @@
//! - `OTEL_SERVICE_VERSION`: The version of the service.
//! - `OTEL_SERVICE_INSTANCE_ID`: The instance ID of the service.
//! - `OTEL_DEPLOYMENT_ENVIRONMENT`: The deployment environment of the service.
//!
//!
//! The OpenTelemetry logger can also be configured with the `OtlpConfig` struct, which
//! can be passed to the `init_with_config` function. The `OtlpConfig` struct can be built
//! with the `OtlpConfigBuilder` struct.
//!
//!
//! Once the logger is initialized, you can use the [`tracing`] macros to log
//! messages. For example:
//! ```rust
//! use tracing::{info, error};
//!
//!
//! #[tokio::main]
//! async fn main() {
//! otlp_logger::init().await;
//! info!("This is an info message");
//! error!("This is an error message");
//! }
//! ```
//!
//!
//! Traces and logs are sent to the configured OTLP endpoint. The traces
//! and log levels are configured via the RUST_LOG environment variable.
//! This behavior can be overridden by setting the `trace_level` or
//! `stdout_level` fields in the `OtlpConfig` struct.
//! ```rust
//! use otlp_logger::{OtlpConfigBuilder, LevelFilter};
//!
//!
//! #[tokio::main]
//! async fn main() {
//! let config = OtlpConfigBuilder::default()
Expand All @@ -75,16 +75,16 @@
//! .stdout_level(LevelFilter::ERROR)
//! .build()
//! .expect("failed to create otlp config builder");
//!
//!
//! otlp_logger::init_with_config(config).await.expect("failed to initialize logger");
//!
//!
//! // ... your application code
//!
//!
//! // shutdown the logger
//! otlp_logger::shutdown();
//! }
//! ```
//!
//!
//! [`tokio`]: https://crates.io/crates/tokio
//! [`tracing`]: https://crates.io/crates/tracing
//! [`opentelemetry`]: https://crates.io/crates/opentelemetry
Expand All @@ -106,17 +106,16 @@ mod trace;
use resource::*;
use trace::*;


#[derive(Default, Builder)]
#[builder(setter(into), default)]
pub struct OtlpConfig {
pub struct OtlpConfig {
service_name: Option<String>,
service_namespace: Option<String>,
service_version: Option<String>,
service_instant_id: Option<String>,
deployment_environment: Option<String>,
otlp_endpoint: Option<String>,
trace_level: Option<LevelFilter>,
deployment_environment: Option<String>,
otlp_endpoint: Option<String>,
trace_level: Option<LevelFilter>,
stdout_level: Option<LevelFilter>,
}

Expand All @@ -129,7 +128,10 @@ impl OtlpConfig {
fn init_otel(config: &OtlpConfig) -> Result<()> {
opentelemetry::global::set_text_map_propagator(TraceContextPropagator::new());

let otlp_endpoint = config.otlp_endpoint.as_ref().context("OTLP endpoint not set")?;
let otlp_endpoint = config
.otlp_endpoint
.as_ref()
.context("OTLP endpoint not set")?;

let resource = otel_resource(config);

Expand Down Expand Up @@ -238,45 +240,55 @@ mod tests {
assert_eq!(config.service_name, Some("test-service".to_string()));
assert_eq!(config.service_namespace, Some("test-namespace".to_string()));
assert_eq!(config.service_version, Some("test-version".to_string()));
assert_eq!(config.service_instant_id, Some("test-instant-id".to_string()));
assert_eq!(config.deployment_environment, Some("test-environment".to_string()));
assert_eq!(config.otlp_endpoint, Some("http://localhost:4317".to_string()));
assert_eq!(
config.service_instant_id,
Some("test-instant-id".to_string())
);
assert_eq!(
config.deployment_environment,
Some("test-environment".to_string())
);
assert_eq!(
config.otlp_endpoint,
Some("http://localhost:4317".to_string())
);
assert_eq!(config.trace_level, Some(LevelFilter::DEBUG));
assert_eq!(config.stdout_level, Some(LevelFilter::WARN));
}

#[test]
fn test_config_builder_some() {
let config = OtlpConfig::builder()
.otlp_endpoint("http://localhost:4317".to_string())
.trace_level(LevelFilter::INFO)
.stdout_level(LevelFilter::ERROR)
.build()
.expect("failed to configure otlp-logger");
.otlp_endpoint("http://localhost:4317".to_string())
.trace_level(LevelFilter::INFO)
.stdout_level(LevelFilter::ERROR)
.build()
.expect("failed to configure otlp-logger");

assert_eq!(config.service_name, None);
assert_eq!(config.service_namespace, None);
assert_eq!(config.service_version, None);
assert_eq!(config.service_instant_id, None);
assert_eq!(config.deployment_environment, None);
assert_eq!(config.otlp_endpoint, Some("http://localhost:4317".to_string()));
assert_eq!(
config.otlp_endpoint,
Some("http://localhost:4317".to_string())
);
assert_eq!(config.trace_level, Some(LevelFilter::INFO));
assert_eq!(config.stdout_level, Some(LevelFilter::ERROR));
assert_eq!(config.stdout_level, Some(LevelFilter::ERROR));
}

#[test]
fn test_config_builder_none() {
let config = OtlpConfig::builder()
.build()
.unwrap();
let config = OtlpConfig::builder().build().unwrap();

assert_eq!(config.service_name, None);
assert_eq!(config.service_namespace, None);
assert_eq!(config.service_version, None);
assert_eq!(config.service_instant_id, None);
assert_eq!(config.deployment_environment, None);
assert_eq!(config.otlp_endpoint, None);
assert_eq!(config.otlp_endpoint, None);
assert_eq!(config.trace_level, None);
assert_eq!(config.stdout_level, None);
assert_eq!(config.stdout_level, None);
}
}
}
47 changes: 33 additions & 14 deletions src/resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,50 @@ use std::env::args_os;
use std::process::id;

use opentelemetry::{KeyValue, StringValue, Value};
use opentelemetry_sdk::{resource::{ResourceDetector, SdkProvidedResourceDetector, TelemetryResourceDetector}, Resource};
use opentelemetry_sdk::{
resource::{ResourceDetector, SdkProvidedResourceDetector, TelemetryResourceDetector},
Resource,
};
use opentelemetry_semantic_conventions::resource as otel_resource;

use crate::OtlpConfig;

pub fn otel_resource(config: &OtlpConfig) -> Resource {

let os_resource = detect_os();
let process_resource = detect_process();
let telemetry_resource = TelemetryResourceDetector.detect(Duration::from_secs(0));
let sdk_resource = SdkProvidedResourceDetector.detect(Duration::from_secs(0));

let mut provided = Vec::new();
if let Some(service_name) = &config.service_name {
provided.push(KeyValue::new(otel_resource::SERVICE_NAME, service_name.clone()));
provided.push(KeyValue::new(
otel_resource::SERVICE_NAME,
service_name.clone(),
));
}
if let Some(service_namespace) = &config.service_namespace {
provided.push(KeyValue::new(otel_resource::SERVICE_NAMESPACE, service_namespace.clone()));
provided.push(KeyValue::new(
otel_resource::SERVICE_NAMESPACE,
service_namespace.clone(),
));
}
if let Some(service_version) = &config.service_version {
provided.push(KeyValue::new(otel_resource::SERVICE_VERSION, service_version.clone()));
provided.push(KeyValue::new(
otel_resource::SERVICE_VERSION,
service_version.clone(),
));
}
if let Some(service_instant_id) = &config.service_instant_id {
provided.push(KeyValue::new(otel_resource::SERVICE_INSTANCE_ID, service_instant_id.clone()));
provided.push(KeyValue::new(
otel_resource::SERVICE_INSTANCE_ID,
service_instant_id.clone(),
));
}
if let Some(deployment_environment) = &config.deployment_environment {
provided.push(KeyValue::new(otel_resource::DEPLOYMENT_ENVIRONMENT_NAME, deployment_environment.clone()));
provided.push(KeyValue::new(
otel_resource::DEPLOYMENT_ENVIRONMENT_NAME,
deployment_environment.clone(),
));
}

let app = Resource::new(provided);
Expand All @@ -43,7 +60,10 @@ pub fn otel_resource(config: &OtlpConfig) -> Resource {
}

fn detect_os() -> Resource {
Resource::new(vec![KeyValue::new(otel_resource::OS_TYPE, std::env::consts::OS)])
Resource::new(vec![KeyValue::new(
otel_resource::OS_TYPE,
std::env::consts::OS,
)])
}

fn detect_process() -> Resource {
Expand All @@ -52,7 +72,9 @@ fn detect_process() -> Resource {
.into_iter()
.map(|arg| arg.to_string_lossy().into_owned().into())
.collect::<Vec<StringValue>>();
let current_exe = std::env::current_exe().map(|exe| exe.display().to_string()).unwrap_or_default();
let current_exe = std::env::current_exe()
.map(|exe| exe.display().to_string())
.unwrap_or_default();
Resource::new(vec![
KeyValue::new(
opentelemetry_semantic_conventions::resource::PROCESS_COMMAND_ARGS,
Expand All @@ -62,9 +84,6 @@ fn detect_process() -> Resource {
opentelemetry_semantic_conventions::resource::PROCESS_PID,
id() as i64,
),
KeyValue::new(
otel_resource::PROCESS_EXECUTABLE_NAME,
current_exe
),
KeyValue::new(otel_resource::PROCESS_EXECUTABLE_NAME, current_exe),
])
}
}
39 changes: 18 additions & 21 deletions src/trace.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,21 @@
use anyhow::{Context, Result};

use opentelemetry::trace::TracerProvider as _;
use anyhow::Result;
use opentelemetry::global;
use opentelemetry::trace::TracerProvider;
use opentelemetry_otlp::SpanExporter;
use opentelemetry_otlp::WithExportConfig;
use opentelemetry_sdk::{trace as sdktrace, Resource};

use opentelemetry_sdk::trace::Config;
use opentelemetry_sdk::trace::Tracer;
use opentelemetry_sdk::{runtime, trace as sdktrace, Resource};

pub fn otel_tracer(endpoint: &str, resource: Resource) -> Result<sdktrace::Tracer> {
opentelemetry_otlp::new_pipeline()
.tracing()
.with_exporter(
opentelemetry_otlp::new_exporter()
.tonic()
.with_endpoint(endpoint)
)
.with_trace_config(sdktrace::Config::default().with_resource(resource))
.with_batch_config(
sdktrace::BatchConfigBuilder::default().build(),
)
.install_batch(opentelemetry_sdk::runtime::Tokio)
.map( |p| p.tracer_builder("tracing").build() )
.context("Unable to initialize metrics OtlpPipeline")
pub fn otel_tracer(endpoint: &str, resource: Resource) -> Result<Tracer> {
let exporter = SpanExporter::builder()
.with_tonic()
.with_endpoint(endpoint)
.build()?;
let provider: sdktrace::TracerProvider = sdktrace::TracerProvider::builder()
.with_config(Config::default().with_resource(resource))
.with_batch_exporter(exporter, runtime::Tokio)
.build();
global::set_tracer_provider(provider.clone());
Ok(provider.tracer("tracing-otel-subscriber"))
}