diff --git a/apps/pass-image-api/Cargo.lock b/apps/pass-image-api/Cargo.lock index 06d48df..d5daebc 100644 --- a/apps/pass-image-api/Cargo.lock +++ b/apps/pass-image-api/Cargo.lock @@ -254,6 +254,56 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +dependencies = [ + "windows-sys 0.60.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.60.2", +] + [[package]] name = "anyhow" version = "1.0.98" @@ -375,7 +425,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -510,6 +560,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + [[package]] name = "cookie" version = "0.16.2" @@ -646,6 +702,29 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env_filter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + [[package]] name = "equator" version = "0.4.2" @@ -1328,6 +1407,12 @@ dependencies = [ "serde", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itertools" version = "0.12.1" @@ -1352,6 +1437,30 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jiff" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "jobserver" version = "0.1.33" @@ -1613,6 +1722,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "opentelemetry" version = "0.30.0" @@ -1751,7 +1866,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1762,6 +1877,7 @@ dependencies = [ "anyhow", "awc", "bytes", + "env_logger", "float-cmp", "futures", "futures-executor 0.2.1", @@ -1839,6 +1955,21 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "potential_utf" version = "0.1.2" @@ -2750,6 +2881,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "v_frame" version = "0.3.9" @@ -2925,13 +3062,19 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -2940,7 +3083,16 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", ] [[package]] @@ -2949,14 +3101,31 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] @@ -2965,48 +3134,96 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + [[package]] name = "winnow" version = "0.7.12" diff --git a/apps/pass-image-api/Cargo.toml b/apps/pass-image-api/Cargo.toml index 7201929..e2273e0 100644 --- a/apps/pass-image-api/Cargo.toml +++ b/apps/pass-image-api/Cargo.toml @@ -10,6 +10,7 @@ futures = "0.3.31" futures-executor = { version = "0.2.0-beta" } image = "0.25.2" log = { version = "0.4.22", features = ["kv"] } +env_logger = "0.11.8" opentelemetry = "0.30.0" opentelemetry-appender-log = "0.30.0" opentelemetry-otlp = { version = "0.30.0", features = ["grpc-tonic", "trace", "metrics", "logs"] } diff --git a/apps/pass-image-api/src/main.rs b/apps/pass-image-api/src/main.rs index 9eb5c91..cdb7d07 100644 --- a/apps/pass-image-api/src/main.rs +++ b/apps/pass-image-api/src/main.rs @@ -3,8 +3,8 @@ use std::collections::HashMap; use crate::coordinates::LatLong; use crate::tiles::fetch_image_from_point; use actix_web::{get, http::header::ContentType, web, App, HttpResponse, HttpServer, Responder}; -use opentelemetry_instrumentation_actix_web::RequestTracing; use log::{info, warn}; +use opentelemetry_instrumentation_actix_web::RequestTracing; use tiles::TileSet; mod coordinates; mod tiles; @@ -59,19 +59,22 @@ async fn get_image( #[actix_web::main] async fn main() -> std::io::Result<()> { // Roll otel errors up to here and log them in aggregate - match init_otel() { - Ok(_) => { + // Hold providers to keep them alive until shutdown + let otel_providers = match init_otel() { + Ok(providers) => { info!("Successfully configured OTel"); + Some(providers) } Err(err) => { warn!( "Couldn't start OTel! Will proudly soldier on without telemetry: {0}", err ); + None } }; - HttpServer::new(|| { + let server_result = HttpServer::new(|| { App::new() .wrap(RequestTracing::new()) .route("/", web::get().to(index)) @@ -80,5 +83,26 @@ async fn main() -> std::io::Result<()> { }) .bind(("0.0.0.0", 8080))? .run() - .await + .await; + + // Explicitly shutdown OpenTelemetry providers before exiting + if let Some((tracer_provider, meter_provider, logger_provider)) = otel_providers { + info!("Shutting down OpenTelemetry providers"); + + if let Err(err) = tracer_provider.shutdown() { + warn!("Error shutting down tracer provider: {:?}", err); + } + + if let Err(err) = meter_provider.shutdown() { + warn!("Error shutting down meter provider: {:?}", err); + } + + if let Some(logger_provider) = logger_provider { + if let Err(err) = logger_provider.shutdown() { + warn!("Error shutting down logger provider: {:?}", err); + } + } + } + + server_result } diff --git a/apps/pass-image-api/src/telemetry_conf.rs b/apps/pass-image-api/src/telemetry_conf.rs index 709e1a4..d83ffa4 100644 --- a/apps/pass-image-api/src/telemetry_conf.rs +++ b/apps/pass-image-api/src/telemetry_conf.rs @@ -23,7 +23,7 @@ fn get_resource() -> Resource { // A Tracer Provider is a factory for Tracers // A Tracer creates spans containing more information about what is happening for a given operation, // such as a request in a service. -fn init_tracer() { +fn init_tracer() -> opentelemetry_sdk::trace::SdkTracerProvider { global::set_text_map_propagator(TraceContextPropagator::new()); let exporter = opentelemetry_otlp::SpanExporter::builder() @@ -36,12 +36,13 @@ fn init_tracer() { .with_resource(get_resource()) .build(); - global::set_tracer_provider(tracer_provider); + global::set_tracer_provider(tracer_provider.clone()); + tracer_provider } // A Meter Provider is a factory for Meters // A Meter creates metric instruments, capturing measurements about a service at runtime. -fn init_meter_provider() -> Result<()> { +fn init_meter_provider() -> Result { let exporter = opentelemetry_otlp::MetricExporter::builder() .with_tonic() .with_temporality(opentelemetry_sdk::metrics::Temporality::Delta) @@ -53,41 +54,65 @@ fn init_meter_provider() -> Result<()> { .with_resource(get_resource()) .build(); - global::set_meter_provider(meter_provider); + global::set_meter_provider(meter_provider.clone()); - Ok(()) + Ok(meter_provider) } // A Logger Provider is a factory for Loggers // The init_logger_provider function initialises a Logger Provider // And sets up a Log Appender for the log crate, bridging logs to the OpenTelemetry Logger. -fn init_logger_provider() { - let exporter = opentelemetry_otlp::LogExporter::builder() - .with_tonic() - .build() - .expect("Failed to create log exporter"); - - let logger_provider = opentelemetry_sdk::logs::SdkLoggerProvider::builder() - .with_batch_exporter(exporter) - .with_resource(get_resource()) - .build(); - - // Setup Log Appender for the log crate - let otel_log_appender = OpenTelemetryLogBridge::new(&logger_provider); - log::set_boxed_logger(Box::new(otel_log_appender)).unwrap(); - +// If OTLP setup fails, falls back to stdout logging. +fn init_logger_provider() -> Option { // Read maximum log level from the enironment, using INFO if it's missing or // we can't parse it. let max_level = env::var("LOG_LEVEL") .ok() .and_then(|l| Level::from_str(l.to_lowercase().as_str()).ok()) .unwrap_or(Level::Info); - log::set_max_level(max_level.to_level_filter()); + + // Try to setup OTLP logging first + match opentelemetry_otlp::LogExporter::builder() + .with_tonic() + .build() + { + Ok(exporter) => { + let logger_provider = opentelemetry_sdk::logs::SdkLoggerProvider::builder() + .with_batch_exporter(exporter) + .with_resource(get_resource()) + .build(); + + // Setup Log Appender for the log crate + let otel_log_appender = OpenTelemetryLogBridge::new(&logger_provider); + log::set_boxed_logger(Box::new(otel_log_appender)).unwrap(); + log::set_max_level(max_level.to_level_filter()); + + Some(logger_provider) + } + Err(err) => { + // Fall back to stdout logging if OTLP setup fails + env_logger::Builder::from_default_env() + .filter_level(max_level.to_level_filter()) + .init(); + + log::warn!( + "Failed to initialize OTLP log exporter: {}. Falling back to stdout logging.", + err + ); + + None + } + } } -pub fn init_otel() -> Result<()> { - init_logger_provider(); - init_tracer(); - init_meter_provider().with_context(|| "initialising meter provider")?; - Ok(()) +pub fn init_otel() -> Result<( + opentelemetry_sdk::trace::SdkTracerProvider, + opentelemetry_sdk::metrics::SdkMeterProvider, + Option, +)> { + let logger_provider = init_logger_provider(); + let tracer_provider = init_tracer(); + let meter_provider = init_meter_provider().with_context(|| "initialising meter provider")?; + + Ok((tracer_provider, meter_provider, logger_provider)) } diff --git a/apps/pass-image-api/src/tiles.rs b/apps/pass-image-api/src/tiles.rs index e3e1171..dcef57f 100644 --- a/apps/pass-image-api/src/tiles.rs +++ b/apps/pass-image-api/src/tiles.rs @@ -7,7 +7,6 @@ use crate::coordinates::{ TileCoordinate, }; -use opentelemetry_instrumentation_actix_web::ClientExt; use anyhow::Result; use awc::http::header::CONTENT_TYPE; use awc::http::StatusCode; @@ -17,6 +16,7 @@ use image::{DynamicImage, GenericImage, ImageBuffer}; use log::debug; use opentelemetry::trace::{SpanKind, Status, TraceContextExt, Tracer}; use opentelemetry::{global, Context}; +use opentelemetry_instrumentation_actix_web::ClientExt; use std::borrow::Borrow; use std::collections::{HashMap, HashSet}; use std::io::Cursor;