Skip to content

Commit e44561a

Browse files
committed
Added a handler to dynamically change the log level.
Usage example: just visit. http://localhost:7280/api/v1/log_level?filter=debug,hyper=debug,tantivy=info,quickwit_serve=debug
1 parent 05311f1 commit e44561a

File tree

8 files changed

+99
-11
lines changed

8 files changed

+99
-11
lines changed

quickwit/quickwit-cli/src/cli.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
use anyhow::{bail, Context};
2121
use clap::{arg, Arg, ArgAction, ArgMatches, Command};
22+
use quickwit_serve::EnvFilterReloadFn;
2223
use tracing::Level;
2324

2425
use crate::index::{build_index_command, IndexCliCommand};
@@ -90,10 +91,10 @@ impl CliCommand {
9091
}
9192
}
9293

93-
pub async fn execute(self) -> anyhow::Result<()> {
94+
pub async fn execute(self, env_filter_reload_fn: EnvFilterReloadFn) -> anyhow::Result<()> {
9495
match self {
9596
CliCommand::Index(subcommand) => subcommand.execute().await,
96-
CliCommand::Run(subcommand) => subcommand.execute().await,
97+
CliCommand::Run(subcommand) => subcommand.execute(env_filter_reload_fn).await,
9798
CliCommand::Source(subcommand) => subcommand.execute().await,
9899
CliCommand::Split(subcommand) => subcommand.execute().await,
99100
CliCommand::Tool(subcommand) => subcommand.execute().await,

quickwit/quickwit-cli/src/logger.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,15 @@
1818
// along with this program. If not, see <http://www.gnu.org/licenses/>.
1919

2020
use std::env;
21+
use std::sync::Arc;
2122

2223
use anyhow::Context;
2324
use opentelemetry::sdk::propagation::TraceContextPropagator;
2425
use opentelemetry::sdk::trace::BatchConfig;
2526
use opentelemetry::sdk::{trace, Resource};
2627
use opentelemetry::{global, KeyValue};
2728
use opentelemetry_otlp::WithExportConfig;
28-
use quickwit_serve::BuildInfo;
29+
use quickwit_serve::{BuildInfo, EnvFilterReloadFn};
2930
use tracing::Level;
3031
use tracing_subscriber::fmt::time::UtcTime;
3132
use tracing_subscriber::prelude::*;
@@ -39,7 +40,7 @@ pub fn setup_logging_and_tracing(
3940
level: Level,
4041
ansi_colors: bool,
4142
build_info: &BuildInfo,
42-
) -> anyhow::Result<()> {
43+
) -> anyhow::Result<EnvFilterReloadFn> {
4344
#[cfg(feature = "tokio-console")]
4445
{
4546
if std::env::var_os(QW_ENABLE_TOKIO_CONSOLE_ENV_KEY).is_some() {
@@ -52,7 +53,8 @@ pub fn setup_logging_and_tracing(
5253
.or_else(|_| EnvFilter::try_new(format!("quickwit={level},tantivy=WARN")))
5354
.context("failed to set up tracing env filter")?;
5455
global::set_text_map_propagator(TraceContextPropagator::new());
55-
let registry = tracing_subscriber::registry().with(env_filter);
56+
let (reloadable_env_filter, reload_handle) = tracing_subscriber::reload::Layer::new(env_filter);
57+
let registry = tracing_subscriber::registry().with(reloadable_env_filter);
5658
let event_format = tracing_subscriber::fmt::format()
5759
.with_target(true)
5860
.with_timer(
@@ -102,5 +104,9 @@ pub fn setup_logging_and_tracing(
102104
.try_init()
103105
.context("failed to register tracing subscriber")?;
104106
}
105-
Ok(())
107+
Ok(Arc::new(move |env_filter_def: &str| {
108+
let new_env_filter = EnvFilter::try_new(env_filter_def)?;
109+
reload_handle.reload(new_env_filter)?;
110+
Ok(())
111+
}))
106112
}

quickwit/quickwit-cli/src/main.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,9 @@ async fn main_impl() -> anyhow::Result<()> {
6060
start_jemalloc_metrics_loop();
6161

6262
let build_info = BuildInfo::get();
63-
setup_logging_and_tracing(command.default_log_level(), ansi_colors, build_info)?;
64-
let return_code: i32 = if let Err(err) = command.execute().await {
63+
let env_filter_reload_fn =
64+
setup_logging_and_tracing(command.default_log_level(), ansi_colors, build_info)?;
65+
let return_code: i32 = if let Err(err) = command.execute(env_filter_reload_fn).await {
6566
eprintln!("{} command failed: {:?}\n", "✘".color(RED_COLOR), err);
6667
1
6768
} else {

quickwit/quickwit-cli/src/service.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use quickwit_common::runtimes::RuntimesConfig;
2727
use quickwit_common::uri::{Protocol, Uri};
2828
use quickwit_config::service::QuickwitService;
2929
use quickwit_config::NodeConfig;
30-
use quickwit_serve::{serve_quickwit, BuildInfo};
30+
use quickwit_serve::{serve_quickwit, BuildInfo, EnvFilterReloadFn};
3131
use quickwit_telemetry::payload::{QuickwitFeature, QuickwitTelemetryInfo, TelemetryEvent};
3232
use tokio::signal;
3333
use tracing::{debug, info};
@@ -74,7 +74,7 @@ impl RunCliCommand {
7474
})
7575
}
7676

77-
pub async fn execute(&self) -> anyhow::Result<()> {
77+
pub async fn execute(&self, env_filter_reload_fn: EnvFilterReloadFn) -> anyhow::Result<()> {
7878
debug!(args = ?self, "run-service");
7979
let version_text = BuildInfo::get_version_text();
8080
info!("quickwit version: {version_text}");
@@ -115,6 +115,7 @@ impl RunCliCommand {
115115
metastore_resolver,
116116
storage_resolver,
117117
shutdown_signal,
118+
env_filter_reload_fn,
118119
)
119120
.await;
120121
let return_code = match serve_result {

quickwit/quickwit-cli/tests/helpers.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,10 @@ impl TestEnv {
158158
services: Some(QuickwitService::supported_services()),
159159
};
160160
tokio::spawn(async move {
161-
if let Err(error) = run_command.execute().await {
161+
if let Err(error) = run_command
162+
.execute(quickwit_serve::do_nothing_env_filter_reload_fn())
163+
.await
164+
{
162165
error!(err=?error, "failed to start a quickwit server");
163166
}
164167
});

quickwit/quickwit-serve/src/lib.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ mod index_api;
3030
mod indexing_api;
3131
mod ingest_api;
3232
mod jaeger_api;
33+
mod log_level_handler;
3334
mod metrics;
3435
mod metrics_api;
3536
mod node_info_handler;
@@ -131,6 +132,12 @@ const METASTORE_CLIENT_MAX_CONCURRENCY_ENV_KEY: &str = "QW_METASTORE_CLIENT_MAX_
131132
const DEFAULT_METASTORE_CLIENT_MAX_CONCURRENCY: usize = 6;
132133
const DISABLE_DELETE_TASK_SERVICE_ENV_KEY: &str = "QW_DISABLE_DELETE_TASK_SERVICE";
133134

135+
pub type EnvFilterReloadFn = Arc<dyn Fn(&str) -> anyhow::Result<()> + Send + Sync>;
136+
137+
pub fn do_nothing_env_filter_reload_fn() -> EnvFilterReloadFn {
138+
Arc::new(|_| Ok(()))
139+
}
140+
134141
fn get_metastore_client_max_concurrency() -> usize {
135142
std::env::var(METASTORE_CLIENT_MAX_CONCURRENCY_ENV_KEY).ok()
136143
.and_then(|metastore_client_max_concurrency_str| {
@@ -187,6 +194,8 @@ struct QuickwitServices {
187194
/// the root requests.
188195
pub search_service: Arc<dyn SearchService>,
189196

197+
pub env_filter_reload_fn: EnvFilterReloadFn,
198+
190199
/// The control plane listens to various events.
191200
/// We must maintain a reference to the subscription handles to continue receiving
192201
/// notifications. Otherwise, the subscriptions are dropped.
@@ -359,6 +368,7 @@ pub async fn serve_quickwit(
359368
metastore_resolver: MetastoreResolver,
360369
storage_resolver: StorageResolver,
361370
shutdown_signal: BoxFutureInfaillible<()>,
371+
env_filter_reload_fn: EnvFilterReloadFn,
362372
) -> anyhow::Result<HashMap<String, ActorExitStatus>> {
363373
let cluster = start_cluster_service(&node_config).await?;
364374

@@ -627,6 +637,7 @@ pub async fn serve_quickwit(
627637
otlp_logs_service_opt,
628638
otlp_traces_service_opt,
629639
search_service,
640+
env_filter_reload_fn,
630641
});
631642
// Setup and start gRPC server.
632643
let (grpc_readiness_trigger_tx, grpc_readiness_signal_rx) = oneshot::channel::<()>();
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright (C) 2024 Quickwit, Inc.
2+
//
3+
// Quickwit is offered under the AGPL v3.0 and as commercial software.
4+
// For commercial licensing, contact us at [email protected].
5+
//
6+
// AGPL:
7+
// This program is free software: you can redistribute it and/or modify
8+
// it under the terms of the GNU Affero General Public License as
9+
// published by the Free Software Foundation, either version 3 of the
10+
// License, or (at your option) any later version.
11+
//
12+
// This program is distributed in the hope that it will be useful,
13+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
// GNU Affero General Public License for more details.
16+
//
17+
// You should have received a copy of the GNU Affero General Public License
18+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
20+
use hyper::StatusCode;
21+
use serde::Deserialize;
22+
use tracing::{error, info};
23+
use warp::{Filter, Rejection};
24+
25+
use crate::{with_arg, EnvFilterReloadFn};
26+
27+
#[derive(Deserialize)]
28+
struct EnvFilter {
29+
filter: String,
30+
}
31+
32+
#[utoipa::path(get, tag = "Log level", path = "/log_level")]
33+
pub fn log_level_handler(
34+
env_filter_reload_fn: EnvFilterReloadFn,
35+
) -> impl warp::Filter<Extract = (impl warp::Reply,), Error = Rejection> + Clone {
36+
warp::path("log_level")
37+
.and(warp::path::end())
38+
.and(with_arg(env_filter_reload_fn))
39+
.and(warp::query::<EnvFilter>())
40+
.then(
41+
|env_filter_reload_fn: EnvFilterReloadFn, env_filter: EnvFilter| async move {
42+
match env_filter_reload_fn(env_filter.filter.as_str()) {
43+
Ok(_) => {
44+
info!(filter = env_filter.filter, "change log level");
45+
warp::reply::with_status(
46+
format!("changed log level to:[{}]", env_filter.filter),
47+
StatusCode::OK,
48+
)
49+
}
50+
Err(_) => {
51+
error!(filter = env_filter.filter, "invalid log level");
52+
warp::reply::with_status(
53+
format!("invalid log level:[{}]", env_filter.filter),
54+
StatusCode::BAD_REQUEST,
55+
)
56+
}
57+
}
58+
},
59+
)
60+
}

quickwit/quickwit-serve/src/rest.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ use crate::index_api::index_management_handlers;
4242
use crate::indexing_api::indexing_get_handler;
4343
use crate::ingest_api::ingest_api_handlers;
4444
use crate::jaeger_api::jaeger_api_handlers;
45+
use crate::log_level_handler::log_level_handler;
4546
use crate::metrics_api::metrics_handler;
4647
use crate::node_info_handler::node_info_handler;
4748
use crate::otlp_api::otlp_ingest_api_handlers;
@@ -172,6 +173,9 @@ fn api_v1_routes(
172173
RuntimeInfo::get(),
173174
quickwit_services.node_config.clone(),
174175
))
176+
.or(log_level_handler(
177+
quickwit_services.env_filter_reload_fn.clone(),
178+
))
175179
.or(indexing_get_handler(
176180
quickwit_services.indexing_service_opt.clone(),
177181
))
@@ -630,6 +634,7 @@ mod tests {
630634
node_config: Arc::new(node_config.clone()),
631635
search_service: Arc::new(MockSearchService::new()),
632636
jaeger_service_opt: None,
637+
env_filter_reload_fn: crate::do_nothing_env_filter_reload_fn(),
633638
};
634639

635640
let handler = api_v1_routes(Arc::new(quickwit_services))

0 commit comments

Comments
 (0)