diff --git a/crates/cli/src/config.rs b/crates/cli/src/commands/config.rs similarity index 87% rename from crates/cli/src/config.rs rename to crates/cli/src/commands/config.rs index 1a6ca92c..e3db7121 100644 --- a/crates/cli/src/config.rs +++ b/crates/cli/src/commands/config.rs @@ -1,4 +1,4 @@ -// Copyright 2021 The Matrix.org Foundation C.I.C. +// Copyright 2021, 2022 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,16 +17,14 @@ use mas_config::{ConfigurationSection, RootConfig}; use schemars::gen::SchemaSettings; use tracing::info; -use super::RootCommand; - #[derive(Parser, Debug)] -pub(super) struct ConfigCommand { +pub(super) struct Options { #[clap(subcommand)] - subcommand: ConfigSubcommand, + subcommand: Subcommand, } #[derive(Parser, Debug)] -enum ConfigSubcommand { +enum Subcommand { /// Dump the current config as YAML Dump, @@ -40,9 +38,9 @@ enum ConfigSubcommand { Generate, } -impl ConfigCommand { - pub async fn run(&self, root: &RootCommand) -> anyhow::Result<()> { - use ConfigSubcommand as SC; +impl Options { + pub async fn run(&self, root: &super::Options) -> anyhow::Result<()> { + use Subcommand as SC; match &self.subcommand { SC::Dump => { let config: RootConfig = root.load_config()?; diff --git a/crates/cli/src/database.rs b/crates/cli/src/commands/database.rs similarity index 79% rename from crates/cli/src/database.rs rename to crates/cli/src/commands/database.rs index c2464147..722c3235 100644 --- a/crates/cli/src/database.rs +++ b/crates/cli/src/commands/database.rs @@ -1,4 +1,4 @@ -// Copyright 2021 The Matrix.org Foundation C.I.C. +// Copyright 2021, 2022 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,22 +17,20 @@ use clap::Parser; use mas_config::DatabaseConfig; use mas_storage::MIGRATOR; -use super::RootCommand; - #[derive(Parser, Debug)] -pub(super) struct DatabaseCommand { +pub(super) struct Options { #[clap(subcommand)] - subcommand: DatabaseSubcommand, + subcommand: Subcommand, } #[derive(Parser, Debug)] -enum DatabaseSubcommand { +enum Subcommand { /// Run database migrations Migrate, } -impl DatabaseCommand { - pub async fn run(&self, root: &RootCommand) -> anyhow::Result<()> { +impl Options { + pub async fn run(&self, root: &super::Options) -> anyhow::Result<()> { let config: DatabaseConfig = root.load_config()?; let pool = config.connect().await?; diff --git a/crates/cli/src/manage.rs b/crates/cli/src/commands/manage.rs similarity index 88% rename from crates/cli/src/manage.rs rename to crates/cli/src/commands/manage.rs index 190cded7..5511a8c6 100644 --- a/crates/cli/src/manage.rs +++ b/crates/cli/src/commands/manage.rs @@ -1,4 +1,4 @@ -// Copyright 2021 The Matrix.org Foundation C.I.C. +// Copyright 2021, 2022 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -20,16 +20,14 @@ use mas_storage::user::{ }; use tracing::{info, warn}; -use super::RootCommand; - #[derive(Parser, Debug)] -pub(super) struct ManageCommand { +pub(super) struct Options { #[clap(subcommand)] - subcommand: ManageSubcommand, + subcommand: Subcommand, } #[derive(Parser, Debug)] -enum ManageSubcommand { +enum Subcommand { /// Register a new user Register { username: String, password: String }, @@ -40,9 +38,9 @@ enum ManageSubcommand { VerifyEmail { username: String, email: String }, } -impl ManageCommand { - pub async fn run(&self, root: &RootCommand) -> anyhow::Result<()> { - use ManageSubcommand as SC; +impl Options { + pub async fn run(&self, root: &super::Options) -> anyhow::Result<()> { + use Subcommand as SC; match &self.subcommand { SC::Register { username, password } => { let config: DatabaseConfig = root.load_config()?; diff --git a/crates/cli/src/commands/mod.rs b/crates/cli/src/commands/mod.rs new file mode 100644 index 00000000..cff1372d --- /dev/null +++ b/crates/cli/src/commands/mod.rs @@ -0,0 +1,77 @@ +// Copyright 2022 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::path::PathBuf; + +use anyhow::Context; +use clap::Parser; +use mas_config::ConfigurationSection; + +mod config; +mod database; +mod manage; +mod server; +mod templates; + +#[derive(Parser, Debug)] +enum Subcommand { + /// Configuration-related commands + Config(self::config::Options), + + /// Manage the database + Database(self::database::Options), + + /// Runs the web server + Server(self::server::Options), + + /// Manage the instance + Manage(self::manage::Options), + + /// Templates-related commands + Templates(self::templates::Options), +} + +#[derive(Parser, Debug)] +pub struct Options { + /// Path to the configuration file + #[clap( + short, + long, + global = true, + default_value = "config.yaml", + multiple_occurrences(true) + )] + config: Vec, + + #[clap(subcommand)] + subcommand: Option, +} + +impl Options { + pub async fn run(&self) -> anyhow::Result<()> { + use Subcommand as S; + match &self.subcommand { + Some(S::Config(c)) => c.run(self).await, + Some(S::Database(c)) => c.run(self).await, + Some(S::Server(c)) => c.run(self).await, + Some(S::Manage(c)) => c.run(self).await, + Some(S::Templates(c)) => c.run(self).await, + None => self::server::Options::default().run(self).await, + } + } + + pub fn load_config<'de, T: ConfigurationSection<'de>>(&self) -> anyhow::Result { + T::load_from_files(&self.config).context("could not load configuration") + } +} diff --git a/crates/cli/src/server.rs b/crates/cli/src/commands/server.rs similarity index 74% rename from crates/cli/src/server.rs rename to crates/cli/src/commands/server.rs index b23cde25..2460155d 100644 --- a/crates/cli/src/server.rs +++ b/crates/cli/src/commands/server.rs @@ -1,4 +1,4 @@ -// Copyright 2021 The Matrix.org Foundation C.I.C. +// Copyright 2021, 2022 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -21,26 +21,22 @@ use std::{ use anyhow::Context; use clap::Parser; use futures::{future::TryFutureExt, stream::TryStreamExt}; -use hyper::{header, Server, Version}; +use hyper::{header, Server}; use mas_config::RootConfig; use mas_email::{MailTransport, Mailer}; use mas_storage::MIGRATOR; use mas_tasks::TaskQueue; use mas_templates::Templates; -use opentelemetry::trace::TraceContextExt; -use opentelemetry_http::HeaderExtractor; use tower::{make::Shared, ServiceBuilder}; use tower_http::{ - compression::CompressionLayer, - sensitive_headers::SetSensitiveHeadersLayer, - trace::{MakeSpan, OnResponse, TraceLayer}, + compression::CompressionLayer, sensitive_headers::SetSensitiveHeadersLayer, trace::TraceLayer, }; -use tracing::{error, field, info}; +use tracing::{error, info}; -use super::RootCommand; +use crate::telemetry::{OtelMakeSpan, OtelOnResponse}; #[derive(Parser, Debug, Default)] -pub(super) struct ServerCommand { +pub(super) struct Options { /// Automatically apply pending migrations #[clap(long)] migrate: bool, @@ -50,78 +46,6 @@ pub(super) struct ServerCommand { watch: bool, } -#[derive(Debug, Clone, Default)] -struct OtelMakeSpan; - -impl MakeSpan for OtelMakeSpan { - fn make_span(&mut self, request: &hyper::Request) -> tracing::Span { - // Extract the context from the headers - let headers = request.headers(); - let extractor = HeaderExtractor(headers); - - let cx = opentelemetry::global::get_text_map_propagator(|propagator| { - propagator.extract(&extractor) - }); - - let cx = if cx.span().span_context().is_remote() { - cx - } else { - opentelemetry::Context::new() - }; - - // Attach the context so when the request span is created it gets properly - // parented - let _guard = cx.attach(); - - let version = match request.version() { - Version::HTTP_09 => "0.9", - Version::HTTP_10 => "1.0", - Version::HTTP_11 => "1.1", - Version::HTTP_2 => "2.0", - Version::HTTP_3 => "3.0", - _ => "", - }; - - let span = tracing::info_span!( - "request", - http.method = %request.method(), - http.target = %request.uri(), - http.flavor = version, - http.status_code = field::Empty, - http.user_agent = field::Empty, - otel.kind = "server", - otel.status_code = field::Empty, - ); - - if let Some(user_agent) = headers - .get(header::USER_AGENT) - .and_then(|s| s.to_str().ok()) - { - span.record("http.user_agent", &user_agent); - } - - span - } -} - -#[derive(Debug, Clone, Default)] -struct OtelOnResponse; - -impl OnResponse for OtelOnResponse { - fn on_response(self, response: &hyper::Response, _latency: Duration, span: &tracing::Span) { - let s = response.status(); - let status = if s.is_success() { - "ok" - } else if s.is_client_error() || s.is_server_error() { - "error" - } else { - "unset" - }; - span.record("otel.status_code", &status); - span.record("http.status_code", &s.as_u16()); - } -} - #[cfg(not(unix))] async fn shutdown_signal() { // Wait for the CTRL+C signal @@ -218,8 +142,8 @@ async fn watch_templates( Ok(()) } -impl ServerCommand { - pub async fn run(&self, root: &RootCommand) -> anyhow::Result<()> { +impl Options { + pub async fn run(&self, root: &super::Options) -> anyhow::Result<()> { let config: RootConfig = root.load_config()?; let addr: SocketAddr = config diff --git a/crates/cli/src/templates.rs b/crates/cli/src/commands/templates.rs similarity index 84% rename from crates/cli/src/templates.rs rename to crates/cli/src/commands/templates.rs index fb0fd0e0..2c6e4023 100644 --- a/crates/cli/src/templates.rs +++ b/crates/cli/src/commands/templates.rs @@ -1,4 +1,4 @@ -// Copyright 2021 The Matrix.org Foundation C.I.C. +// Copyright 2021, 2022 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -18,16 +18,14 @@ use clap::Parser; use mas_config::TemplatesConfig; use mas_templates::Templates; -use super::RootCommand; - #[derive(Parser, Debug)] -pub(super) struct TemplatesCommand { +pub(super) struct Options { #[clap(subcommand)] - subcommand: TemplatesSubcommand, + subcommand: Subcommand, } #[derive(Parser, Debug)] -enum TemplatesSubcommand { +enum Subcommand { /// Save the builtin templates to a folder Save { /// Where the templates should be saved @@ -49,9 +47,9 @@ enum TemplatesSubcommand { }, } -impl TemplatesCommand { - pub async fn run(&self, _root: &RootCommand) -> anyhow::Result<()> { - use TemplatesSubcommand as SC; +impl Options { + pub async fn run(&self, _root: &super::Options) -> anyhow::Result<()> { + use Subcommand as SC; match &self.subcommand { SC::Save { path, overwrite } => { Templates::save(path, *overwrite).await?; diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 6a014b19..f757d23f 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -1,4 +1,4 @@ -// Copyright 2021 The Matrix.org Foundation C.I.C. +// Copyright 2021, 2022 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -20,76 +20,15 @@ use std::path::PathBuf; use anyhow::Context; -use clap::Parser; -use mas_config::{ConfigurationSection, TelemetryConfig}; +use clap::StructOpt; +use mas_config::TelemetryConfig; use tracing_subscriber::{ filter::LevelFilter, layer::SubscriberExt, reload, util::SubscriberInitExt, EnvFilter, Layer, Registry, }; -use self::{ - config::ConfigCommand, database::DatabaseCommand, manage::ManageCommand, server::ServerCommand, - templates::TemplatesCommand, -}; - -mod config; -mod database; -mod manage; -mod server; +mod commands; mod telemetry; -mod templates; - -#[derive(Parser, Debug)] -enum Subcommand { - /// Configuration-related commands - Config(ConfigCommand), - - /// Manage the database - Database(DatabaseCommand), - - /// Runs the web server - Server(ServerCommand), - - /// Manage the instance - Manage(ManageCommand), - - /// Templates-related commands - Templates(TemplatesCommand), -} - -#[derive(Parser, Debug)] -struct RootCommand { - /// Path to the configuration file - #[clap( - short, - long, - global = true, - default_value = "config.yaml", - multiple_occurrences(true) - )] - config: Vec, - - #[clap(subcommand)] - subcommand: Option, -} - -impl RootCommand { - async fn run(&self) -> anyhow::Result<()> { - use Subcommand as S; - match &self.subcommand { - Some(S::Config(c)) => c.run(self).await, - Some(S::Database(c)) => c.run(self).await, - Some(S::Server(c)) => c.run(self).await, - Some(S::Manage(c)) => c.run(self).await, - Some(S::Templates(c)) => c.run(self).await, - None => ServerCommand::default().run(self).await, - } - } - - fn load_config<'de, T: ConfigurationSection<'de>>(&self) -> anyhow::Result { - T::load_from_files(&self.config).context("could not load configuration") - } -} #[tokio::main] async fn main() -> anyhow::Result<()> { @@ -97,7 +36,7 @@ async fn main() -> anyhow::Result<()> { // chance to shutdown the telemetry exporters regardless of if there was an // error or not let res = try_main().await; - telemetry::shutdown(); + self::telemetry::shutdown(); res } @@ -142,7 +81,7 @@ async fn try_main() -> anyhow::Result<()> { } // Parse the CLI arguments - let opts = RootCommand::parse(); + let opts = self::commands::Options::parse(); // Telemetry config could fail to load, but that's probably OK, since the whole // config will be loaded afterwards, and crash if there is a problem. diff --git a/crates/cli/src/telemetry.rs b/crates/cli/src/telemetry.rs index 75e53b09..263ccfd8 100644 --- a/crates/cli/src/telemetry.rs +++ b/crates/cli/src/telemetry.rs @@ -1,4 +1,4 @@ -// Copyright 2021 The Matrix.org Foundation C.I.C. +// Copyright 2021, 2022 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ use std::{net::SocketAddr, time::Duration}; use anyhow::bail; use futures::stream::{Stream, StreamExt}; +use hyper::{header, Version}; use mas_config::{MetricsExporterConfig, Propagator, TelemetryConfig, TracingExporterConfig}; use opentelemetry::{ global, @@ -26,12 +27,16 @@ use opentelemetry::{ trace::Tracer, Resource, }, + trace::TraceContextExt, }; +use opentelemetry_http::HeaderExtractor; #[cfg(feature = "jaeger")] use opentelemetry_jaeger::Propagator as JaegerPropagator; use opentelemetry_semantic_conventions as semcov; #[cfg(feature = "zipkin")] use opentelemetry_zipkin::{B3Encoding, Propagator as ZipkinPropagator}; +use tower_http::trace::{MakeSpan, OnResponse}; +use tracing::field; use url::Url; pub fn setup(config: &TelemetryConfig) -> anyhow::Result> { @@ -237,3 +242,75 @@ fn resource() -> Resource { resource.merge(&detected) } + +#[derive(Debug, Clone, Default)] +pub struct OtelMakeSpan; + +impl MakeSpan for OtelMakeSpan { + fn make_span(&mut self, request: &hyper::Request) -> tracing::Span { + // Extract the context from the headers + let headers = request.headers(); + let extractor = HeaderExtractor(headers); + + let cx = opentelemetry::global::get_text_map_propagator(|propagator| { + propagator.extract(&extractor) + }); + + let cx = if cx.span().span_context().is_remote() { + cx + } else { + opentelemetry::Context::new() + }; + + // Attach the context so when the request span is created it gets properly + // parented + let _guard = cx.attach(); + + let version = match request.version() { + Version::HTTP_09 => "0.9", + Version::HTTP_10 => "1.0", + Version::HTTP_11 => "1.1", + Version::HTTP_2 => "2.0", + Version::HTTP_3 => "3.0", + _ => "", + }; + + let span = tracing::info_span!( + "request", + http.method = %request.method(), + http.target = %request.uri(), + http.flavor = version, + http.status_code = field::Empty, + http.user_agent = field::Empty, + otel.kind = "server", + otel.status_code = field::Empty, + ); + + if let Some(user_agent) = headers + .get(header::USER_AGENT) + .and_then(|s| s.to_str().ok()) + { + span.record("http.user_agent", &user_agent); + } + + span + } +} + +#[derive(Debug, Clone, Default)] +pub struct OtelOnResponse; + +impl OnResponse for OtelOnResponse { + fn on_response(self, response: &hyper::Response, _latency: Duration, span: &tracing::Span) { + let s = response.status(); + let status = if s.is_success() { + "ok" + } else if s.is_client_error() || s.is_server_error() { + "error" + } else { + "unset" + }; + span.record("otel.status_code", &status); + span.record("http.status_code", &s.as_u16()); + } +}