Instrument with tracing
This commit is contained in:
parent
710fcd0f3d
commit
f0c6bc24f5
771
Cargo.lock
generated
771
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
17
Cargo.toml
17
Cargo.toml
|
@ -18,15 +18,30 @@ actix-web = { version = "4.0.0-beta.8", default-features = false }
|
|||
anyhow = "1.0"
|
||||
awc = { version = "3.0.0-beta.7", default-features = false }
|
||||
dotenv = "0.15.0"
|
||||
env_logger = "0.9"
|
||||
mime = "0.3"
|
||||
minify-html = "0.6.8"
|
||||
once_cell = "1.4"
|
||||
opentelemetry = { version = "0.16", features = ["rt-tokio"] }
|
||||
opentelemetry-otlp = "0.9"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
structopt = "0.3.14"
|
||||
thiserror = "1.0"
|
||||
tracing = "0.1"
|
||||
tracing-awc = { version = "0.1.0-beta.4", features = ["opentelemetry_0_16"] }
|
||||
tracing-error = "0.1"
|
||||
tracing-futures = "0.2"
|
||||
tracing-log = "0.1"
|
||||
tracing-opentelemetry = "0.15"
|
||||
tracing-subscriber = { version = "0.2", features = ["ansi", "fmt"] }
|
||||
url = "2.1"
|
||||
|
||||
[dependencies.tracing-actix-web]
|
||||
version = "0.4.0-beta.12"
|
||||
git = "https://github.com/asonix/tracing-actix-web"
|
||||
branch = "asonix/tracing-error-work-around"
|
||||
features = ["emit_event_on_error", "opentelemetry_0_16"]
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = "1.0"
|
||||
dotenv = "0.15.0"
|
||||
|
|
155
src/main.rs
155
src/main.rs
|
@ -4,17 +4,23 @@ use actix_web::{
|
|||
header::{CacheControl, CacheDirective, ContentType, LastModified, LOCATION},
|
||||
StatusCode,
|
||||
},
|
||||
middleware::Logger,
|
||||
web, App, HttpRequest, HttpResponse, HttpResponseBuilder, HttpServer, ResponseError,
|
||||
};
|
||||
use awc::Client;
|
||||
use once_cell::sync::Lazy;
|
||||
use opentelemetry::{sdk::{Resource, propagation::TraceContextPropagator}, KeyValue};
|
||||
use opentelemetry_otlp::WithExportConfig;
|
||||
use std::{
|
||||
io::Cursor,
|
||||
net::SocketAddr,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
use structopt::StructOpt;
|
||||
use tracing_actix_web::TracingLogger;
|
||||
use tracing_awc::Propagate;
|
||||
use tracing_error::{ErrorLayer, SpanTrace};
|
||||
use tracing_log::LogTracer;
|
||||
use tracing_subscriber::{fmt::format::FmtSpan, layer::SubscriberExt, EnvFilter, Registry};
|
||||
use url::Url;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/templates.rs"));
|
||||
|
@ -50,6 +56,14 @@ struct Config {
|
|||
help = "The scheme, domain, and optional port of the pict-rs proxy server"
|
||||
)]
|
||||
domain: Url,
|
||||
|
||||
#[structopt(
|
||||
short,
|
||||
long,
|
||||
env = "PICTRS_PROXY_OPENTELEMETRY_URL",
|
||||
help = "URL of OpenTelemetry Collector"
|
||||
)]
|
||||
opentelemetry_url: Option<Url>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
|
@ -141,7 +155,7 @@ impl Config {
|
|||
|
||||
static CONFIG: Lazy<Config> = Lazy::new(Config::from_args);
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
enum FileType {
|
||||
#[serde(rename = "jpg")]
|
||||
Jpg,
|
||||
|
@ -240,8 +254,56 @@ fn statics(file: &str) -> String {
|
|||
format!("/static/{}", file)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Error {
|
||||
context: SpanTrace,
|
||||
kind: ErrorKind,
|
||||
}
|
||||
|
||||
impl<T> From<T> for Error
|
||||
where
|
||||
ErrorKind: From<T>,
|
||||
{
|
||||
fn from(error: T) -> Self {
|
||||
Error {
|
||||
context: SpanTrace::capture(),
|
||||
kind: error.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.kind)?;
|
||||
std::fmt::Display::fmt(&self.context, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
self.kind.source()
|
||||
}
|
||||
}
|
||||
|
||||
impl ResponseError for Error {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
}
|
||||
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
match render(HttpResponse::build(self.status_code()), |cursor| {
|
||||
self::templates::error(cursor, &self.kind.to_string())
|
||||
}) {
|
||||
Ok(res) => res,
|
||||
Err(_) => HttpResponse::build(self.status_code())
|
||||
.content_type(mime::TEXT_PLAIN.essence_str())
|
||||
.body(self.kind.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum Error {
|
||||
enum ErrorKind {
|
||||
#[error("{0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
|
||||
|
@ -252,29 +314,14 @@ enum Error {
|
|||
JsonPayload(#[from] awc::error::JsonPayloadError),
|
||||
}
|
||||
|
||||
impl ResponseError for Error {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
}
|
||||
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
match render(HttpResponse::build(self.status_code()), |cursor| {
|
||||
self::templates::error(cursor, &self.to_string())
|
||||
}) {
|
||||
Ok(res) => res,
|
||||
Err(_) => HttpResponse::build(self.status_code())
|
||||
.content_type(mime::TEXT_PLAIN.essence_str())
|
||||
.body(self.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "Upload")]
|
||||
async fn index() -> Result<HttpResponse, Error> {
|
||||
render(HttpResponse::Ok(), |cursor| {
|
||||
self::templates::index(cursor, "/upload", "images[]")
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "Upload", skip(req, body, client))]
|
||||
async fn upload(
|
||||
req: HttpRequest,
|
||||
body: web::Payload,
|
||||
|
@ -283,12 +330,12 @@ async fn upload(
|
|||
let client_request = client.request_from(CONFIG.upstream_upload_url(), req.head());
|
||||
|
||||
let client_request = if let Some(addr) = req.head().peer_addr {
|
||||
client_request.insert_header(("X-Forwarded-For", addr.to_string()))
|
||||
client_request.append_header(("X-Forwarded-For", addr.to_string()))
|
||||
} else {
|
||||
client_request
|
||||
};
|
||||
|
||||
let mut res = client_request.send_stream(body).await?;
|
||||
let mut res = client_request.propagate().send_stream(body).await?;
|
||||
|
||||
let images = res.json::<Images>().await?;
|
||||
|
||||
|
@ -304,6 +351,7 @@ struct ThumbnailQuery {
|
|||
image: String,
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "Thumbs", skip(client))]
|
||||
async fn thumbs(
|
||||
query: web::Query<ThumbnailQuery>,
|
||||
client: web::Data<Client>,
|
||||
|
@ -311,7 +359,7 @@ async fn thumbs(
|
|||
let file = query.into_inner().image;
|
||||
|
||||
let url = CONFIG.upstream_details_url(&file);
|
||||
let mut res = client.get(url).send().await?;
|
||||
let mut res = client.get(url).propagate().send().await?;
|
||||
|
||||
if res.status() == StatusCode::NOT_FOUND {
|
||||
return Ok(to_404());
|
||||
|
@ -330,6 +378,7 @@ async fn thumbs(
|
|||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "Image", skip(req, client))]
|
||||
async fn image(
|
||||
url: String,
|
||||
req: HttpRequest,
|
||||
|
@ -342,7 +391,7 @@ async fn image(
|
|||
client_request
|
||||
};
|
||||
|
||||
let res = client_request.no_decompress().send().await?;
|
||||
let res = client_request.no_decompress().propagate().send().await?;
|
||||
|
||||
if res.status() == StatusCode::NOT_FOUND {
|
||||
return Ok(to_404());
|
||||
|
@ -357,6 +406,7 @@ async fn image(
|
|||
Ok(client_res.body(BodyStream::new(res)))
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "View original", skip(client))]
|
||||
async fn view_original(
|
||||
file: web::Path<String>,
|
||||
client: web::Data<Client>,
|
||||
|
@ -364,7 +414,7 @@ async fn view_original(
|
|||
let file = file.into_inner();
|
||||
|
||||
let url = CONFIG.upstream_details_url(&file);
|
||||
let mut res = client.get(url).send().await?;
|
||||
let mut res = client.get(url).propagate().send().await?;
|
||||
|
||||
if res.status() == StatusCode::NOT_FOUND {
|
||||
return Ok(to_404());
|
||||
|
@ -383,6 +433,7 @@ async fn view_original(
|
|||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "View", skip(client))]
|
||||
async fn view(
|
||||
parts: web::Path<(u64, String)>,
|
||||
client: web::Data<Client>,
|
||||
|
@ -394,7 +445,7 @@ async fn view(
|
|||
}
|
||||
|
||||
let url = CONFIG.upstream_details_url(&file);
|
||||
let mut res = client.get(url).send().await?;
|
||||
let mut res = client.get(url).propagate().send().await?;
|
||||
|
||||
if res.status() == StatusCode::NOT_FOUND {
|
||||
return Ok(to_404());
|
||||
|
@ -413,6 +464,7 @@ async fn view(
|
|||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "Thumbnail", skip(req, client))]
|
||||
async fn thumbnail(
|
||||
parts: web::Path<(u64, FileType, String)>,
|
||||
req: HttpRequest,
|
||||
|
@ -433,6 +485,7 @@ fn valid_thumbnail_size(size: u64) -> bool {
|
|||
THUMBNAIL_SIZES.contains(&size)
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "Full resolution", skip(req, client))]
|
||||
async fn full_res(
|
||||
filename: web::Path<String>,
|
||||
req: HttpRequest,
|
||||
|
@ -443,6 +496,7 @@ async fn full_res(
|
|||
image(url, req, client).await
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "Static files")]
|
||||
async fn static_files(filename: web::Path<String>) -> HttpResponse {
|
||||
let filename = filename.into_inner();
|
||||
|
||||
|
@ -470,6 +524,7 @@ struct DeleteQuery {
|
|||
confirm: bool,
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "Delete", skip(client))]
|
||||
async fn delete(
|
||||
query: web::Query<DeleteQuery>,
|
||||
client: web::Data<Client>,
|
||||
|
@ -481,7 +536,7 @@ async fn delete(
|
|||
} = query.into_inner();
|
||||
|
||||
let url = CONFIG.upstream_details_url(&file);
|
||||
let mut res = client.get(url).send().await?;
|
||||
let mut res = client.get(url).propagate().send().await?;
|
||||
|
||||
if res.status() == StatusCode::NOT_FOUND {
|
||||
return Ok(to_404());
|
||||
|
@ -489,7 +544,7 @@ async fn delete(
|
|||
|
||||
if confirm {
|
||||
let url = CONFIG.upstream_delete_url(&token, &file);
|
||||
client.delete(url).send().await?;
|
||||
client.delete(url).propagate().send().await?;
|
||||
|
||||
render(HttpResponse::Ok(), |cursor| {
|
||||
self::templates::deleted(cursor, &file)
|
||||
|
@ -516,6 +571,7 @@ fn to_404() -> HttpResponse {
|
|||
.finish()
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "Not Found")]
|
||||
async fn not_found() -> Result<HttpResponse, Error> {
|
||||
render(HttpResponse::NotFound(), |cursor| {
|
||||
self::templates::not_found(cursor)
|
||||
|
@ -528,6 +584,7 @@ async fn go_home() -> HttpResponse {
|
|||
.finish()
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "Render", skip(builder, f))]
|
||||
fn render(
|
||||
mut builder: HttpResponseBuilder,
|
||||
f: impl FnOnce(&mut Cursor<&mut Vec<u8>>) -> Result<(), std::io::Error>,
|
||||
|
@ -547,11 +604,43 @@ fn render(
|
|||
async fn main() -> Result<(), anyhow::Error> {
|
||||
dotenv::dotenv().ok();
|
||||
|
||||
if std::env::var("RUST_LOG").is_err() {
|
||||
std::env::set_var("RUST_LOG", "info");
|
||||
}
|
||||
opentelemetry::global::set_text_map_propagator(TraceContextPropagator::new());
|
||||
|
||||
env_logger::init();
|
||||
LogTracer::init()?;
|
||||
|
||||
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
|
||||
|
||||
let format_layer = tracing_subscriber::fmt::layer()
|
||||
.with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
|
||||
.pretty();
|
||||
|
||||
let subscriber = Registry::default()
|
||||
.with(env_filter)
|
||||
.with(format_layer)
|
||||
.with(ErrorLayer::default());
|
||||
|
||||
if let Some(url) = &CONFIG.opentelemetry_url {
|
||||
let tracer =
|
||||
opentelemetry_otlp::new_pipeline()
|
||||
.tracing()
|
||||
.with_trace_config(opentelemetry::sdk::trace::config().with_resource(
|
||||
Resource::new(vec![KeyValue::new("service.name", "pict-rs-proxy")]),
|
||||
))
|
||||
.with_exporter(
|
||||
opentelemetry_otlp::new_exporter()
|
||||
.tonic()
|
||||
.with_endpoint(url.as_str()),
|
||||
)
|
||||
.install_batch(opentelemetry::runtime::Tokio)?;
|
||||
|
||||
let otel_layer = tracing_opentelemetry::layer().with_tracer(tracer);
|
||||
|
||||
let subscriber = subscriber.with(otel_layer);
|
||||
|
||||
tracing::subscriber::set_global_default(subscriber)?;
|
||||
} else {
|
||||
tracing::subscriber::set_global_default(subscriber)?;
|
||||
}
|
||||
|
||||
HttpServer::new(move || {
|
||||
let client = Client::builder()
|
||||
|
@ -561,7 +650,7 @@ async fn main() -> Result<(), anyhow::Error> {
|
|||
|
||||
App::new()
|
||||
.app_data(web::Data::new(client))
|
||||
.wrap(Logger::default())
|
||||
.wrap(TracingLogger::default())
|
||||
.service(web::resource("/").route(web::get().to(index)))
|
||||
.service(web::resource("/upload").route(web::post().to(upload)))
|
||||
.service(web::resource("/image/{filename}").route(web::get().to(full_res)))
|
||||
|
|
Loading…
Reference in a new issue