Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions crates/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,17 +109,18 @@ async fn try_main() -> anyhow::Result<ExitCode> {
// Load the base configuration files
let figment = opts.figment();

// 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.
// Falling back to default.
let telemetry_config = TelemetryConfig::extract(&figment).unwrap_or_default();
let telemetry_config =
TelemetryConfig::extract(&figment).context("Failed to load telemetry config")?;

// Setup Sentry
let sentry = sentry::init((
telemetry_config.sentry.dsn.as_deref(),
sentry::ClientOptions {
transport: Some(Arc::new(SentryTransportFactory::new())),
traces_sample_rate: 1.0,
environment: telemetry_config.sentry.environment.clone().map(Into::into),
release: Some(VERSION.into()),
sample_rate: telemetry_config.sentry.sample_rate.unwrap_or(1.0),
traces_sample_rate: telemetry_config.sentry.traces_sample_rate.unwrap_or(0.0),
auto_session_tracking: true,
session_mode: sentry::SessionMode::Request,
..Default::default()
Expand Down
10 changes: 7 additions & 3 deletions crates/cli/src/telemetry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,10 @@ fn stdout_tracer_provider() -> SdkTracerProvider {
.build()
}

fn otlp_tracer_provider(endpoint: Option<&Url>) -> anyhow::Result<SdkTracerProvider> {
fn otlp_tracer_provider(
endpoint: Option<&Url>,
sample_rate: f64,
) -> anyhow::Result<SdkTracerProvider> {
let mut exporter = opentelemetry_otlp::SpanExporter::builder()
.with_http()
.with_http_client(mas_http::reqwest_client());
Expand All @@ -119,17 +122,18 @@ fn otlp_tracer_provider(endpoint: Option<&Url>) -> anyhow::Result<SdkTracerProvi
let tracer_provider = SdkTracerProvider::builder()
.with_span_processor(batch_processor)
.with_resource(resource())
.with_sampler(Sampler::AlwaysOn)
.with_sampler(Sampler::TraceIdRatioBased(sample_rate))
.build();

Ok(tracer_provider)
}

fn init_tracer(config: &TracingConfig) -> anyhow::Result<()> {
let sample_rate = config.sample_rate.unwrap_or(1.0);
let tracer_provider = match config.exporter {
TracingExporterKind::None => return Ok(()),
TracingExporterKind::Stdout => stdout_tracer_provider(),
TracingExporterKind::Otlp => otlp_tracer_provider(config.endpoint.as_ref())?,
TracingExporterKind::Otlp => otlp_tracer_provider(config.endpoint.as_ref(), sample_rate)?,
};
TRACER_PROVIDER
.set(tracer_provider.clone())
Expand Down
69 changes: 68 additions & 1 deletion crates/config/src/sections/telemetry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@
// Please see LICENSE in the repository root for full details.

use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde::{Deserialize, Serialize, de::Error as _};
use serde_with::skip_serializing_none;
use url::Url;

use super::ConfigurationSection;

fn sample_rate_example() -> f64 {
0.5
}

/// Propagation format for incoming and outgoing requests
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "lowercase")]
Expand Down Expand Up @@ -61,6 +65,13 @@ pub struct TracingConfig {
/// List of propagation formats to use for incoming and outgoing requests
#[serde(default)]
pub propagators: Vec<Propagator>,

/// Sample rate for traces
///
/// Defaults to `1.0` if not set.
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(example = "sample_rate_example", range(min = 0.0, max = 1.0))]
pub sample_rate: Option<f64>,
}

impl TracingConfig {
Expand Down Expand Up @@ -116,13 +127,38 @@ fn sentry_dsn_example() -> &'static str {
"https://public@host:port/1"
}

fn sentry_environment_example() -> &'static str {
"production"
}

/// Configuration related to the Sentry integration
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
pub struct SentryConfig {
/// Sentry DSN
#[schemars(url, example = "sentry_dsn_example")]
#[serde(skip_serializing_if = "Option::is_none")]
pub dsn: Option<String>,

/// Environment to use when sending events to Sentry
///
/// Defaults to `production` if not set.
#[schemars(example = "sentry_environment_example")]
#[serde(skip_serializing_if = "Option::is_none")]
pub environment: Option<String>,

/// Sample rate for event submissions
///
/// Defaults to `1.0` if not set.
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(example = "sample_rate_example", range(min = 0.0, max = 1.0))]
pub sample_rate: Option<f32>,

/// Sample rate for tracing transactions
///
/// Defaults to `0.0` if not set.
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(example = "sample_rate_example", range(min = 0.0, max = 1.0))]
pub traces_sample_rate: Option<f32>,
}

