1use std::collections::BTreeMap;
16use std::fmt::Display;
17use std::io::BufRead;
18use std::str::FromStr;
19use std::sync::Arc;
20use std::time::Instant;
21
22use api::helper::pb_value_to_value_ref;
23use async_trait::async_trait;
24use axum::body::Bytes;
25use axum::extract::{FromRequest, Multipart, Path, Query, Request, State};
26use axum::http::header::CONTENT_TYPE;
27use axum::http::{HeaderMap, StatusCode};
28use axum::response::{IntoResponse, Response};
29use axum::{Extension, Json};
30use axum_extra::TypedHeader;
31use common_catalog::consts::default_engine;
32use common_error::ext::{BoxedError, ErrorExt};
33use common_query::{Output, OutputData};
34use common_telemetry::{error, warn};
35use headers::ContentType;
36use lazy_static::lazy_static;
37use mime_guess::mime;
38use operator::expr_helper::{create_table_expr_by_column_schemas, expr_to_create};
39use pipeline::util::to_pipeline_version;
40use pipeline::{ContextReq, GreptimePipelineParams, PipelineContext, PipelineDefinition};
41use prometheus::{HistogramVec, IntCounterVec};
42use serde::{Deserialize, Serialize};
43use serde_json::{Deserializer, Map, Value as JsonValue, json};
44use session::context::{Channel, QueryContext, QueryContextRef};
45use simd_json::Buffers;
46use snafu::{OptionExt, ResultExt, ensure};
47use store_api::mito_engine_options::APPEND_MODE_KEY;
48use strum::{EnumIter, IntoEnumIterator};
49use table::table_reference::TableReference;
50use vrl::value::{KeyString, Value as VrlValue};
51
52use crate::error::{
53 Error, InvalidParameterSnafu, OtherSnafu, ParseJsonSnafu, PipelineSnafu, Result,
54 status_code_to_http_status,
55};
56use crate::http::HttpResponse;
57use crate::http::header::constants::{
58 GREPTIME_LOG_PIPELINE_NAME_HEADER_NAME, GREPTIME_PIPELINE_NAME_HEADER_NAME,
59 GREPTIME_PIPELINE_PARAMS_HEADER,
60};
61use crate::http::header::{
62 CONTENT_TYPE_NDJSON_STR, CONTENT_TYPE_NDJSON_SUBTYPE_STR, CONTENT_TYPE_PROTOBUF_STR,
63};
64use crate::http::result::greptime_manage_resp::{GreptimedbManageResponse, SqlOutput};
65use crate::http::result::greptime_result_v1::GreptimedbV1Response;
66use crate::interceptor::{LogIngestInterceptor, LogIngestInterceptorRef};
67use crate::metrics::{
68 METRIC_FAILURE_VALUE, METRIC_HTTP_LOGS_INGESTION_COUNTER, METRIC_HTTP_LOGS_INGESTION_ELAPSED,
69 METRIC_SUCCESS_VALUE,
70};
71use crate::pipeline::run_pipeline;
72use crate::query_handler::PipelineHandlerRef;
73
74const GREPTIME_INTERNAL_PIPELINE_NAME_PREFIX: &str = "greptime_";
75const GREPTIME_PIPELINE_SKIP_ERROR_KEY: &str = "skip_error";
76
77const CREATE_TABLE_SQL_SUFFIX_EXISTS: &str =
78 "the pipeline has dispatcher or table_suffix, the table name may not be fixed";
79const CREATE_TABLE_SQL_TABLE_EXISTS: &str =
80 "table already exists, the CREATE TABLE SQL may be different";
81
82lazy_static! {
83 pub static ref JSON_CONTENT_TYPE: ContentType = ContentType::json();
84 pub static ref TEXT_CONTENT_TYPE: ContentType = ContentType::text();
85 pub static ref TEXT_UTF8_CONTENT_TYPE: ContentType = ContentType::text_utf8();
86 pub static ref PB_CONTENT_TYPE: ContentType =
87 ContentType::from_str(CONTENT_TYPE_PROTOBUF_STR).unwrap();
88 pub static ref NDJSON_CONTENT_TYPE: ContentType =
89 ContentType::from_str(CONTENT_TYPE_NDJSON_STR).unwrap();
90}
91
92#[derive(Debug, Default, Serialize, Deserialize)]
94pub struct LogIngesterQueryParams {
95 pub db: Option<String>,
97 pub table: Option<String>,
99 pub pipeline_name: Option<String>,
101 pub version: Option<String>,
103 pub ignore_errors: Option<bool>,
105 pub source: Option<String>,
107 pub msg_field: Option<String>,
110 pub custom_time_index: Option<String>,
119 pub skip_error: Option<bool>,
125}
126
127#[derive(Debug, PartialEq)]
130pub(crate) struct PipelineIngestRequest {
131 pub table: String,
133 pub values: Vec<VrlValue>,
135}
136
137pub struct PipelineContent(String);
138
139impl<S> FromRequest<S> for PipelineContent
140where
141 S: Send + Sync,
142{
143 type Rejection = Response;
144
145 async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
146 let content_type_header = req.headers().get(CONTENT_TYPE);
147 let content_type = content_type_header.and_then(|value| value.to_str().ok());
148 if let Some(content_type) = content_type {
149 if content_type.ends_with("yaml") {
150 let payload = String::from_request(req, state)
151 .await
152 .map_err(IntoResponse::into_response)?;
153 return Ok(Self(payload));
154 }
155
156 if content_type.starts_with("multipart/form-data") {
157 let mut payload: Multipart = Multipart::from_request(req, state)
158 .await
159 .map_err(IntoResponse::into_response)?;
160 let file = payload
161 .next_field()
162 .await
163 .map_err(IntoResponse::into_response)?;
164 let payload = file
165 .ok_or(StatusCode::UNSUPPORTED_MEDIA_TYPE.into_response())?
166 .text()
167 .await
168 .map_err(IntoResponse::into_response)?;
169 return Ok(Self(payload));
170 }
171 }
172
173 Err(StatusCode::UNSUPPORTED_MEDIA_TYPE.into_response())
174 }
175}
176
177#[axum_macros::debug_handler]
178pub async fn query_pipeline(
179 State(state): State<LogState>,
180 Extension(mut query_ctx): Extension<QueryContext>,
181 Query(query_params): Query<LogIngesterQueryParams>,
182 Path(pipeline_name): Path<String>,
183) -> Result<GreptimedbManageResponse> {
184 let start = Instant::now();
185 let handler = state.log_handler;
186 ensure!(
187 !pipeline_name.is_empty(),
188 InvalidParameterSnafu {
189 reason: "pipeline_name is required in path",
190 }
191 );
192
193 let version = to_pipeline_version(query_params.version.as_deref()).context(PipelineSnafu)?;
194
195 query_ctx.set_channel(Channel::Log);
196 let query_ctx = Arc::new(query_ctx);
197
198 let (pipeline, pipeline_version) = handler
199 .get_pipeline_str(&pipeline_name, version, query_ctx)
200 .await?;
201
202 Ok(GreptimedbManageResponse::from_pipeline(
203 pipeline_name,
204 query_params
205 .version
206 .unwrap_or(pipeline_version.0.to_timezone_aware_string(None)),
207 start.elapsed().as_millis() as u64,
208 Some(pipeline),
209 ))
210}
211
212#[axum_macros::debug_handler]
214pub async fn query_pipeline_ddl(
215 State(state): State<LogState>,
216 Extension(mut query_ctx): Extension<QueryContext>,
217 Query(query_params): Query<LogIngesterQueryParams>,
218 Path(pipeline_name): Path<String>,
219) -> Result<GreptimedbManageResponse> {
220 let start = Instant::now();
221 let handler = state.log_handler;
222 ensure!(
223 !pipeline_name.is_empty(),
224 InvalidParameterSnafu {
225 reason: "pipeline_name is required in path",
226 }
227 );
228 ensure!(
229 !pipeline_name.starts_with(GREPTIME_INTERNAL_PIPELINE_NAME_PREFIX),
230 InvalidParameterSnafu {
231 reason: "built-in pipelines don't have fixed table schema",
232 }
233 );
234 let table_name = query_params.table.context(InvalidParameterSnafu {
235 reason: "table name is required",
236 })?;
237
238 let version = to_pipeline_version(query_params.version.as_deref()).context(PipelineSnafu)?;
239
240 query_ctx.set_channel(Channel::Log);
241 let query_ctx = Arc::new(query_ctx);
242
243 let pipeline = handler
244 .get_pipeline(&pipeline_name, version, query_ctx.clone())
245 .await?;
246
247 let schemas_def = pipeline.schemas().context(InvalidParameterSnafu {
248 reason: "auto transform doesn't have fixed table schema",
249 })?;
250
251 let schema = query_ctx.current_schema();
252 let table_name_ref = TableReference {
253 catalog: query_ctx.current_catalog(),
254 schema: &schema,
255 table: &table_name,
256 };
257
258 let mut create_table_expr =
259 create_table_expr_by_column_schemas(&table_name_ref, schemas_def, default_engine(), None)
260 .map_err(BoxedError::new)
261 .context(OtherSnafu)?;
262
263 create_table_expr
265 .table_options
266 .insert(APPEND_MODE_KEY.to_string(), "true".to_string());
267
268 let expr = expr_to_create(&create_table_expr, None)
269 .map_err(BoxedError::new)
270 .context(OtherSnafu)?;
271
272 let message = if handler.get_table(&table_name, &query_ctx).await?.is_some() {
273 Some(CREATE_TABLE_SQL_TABLE_EXISTS.to_string())
274 } else if pipeline.is_variant_table_name() {
275 Some(CREATE_TABLE_SQL_SUFFIX_EXISTS.to_string())
276 } else {
277 None
278 };
279
280 let sql = SqlOutput {
281 sql: format!("{:#}", expr),
282 message,
283 };
284
285 Ok(GreptimedbManageResponse::from_sql(
286 sql,
287 start.elapsed().as_millis() as u64,
288 ))
289}
290
291#[axum_macros::debug_handler]
292pub async fn add_pipeline(
293 State(state): State<LogState>,
294 Path(pipeline_name): Path<String>,
295 Extension(mut query_ctx): Extension<QueryContext>,
296 PipelineContent(payload): PipelineContent,
297) -> Result<GreptimedbManageResponse> {
298 let start = Instant::now();
299 let handler = state.log_handler;
300 ensure!(
301 !pipeline_name.is_empty(),
302 InvalidParameterSnafu {
303 reason: "pipeline_name is required in path",
304 }
305 );
306 ensure!(
307 !pipeline_name.starts_with(GREPTIME_INTERNAL_PIPELINE_NAME_PREFIX),
308 InvalidParameterSnafu {
309 reason: "pipeline_name cannot start with greptime_",
310 }
311 );
312 ensure!(
313 !payload.is_empty(),
314 InvalidParameterSnafu {
315 reason: "pipeline is required in body",
316 }
317 );
318
319 query_ctx.set_channel(Channel::Log);
320 let query_ctx = Arc::new(query_ctx);
321
322 let content_type = "yaml";
323 let result = handler
324 .insert_pipeline(&pipeline_name, content_type, &payload, query_ctx)
325 .await;
326
327 result
328 .map(|pipeline| {
329 GreptimedbManageResponse::from_pipeline(
330 pipeline_name,
331 pipeline.0.to_timezone_aware_string(None),
332 start.elapsed().as_millis() as u64,
333 None,
334 )
335 })
336 .map_err(|e| {
337 error!(e; "failed to insert pipeline");
338 e
339 })
340}
341
342#[axum_macros::debug_handler]
343pub async fn delete_pipeline(
344 State(state): State<LogState>,
345 Extension(mut query_ctx): Extension<QueryContext>,
346 Query(query_params): Query<LogIngesterQueryParams>,
347 Path(pipeline_name): Path<String>,
348) -> Result<GreptimedbManageResponse> {
349 let start = Instant::now();
350 let handler = state.log_handler;
351 ensure!(
352 !pipeline_name.is_empty(),
353 InvalidParameterSnafu {
354 reason: "pipeline_name is required",
355 }
356 );
357
358 let version_str = query_params.version.context(InvalidParameterSnafu {
359 reason: "version is required",
360 })?;
361
362 let version = to_pipeline_version(Some(&version_str)).context(PipelineSnafu)?;
363
364 query_ctx.set_channel(Channel::Log);
365 let query_ctx = Arc::new(query_ctx);
366
367 handler
368 .delete_pipeline(&pipeline_name, version, query_ctx)
369 .await
370 .map(|v| {
371 if v.is_some() {
372 GreptimedbManageResponse::from_pipeline(
373 pipeline_name,
374 version_str,
375 start.elapsed().as_millis() as u64,
376 None,
377 )
378 } else {
379 GreptimedbManageResponse::from_pipelines(vec![], start.elapsed().as_millis() as u64)
380 }
381 })
382 .map_err(|e| {
383 error!(e; "failed to delete pipeline");
384 e
385 })
386}
387
388pub(crate) fn transform_ndjson_array_factory(
391 values: impl IntoIterator<Item = Result<VrlValue, serde_json::Error>>,
392 ignore_error: bool,
393) -> Result<Vec<VrlValue>> {
394 values
395 .into_iter()
396 .try_fold(Vec::with_capacity(100), |mut acc_array, item| match item {
397 Ok(item_value) => {
398 match item_value {
399 VrlValue::Array(item_array) => {
400 acc_array.extend(item_array);
401 }
402 VrlValue::Object(_) => {
403 acc_array.push(item_value);
404 }
405 _ => {
406 if !ignore_error {
407 warn!("invalid item in array: {:?}", item_value);
408 return InvalidParameterSnafu {
409 reason: format!("invalid item: {} in array", item_value),
410 }
411 .fail();
412 }
413 }
414 }
415 Ok(acc_array)
416 }
417 Err(_) if !ignore_error => item.map(|x| vec![x]).context(ParseJsonSnafu),
418 Err(_) => {
419 warn!("invalid item in array: {:?}", item);
420 Ok(acc_array)
421 }
422 })
423}
424
425async fn dryrun_pipeline_inner(
427 value: Vec<VrlValue>,
428 pipeline: Arc<pipeline::Pipeline>,
429 pipeline_handler: PipelineHandlerRef,
430 query_ctx: &QueryContextRef,
431) -> Result<Response> {
432 let params = GreptimePipelineParams::default();
433
434 let pipeline_def = PipelineDefinition::Resolved(pipeline);
435 let pipeline_ctx = PipelineContext::new(&pipeline_def, ¶ms, query_ctx.channel());
436 let results = run_pipeline(
437 &pipeline_handler,
438 &pipeline_ctx,
439 PipelineIngestRequest {
440 table: "dry_run".to_owned(),
441 values: value,
442 },
443 query_ctx,
444 true,
445 )
446 .await?;
447
448 let column_type_key = "column_type";
449 let data_type_key = "data_type";
450 let name_key = "name";
451
452 let results = results
453 .all_req()
454 .filter_map(|row| {
455 if let Some(rows) = row.rows {
456 let table_name = row.table_name;
457 let result_schema = rows.schema;
458
459 let schema = result_schema
460 .iter()
461 .map(|cs| {
462 let mut map = Map::new();
463 map.insert(
464 name_key.to_string(),
465 JsonValue::String(cs.column_name.clone()),
466 );
467 map.insert(
468 data_type_key.to_string(),
469 JsonValue::String(cs.datatype().as_str_name().to_string()),
470 );
471 map.insert(
472 column_type_key.to_string(),
473 JsonValue::String(cs.semantic_type().as_str_name().to_string()),
474 );
475 map.insert(
476 "fulltext".to_string(),
477 JsonValue::Bool(
478 cs.options
479 .clone()
480 .is_some_and(|x| x.options.contains_key("fulltext")),
481 ),
482 );
483 JsonValue::Object(map)
484 })
485 .collect::<Vec<_>>();
486
487 let rows = rows
488 .rows
489 .into_iter()
490 .map(|row| {
491 row.values
492 .into_iter()
493 .enumerate()
494 .map(|(idx, v)| {
495 let mut map = Map::new();
496 let value_ref = pb_value_to_value_ref(
497 &v,
498 result_schema[idx].datatype_extension.as_ref(),
499 );
500 let greptime_value: datatypes::value::Value = value_ref.into();
501 let serde_json_value =
502 serde_json::Value::try_from(greptime_value).unwrap();
503 map.insert("value".to_string(), serde_json_value);
504 map.insert("key".to_string(), schema[idx][name_key].clone());
505 map.insert(
506 "semantic_type".to_string(),
507 schema[idx][column_type_key].clone(),
508 );
509 map.insert(
510 "data_type".to_string(),
511 schema[idx][data_type_key].clone(),
512 );
513 JsonValue::Object(map)
514 })
515 .collect()
516 })
517 .collect();
518
519 let mut result = Map::new();
520 result.insert("schema".to_string(), JsonValue::Array(schema));
521 result.insert("rows".to_string(), JsonValue::Array(rows));
522 result.insert("table_name".to_string(), JsonValue::String(table_name));
523 let result = JsonValue::Object(result);
524 Some(result)
525 } else {
526 None
527 }
528 })
529 .collect();
530 Ok(Json(JsonValue::Array(results)).into_response())
531}
532
533#[derive(Debug, Default, Serialize, Deserialize)]
539pub struct PipelineDryrunParams {
540 pub pipeline_name: Option<String>,
541 pub pipeline_version: Option<String>,
542 pub pipeline: Option<String>,
543 pub data_type: Option<String>,
544 pub data: String,
545}
546
547fn check_pipeline_dryrun_params_valid(payload: &Bytes) -> Option<PipelineDryrunParams> {
551 match serde_json::from_slice::<PipelineDryrunParams>(payload) {
552 Ok(params) if params.pipeline.is_some() || params.pipeline_name.is_some() => Some(params),
554 Ok(_) => None,
556 Err(_) => None,
558 }
559}
560
561fn check_pipeline_name_exists(pipeline_name: Option<String>) -> Result<String> {
563 pipeline_name.context(InvalidParameterSnafu {
564 reason: "pipeline_name is required",
565 })
566}
567
568fn check_data_valid(data_len: usize) -> Result<()> {
570 ensure!(
571 data_len <= 10,
572 InvalidParameterSnafu {
573 reason: "data is required",
574 }
575 );
576 Ok(())
577}
578
579fn add_step_info_for_pipeline_dryrun_error(step_msg: &str, e: Error) -> Response {
580 let body = Json(json!({
581 "error": format!("{}: {}", step_msg,e.output_msg()),
582 }));
583
584 (status_code_to_http_status(&e.status_code()), body).into_response()
585}
586
587fn parse_dryrun_data(data_type: String, data: String) -> Result<Vec<VrlValue>> {
591 if let Ok(content_type) = ContentType::from_str(&data_type) {
592 extract_pipeline_value_by_content_type(content_type, Bytes::from(data), false)
593 } else {
594 InvalidParameterSnafu {
595 reason: format!(
596 "invalid content type: {}, expected: one of {}",
597 data_type,
598 EventPayloadResolver::support_content_type_list().join(", ")
599 ),
600 }
601 .fail()
602 }
603}
604
605#[axum_macros::debug_handler]
606pub async fn pipeline_dryrun(
607 State(log_state): State<LogState>,
608 Query(query_params): Query<LogIngesterQueryParams>,
609 Extension(mut query_ctx): Extension<QueryContext>,
610 TypedHeader(content_type): TypedHeader<ContentType>,
611 payload: Bytes,
612) -> Result<Response> {
613 let handler = log_state.log_handler;
614
615 query_ctx.set_channel(Channel::Log);
616 let query_ctx = Arc::new(query_ctx);
617
618 match check_pipeline_dryrun_params_valid(&payload) {
619 Some(params) => {
620 let data = parse_dryrun_data(
621 params.data_type.unwrap_or("application/json".to_string()),
622 params.data,
623 )?;
624
625 check_data_valid(data.len())?;
626
627 match params.pipeline {
628 None => {
629 let version = to_pipeline_version(params.pipeline_version.as_deref())
630 .context(PipelineSnafu)?;
631 let pipeline_name = check_pipeline_name_exists(params.pipeline_name)?;
632 let pipeline = handler
633 .get_pipeline(&pipeline_name, version, query_ctx.clone())
634 .await?;
635 dryrun_pipeline_inner(data, pipeline, handler, &query_ctx).await
636 }
637 Some(pipeline) => {
638 let pipeline = handler.build_pipeline(&pipeline);
639 match pipeline {
640 Ok(pipeline) => {
641 match dryrun_pipeline_inner(
642 data,
643 Arc::new(pipeline),
644 handler,
645 &query_ctx,
646 )
647 .await
648 {
649 Ok(response) => Ok(response),
650 Err(e) => Ok(add_step_info_for_pipeline_dryrun_error(
651 "Failed to exec pipeline",
652 e,
653 )),
654 }
655 }
656 Err(e) => Ok(add_step_info_for_pipeline_dryrun_error(
657 "Failed to build pipeline",
658 e,
659 )),
660 }
661 }
662 }
663 }
664 None => {
665 let pipeline_name = check_pipeline_name_exists(query_params.pipeline_name)?;
669
670 let version =
671 to_pipeline_version(query_params.version.as_deref()).context(PipelineSnafu)?;
672
673 let ignore_errors = query_params.ignore_errors.unwrap_or(false);
674
675 let value =
676 extract_pipeline_value_by_content_type(content_type, payload, ignore_errors)?;
677
678 check_data_valid(value.len())?;
679
680 let pipeline = handler
681 .get_pipeline(&pipeline_name, version, query_ctx.clone())
682 .await?;
683
684 dryrun_pipeline_inner(value, pipeline, handler, &query_ctx).await
685 }
686 }
687}
688
689pub(crate) fn extract_pipeline_params_map_from_headers(
690 headers: &HeaderMap,
691) -> ahash::HashMap<String, String> {
692 GreptimePipelineParams::parse_header_str_to_map(
693 headers
694 .get(GREPTIME_PIPELINE_PARAMS_HEADER)
695 .and_then(|v| v.to_str().ok()),
696 )
697}
698
699fn pipeline_name_from_headers(headers: &HeaderMap) -> Option<String> {
707 [
708 GREPTIME_PIPELINE_NAME_HEADER_NAME,
709 GREPTIME_LOG_PIPELINE_NAME_HEADER_NAME,
710 ]
711 .iter()
712 .find_map(|name| {
713 headers
714 .get(*name)
715 .and_then(|value| value.to_str().ok())
716 .map(str::trim)
717 .filter(|value| !value.is_empty())
718 .map(ToString::to_string)
719 })
720}
721
722#[axum_macros::debug_handler]
723pub async fn log_ingester(
724 State(log_state): State<LogState>,
725 Query(query_params): Query<LogIngesterQueryParams>,
726 Extension(mut query_ctx): Extension<QueryContext>,
727 TypedHeader(content_type): TypedHeader<ContentType>,
728 headers: HeaderMap,
729 payload: Bytes,
730) -> Result<HttpResponse> {
731 let source = query_params.source.as_deref();
733 let response = match &log_state.log_validator {
734 Some(validator) => validator.validate(source, &payload).await,
735 None => None,
736 };
737 if let Some(response) = response {
738 return response;
739 }
740
741 let handler = log_state.log_handler;
742
743 let table_name = query_params.table.context(InvalidParameterSnafu {
744 reason: "table is required",
745 })?;
746
747 let ignore_errors = query_params.ignore_errors.unwrap_or(false);
748
749 let pipeline_name = pipeline_name_from_headers(&headers)
753 .or(query_params.pipeline_name)
754 .context(InvalidParameterSnafu {
755 reason: "pipeline_name is required",
756 })?;
757 let skip_error = query_params.skip_error.unwrap_or(false);
758 let version = to_pipeline_version(query_params.version.as_deref()).context(PipelineSnafu)?;
759 let pipeline = PipelineDefinition::from_name(
760 &pipeline_name,
761 version,
762 query_params.custom_time_index.map(|s| (s, ignore_errors)),
763 )
764 .context(PipelineSnafu)?;
765
766 let value = extract_pipeline_value_by_content_type(content_type, payload, ignore_errors)?;
767
768 query_ctx.set_channel(Channel::Log);
769 let query_ctx = Arc::new(query_ctx);
770
771 let value = log_state
772 .ingest_interceptor
773 .as_ref()
774 .pre_pipeline(value, query_ctx.clone())?;
775
776 let mut pipeline_params_map = extract_pipeline_params_map_from_headers(&headers);
777 if !pipeline_params_map.contains_key(GREPTIME_PIPELINE_SKIP_ERROR_KEY) && skip_error {
778 pipeline_params_map.insert(GREPTIME_PIPELINE_SKIP_ERROR_KEY.to_string(), "true".into());
779 }
780 let pipeline_params = GreptimePipelineParams::from_map(pipeline_params_map);
781
782 ingest_logs_inner(
783 handler,
784 pipeline,
785 vec![PipelineIngestRequest {
786 table: table_name,
787 values: value,
788 }],
789 query_ctx,
790 pipeline_params,
791 )
792 .await
793}
794
795#[derive(Debug, EnumIter)]
796enum EventPayloadResolverInner {
797 Json,
798 Ndjson,
799 Text,
800}
801
802impl Display for EventPayloadResolverInner {
803 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
804 match self {
805 EventPayloadResolverInner::Json => write!(f, "{}", *JSON_CONTENT_TYPE),
806 EventPayloadResolverInner::Ndjson => write!(f, "{}", *NDJSON_CONTENT_TYPE),
807 EventPayloadResolverInner::Text => write!(f, "{}", *TEXT_CONTENT_TYPE),
808 }
809 }
810}
811
812impl TryFrom<&ContentType> for EventPayloadResolverInner {
813 type Error = Error;
814
815 fn try_from(content_type: &ContentType) -> Result<Self> {
816 let mime: mime_guess::Mime = content_type.clone().into();
817 match (mime.type_(), mime.subtype()) {
818 (mime::APPLICATION, mime::JSON) => Ok(EventPayloadResolverInner::Json),
819 (mime::APPLICATION, subtype) if subtype == CONTENT_TYPE_NDJSON_SUBTYPE_STR => {
820 Ok(EventPayloadResolverInner::Ndjson)
821 }
822 (mime::TEXT, mime::PLAIN) => Ok(EventPayloadResolverInner::Text),
823 _ => InvalidParameterSnafu {
824 reason: format!(
825 "invalid content type: {}, expected: one of {}",
826 content_type,
827 EventPayloadResolver::support_content_type_list().join(", ")
828 ),
829 }
830 .fail(),
831 }
832 }
833}
834
835#[derive(Debug)]
836struct EventPayloadResolver<'a> {
837 inner: EventPayloadResolverInner,
838 #[allow(dead_code)]
841 content_type: &'a ContentType,
842}
843
844impl EventPayloadResolver<'_> {
845 pub(super) fn support_content_type_list() -> Vec<String> {
846 EventPayloadResolverInner::iter()
847 .map(|x| x.to_string())
848 .collect()
849 }
850}
851
852impl<'a> TryFrom<&'a ContentType> for EventPayloadResolver<'a> {
853 type Error = Error;
854
855 fn try_from(content_type: &'a ContentType) -> Result<Self> {
856 let inner = EventPayloadResolverInner::try_from(content_type)?;
857 Ok(EventPayloadResolver {
858 inner,
859 content_type,
860 })
861 }
862}
863
864impl EventPayloadResolver<'_> {
865 fn parse_payload(&self, payload: Bytes, ignore_errors: bool) -> Result<Vec<VrlValue>> {
866 match self.inner {
867 EventPayloadResolverInner::Json => transform_ndjson_array_factory(
868 Deserializer::from_slice(&payload).into_iter(),
869 ignore_errors,
870 ),
871 EventPayloadResolverInner::Ndjson => {
872 let mut result = Vec::with_capacity(1000);
873 let mut buffer = Buffers::new(1000);
874 for (index, line) in payload.lines().enumerate() {
875 let mut line = match line {
876 Ok(line) if !line.is_empty() => line,
877 Ok(_) => continue, Err(_) if ignore_errors => continue,
879 Err(e) => {
880 warn!(e; "invalid string at index: {}", index);
881 return InvalidParameterSnafu {
882 reason: format!("invalid line at index: {}", index),
883 }
884 .fail();
885 }
886 };
887
888 if let Ok(v) = simd_json::serde::from_slice_with_buffers(
891 unsafe { line.as_bytes_mut() },
892 &mut buffer,
893 ) {
894 result.push(v);
895 } else if !ignore_errors {
896 warn!("invalid JSON at index: {}, content: {:?}", index, line);
897 return InvalidParameterSnafu {
898 reason: format!("invalid JSON at index: {}", index),
899 }
900 .fail();
901 }
902 }
903 Ok(result)
904 }
905 EventPayloadResolverInner::Text => {
906 let result = payload
907 .lines()
908 .filter_map(|line| line.ok().filter(|line| !line.is_empty()))
909 .map(|line| {
910 let mut map = BTreeMap::new();
911 map.insert(
912 KeyString::from("message"),
913 VrlValue::Bytes(Bytes::from(line)),
914 );
915 VrlValue::Object(map)
916 })
917 .collect::<Vec<_>>();
918 Ok(result)
919 }
920 }
921 }
922}
923
924fn extract_pipeline_value_by_content_type(
925 content_type: ContentType,
926 payload: Bytes,
927 ignore_errors: bool,
928) -> Result<Vec<VrlValue>> {
929 EventPayloadResolver::try_from(&content_type).and_then(|resolver| {
930 resolver
931 .parse_payload(payload, ignore_errors)
932 .map_err(|e| match &e {
933 Error::InvalidParameter { reason, .. } if content_type == *JSON_CONTENT_TYPE => {
934 if reason.contains("invalid item:") {
935 InvalidParameterSnafu {
936 reason: "json format error, please check the date is valid JSON.",
937 }
938 .build()
939 } else {
940 e
941 }
942 }
943 _ => e,
944 })
945 })
946}
947
948pub(crate) async fn ingest_logs_inner(
949 handler: PipelineHandlerRef,
950 pipeline: PipelineDefinition,
951 log_ingest_requests: Vec<PipelineIngestRequest>,
952 query_ctx: QueryContextRef,
953 pipeline_params: GreptimePipelineParams,
954) -> Result<HttpResponse> {
955 let exec_timer = Instant::now();
958 let mut req = ContextReq::default();
959
960 let pipeline_ctx = PipelineContext::new(&pipeline, &pipeline_params, query_ctx.channel());
961 for pipeline_req in log_ingest_requests {
962 let requests =
963 run_pipeline(&handler, &pipeline_ctx, pipeline_req, &query_ctx, true).await?;
964
965 req.merge(requests);
966 }
967
968 execute_log_context_req(
969 handler,
970 req,
971 query_ctx,
972 exec_timer,
973 &METRIC_HTTP_LOGS_INGESTION_COUNTER,
974 &METRIC_HTTP_LOGS_INGESTION_ELAPSED,
975 )
976 .await
977}
978
979pub(crate) async fn execute_log_context_req(
980 handler: PipelineHandlerRef,
981 ctx_req: ContextReq,
982 query_ctx: QueryContextRef,
983 exec_timer: Instant,
984 counter: &IntCounterVec,
985 elapsed: &HistogramVec,
986) -> Result<HttpResponse> {
987 let db = query_ctx.get_db_string();
988
989 let mut outputs = Vec::with_capacity(ctx_req.map_len());
990 let mut total_rows: u64 = 0;
991 let mut fail = false;
992 for (temp_ctx, act_req) in ctx_req.as_req_iter(query_ctx) {
993 let output = handler.insert(act_req, temp_ctx).await;
994
995 if let Ok(Output {
996 data: OutputData::AffectedRows(rows),
997 meta: _,
998 }) = &output
999 {
1000 total_rows += *rows as u64;
1001 } else {
1002 fail = true;
1003 }
1004 outputs.push(output);
1005 }
1006
1007 if total_rows > 0 {
1009 counter.with_label_values(&[db.as_str()]).inc_by(total_rows);
1010 elapsed
1011 .with_label_values(&[db.as_str(), METRIC_SUCCESS_VALUE])
1012 .observe(exec_timer.elapsed().as_secs_f64());
1013 }
1014 if fail {
1015 elapsed
1016 .with_label_values(&[db.as_str(), METRIC_FAILURE_VALUE])
1017 .observe(exec_timer.elapsed().as_secs_f64());
1018 }
1019
1020 let response = GreptimedbV1Response::from_output(outputs)
1021 .await
1022 .with_execution_time(exec_timer.elapsed().as_millis() as u64);
1023 Ok(response)
1024}
1025
1026#[async_trait]
1027pub trait LogValidator: Send + Sync {
1028 async fn validate(&self, source: Option<&str>, payload: &Bytes)
1031 -> Option<Result<HttpResponse>>;
1032}
1033
1034pub type LogValidatorRef = Arc<dyn LogValidator + 'static>;
1035
1036#[derive(Clone)]
1038pub struct LogState {
1039 pub log_handler: PipelineHandlerRef,
1040 pub log_validator: Option<LogValidatorRef>,
1041 pub ingest_interceptor: Option<LogIngestInterceptorRef<Error>>,
1042}
1043
1044#[cfg(test)]
1045mod tests {
1046
1047 use super::*;
1048
1049 #[test]
1050 fn test_transform_ndjson() {
1051 let s = "{\"a\": 1}\n{\"b\": 2}";
1052 let a = serde_json::to_string(
1053 &transform_ndjson_array_factory(Deserializer::from_str(s).into_iter(), false).unwrap(),
1054 )
1055 .unwrap();
1056 assert_eq!(a, "[{\"a\":1},{\"b\":2}]");
1057
1058 let s = "{\"a\": 1}";
1059 let a = serde_json::to_string(
1060 &transform_ndjson_array_factory(Deserializer::from_str(s).into_iter(), false).unwrap(),
1061 )
1062 .unwrap();
1063 assert_eq!(a, "[{\"a\":1}]");
1064
1065 let s = "[{\"a\": 1}]";
1066 let a = serde_json::to_string(
1067 &transform_ndjson_array_factory(Deserializer::from_str(s).into_iter(), false).unwrap(),
1068 )
1069 .unwrap();
1070 assert_eq!(a, "[{\"a\":1}]");
1071
1072 let s = "[{\"a\": 1}, {\"b\": 2}]";
1073 let a = serde_json::to_string(
1074 &transform_ndjson_array_factory(Deserializer::from_str(s).into_iter(), false).unwrap(),
1075 )
1076 .unwrap();
1077 assert_eq!(a, "[{\"a\":1},{\"b\":2}]");
1078 }
1079
1080 #[test]
1081 fn test_extract_by_content() {
1082 let payload = r#"
1083 {"a": 1}
1084 {"b": 2"}
1085 {"c": 1}
1086"#
1087 .as_bytes();
1088 let payload = Bytes::from_static(payload);
1089
1090 let fail_rest =
1091 extract_pipeline_value_by_content_type(ContentType::json(), payload.clone(), true);
1092 assert!(fail_rest.is_ok());
1093 assert_eq!(fail_rest.unwrap(), vec![json!({"a": 1}).into()]);
1094
1095 let fail_only_wrong =
1096 extract_pipeline_value_by_content_type(NDJSON_CONTENT_TYPE.clone(), payload, true);
1097 assert!(fail_only_wrong.is_ok());
1098
1099 let mut map1 = BTreeMap::new();
1100 map1.insert(KeyString::from("a"), VrlValue::Integer(1));
1101 let map1 = VrlValue::Object(map1);
1102 let mut map2 = BTreeMap::new();
1103 map2.insert(KeyString::from("c"), VrlValue::Integer(1));
1104 let map2 = VrlValue::Object(map2);
1105 assert_eq!(fail_only_wrong.unwrap(), vec![map1, map2]);
1106 }
1107}