Compare commits
2 commits
e0a3f35caf
...
a855c0c63f
Author | SHA1 | Date | |
---|---|---|---|
a855c0c63f | |||
e9549217ae |
10 changed files with 818 additions and 443 deletions
1169
Cargo.lock
generated
1169
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
23
Cargo.toml
23
Cargo.toml
|
@ -6,13 +6,14 @@ edition = "2021"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
axum = { version = "0.6.12", features = ["http2", "original-uri"] }
|
||||
cached = "0.44.0"
|
||||
chrono = { version = "0.4.24", features = ["serde"] }
|
||||
axum = { version = "0.6.20", features = ["http2", "original-uri", "tracing"] }
|
||||
cached = "0.46.1"
|
||||
chrono = { version = "0.4.31", features = ["serde"] }
|
||||
color-eyre = "0.6.1"
|
||||
config = "0.13.3"
|
||||
glob = "0.3.0"
|
||||
opentelemetry = { version = "0.19.0", features = ["metrics"] }
|
||||
opentelemetry = { version = "0.21.0", features = ["metrics"] }
|
||||
opentelemetry-jaeger = { version = "0.20.0", features = ["collector_client", "isahc_collector_client"]}
|
||||
prometheus = { version = "0.13.3", features = ["process"] }
|
||||
pulldown-cmark = "0.9.2"
|
||||
regex = "1.7.2"
|
||||
|
@ -20,11 +21,11 @@ serde = "1.0.144"
|
|||
serde_derive = "1.0.144"
|
||||
serde_json = "1.0.85"
|
||||
syntect = "5.0.0"
|
||||
tera = { version = "1.17.0", features = ["builtins"] }
|
||||
tokio = { version = "1.19.2", features = ["full"] }
|
||||
toml = "0.7.3"
|
||||
tower = { version = "0.4.12", features = ["full"] }
|
||||
tower-http = { version = "0.4.0", features = ["full"] }
|
||||
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"] }
|
||||
tracing = "0.1.35"
|
||||
tracing-opentelemetry = "0.19.0"
|
||||
tracing-subscriber = { version = "0.3.11", features = ["fmt", "env-filter", "json", "tracing-log"] }
|
||||
tracing-opentelemetry = "0.22.0"
|
||||
tracing-subscriber = { version = "0.3.17", features = ["fmt", "env-filter", "json", "tracing-log"] }
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
base_url = "http://localhost:8080/"
|
||||
bind_address = "0.0.0.0:8080"
|
||||
logging = "info"
|
||||
logging = "info,website=debug"
|
||||
|
|
|
@ -37,9 +37,7 @@ pub fn routes(state: &Arc<AppState>) -> Router<Arc<AppState>> {
|
|||
.merge(posts::router())
|
||||
.merge(tags::router())
|
||||
.merge(posts::alias_router(state.posts.values()))
|
||||
.layer(axum::middleware::from_fn(metrics_middleware))
|
||||
.route("/healthcheck", get(healthcheck))
|
||||
.route("/metrics", get(metrics))
|
||||
.route_service(
|
||||
"/posts/:slug/*path",
|
||||
ServeDir::new("./"),
|
||||
|
@ -48,6 +46,8 @@ pub fn routes(state: &Arc<AppState>) -> Router<Arc<AppState>> {
|
|||
"/static/*path",
|
||||
ServeDir::new("./"),
|
||||
)
|
||||
.layer(axum::middleware::from_fn(metrics_middleware))
|
||||
.route("/metrics", get(metrics))
|
||||
}
|
||||
|
||||
#[instrument(skip(state))]
|
||||
|
@ -76,6 +76,7 @@ async fn healthcheck() -> &'static str {
|
|||
"OK"
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
async fn metrics() -> impl IntoResponse {
|
||||
let encoder = TextEncoder::new();
|
||||
let metric_families = prometheus::gather();
|
||||
|
@ -93,6 +94,7 @@ pub async fn not_found() -> impl IntoResponse {
|
|||
(StatusCode::NOT_FOUND, ())
|
||||
}
|
||||
|
||||
#[instrument(skip(request, next))]
|
||||
pub async fn metrics_middleware<B>(request: Request<B>, next: Next<B>) -> Response {
|
||||
let path = request.uri().path().to_string();
|
||||
let method = request.method().clone();
|
||||
|
|
|
@ -76,18 +76,13 @@ pub async fn index(
|
|||
|
||||
let res = state.tera.render("posts_index.html", &c)?;
|
||||
|
||||
let mut headers = vec![];
|
||||
|
||||
if let Some(date) = last_changed {
|
||||
headers.push((header::LAST_MODIFIED, date.to_rfc2822()));
|
||||
}
|
||||
|
||||
Ok((
|
||||
StatusCode::OK,
|
||||
[(
|
||||
header::LAST_MODIFIED,
|
||||
last_changed.map_or_else(
|
||||
|| chrono::offset::Utc::now().to_rfc2822(),
|
||||
|| state.startup_time.to_rfc2822(),
|
||||
|d| d.to_rfc2822(),
|
||||
),
|
||||
)],
|
||||
|
@ -122,7 +117,7 @@ pub async fn view(
|
|||
[(
|
||||
header::LAST_MODIFIED,
|
||||
last_changed.map_or_else(
|
||||
|| chrono::offset::Utc::now().to_rfc2822(),
|
||||
|| state.startup_time.to_rfc2822(),
|
||||
|d| d.to_rfc2822(),
|
||||
),
|
||||
)],
|
||||
|
@ -131,6 +126,7 @@ pub async fn view(
|
|||
.into_response())
|
||||
}
|
||||
|
||||
#[instrument(skip(state))]
|
||||
pub async fn feed(
|
||||
State(state): State<Arc<AppState>>,
|
||||
headers: HeaderMap,
|
||||
|
@ -154,7 +150,7 @@ pub async fn feed(
|
|||
(
|
||||
header::LAST_MODIFIED,
|
||||
&last_changed.map_or_else(
|
||||
|| chrono::offset::Utc::now().to_rfc2822(),
|
||||
|| state.startup_time.to_rfc2822(),
|
||||
|d| d.to_rfc2822(),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -74,6 +74,7 @@ pub async fn view(
|
|||
let ctx = TagContext { title: &title };
|
||||
|
||||
let mut c = tera::Context::new();
|
||||
c.insert("base_url", &state.base_url.to_string());
|
||||
c.insert("tag_slug", &tag);
|
||||
c.insert("page", &ctx);
|
||||
c.insert("posts", &posts);
|
||||
|
@ -94,6 +95,7 @@ pub async fn view(
|
|||
.into_response())
|
||||
}
|
||||
|
||||
#[instrument(skip(state))]
|
||||
pub async fn feed(
|
||||
Path(slug): Path<String>,
|
||||
State(state): State<Arc<AppState>>,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use syntect::{highlighting::ThemeSet, parsing::SyntaxSet};
|
||||
use tracing::error;
|
||||
use tracing::{error, instrument};
|
||||
|
||||
#[instrument(skip(content, lang, theme))]
|
||||
pub fn hilight(content: &str, lang: &str, theme: Option<&str>) -> color_eyre::Result<String> {
|
||||
let ss = SyntaxSet::load_defaults_newlines();
|
||||
let s = ss
|
||||
|
|
40
src/main.rs
40
src/main.rs
|
@ -17,7 +17,7 @@ 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};
|
||||
use tracing::{field::Empty, info_span, log::info, Span, instrument};
|
||||
|
||||
use tracing_subscriber::{prelude::*, EnvFilter};
|
||||
|
||||
|
@ -40,16 +40,25 @@ 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("APP"))
|
||||
.add_source(config::Environment::with_prefix("WEBSITE"))
|
||||
.build()?;
|
||||
|
||||
color_eyre::install()?;
|
||||
init_tracing(&cfg);
|
||||
|
||||
info!("Starting server...");
|
||||
|
||||
let app = init_app(&cfg).await?;
|
||||
axum::Server::bind(&cfg.get_string("bind_address")?.parse().unwrap())
|
||||
.serve(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();
|
||||
|
@ -68,7 +77,8 @@ async fn main() -> Result<()> {
|
|||
state.tags = tags;
|
||||
let state = Arc::new(state);
|
||||
|
||||
let app = handlers::routes(&state)
|
||||
info!("Listening at {}", state.base_url);
|
||||
Ok(handlers::routes(&state)
|
||||
.layer(CorsLayer::permissive())
|
||||
.layer(CompressionLayer::new())
|
||||
.layer(
|
||||
|
@ -76,15 +86,7 @@ async fn main() -> Result<()> {
|
|||
.make_span_with(make_span)
|
||||
.on_response(on_response),
|
||||
)
|
||||
.with_state(state.clone());
|
||||
|
||||
info!("Now listening at {}", state.base_url);
|
||||
|
||||
axum::Server::bind(&cfg.get_string("bind_address")?.parse().unwrap())
|
||||
.serve(app.into_make_service())
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
.with_state(state.clone()))
|
||||
}
|
||||
|
||||
fn init_tracing(cfg: &Config) {
|
||||
|
@ -98,12 +100,22 @@ fn init_tracing(cfg: &Config) {
|
|||
.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
|
||||
|
|
|
@ -6,10 +6,12 @@ use pulldown_cmark::Event;
|
|||
use pulldown_cmark::Tag;
|
||||
use pulldown_cmark::{Options, Parser};
|
||||
use regex::Regex;
|
||||
use tracing::instrument;
|
||||
|
||||
static STARTS_WITH_SCHEMA_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"^\w+:").unwrap());
|
||||
static EMAIL_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"^.+?@\w+(\.\w+)*$").unwrap());
|
||||
|
||||
#[instrument(skip(markdown))]
|
||||
pub fn render_markdown_to_html(base_uri: Option<&Uri>, markdown: &str) -> String {
|
||||
let mut opt = Options::empty();
|
||||
opt.insert(Options::ENABLE_FOOTNOTES);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use serde_derive::Serialize;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::post::Post;
|
||||
|
||||
|
@ -11,6 +12,7 @@ pub struct Tag {
|
|||
pub posts: Vec<String>,
|
||||
}
|
||||
|
||||
#[instrument(skip(posts))]
|
||||
pub fn get_tags<'a>(posts: impl IntoIterator<Item = &'a Post>) -> HashMap<String, Tag> {
|
||||
let mut tags: HashMap<String, Tag> = HashMap::new();
|
||||
|
||||
|
|
Loading…
Reference in a new issue