impl SentryConfig {
Expand Down Expand Up @@ -157,4 +193,35 @@ impl TelemetryConfig {

impl ConfigurationSection for TelemetryConfig {
const PATH: Option<&'static str> = Some("telemetry");

fn validate(&self, _figment: &figment::Figment) -> Result<(), figment::Error> {
if let Some(sample_rate) = self.sentry.sample_rate {
if !(0.0..=1.0).contains(&sample_rate) {
return Err(figment::error::Error::custom(
"Sentry sample rate must be between 0.0 and 1.0",
)
.with_path("sentry.sample_rate"));
}
}

if let Some(sample_rate) = self.sentry.traces_sample_rate {
if !(0.0..=1.0).contains(&sample_rate) {
return Err(figment::error::Error::custom(
"Sentry sample rate must be between 0.0 and 1.0",
)
.with_path("sentry.traces_sample_rate"));
}
}

if let Some(sample_rate) = self.tracing.sample_rate {
if !(0.0..=1.0).contains(&sample_rate) {
return Err(figment::error::Error::custom(
"Tracing sample rate must be between 0.0 and 1.0",
)
.with_path("tracing.sample_rate"));
}
}

Ok(())
}
}
35 changes: 33 additions & 2 deletions crates/handlers/src/compat/login.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.

use std::sync::Arc;
use std::sync::{Arc, LazyLock};

use axum::{
Json,
Expand All @@ -27,6 +27,7 @@ use mas_storage::{
},
user::{UserPasswordRepository, UserRepository},
};
use opentelemetry::{Key, KeyValue, metrics::Counter};
use rand::{CryptoRng, RngCore};
use serde::{Deserialize, Serialize};
use serde_with::{DurationMilliSeconds, serde_as, skip_serializing_none};
Expand All @@ -35,10 +36,20 @@ use zeroize::Zeroizing;

use super::MatrixError;
use crate::{
BoundActivityTracker, Limiter, RequesterFingerprint, impl_from_error_for_route,
BoundActivityTracker, Limiter, METER, RequesterFingerprint, impl_from_error_for_route,
passwords::PasswordManager, rate_limit::PasswordCheckLimitedError,
};

static LOGIN_COUNTER: LazyLock<Counter<u64>> = LazyLock::new(|| {
METER
.u64_counter("mas.compat.login_request")
.with_description("How many compatibility login requests have happened")
.with_unit("{request}")
.build()
});
const TYPE: Key = Key::from_static_str("type");
const RESULT: Key = Key::from_static_str("result");

#[derive(Debug, Serialize)]
#[serde(tag = "type")]
enum LoginType {
Expand Down Expand Up @@ -123,6 +134,16 @@ pub enum Credentials {
Unsupported,
}

impl Credentials {
fn login_type(&self) -> &'static str {
match self {
Self::Password { .. } => "m.login.password",
Self::Token { .. } => "m.login.token",
Self::Unsupported => "unsupported",
}
}
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum Identifier {
Expand Down Expand Up @@ -192,6 +213,7 @@ impl_from_error_for_route!(mas_storage::RepositoryError);
impl IntoResponse for RouteError {
fn into_response(self) -> axum::response::Response {
let event_id = sentry::capture_error(&self);
LOGIN_COUNTER.add(1, &[KeyValue::new(RESULT, "error")]);
let response = match self {
Self::Internal(_) | Self::SessionNotFound | Self::ProvisionDeviceFailed(_) => {
MatrixError {
Expand Down Expand Up @@ -278,6 +300,7 @@ pub(crate) async fn post(
WithRejection(Json(input), _): WithRejection<Json<RequestBody>, RouteError>,
) -> Result<impl IntoResponse, RouteError> {
let user_agent = user_agent.map(|ua| UserAgent::parse(ua.as_str().to_owned()));
let login_type = input.credentials.login_type();
let (mut session, user) = match (password_manager.is_enabled(), input.credentials) {
(
true,
Expand Down Expand Up @@ -360,6 +383,14 @@ pub(crate) async fn post(
.record_compat_session(&clock, &session)
.await;

LOGIN_COUNTER.add(
1,
&[
KeyValue::new(TYPE, login_type),
KeyValue::new(RESULT, "success"),
],
);

Ok(Json(ResponseBody {
access_token: access_token.token,
device_id: session.device,
Expand Down
17 changes: 16 additions & 1 deletion crates/handlers/src/compat/logout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.

use std::sync::LazyLock;

use axum::{Json, response::IntoResponse};
use axum_extra::typed_header::TypedHeader;
use headers::{Authorization, authorization::Bearer};
Expand All @@ -15,10 +17,20 @@ use mas_storage::{
compat::{CompatAccessTokenRepository, CompatSessionRepository},
queue::{QueueJobRepositoryExt as _, SyncDevicesJob},
};
use opentelemetry::{Key, KeyValue, metrics::Counter};
use thiserror::Error;

use super::MatrixError;
use crate::{BoundActivityTracker, impl_from_error_for_route};
use crate::{BoundActivityTracker, METER, impl_from_error_for_route};

static LOGOUT_COUNTER: LazyLock<Counter<u64>> = LazyLock::new(|| {
METER
.u64_counter("mas.compat.logout_request")
.with_description("How many compatibility logout request have happened")
.with_unit("{request}")
.build()
});
const RESULT: Key = Key::from_static_str("result");

#[derive(Error, Debug)]
pub enum RouteError {
Expand All @@ -40,6 +52,7 @@ impl_from_error_for_route!(mas_storage::RepositoryError);
impl IntoResponse for RouteError {
fn into_response(self) -> axum::response::Response {
let event_id = sentry::capture_error(&self);
LOGOUT_COUNTER.add(1, &[KeyValue::new(RESULT, "error")]);
let response = match self {
Self::Internal(_) => MatrixError {
errcode: "M_UNKNOWN",
Expand Down Expand Up @@ -113,5 +126,7 @@ pub(crate) async fn post(

repo.save().await?;

LOGOUT_COUNTER.add(1, &[KeyValue::new(RESULT, "success")]);

Ok(Json(serde_json::json!({})))
}
Loading
Loading