updates
This commit is contained in:
parent
82e377dbea
commit
d8050d2e89
905
Cargo.lock
generated
905
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
19
Cargo.toml
19
Cargo.toml
|
@ -6,16 +6,17 @@ 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 = ["http2", "original-uri", "tracing"] }
|
||||
cached = "0.46.1"
|
||||
anyhow = { version = "1.0.82", features = ["backtrace"] }
|
||||
axum = { version = "0.7.5", features = ["http2", "original-uri", "tracing"] }
|
||||
cached = "0.49.3"
|
||||
chrono = { version = "0.4.31", features = ["serde"] }
|
||||
color-eyre = "0.6.1"
|
||||
config = "0.13.3"
|
||||
config = "0.14.0"
|
||||
glob = "0.3.0"
|
||||
opentelemetry = { version = "0.21.0", features = ["metrics"] }
|
||||
opentelemetry-jaeger = { version = "0.20.0", features = ["collector_client", "isahc_collector_client"]}
|
||||
opentelemetry = { version = "0.22.0", features = ["trace", "metrics"] }
|
||||
opentelemetry-otlp = { version = "0.15.0", features = ["trace", "metrics"] }
|
||||
opentelemetry_sdk = { version = "0.22.1", features = ["rt-tokio", "trace", "metrics"] }
|
||||
prometheus = { version = "0.13.3", features = ["process"] }
|
||||
pulldown-cmark = "0.9.2"
|
||||
pulldown-cmark = "0.10.2"
|
||||
regex = "1.7.2"
|
||||
serde = "1.0.144"
|
||||
serde_derive = "1.0.144"
|
||||
|
@ -25,7 +26,7 @@ tera = { version = "1.19.1", features = ["builtins"] }
|
|||
tokio = { version = "1.34.0", features = ["full", "tracing"] }
|
||||
toml = "0.8.8"
|
||||
tower = { version = "0.4.13", features = ["full"] }
|
||||
tower-http = { version = "0.4.4", features = ["full"] }
|
||||
tower-http = { version = "0.5.2", features = ["full"] }
|
||||
tracing = "0.1.35"
|
||||
tracing-opentelemetry = "0.22.0"
|
||||
tracing-opentelemetry = "0.23.0"
|
||||
tracing-subscriber = { version = "0.3.17", features = ["fmt", "env-filter", "json", "tracing-log"] }
|
||||
|
|
18
fly.toml
Normal file
18
fly.toml
Normal file
|
@ -0,0 +1,18 @@
|
|||
# fly.toml app configuration file generated for cool-glade-6208 on 2023-06-24T10:46:20+02:00
|
||||
#
|
||||
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
|
||||
#
|
||||
|
||||
app = "cool-glade-6208"
|
||||
primary_region = "arn"
|
||||
|
||||
[http_service]
|
||||
internal_port = 8080
|
||||
force_https = true
|
||||
auto_stop_machines = true
|
||||
auto_start_machines = true
|
||||
min_machines_running = 0
|
||||
|
||||
[metrics]
|
||||
port = 8180
|
||||
path = "/metrics"
|
|
@ -1,4 +1,4 @@
|
|||
use color_eyre::Result;
|
||||
use anyhow::Result;
|
||||
use serde_derive::Serialize;
|
||||
|
||||
use tracing::instrument;
|
||||
|
@ -16,7 +16,11 @@ struct FeedContext<'a> {
|
|||
|
||||
#[instrument(skip(state))]
|
||||
pub fn render_atom_feed(state: &AppState) -> Result<String> {
|
||||
let mut posts: Vec<_> = state.posts.values().filter(|p| !p.draft && p.is_published()).collect();
|
||||
let mut posts: Vec<_> = state
|
||||
.posts
|
||||
.values()
|
||||
.filter(|p| !p.draft && p.is_published())
|
||||
.collect();
|
||||
|
||||
posts.sort_by_key(|p| &p.date);
|
||||
posts.reverse();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use axum::{
|
||||
body,
|
||||
extract::State,
|
||||
http::{header, HeaderMap, Request, StatusCode},
|
||||
body::Body,
|
||||
extract::{Request, State},
|
||||
http::{header, HeaderMap, StatusCode},
|
||||
middleware::Next,
|
||||
response::{Html, IntoResponse, Response},
|
||||
routing::get,
|
||||
|
@ -14,7 +14,7 @@ use std::sync::Arc;
|
|||
use tower_http::services::ServeDir;
|
||||
use tracing::{
|
||||
instrument,
|
||||
log::{error, info, debug},
|
||||
log::{debug, error, info},
|
||||
};
|
||||
|
||||
use crate::{AppState, WebsiteError};
|
||||
|
@ -22,14 +22,16 @@ use crate::{AppState, WebsiteError};
|
|||
pub mod posts;
|
||||
pub mod tags;
|
||||
|
||||
pub static HIT_COUNTER: Lazy<IntCounterVec> = Lazy::new(|| prometheus::register_int_counter_vec!(
|
||||
opts!(
|
||||
"http_requests_total",
|
||||
"Total amount of http requests received"
|
||||
),
|
||||
&["route", "method", "status"]
|
||||
)
|
||||
.unwrap());
|
||||
pub static HIT_COUNTER: Lazy<IntCounterVec> = Lazy::new(|| {
|
||||
prometheus::register_int_counter_vec!(
|
||||
opts!(
|
||||
"http_requests_total",
|
||||
"Total amount of http requests received"
|
||||
),
|
||||
&["route", "method", "status"]
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
pub fn routes(state: &Arc<AppState>) -> Router<Arc<AppState>> {
|
||||
Router::new()
|
||||
|
@ -38,14 +40,8 @@ pub fn routes(state: &Arc<AppState>) -> Router<Arc<AppState>> {
|
|||
.merge(tags::router())
|
||||
.merge(posts::alias_router(state.posts.values()))
|
||||
.route("/healthcheck", get(healthcheck))
|
||||
.route_service(
|
||||
"/posts/:slug/*path",
|
||||
ServeDir::new("./"),
|
||||
)
|
||||
.route_service(
|
||||
"/static/*path",
|
||||
ServeDir::new("./"),
|
||||
)
|
||||
.route_service("/posts/:slug/*path", ServeDir::new("./"))
|
||||
.route_service("/static/*path", ServeDir::new("./"))
|
||||
.layer(axum::middleware::from_fn(metrics_middleware))
|
||||
.route("/metrics", get(metrics))
|
||||
}
|
||||
|
@ -86,7 +82,7 @@ async fn metrics() -> impl IntoResponse {
|
|||
Response::builder()
|
||||
.status(200)
|
||||
.header(header::CONTENT_TYPE, encoder.format_type())
|
||||
.body(body::boxed(body::Full::from(buffer)))
|
||||
.body(Body::from(buffer))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
|
@ -95,7 +91,7 @@ pub async fn not_found() -> impl IntoResponse {
|
|||
}
|
||||
|
||||
#[instrument(skip(request, next))]
|
||||
pub async fn metrics_middleware<B>(request: Request<B>, next: Next<B>) -> Response {
|
||||
pub async fn metrics_middleware(request: Request, next: Next) -> Response {
|
||||
let path = request.uri().path().to_string();
|
||||
let method = request.method().clone();
|
||||
|
||||
|
|
|
@ -37,8 +37,8 @@ pub fn alias_router<'a>(posts: impl IntoIterator<Item = &'a Post>) -> Router<Arc
|
|||
router = router.route(
|
||||
alias,
|
||||
get(move || async {
|
||||
let path = path;
|
||||
Redirect::permanent(&path)
|
||||
let p = path;
|
||||
Redirect::permanent(&p)
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
@ -56,7 +56,11 @@ pub async fn index(
|
|||
State(state): State<Arc<AppState>>,
|
||||
headers: HeaderMap,
|
||||
) -> Result<Response, WebsiteError> {
|
||||
let mut posts: Vec<&Post> = state.posts.values().filter(|p| !p.draft && p.is_published()).collect();
|
||||
let mut posts: Vec<&Post> = state
|
||||
.posts
|
||||
.values()
|
||||
.filter(|p| !p.draft && p.is_published())
|
||||
.collect();
|
||||
|
||||
let last_changed = posts.iter().filter_map(|p| p.last_modified()).max();
|
||||
|
||||
|
@ -76,15 +80,11 @@ pub async fn index(
|
|||
|
||||
let res = state.tera.render("posts_index.html", &c)?;
|
||||
|
||||
|
||||
Ok((
|
||||
StatusCode::OK,
|
||||
[(
|
||||
header::LAST_MODIFIED,
|
||||
last_changed.map_or_else(
|
||||
|| state.startup_time.to_rfc2822(),
|
||||
|d| d.to_rfc2822(),
|
||||
),
|
||||
last_changed.map_or_else(|| state.startup_time.to_rfc2822(), |d| d.to_rfc2822()),
|
||||
)],
|
||||
Html(res),
|
||||
)
|
||||
|
@ -116,10 +116,7 @@ pub async fn view(
|
|||
StatusCode::OK,
|
||||
[(
|
||||
header::LAST_MODIFIED,
|
||||
last_changed.map_or_else(
|
||||
|| state.startup_time.to_rfc2822(),
|
||||
|d| d.to_rfc2822(),
|
||||
),
|
||||
last_changed.map_or_else(|| state.startup_time.to_rfc2822(), |d| d.to_rfc2822()),
|
||||
)],
|
||||
Html(res),
|
||||
)
|
||||
|
@ -131,7 +128,11 @@ pub async fn feed(
|
|||
State(state): State<Arc<AppState>>,
|
||||
headers: HeaderMap,
|
||||
) -> Result<Response, WebsiteError> {
|
||||
let mut posts: Vec<&Post> = state.posts.values().filter(|p| !p.draft && p.is_published()).collect();
|
||||
let mut posts: Vec<&Post> = state
|
||||
.posts
|
||||
.values()
|
||||
.filter(|p| !p.draft && p.is_published())
|
||||
.collect();
|
||||
|
||||
let last_changed = posts.iter().filter_map(|p| p.last_modified()).max();
|
||||
|
||||
|
@ -149,10 +150,7 @@ pub async fn feed(
|
|||
(header::CONTENT_TYPE, "application/atom+xml"),
|
||||
(
|
||||
header::LAST_MODIFIED,
|
||||
&last_changed.map_or_else(
|
||||
|| state.startup_time.to_rfc2822(),
|
||||
|d| d.to_rfc2822(),
|
||||
),
|
||||
&last_changed.map_or_else(|| state.startup_time.to_rfc2822(), |d| d.to_rfc2822()),
|
||||
),
|
||||
],
|
||||
crate::feed::render_atom_feed(&state)?,
|
||||
|
|
|
@ -85,10 +85,7 @@ pub async fn view(
|
|||
StatusCode::OK,
|
||||
[(
|
||||
header::LAST_MODIFIED,
|
||||
&last_changed.map_or_else(
|
||||
|| state.startup_time.to_rfc2822(),
|
||||
|d| d.to_rfc2822(),
|
||||
),
|
||||
&last_changed.map_or_else(|| state.startup_time.to_rfc2822(), |d| d.to_rfc2822()),
|
||||
)],
|
||||
Html(res),
|
||||
)
|
||||
|
@ -125,10 +122,7 @@ pub async fn feed(
|
|||
(header::CONTENT_TYPE, "application/atom+xml"),
|
||||
(
|
||||
header::LAST_MODIFIED,
|
||||
&last_changed.map_or_else(
|
||||
|| state.startup_time.to_rfc2822(),
|
||||
|d| d.to_rfc2822(),
|
||||
),
|
||||
&last_changed.map_or_else(|| state.startup_time.to_rfc2822(), |d| d.to_rfc2822()),
|
||||
),
|
||||
],
|
||||
crate::feed::render_atom_tag_feed(tag, &state)?,
|
||||
|
|
|
@ -36,23 +36,32 @@ pub fn uri_with_path(uri: &Uri, path: &str) -> Uri {
|
|||
mod tests {
|
||||
use axum::http::Uri;
|
||||
|
||||
use crate::helpers::uri_with_path;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn uri_with_relative_path() {
|
||||
let uri: Uri = "http://localhost/baba/".parse().unwrap();
|
||||
assert_eq!(uri_with_path(&uri, "is/you").to_string(), "http://localhost/baba/is/you");
|
||||
assert_eq!(
|
||||
uri_with_path(&uri, "is/you").to_string(),
|
||||
"http://localhost/baba/is/you"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uri_with_absolute_path() {
|
||||
let uri: Uri = "http://localhost/baba/is/you".parse().unwrap();
|
||||
assert_eq!(uri_with_path(&uri, "/keke/is/move").to_string(), "http://localhost/keke/is/move");
|
||||
assert_eq!(
|
||||
uri_with_path(&uri, "/keke/is/move").to_string(),
|
||||
"http://localhost/keke/is/move"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uri_with_index_relative_path() {
|
||||
let uri: Uri = "http://localhost/baba/is/you".parse().unwrap();
|
||||
assert_eq!(uri_with_path(&uri, "happy").to_string(), "http://localhost/baba/is/happy");
|
||||
assert_eq!(
|
||||
uri_with_path(&uri, "happy/and/you").to_string(),
|
||||
"http://localhost/baba/is/happy/and/you"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use syntect::{highlighting::ThemeSet, parsing::SyntaxSet};
|
|||
use tracing::{error, instrument};
|
||||
|
||||
#[instrument(skip(content, lang, theme))]
|
||||
pub fn hilight(content: &str, lang: &str, theme: Option<&str>) -> color_eyre::Result<String> {
|
||||
pub fn hilight(content: &str, lang: &str, theme: Option<&str>) -> anyhow::Result<String> {
|
||||
let ss = SyntaxSet::load_defaults_newlines();
|
||||
let s = ss
|
||||
.find_syntax_by_extension(lang)
|
||||
|
|
98
src/main.rs
98
src/main.rs
|
@ -1,31 +1,26 @@
|
|||
#![warn(clippy::pedantic)]
|
||||
#![allow(clippy::unused_async)] // axum handlers needs async, even if no awaiting happens
|
||||
use std::{collections::HashMap, fmt::Display, sync::Arc, time::Duration};
|
||||
use std::{collections::HashMap, fmt::Display, sync::Arc};
|
||||
|
||||
use axum::{
|
||||
body::Body,
|
||||
extract::{MatchedPath, OriginalUri},
|
||||
http::{uri::PathAndQuery, Request, Uri},
|
||||
response::Response,
|
||||
};
|
||||
use axum::http::Uri;
|
||||
use chrono::DateTime;
|
||||
use color_eyre::eyre::{Error, Result};
|
||||
|
||||
use config::Config;
|
||||
use post::Post;
|
||||
|
||||
use tag::Tag;
|
||||
use tera::Tera;
|
||||
use tower_http::{compression::CompressionLayer, cors::CorsLayer};
|
||||
use tracing::{field::Empty, info_span, log::info, Span, instrument};
|
||||
|
||||
use tracing_subscriber::{prelude::*, EnvFilter};
|
||||
use tower_http::{compression::CompressionLayer, cors::CorsLayer};
|
||||
use tracing::{instrument, log::info};
|
||||
|
||||
use anyhow::{Error, Result};
|
||||
|
||||
mod feed;
|
||||
mod handlers;
|
||||
mod helpers;
|
||||
mod hilighting;
|
||||
mod markdown;
|
||||
mod observability;
|
||||
mod post;
|
||||
mod tag;
|
||||
|
||||
|
@ -40,28 +35,24 @@ pub struct AppState {
|
|||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
color_eyre::install()?;
|
||||
let cfg = Config::builder()
|
||||
.add_source(config::File::with_name("config.toml"))
|
||||
.add_source(config::Environment::with_prefix("WEBSITE"))
|
||||
.build()?;
|
||||
|
||||
init_tracing(&cfg);
|
||||
observability::init_tracing(&cfg);
|
||||
info!("Starting server...");
|
||||
|
||||
let addr = cfg.get_string("bind_address")?;
|
||||
let app = init_app(&cfg).await?;
|
||||
axum::Server::bind(&cfg.get_string("bind_address")?.parse().unwrap())
|
||||
.serve(app.into_make_service())
|
||||
.await?;
|
||||
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
|
||||
axum::serve(listener, app.into_make_service()).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
async fn init_app(cfg: &Config) -> Result<axum::routing::Router> {
|
||||
let base_url: Uri = cfg.get_string("base_url")?
|
||||
.parse()
|
||||
.unwrap();
|
||||
let base_url: Uri = cfg.get_string("base_url")?.parse().unwrap();
|
||||
|
||||
let tera = Tera::new("templates/**/*")?;
|
||||
let mut state = AppState {
|
||||
|
@ -83,69 +74,12 @@ async fn init_app(cfg: &Config) -> Result<axum::routing::Router> {
|
|||
.layer(CompressionLayer::new())
|
||||
.layer(
|
||||
tower_http::trace::TraceLayer::new_for_http()
|
||||
.make_span_with(make_span)
|
||||
.on_response(on_response),
|
||||
.make_span_with(observability::make_span)
|
||||
.on_response(observability::on_response),
|
||||
)
|
||||
.with_state(state.clone()))
|
||||
}
|
||||
|
||||
fn init_tracing(cfg: &Config) {
|
||||
let filter = if let Ok(filter) = cfg.get_string("logging") {
|
||||
EnvFilter::builder()
|
||||
.with_default_directive("info".parse().unwrap())
|
||||
.parse_lossy(filter)
|
||||
} else {
|
||||
EnvFilter::builder()
|
||||
.with_default_directive("info".parse().unwrap())
|
||||
.from_env_lossy()
|
||||
};
|
||||
|
||||
opentelemetry::global::set_text_map_propagator(opentelemetry_jaeger::Propagator::new());
|
||||
|
||||
let tracer = opentelemetry_jaeger::new_agent_pipeline()
|
||||
.with_service_name("website")
|
||||
.install_simple().unwrap();
|
||||
|
||||
let otel = tracing_opentelemetry::layer().with_tracer(tracer);
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(filter)
|
||||
.with(otel)
|
||||
.with(tracing_subscriber::fmt::layer())
|
||||
.init();
|
||||
}
|
||||
|
||||
|
||||
fn make_span(request: &Request<Body>) -> Span {
|
||||
let uri = if let Some(OriginalUri(uri)) = request.extensions().get::<OriginalUri>() {
|
||||
uri
|
||||
} else {
|
||||
request.uri()
|
||||
};
|
||||
let route = request
|
||||
.extensions()
|
||||
.get::<MatchedPath>()
|
||||
.map_or(uri.path(), axum::extract::MatchedPath::as_str);
|
||||
let method = request.method().as_str();
|
||||
let target = uri
|
||||
.path_and_query()
|
||||
.map_or(uri.path(), PathAndQuery::as_str);
|
||||
let name = format!("{method} {route}");
|
||||
|
||||
info_span!(
|
||||
"request",
|
||||
otel.name = %name,
|
||||
http.route = %route,
|
||||
http.method = %method,
|
||||
http.target = %target,
|
||||
http.status_code = Empty
|
||||
)
|
||||
}
|
||||
|
||||
fn on_response(response: &Response, _latency: Duration, span: &Span) {
|
||||
span.record("http.status_code", response.status().as_str());
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum WebsiteError {
|
||||
NotFound,
|
||||
|
@ -176,8 +110,8 @@ impl From<tera::Error> for WebsiteError {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<color_eyre::Report> for WebsiteError {
|
||||
fn from(value: color_eyre::Report) -> Self {
|
||||
impl From<Error> for WebsiteError {
|
||||
fn from(value: Error) -> Self {
|
||||
WebsiteError::InternalError(value)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,10 @@ use crate::helpers;
|
|||
use crate::hilighting;
|
||||
use axum::http::Uri;
|
||||
use cached::once_cell::sync::Lazy;
|
||||
use pulldown_cmark::CodeBlockKind;
|
||||
use pulldown_cmark::Event;
|
||||
use pulldown_cmark::Tag;
|
||||
use pulldown_cmark::TagEnd;
|
||||
use pulldown_cmark::{Options, Parser};
|
||||
use regex::Regex;
|
||||
use tracing::instrument;
|
||||
|
@ -24,46 +26,65 @@ pub fn render_markdown_to_html(base_uri: Option<&Uri>, markdown: &str) -> String
|
|||
let mut content_html = String::new();
|
||||
let parser = Parser::new_ext(markdown, opt);
|
||||
|
||||
let mut code_block = false;
|
||||
let mut code_lang = None;
|
||||
let mut code_accumulator = String::new();
|
||||
let mut events = Vec::new();
|
||||
for event in parser {
|
||||
match event {
|
||||
Event::Text(text) => {
|
||||
if code_block {
|
||||
if code_lang.is_some() {
|
||||
code_accumulator.push_str(&text);
|
||||
} else {
|
||||
events.push(Event::Text(text));
|
||||
}
|
||||
}
|
||||
Event::Start(Tag::Link(t, mut link, title)) => {
|
||||
Event::Start(Tag::Link {
|
||||
mut dest_url,
|
||||
link_type,
|
||||
title,
|
||||
id,
|
||||
}) => {
|
||||
if let Some(uri) = base_uri {
|
||||
if !link.starts_with('#') && !STARTS_WITH_SCHEMA_RE.is_match(&link) && !EMAIL_RE.is_match(&link) {
|
||||
if !dest_url.starts_with('#')
|
||||
&& !STARTS_WITH_SCHEMA_RE.is_match(&dest_url)
|
||||
&& !EMAIL_RE.is_match(&dest_url)
|
||||
{
|
||||
// convert relative URIs to absolute URIs
|
||||
link = helpers::uri_with_path(uri, &link).to_string().into();
|
||||
dest_url = helpers::uri_with_path(uri, &dest_url).to_string().into();
|
||||
}
|
||||
}
|
||||
events.push(Event::Start(Tag::Link(t, link, title)));
|
||||
events.push(Event::Start(Tag::Link {
|
||||
dest_url,
|
||||
link_type,
|
||||
title,
|
||||
id,
|
||||
}));
|
||||
}
|
||||
Event::Start(Tag::Image(t, mut link, title)) => {
|
||||
Event::Start(Tag::Image {
|
||||
link_type,
|
||||
mut dest_url,
|
||||
title,
|
||||
id,
|
||||
}) => {
|
||||
if let Some(uri) = base_uri {
|
||||
if !link.contains("://") && !link.contains('@') {
|
||||
if !dest_url.contains("://") && !dest_url.contains('@') {
|
||||
// convert relative URIs to absolute URIs
|
||||
link = helpers::uri_with_path(uri, &link).to_string().into();
|
||||
dest_url = helpers::uri_with_path(uri, &dest_url).to_string().into();
|
||||
}
|
||||
}
|
||||
events.push(Event::Start(Tag::Image(t, link, title)));
|
||||
events.push(Event::Start(Tag::Image {
|
||||
link_type,
|
||||
dest_url,
|
||||
title,
|
||||
id,
|
||||
}));
|
||||
}
|
||||
Event::Start(Tag::CodeBlock(kind)) => {
|
||||
code_block = true;
|
||||
if let pulldown_cmark::CodeBlockKind::Fenced(lang) = kind {
|
||||
if let CodeBlockKind::Fenced(lang) = kind {
|
||||
code_lang = Some(lang);
|
||||
}
|
||||
}
|
||||
Event::End(Tag::CodeBlock(_)) => {
|
||||
code_block = false;
|
||||
|
||||
Event::End(TagEnd::CodeBlock) => {
|
||||
let lang = code_lang.take().unwrap_or("".into());
|
||||
let res = hilighting::hilight(&code_accumulator, &lang, Some("base16-ocean.dark"))
|
||||
.unwrap();
|
||||
|
|
70
src/observability.rs
Normal file
70
src/observability.rs
Normal file
|
@ -0,0 +1,70 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use axum::{
|
||||
extract::{MatchedPath, OriginalUri, Request},
|
||||
http::uri::PathAndQuery,
|
||||
response::Response,
|
||||
};
|
||||
use config::Config;
|
||||
use opentelemetry::global;
|
||||
use opentelemetry_sdk::propagation::TraceContextPropagator;
|
||||
use tracing::{field::Empty, info_span, Span};
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
|
||||
|
||||
pub fn init_tracing(cfg: &Config) {
|
||||
let filter = if let Ok(filter) = cfg.get_string("logging") {
|
||||
EnvFilter::builder()
|
||||
.with_default_directive("info".parse().unwrap())
|
||||
.parse_lossy(filter)
|
||||
} else {
|
||||
EnvFilter::builder()
|
||||
.with_default_directive("info".parse().unwrap())
|
||||
.from_env_lossy()
|
||||
};
|
||||
|
||||
global::set_text_map_propagator(TraceContextPropagator::new());
|
||||
|
||||
let tracer = opentelemetry_otlp::new_pipeline()
|
||||
.tracing()
|
||||
.with_exporter(opentelemetry_otlp::new_exporter().tonic())
|
||||
.install_batch(opentelemetry_sdk::runtime::Tokio)
|
||||
.unwrap();
|
||||
|
||||
let otel = tracing_opentelemetry::layer().with_tracer(tracer);
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(filter)
|
||||
.with(otel)
|
||||
.with(tracing_subscriber::fmt::layer())
|
||||
.init();
|
||||
}
|
||||
|
||||
pub fn make_span(request: &Request) -> Span {
|
||||
let uri = if let Some(OriginalUri(uri)) = request.extensions().get::<OriginalUri>() {
|
||||
uri
|
||||
} else {
|
||||
request.uri()
|
||||
};
|
||||
let route = request
|
||||
.extensions()
|
||||
.get::<MatchedPath>()
|
||||
.map_or(uri.path(), axum::extract::MatchedPath::as_str);
|
||||
let method = request.method().as_str();
|
||||
let target = uri
|
||||
.path_and_query()
|
||||
.map_or(uri.path(), PathAndQuery::as_str);
|
||||
let name = format!("{method} {route}");
|
||||
|
||||
info_span!(
|
||||
"request",
|
||||
otel.name = %name,
|
||||
http.route = %route,
|
||||
http.method = %method,
|
||||
http.target = %target,
|
||||
http.status_code = Empty
|
||||
)
|
||||
}
|
||||
|
||||
pub fn on_response(response: &Response, _latency: Duration, span: &Span) {
|
||||
span.record("http.status_code", response.status().as_str());
|
||||
}
|
17
src/post.rs
17
src/post.rs
|
@ -1,5 +1,7 @@
|
|||
use std::{collections::HashMap, path::Path};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use cached::once_cell::sync::Lazy;
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
use glob::glob;
|
||||
|
@ -15,10 +17,9 @@ use tracing::{
|
|||
|
||||
use crate::{helpers, markdown, AppState, WebsiteError};
|
||||
|
||||
static FRONTMATTER_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(
|
||||
r"^[\s]*\+{3}(\r?\n(?s).*?(?-s))\+{3}[\s]*(?:$|(?:\r?\n((?s).*(?-s))$))"
|
||||
)
|
||||
.unwrap());
|
||||
static FRONTMATTER_REGEX: Lazy<Regex> = Lazy::new(|| {
|
||||
Regex::new(r"^[\s]*\+{3}(\r?\n(?s).*?(?-s))\+{3}[\s]*(?:$|(?:\r?\n((?s).*(?-s))$))").unwrap()
|
||||
});
|
||||
|
||||
#[derive(Deserialize, Debug, Default)]
|
||||
pub struct TomlFrontMatter {
|
||||
|
@ -76,7 +77,7 @@ impl Post {
|
|||
}
|
||||
|
||||
#[instrument(skip(state))]
|
||||
pub async fn load_all(state: &AppState) -> color_eyre::eyre::Result<HashMap<String, Post>> {
|
||||
pub async fn load_all(state: &AppState) -> Result<HashMap<String, Post>> {
|
||||
let mut res = HashMap::<String, Post>::new();
|
||||
for path in glob("posts/**/*.md")? {
|
||||
let path = path.unwrap();
|
||||
|
@ -101,7 +102,7 @@ pub async fn load_all(state: &AppState) -> color_eyre::eyre::Result<HashMap<Stri
|
|||
}
|
||||
|
||||
#[instrument(skip(state))]
|
||||
pub async fn load_post(state: &AppState, slug: &str) -> color_eyre::eyre::Result<Post> {
|
||||
pub async fn load_post(state: &AppState, slug: &str) -> Result<Post> {
|
||||
debug!("loading post: {slug}");
|
||||
|
||||
let file_path = Path::new("posts").join(slug);
|
||||
|
@ -127,9 +128,7 @@ pub async fn load_post(state: &AppState, slug: &str) -> color_eyre::eyre::Result
|
|||
}
|
||||
|
||||
#[instrument(skip(src))]
|
||||
fn parse_frontmatter(
|
||||
src: String,
|
||||
) -> color_eyre::eyre::Result<(Option<TomlFrontMatter>, Option<String>)> {
|
||||
fn parse_frontmatter(src: String) -> Result<(Option<TomlFrontMatter>, Option<String>)> {
|
||||
Ok(if let Some(captures) = FRONTMATTER_REGEX.captures(&src) {
|
||||
(
|
||||
Some(toml::from_str(captures.get(1).unwrap().as_str())?),
|
||||
|
|
Loading…
Reference in a new issue