updates
This commit is contained in:
parent
82e377dbea
commit
d8050d2e89
13 changed files with 639 additions and 648 deletions
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