mirror of
https://github.com/meilisearch/meilisearch.git
synced 2024-11-25 19:45:05 +08:00
implements more routes
This commit is contained in:
parent
6fd5258ec1
commit
143a866b48
@ -6,6 +6,7 @@ use meilisearch_types::error::ResponseError;
|
|||||||
use meilisearch_types::tasks::KindWithContent;
|
use meilisearch_types::tasks::KindWithContent;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
use utoipa::OpenApi;
|
||||||
|
|
||||||
use crate::analytics::Analytics;
|
use crate::analytics::Analytics;
|
||||||
use crate::extractors::authentication::policies::*;
|
use crate::extractors::authentication::policies::*;
|
||||||
@ -14,10 +15,59 @@ use crate::extractors::sequential_extractor::SeqHandler;
|
|||||||
use crate::routes::{get_task_id, is_dry_run, SummarizedTaskView};
|
use crate::routes::{get_task_id, is_dry_run, SummarizedTaskView};
|
||||||
use crate::Opt;
|
use crate::Opt;
|
||||||
|
|
||||||
|
#[derive(OpenApi)]
|
||||||
|
#[openapi(
|
||||||
|
paths(create_dump),
|
||||||
|
tags((
|
||||||
|
name = "Dumps",
|
||||||
|
description = "The `dumps` route allows the creation of database dumps.
|
||||||
|
Dumps are `.dump` files that can be used to launch Meilisearch. Dumps are compatible between Meilisearch versions.
|
||||||
|
Creating a dump is also referred to as exporting it, whereas launching Meilisearch with a dump is referred to as importing it.
|
||||||
|
During a [dump export](https://www.meilisearch.com/docs/reference/api/dump#create-a-dump), all indexes of the current instance are
|
||||||
|
exported—together with their documents and settings—and saved as a single `.dump` file. During a dump import,
|
||||||
|
all indexes contained in the indicated `.dump` file are imported along with their associated documents and settings.
|
||||||
|
Any existing index with the same uid as an index in the dump file will be overwritten.
|
||||||
|
Dump imports are [performed at launch](https://www.meilisearch.com/docs/learn/advanced/dumps#importing-a-dump) using an option.",
|
||||||
|
external_docs(url = "https://www.meilisearch.com/docs/reference/api/dump"),
|
||||||
|
|
||||||
|
)),
|
||||||
|
)]
|
||||||
|
pub struct DumpApi;
|
||||||
|
|
||||||
pub fn configure(cfg: &mut web::ServiceConfig) {
|
pub fn configure(cfg: &mut web::ServiceConfig) {
|
||||||
cfg.service(web::resource("").route(web::post().to(SeqHandler(create_dump))));
|
cfg.service(web::resource("").route(web::post().to(SeqHandler(create_dump))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a dump
|
||||||
|
///
|
||||||
|
/// Triggers a dump creation process. Once the process is complete, a dump is created in the
|
||||||
|
/// [dump directory](https://www.meilisearch.com/docs/learn/self_hosted/configure_meilisearch_at_launch#dump-directory).
|
||||||
|
/// If the dump directory does not exist yet, it will be created.
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/",
|
||||||
|
tag = "Dumps",
|
||||||
|
security(("Bearer" = ["dumps.create", "dumps.*", "*"])),
|
||||||
|
responses(
|
||||||
|
(status = 202, description = "Dump is being created", body = SummarizedTaskView, content_type = "application/json", example = json!(
|
||||||
|
{
|
||||||
|
"taskUid": 0,
|
||||||
|
"indexUid": null,
|
||||||
|
"status": "enqueued",
|
||||||
|
"type": "DumpCreation",
|
||||||
|
"enqueuedAt": "2021-01-01T09:39:00.000000Z"
|
||||||
|
}
|
||||||
|
)),
|
||||||
|
(status = 401, description = "The authorization header is missing", body = ResponseError, content_type = "application/json", example = json!(
|
||||||
|
{
|
||||||
|
"message": "The Authorization header is missing. It must use the bearer authorization method.",
|
||||||
|
"code": "missing_authorization_header",
|
||||||
|
"type": "auth",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#missing_authorization_header"
|
||||||
|
}
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
)]
|
||||||
pub async fn create_dump(
|
pub async fn create_dump(
|
||||||
index_scheduler: GuardedData<ActionPolicy<{ actions::DUMPS_CREATE }>, Data<IndexScheduler>>,
|
index_scheduler: GuardedData<ActionPolicy<{ actions::DUMPS_CREATE }>, Data<IndexScheduler>>,
|
||||||
auth_controller: GuardedData<ActionPolicy<{ actions::DUMPS_CREATE }>, Data<AuthController>>,
|
auth_controller: GuardedData<ActionPolicy<{ actions::DUMPS_CREATE }>, Data<AuthController>>,
|
||||||
|
@ -16,6 +16,7 @@ use serde::Serialize;
|
|||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
use super::{get_task_id, Pagination, SummarizedTaskView, PAGINATION_DEFAULT_LIMIT};
|
use super::{get_task_id, Pagination, SummarizedTaskView, PAGINATION_DEFAULT_LIMIT};
|
||||||
use crate::analytics::Analytics;
|
use crate::analytics::Analytics;
|
||||||
@ -247,12 +248,12 @@ pub async fn delete_index(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Stats of an `Index`, as known to the `stats` route.
|
/// Stats of an `Index`, as known to the `stats` route.
|
||||||
#[derive(Serialize, Debug)]
|
#[derive(Serialize, Debug, ToSchema)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct IndexStats {
|
pub struct IndexStats {
|
||||||
/// Number of documents in the index
|
/// Number of documents in the index
|
||||||
pub number_of_documents: u64,
|
pub number_of_documents: u64,
|
||||||
/// Whether the index is currently performing indexation, according to the scheduler.
|
/// Whether or not the index is currently ingesting document
|
||||||
pub is_indexing: bool,
|
pub is_indexing: bool,
|
||||||
/// Association of every field name with the number of times it occurs in the documents.
|
/// Association of every field name with the number of times it occurs in the documents.
|
||||||
pub field_distribution: FieldDistribution,
|
pub field_distribution: FieldDistribution,
|
||||||
|
@ -14,9 +14,11 @@ use index_scheduler::IndexScheduler;
|
|||||||
use meilisearch_types::deserr::DeserrJsonError;
|
use meilisearch_types::deserr::DeserrJsonError;
|
||||||
use meilisearch_types::error::deserr_codes::*;
|
use meilisearch_types::error::deserr_codes::*;
|
||||||
use meilisearch_types::error::{Code, ResponseError};
|
use meilisearch_types::error::{Code, ResponseError};
|
||||||
|
use serde::Serialize;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tracing_subscriber::filter::Targets;
|
use tracing_subscriber::filter::Targets;
|
||||||
use tracing_subscriber::Layer;
|
use tracing_subscriber::Layer;
|
||||||
|
use utoipa::{OpenApi, ToSchema};
|
||||||
|
|
||||||
use crate::error::MeilisearchHttpError;
|
use crate::error::MeilisearchHttpError;
|
||||||
use crate::extractors::authentication::policies::*;
|
use crate::extractors::authentication::policies::*;
|
||||||
@ -24,6 +26,20 @@ use crate::extractors::authentication::GuardedData;
|
|||||||
use crate::extractors::sequential_extractor::SeqHandler;
|
use crate::extractors::sequential_extractor::SeqHandler;
|
||||||
use crate::{LogRouteHandle, LogStderrHandle};
|
use crate::{LogRouteHandle, LogStderrHandle};
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(OpenApi)]
|
||||||
|
#[openapi(
|
||||||
|
paths(get_logs, cancel_logs, update_stderr_target),
|
||||||
|
tags((
|
||||||
|
name = "Logs",
|
||||||
|
description = "Everything about retrieving or customizing logs.
|
||||||
|
Currently [experimental](https://www.meilisearch.com/docs/learn/experimental/overview).",
|
||||||
|
external_docs(url = "https://www.meilisearch.com/docs/learn/experimental/log_customization"),
|
||||||
|
|
||||||
|
)),
|
||||||
|
)]
|
||||||
|
pub struct LogsApi;
|
||||||
|
|
||||||
pub fn configure(cfg: &mut web::ServiceConfig) {
|
pub fn configure(cfg: &mut web::ServiceConfig) {
|
||||||
cfg.service(
|
cfg.service(
|
||||||
web::resource("stream")
|
web::resource("stream")
|
||||||
@ -33,12 +49,16 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
|
|||||||
.service(web::resource("stderr").route(web::post().to(SeqHandler(update_stderr_target))));
|
.service(web::resource("stderr").route(web::post().to(SeqHandler(update_stderr_target))));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Copy, Deserr, PartialEq, Eq)]
|
#[derive(Debug, Default, Clone, Copy, Deserr, Serialize, PartialEq, Eq, ToSchema)]
|
||||||
#[deserr(rename_all = camelCase)]
|
#[deserr(rename_all = camelCase)]
|
||||||
|
#[schema(rename_all = "camelCase")]
|
||||||
pub enum LogMode {
|
pub enum LogMode {
|
||||||
|
/// Output the logs in a human readable form.
|
||||||
#[default]
|
#[default]
|
||||||
Human,
|
Human,
|
||||||
|
/// Output the logs in json.
|
||||||
Json,
|
Json,
|
||||||
|
/// Output the logs in the firefox profiler format. They can then be loaded and visualized at https://profiler.firefox.com/
|
||||||
Profile,
|
Profile,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,16 +103,26 @@ impl MergeWithError<MyParseError> for DeserrJsonError<BadRequest> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserr)]
|
#[derive(Debug, Deserr, ToSchema)]
|
||||||
#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields, validate = validate_get_logs -> DeserrJsonError<InvalidSettingsTypoTolerance>)]
|
#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields, validate = validate_get_logs -> DeserrJsonError<InvalidSettingsTypoTolerance>)]
|
||||||
|
#[schema(rename_all = "camelCase")]
|
||||||
pub struct GetLogs {
|
pub struct GetLogs {
|
||||||
|
/// Lets you specify which parts of the code you want to inspect and is formatted like that: code_part=log_level,code_part=log_level
|
||||||
|
/// - If the `code_part` is missing, then the `log_level` will be applied to everything.
|
||||||
|
/// - If the `log_level` is missing, then the `code_part` will be selected in `info` log level.
|
||||||
#[deserr(default = "info".parse().unwrap(), try_from(&String) = MyTargets::from_str -> DeserrJsonError<BadRequest>)]
|
#[deserr(default = "info".parse().unwrap(), try_from(&String) = MyTargets::from_str -> DeserrJsonError<BadRequest>)]
|
||||||
|
#[schema(value_type = String, default = "info", example = json!("milli=trace,index_scheduler,actix_web=off"))]
|
||||||
target: MyTargets,
|
target: MyTargets,
|
||||||
|
|
||||||
|
/// Lets you customize the format of the logs.
|
||||||
#[deserr(default, error = DeserrJsonError<BadRequest>)]
|
#[deserr(default, error = DeserrJsonError<BadRequest>)]
|
||||||
|
#[schema(default = LogMode::default)]
|
||||||
mode: LogMode,
|
mode: LogMode,
|
||||||
|
|
||||||
|
/// A boolean to indicate if you want to profile the memory as well. This is only useful while using the `profile` mode.
|
||||||
|
/// Be cautious, though; it slows down the engine a lot.
|
||||||
#[deserr(default = false, error = DeserrJsonError<BadRequest>)]
|
#[deserr(default = false, error = DeserrJsonError<BadRequest>)]
|
||||||
|
#[schema(default = false)]
|
||||||
profile_memory: bool,
|
profile_memory: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,6 +278,45 @@ fn entry_stream(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieve logs
|
||||||
|
///
|
||||||
|
/// Stream logs over HTTP. The format of the logs depends on the configuration specified in the payload.
|
||||||
|
/// The logs are sent as multi-part, and the stream never stops, so make sure your clients correctly handle that.
|
||||||
|
/// To make the server stop sending you logs, you can call the `DELETE /logs/stream` route.
|
||||||
|
///
|
||||||
|
/// There can only be one listener at a timeand an error will be returned if you call this route while it's being used by another client.
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/stream",
|
||||||
|
tag = "Logs",
|
||||||
|
security(("Bearer" = ["metrics.get", "metrics.*", "*"])),
|
||||||
|
request_body = GetLogs,
|
||||||
|
responses(
|
||||||
|
(status = OK, description = "Logs are being returned", body = String, content_type = "application/json", example = json!(
|
||||||
|
r#"
|
||||||
|
2024-10-08T13:35:02.643750Z WARN HTTP request{method=GET host="localhost:7700" route=/metrics query_parameters= user_agent=HTTPie/3.2.3 status_code=400 error=Getting metrics requires enabling the `metrics` experimental feature. See https://github.com/meilisearch/product/discussions/625}: tracing_actix_web::middleware: Error encountered while processing the incoming HTTP request: ResponseError { code: 400, message: "Getting metrics requires enabling the `metrics` experimental feature. See https://github.com/meilisearch/product/discussions/625", error_code: "feature_not_enabled", error_type: "invalid_request", error_link: "https://docs.meilisearch.com/errors#feature_not_enabled" }
|
||||||
|
2024-10-08T13:35:02.644191Z INFO HTTP request{method=GET host="localhost:7700" route=/metrics query_parameters= user_agent=HTTPie/3.2.3 status_code=400 error=Getting metrics requires enabling the `metrics` experimental feature. See https://github.com/meilisearch/product/discussions/625}: meilisearch: close time.busy=1.66ms time.idle=658µs
|
||||||
|
2024-10-08T13:35:18.564152Z INFO HTTP request{method=PATCH host="localhost:7700" route=/experimental-features query_parameters= user_agent=curl/8.6.0 status_code=200}: meilisearch: close time.busy=1.17ms time.idle=127µs
|
||||||
|
2024-10-08T13:35:23.094987Z INFO HTTP request{method=GET host="localhost:7700" route=/metrics query_parameters= user_agent=HTTPie/3.2.3 status_code=200}: meilisearch: close time.busy=2.12ms time.idle=595µs "#
|
||||||
|
)),
|
||||||
|
(status = 400, description = "The route is already being used", body = ResponseError, content_type = "application/json", example = json!(
|
||||||
|
{
|
||||||
|
"message": "The `/logs/stream` route is currently in use by someone else.",
|
||||||
|
"code": "bad_request",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#bad_request"
|
||||||
|
}
|
||||||
|
)),
|
||||||
|
(status = 401, description = "The authorization header is missing", body = ResponseError, content_type = "application/json", example = json!(
|
||||||
|
{
|
||||||
|
"message": "The Authorization header is missing. It must use the bearer authorization method.",
|
||||||
|
"code": "missing_authorization_header",
|
||||||
|
"type": "auth",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#missing_authorization_header"
|
||||||
|
}
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
)]
|
||||||
pub async fn get_logs(
|
pub async fn get_logs(
|
||||||
index_scheduler: GuardedData<ActionPolicy<{ actions::METRICS_GET }>, Data<IndexScheduler>>,
|
index_scheduler: GuardedData<ActionPolicy<{ actions::METRICS_GET }>, Data<IndexScheduler>>,
|
||||||
logs: Data<LogRouteHandle>,
|
logs: Data<LogRouteHandle>,
|
||||||
@ -280,6 +349,27 @@ pub async fn get_logs(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Stop retrieving logs
|
||||||
|
///
|
||||||
|
/// Call this route to make the engine stops sending logs through the `POST /logs/stream` route.
|
||||||
|
#[utoipa::path(
|
||||||
|
delete,
|
||||||
|
path = "/stream",
|
||||||
|
tag = "Logs",
|
||||||
|
security(("Bearer" = ["metrics.get", "metrics.*", "*"])),
|
||||||
|
responses(
|
||||||
|
(status = NO_CONTENT, description = "Logs are being returned"),
|
||||||
|
(status = 401, description = "The authorization header is missing", body = ResponseError, content_type = "application/json", example = json!(
|
||||||
|
{
|
||||||
|
"message": "The Authorization header is missing. It must use the bearer authorization method.",
|
||||||
|
"code": "missing_authorization_header",
|
||||||
|
"type": "auth",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#missing_authorization_header"
|
||||||
|
}
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
)]
|
||||||
pub async fn cancel_logs(
|
pub async fn cancel_logs(
|
||||||
index_scheduler: GuardedData<ActionPolicy<{ actions::METRICS_GET }>, Data<IndexScheduler>>,
|
index_scheduler: GuardedData<ActionPolicy<{ actions::METRICS_GET }>, Data<IndexScheduler>>,
|
||||||
logs: Data<LogRouteHandle>,
|
logs: Data<LogRouteHandle>,
|
||||||
@ -293,13 +383,38 @@ pub async fn cancel_logs(
|
|||||||
Ok(HttpResponse::NoContent().finish())
|
Ok(HttpResponse::NoContent().finish())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserr)]
|
#[derive(Debug, Deserr, ToSchema)]
|
||||||
#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)]
|
#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)]
|
||||||
pub struct UpdateStderrLogs {
|
pub struct UpdateStderrLogs {
|
||||||
|
/// Lets you specify which parts of the code you want to inspect and is formatted like that: code_part=log_level,code_part=log_level
|
||||||
|
/// - If the `code_part` is missing, then the `log_level` will be applied to everything.
|
||||||
|
/// - If the `log_level` is missing, then the `code_part` will be selected in `info` log level.
|
||||||
#[deserr(default = "info".parse().unwrap(), try_from(&String) = MyTargets::from_str -> DeserrJsonError<BadRequest>)]
|
#[deserr(default = "info".parse().unwrap(), try_from(&String) = MyTargets::from_str -> DeserrJsonError<BadRequest>)]
|
||||||
|
#[schema(value_type = String, default = "info", example = json!("milli=trace,index_scheduler,actix_web=off"))]
|
||||||
target: MyTargets,
|
target: MyTargets,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update target of the console logs
|
||||||
|
///
|
||||||
|
/// This route lets you specify at runtime the level of the console logs outputted on stderr.
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/stderr",
|
||||||
|
tag = "Logs",
|
||||||
|
request_body = UpdateStderrLogs,
|
||||||
|
security(("Bearer" = ["metrics.get", "metrics.*", "*"])),
|
||||||
|
responses(
|
||||||
|
(status = NO_CONTENT, description = "The console logs have been updated"),
|
||||||
|
(status = 401, description = "The authorization header is missing", body = ResponseError, content_type = "application/json", example = json!(
|
||||||
|
{
|
||||||
|
"message": "The Authorization header is missing. It must use the bearer authorization method.",
|
||||||
|
"code": "missing_authorization_header",
|
||||||
|
"type": "auth",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#missing_authorization_header"
|
||||||
|
}
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
)]
|
||||||
pub async fn update_stderr_target(
|
pub async fn update_stderr_target(
|
||||||
index_scheduler: GuardedData<ActionPolicy<{ actions::METRICS_GET }>, Data<IndexScheduler>>,
|
index_scheduler: GuardedData<ActionPolicy<{ actions::METRICS_GET }>, Data<IndexScheduler>>,
|
||||||
logs: Data<LogStderrHandle>,
|
logs: Data<LogStderrHandle>,
|
||||||
|
@ -6,15 +6,106 @@ use meilisearch_auth::AuthController;
|
|||||||
use meilisearch_types::error::ResponseError;
|
use meilisearch_types::error::ResponseError;
|
||||||
use meilisearch_types::keys::actions;
|
use meilisearch_types::keys::actions;
|
||||||
use prometheus::{Encoder, TextEncoder};
|
use prometheus::{Encoder, TextEncoder};
|
||||||
|
use utoipa::OpenApi;
|
||||||
|
|
||||||
use crate::extractors::authentication::policies::ActionPolicy;
|
use crate::extractors::authentication::policies::ActionPolicy;
|
||||||
use crate::extractors::authentication::{AuthenticationError, GuardedData};
|
use crate::extractors::authentication::{AuthenticationError, GuardedData};
|
||||||
use crate::routes::create_all_stats;
|
use crate::routes::create_all_stats;
|
||||||
|
|
||||||
|
#[derive(OpenApi)]
|
||||||
|
#[openapi(paths(get_metrics))]
|
||||||
|
pub struct MetricApi;
|
||||||
|
|
||||||
pub fn configure(config: &mut web::ServiceConfig) {
|
pub fn configure(config: &mut web::ServiceConfig) {
|
||||||
config.service(web::resource("").route(web::get().to(get_metrics)));
|
config.service(web::resource("").route(web::get().to(get_metrics)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get prometheus metrics
|
||||||
|
///
|
||||||
|
/// Retrieve metrics on the engine. See https://www.meilisearch.com/docs/learn/experimental/metrics
|
||||||
|
/// Currently, [the feature is experimental](https://www.meilisearch.com/docs/learn/experimental/overview)
|
||||||
|
/// which means it must be enabled.
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/",
|
||||||
|
tag = "Stats",
|
||||||
|
security(("Bearer" = ["metrics.get", "metrics.*", "*"])),
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "The metrics of the instance", body = String, content_type = "text/plain", example = json!(
|
||||||
|
r#"
|
||||||
|
# HELP meilisearch_db_size_bytes Meilisearch DB Size In Bytes
|
||||||
|
# TYPE meilisearch_db_size_bytes gauge
|
||||||
|
meilisearch_db_size_bytes 1130496
|
||||||
|
# HELP meilisearch_http_requests_total Meilisearch HTTP requests total
|
||||||
|
# TYPE meilisearch_http_requests_total counter
|
||||||
|
meilisearch_http_requests_total{method="GET",path="/metrics",status="400"} 1
|
||||||
|
meilisearch_http_requests_total{method="PATCH",path="/experimental-features",status="200"} 1
|
||||||
|
# HELP meilisearch_http_response_time_seconds Meilisearch HTTP response times
|
||||||
|
# TYPE meilisearch_http_response_time_seconds histogram
|
||||||
|
meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="0.005"} 0
|
||||||
|
meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="0.01"} 0
|
||||||
|
meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="0.025"} 0
|
||||||
|
meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="0.05"} 0
|
||||||
|
meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="0.075"} 0
|
||||||
|
meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="0.1"} 0
|
||||||
|
meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="0.25"} 0
|
||||||
|
meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="0.5"} 0
|
||||||
|
meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="0.75"} 0
|
||||||
|
meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="1"} 0
|
||||||
|
meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="2.5"} 0
|
||||||
|
meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="5"} 0
|
||||||
|
meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="7.5"} 0
|
||||||
|
meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="10"} 0
|
||||||
|
meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="+Inf"} 0
|
||||||
|
meilisearch_http_response_time_seconds_sum{method="GET",path="/metrics"} 0
|
||||||
|
meilisearch_http_response_time_seconds_count{method="GET",path="/metrics"} 0
|
||||||
|
# HELP meilisearch_index_count Meilisearch Index Count
|
||||||
|
# TYPE meilisearch_index_count gauge
|
||||||
|
meilisearch_index_count 1
|
||||||
|
# HELP meilisearch_index_docs_count Meilisearch Index Docs Count
|
||||||
|
# TYPE meilisearch_index_docs_count gauge
|
||||||
|
meilisearch_index_docs_count{index="mieli"} 2
|
||||||
|
# HELP meilisearch_is_indexing Meilisearch Is Indexing
|
||||||
|
# TYPE meilisearch_is_indexing gauge
|
||||||
|
meilisearch_is_indexing 0
|
||||||
|
# HELP meilisearch_last_update Meilisearch Last Update
|
||||||
|
# TYPE meilisearch_last_update gauge
|
||||||
|
meilisearch_last_update 1726675964
|
||||||
|
# HELP meilisearch_nb_tasks Meilisearch Number of tasks
|
||||||
|
# TYPE meilisearch_nb_tasks gauge
|
||||||
|
meilisearch_nb_tasks{kind="indexes",value="mieli"} 39
|
||||||
|
meilisearch_nb_tasks{kind="statuses",value="canceled"} 0
|
||||||
|
meilisearch_nb_tasks{kind="statuses",value="enqueued"} 0
|
||||||
|
meilisearch_nb_tasks{kind="statuses",value="failed"} 4
|
||||||
|
meilisearch_nb_tasks{kind="statuses",value="processing"} 0
|
||||||
|
meilisearch_nb_tasks{kind="statuses",value="succeeded"} 35
|
||||||
|
meilisearch_nb_tasks{kind="types",value="documentAdditionOrUpdate"} 9
|
||||||
|
meilisearch_nb_tasks{kind="types",value="documentDeletion"} 0
|
||||||
|
meilisearch_nb_tasks{kind="types",value="documentEdition"} 0
|
||||||
|
meilisearch_nb_tasks{kind="types",value="dumpCreation"} 0
|
||||||
|
meilisearch_nb_tasks{kind="types",value="indexCreation"} 0
|
||||||
|
meilisearch_nb_tasks{kind="types",value="indexDeletion"} 8
|
||||||
|
meilisearch_nb_tasks{kind="types",value="indexSwap"} 0
|
||||||
|
meilisearch_nb_tasks{kind="types",value="indexUpdate"} 0
|
||||||
|
meilisearch_nb_tasks{kind="types",value="settingsUpdate"} 22
|
||||||
|
meilisearch_nb_tasks{kind="types",value="snapshotCreation"} 0
|
||||||
|
meilisearch_nb_tasks{kind="types",value="taskCancelation"} 0
|
||||||
|
meilisearch_nb_tasks{kind="types",value="taskDeletion"} 0
|
||||||
|
# HELP meilisearch_used_db_size_bytes Meilisearch Used DB Size In Bytes
|
||||||
|
# TYPE meilisearch_used_db_size_bytes gauge
|
||||||
|
meilisearch_used_db_size_bytes 409600
|
||||||
|
"#
|
||||||
|
)),
|
||||||
|
(status = 401, description = "The authorization header is missing", body = ResponseError, content_type = "application/json", example = json!(
|
||||||
|
{
|
||||||
|
"message": "The Authorization header is missing. It must use the bearer authorization method.",
|
||||||
|
"code": "missing_authorization_header",
|
||||||
|
"type": "auth",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#missing_authorization_header"
|
||||||
|
}
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
)]
|
||||||
pub async fn get_metrics(
|
pub async fn get_metrics(
|
||||||
index_scheduler: GuardedData<ActionPolicy<{ actions::METRICS_GET }>, Data<IndexScheduler>>,
|
index_scheduler: GuardedData<ActionPolicy<{ actions::METRICS_GET }>, Data<IndexScheduler>>,
|
||||||
auth_controller: Data<AuthController>,
|
auth_controller: Data<AuthController>,
|
||||||
|
@ -27,6 +27,10 @@ use utoipa::ToSchema;
|
|||||||
use utoipa_rapidoc::RapiDoc;
|
use utoipa_rapidoc::RapiDoc;
|
||||||
use utoipa_scalar::{Scalar, Servable as ScalarServable};
|
use utoipa_scalar::{Scalar, Servable as ScalarServable};
|
||||||
|
|
||||||
|
use self::indexes::IndexStats;
|
||||||
|
use self::logs::GetLogs;
|
||||||
|
use self::logs::LogMode;
|
||||||
|
use self::logs::UpdateStderrLogs;
|
||||||
use self::open_api_utils::OpenApiAuth;
|
use self::open_api_utils::OpenApiAuth;
|
||||||
use self::tasks::AllTasks;
|
use self::tasks::AllTasks;
|
||||||
|
|
||||||
@ -46,10 +50,16 @@ pub mod tasks;
|
|||||||
|
|
||||||
#[derive(OpenApi)]
|
#[derive(OpenApi)]
|
||||||
#[openapi(
|
#[openapi(
|
||||||
nest((path = "/tasks", api = tasks::TaskApi) ),
|
nest(
|
||||||
paths(get_health, get_version),
|
(path = "/tasks", api = tasks::TaskApi),
|
||||||
|
(path = "/snapshots", api = snapshot::SnapshotApi),
|
||||||
|
(path = "/dumps", api = dump::DumpApi),
|
||||||
|
(path = "/metrics", api = metrics::MetricApi),
|
||||||
|
(path = "/logs", api = logs::LogsApi),
|
||||||
|
),
|
||||||
|
paths(get_health, get_version, get_stats),
|
||||||
modifiers(&OpenApiAuth),
|
modifiers(&OpenApiAuth),
|
||||||
components(schemas(HealthStatus, HealthResponse, VersionResponse, Code, ErrorType, AllTasks, TaskView, Status, DetailsView, ResponseError, Settings<Unchecked>, Settings<Checked>, TypoSettings, MinWordSizeTyposSetting, FacetingSettings, PaginationSettings, SummarizedTaskView, Kind))
|
components(schemas(UpdateStderrLogs, LogMode, GetLogs, IndexStats, Stats, HealthStatus, HealthResponse, VersionResponse, Code, ErrorType, AllTasks, TaskView, Status, DetailsView, ResponseError, Settings<Unchecked>, Settings<Checked>, TypoSettings, MinWordSizeTyposSetting, FacetingSettings, PaginationSettings, SummarizedTaskView, Kind))
|
||||||
)]
|
)]
|
||||||
pub struct MeilisearchApi;
|
pub struct MeilisearchApi;
|
||||||
|
|
||||||
@ -313,17 +323,56 @@ pub async fn running() -> HttpResponse {
|
|||||||
HttpResponse::Ok().json(serde_json::json!({ "status": "Meilisearch is running" }))
|
HttpResponse::Ok().json(serde_json::json!({ "status": "Meilisearch is running" }))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Debug)]
|
#[derive(Serialize, Debug, ToSchema)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Stats {
|
pub struct Stats {
|
||||||
|
/// The size of the database, in bytes.
|
||||||
pub database_size: u64,
|
pub database_size: u64,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub used_database_size: u64,
|
pub used_database_size: u64,
|
||||||
|
/// The date of the last update in the RFC 3339 formats. Can be `null` if no update has ever been processed.
|
||||||
#[serde(serialize_with = "time::serde::rfc3339::option::serialize")]
|
#[serde(serialize_with = "time::serde::rfc3339::option::serialize")]
|
||||||
pub last_update: Option<OffsetDateTime>,
|
pub last_update: Option<OffsetDateTime>,
|
||||||
|
/// The stats of every individual index your API key lets you access.
|
||||||
|
#[schema(value_type = HashMap<String, indexes::IndexStats>)]
|
||||||
pub indexes: BTreeMap<String, indexes::IndexStats>,
|
pub indexes: BTreeMap<String, indexes::IndexStats>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get stats of all indexes.
|
||||||
|
///
|
||||||
|
/// Get stats of all indexes.
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/stats",
|
||||||
|
tag = "Stats",
|
||||||
|
security(("Bearer" = ["stats.get", "stats.*", "*"])),
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "The stats of the instance", body = Stats, content_type = "application/json", example = json!(
|
||||||
|
{
|
||||||
|
"databaseSize": 567,
|
||||||
|
"lastUpdate": "2019-11-20T09:40:33.711324Z",
|
||||||
|
"indexes": {
|
||||||
|
"movies": {
|
||||||
|
"numberOfDocuments": 10,
|
||||||
|
"isIndexing": true,
|
||||||
|
"fieldDistribution": {
|
||||||
|
"genre": 10,
|
||||||
|
"author": 9
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)),
|
||||||
|
(status = 401, description = "The authorization header is missing", body = ResponseError, content_type = "application/json", example = json!(
|
||||||
|
{
|
||||||
|
"message": "The Authorization header is missing. It must use the bearer authorization method.",
|
||||||
|
"code": "missing_authorization_header",
|
||||||
|
"type": "auth",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#missing_authorization_header"
|
||||||
|
}
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
)]
|
||||||
async fn get_stats(
|
async fn get_stats(
|
||||||
index_scheduler: GuardedData<ActionPolicy<{ actions::STATS_GET }>, Data<IndexScheduler>>,
|
index_scheduler: GuardedData<ActionPolicy<{ actions::STATS_GET }>, Data<IndexScheduler>>,
|
||||||
auth_controller: GuardedData<ActionPolicy<{ actions::STATS_GET }>, Data<AuthController>>,
|
auth_controller: GuardedData<ActionPolicy<{ actions::STATS_GET }>, Data<AuthController>>,
|
||||||
|
@ -5,6 +5,7 @@ use meilisearch_types::error::ResponseError;
|
|||||||
use meilisearch_types::tasks::KindWithContent;
|
use meilisearch_types::tasks::KindWithContent;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
use utoipa::OpenApi;
|
||||||
|
|
||||||
use crate::analytics::Analytics;
|
use crate::analytics::Analytics;
|
||||||
use crate::extractors::authentication::policies::*;
|
use crate::extractors::authentication::policies::*;
|
||||||
@ -13,10 +14,54 @@ use crate::extractors::sequential_extractor::SeqHandler;
|
|||||||
use crate::routes::{get_task_id, is_dry_run, SummarizedTaskView};
|
use crate::routes::{get_task_id, is_dry_run, SummarizedTaskView};
|
||||||
use crate::Opt;
|
use crate::Opt;
|
||||||
|
|
||||||
|
#[derive(OpenApi)]
|
||||||
|
#[openapi(
|
||||||
|
paths(create_snapshot),
|
||||||
|
tags((
|
||||||
|
name = "Snapshots",
|
||||||
|
description = "The snapshots route allows the creation of database snapshots. Snapshots are .snapshot files that can be used to launch Meilisearch.
|
||||||
|
Creating a snapshot is also referred to as exporting it, whereas launching Meilisearch with a snapshot is referred to as importing it.
|
||||||
|
During a snapshot export, all indexes of the current instance are exported—together with their documents and settings—and saved as a single .snapshot file.
|
||||||
|
During a snapshot import, all indexes contained in the indicated .snapshot file are imported along with their associated documents and settings.
|
||||||
|
Snapshot imports are performed at launch using an option.",
|
||||||
|
external_docs(url = "https://www.meilisearch.com/docs/reference/api/snapshots"),
|
||||||
|
|
||||||
|
)),
|
||||||
|
)]
|
||||||
|
pub struct SnapshotApi;
|
||||||
|
|
||||||
pub fn configure(cfg: &mut web::ServiceConfig) {
|
pub fn configure(cfg: &mut web::ServiceConfig) {
|
||||||
cfg.service(web::resource("").route(web::post().to(SeqHandler(create_snapshot))));
|
cfg.service(web::resource("").route(web::post().to(SeqHandler(create_snapshot))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a snapshot
|
||||||
|
///
|
||||||
|
/// Triggers a snapshot creation process. Once the process is complete, a snapshot is created in the snapshot directory. If the snapshot directory does not exist yet, it will be created.
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/",
|
||||||
|
tag = "Snapshots",
|
||||||
|
security(("Bearer" = ["snapshots.create", "snapshots.*", "*"])),
|
||||||
|
responses(
|
||||||
|
(status = 202, description = "Snapshot is being created", body = SummarizedTaskView, content_type = "application/json", example = json!(
|
||||||
|
{
|
||||||
|
"taskUid": 0,
|
||||||
|
"indexUid": null,
|
||||||
|
"status": "enqueued",
|
||||||
|
"type": "snapshotCreation",
|
||||||
|
"enqueuedAt": "2021-01-01T09:39:00.000000Z"
|
||||||
|
}
|
||||||
|
)),
|
||||||
|
(status = 401, description = "The authorization header is missing", body = ResponseError, content_type = "application/json", example = json!(
|
||||||
|
{
|
||||||
|
"message": "The Authorization header is missing. It must use the bearer authorization method.",
|
||||||
|
"code": "missing_authorization_header",
|
||||||
|
"type": "auth",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#missing_authorization_header"
|
||||||
|
}
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
)]
|
||||||
pub async fn create_snapshot(
|
pub async fn create_snapshot(
|
||||||
index_scheduler: GuardedData<ActionPolicy<{ actions::SNAPSHOTS_CREATE }>, Data<IndexScheduler>>,
|
index_scheduler: GuardedData<ActionPolicy<{ actions::SNAPSHOTS_CREATE }>, Data<IndexScheduler>>,
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
|
@ -29,29 +29,6 @@ use crate::Opt;
|
|||||||
|
|
||||||
const DEFAULT_LIMIT: u32 = 20;
|
const DEFAULT_LIMIT: u32 = 20;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
|
||||||
pub struct OpenApiAuth;
|
|
||||||
|
|
||||||
impl utoipa::Modify for OpenApiAuth {
|
|
||||||
fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
|
|
||||||
if let Some(schema) = openapi.components.as_mut() {
|
|
||||||
schema.add_security_scheme(
|
|
||||||
"Bearer",
|
|
||||||
utoipa::openapi::security::SecurityScheme::Http(
|
|
||||||
utoipa::openapi::security::HttpBuilder::new()
|
|
||||||
.scheme(utoipa::openapi::security::HttpAuthScheme::Bearer)
|
|
||||||
.bearer_format("Uuidv4, string or JWT")
|
|
||||||
.description(Some(
|
|
||||||
"An API key is a token that you provide when making API calls. Include the token in a header parameter called `Authorization`.
|
|
||||||
Example: `Authorization: Bearer 8fece4405662dd830e4cb265e7e047aab2e79672a760a12712d2a263c9003509`"))
|
|
||||||
.build(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(OpenApi)]
|
#[derive(OpenApi)]
|
||||||
#[openapi(
|
#[openapi(
|
||||||
paths(get_tasks, delete_tasks, cancel_tasks, get_task),
|
paths(get_tasks, delete_tasks, cancel_tasks, get_task),
|
||||||
|
Loading…
Reference in New Issue
Block a user