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 handler.check_pipeline_query_permission(&query_ctx)?;
244 let pipeline = handler
245 .get_pipeline(&pipeline_name, version, query_ctx.clone())
246 .await?;
247
248 let schemas_def = pipeline.schemas().context(InvalidParameterSnafu {
249 reason: "auto transform doesn't have fixed table schema",
250 })?;
251
252 let schema = query_ctx.current_schema();
253 let table_name_ref = TableReference {
254 catalog: query_ctx.current_catalog(),
255 schema: &schema,
256 table: &table_name,
257 };
258
259 let mut create_table_expr =
260 create_table_expr_by_column_schemas(&table_name_ref, schemas_def, default_engine(), None)
261 .map_err(BoxedError::new)
262 .context(OtherSnafu)?;
263
264 create_table_expr
266 .table_options
267 .insert(APPEND_MODE_KEY.to_string(), "true".to_string());
268
269 let expr = expr_to_create(&create_table_expr, None)
270 .map_err(BoxedError::new)
271 .context(OtherSnafu)?;
272
273 let message = if handler.get_table(&table_name, &query_ctx).await?.is_some() {
274 Some(CREATE_TABLE_SQL_TABLE_EXISTS.to_string())
275 } else if pipeline.is_variant_table_name() {
276 Some(CREATE_TABLE_SQL_SUFFIX_EXISTS.to_string())
277 } else {
278 None
279 };
280
281 let sql = SqlOutput {
282 sql: format!("{:#}", expr),
283 message,
284 };
285
286 Ok(GreptimedbManageResponse::from_sql(
287 sql,
288 start.elapsed().as_millis() as u64,
289 ))
290}
291
292#[axum_macros::debug_handler]
293pub async fn add_pipeline(
294 State(state): State<LogState>,
295 Path(pipeline_name): Path<String>,
296 Extension(mut query_ctx): Extension<QueryContext>,
297 PipelineContent(payload): PipelineContent,
298) -> Result<GreptimedbManageResponse> {
299 let start = Instant::now();
300 let handler = state.log_handler;
301 ensure!(
302 !pipeline_name.is_empty(),
303 InvalidParameterSnafu {
304 reason: "pipeline_name is required in path",
305 }
306 );
307 ensure!(
308 !pipeline_name.starts_with(GREPTIME_INTERNAL_PIPELINE_NAME_PREFIX),
309 InvalidParameterSnafu {
310 reason: "pipeline_name cannot start with greptime_",
311 }
312 );
313 ensure!(
314 !payload.is_empty(),
315 InvalidParameterSnafu {
316 reason: "pipeline is required in body",
317 }
318 );
319
320 query_ctx.set_channel(Channel::Log);
321 let query_ctx = Arc::new(query_ctx);
322
323 let content_type = "yaml";
324 let result = handler
325 .insert_pipeline(&pipeline_name, content_type, &payload, query_ctx)
326 .await;
327
328 result
329 .map(|pipeline| {
330 GreptimedbManageResponse::from_pipeline(
331 pipeline_name,
332 pipeline.0.to_timezone_aware_string(None),
333 start.elapsed().as_millis() as u64,
334 None,
335 )
336 })
337 .map_err(|e| {
338 error!(e; "failed to insert pipeline");
339 e
340 })
341}
342
343#[axum_macros::debug_handler]
344pub async fn delete_pipeline(
345 State(state): State<LogState>,
346 Extension(mut query_ctx): Extension<QueryContext>,
347 Query(query_params): Query<LogIngesterQueryParams>,
348 Path(pipeline_name): Path<String>,
349) -> Result<GreptimedbManageResponse> {
350 let start = Instant::now();
351 let handler = state.log_handler;
352 ensure!(
353 !pipeline_name.is_empty(),
354 InvalidParameterSnafu {
355 reason: "pipeline_name is required",
356 }
357 );
358
359 let version_str = query_params.version.context(InvalidParameterSnafu {
360 reason: "version is required",
361 })?;
362
363 let version = to_pipeline_version(Some(&version_str)).context(PipelineSnafu)?;
364
365 query_ctx.set_channel(Channel::Log);
366 let query_ctx = Arc::new(query_ctx);
367
368 handler
369 .delete_pipeline(&pipeline_name, version, query_ctx)
370 .await
371 .map(|v| {
372 if v.is_some() {
373 GreptimedbManageResponse::from_pipeline(
374 pipeline_name,
375 version_str,
376 start.elapsed().as_millis() as u64,
377 None,
378 )
379 } else {
380 GreptimedbManageResponse::from_pipelines(vec![], start.elapsed().as_millis() as u64)
381 }
382 })
383 .map_err(|e| {
384 error!(e; "failed to delete pipeline");
385 e
386 })
387}
388
389pub(crate) fn transform_ndjson_array_factory(
392 values: impl IntoIterator<Item = Result<VrlValue, serde_json::Error>>,
393 ignore_error: bool,
394) -> Result<Vec<VrlValue>> {
395 values
396 .into_iter()
397 .try_fold(Vec::with_capacity(100), |mut acc_array, item| match item {
398 Ok(item_value) => {
399 match item_value {
400 VrlValue::Array(item_array) => {
401 acc_array.extend(item_array);
402 }
403 VrlValue::Object(_) => {
404 acc_array.push(item_value);
405 }
406 _ => {
407 if !ignore_error {
408 warn!("invalid item in array: {:?}", item_value);
409 return InvalidParameterSnafu {
410 reason: format!("invalid item: {} in array", item_value),
411 }
412 .fail();
413 }
414 }
415 }
416 Ok(acc_array)
417 }
418 Err(_) if !ignore_error => item.map(|x| vec![x]).context(ParseJsonSnafu),
419 Err(_) => {
420 warn!("invalid item in array: {:?}", item);
421 Ok(acc_array)
422 }
423 })
424}
425
426async fn dryrun_pipeline_inner(
428 value: Vec<VrlValue>,
429 pipeline: Arc<pipeline::Pipeline>,
430 pipeline_handler: PipelineHandlerRef,
431 query_ctx: &QueryContextRef,
432) -> Result<Response> {
433 let params = GreptimePipelineParams::default();
434
435 let pipeline_def = PipelineDefinition::Resolved(pipeline);
436 let pipeline_ctx = PipelineContext::new(&pipeline_def, ¶ms, query_ctx.channel());
437 let results = run_pipeline(
438 &pipeline_handler,
439 &pipeline_ctx,
440 PipelineIngestRequest {
441 table: "dry_run".to_owned(),
442 values: value,
443 },
444 query_ctx,
445 true,
446 )
447 .await?;
448
449 let column_type_key = "column_type";
450 let data_type_key = "data_type";
451 let name_key = "name";
452
453 let results = results
454 .all_req()
455 .filter_map(|row| {
456 if let Some(rows) = row.rows {
457 let table_name = row.table_name;
458 let result_schema = rows.schema;
459
460 let schema = result_schema
461 .iter()
462 .map(|cs| {
463 let mut map = Map::new();
464 map.insert(
465 name_key.to_string(),
466 JsonValue::String(cs.column_name.clone()),
467 );
468 map.insert(
469 data_type_key.to_string(),
470 JsonValue::String(cs.datatype().as_str_name().to_string()),
471 );
472 map.insert(
473 column_type_key.to_string(),
474 JsonValue::String(cs.semantic_type().as_str_name().to_string()),
475 );
476 map.insert(
477 "fulltext".to_string(),
478 JsonValue::Bool(
479 cs.options
480 .clone()
481 .is_some_and(|x| x.options.contains_key("fulltext")),
482 ),
483 );
484 JsonValue::Object(map)
485 })
486 .collect::<Vec<_>>();
487
488 let rows = rows
489 .rows
490 .into_iter()
491 .map(|row| {
492 row.values
493 .into_iter()
494 .enumerate()
495 .map(|(idx, v)| {
496 let mut map = Map::new();
497 let value_ref = pb_value_to_value_ref(
498 &v,
499 result_schema[idx].datatype_extension.as_ref(),
500 );
501 let greptime_value: datatypes::value::Value = value_ref.into();
502 let serde_json_value =
503 serde_json::Value::try_from(greptime_value).unwrap();
504 map.insert("value".to_string(), serde_json_value);
505 map.insert("key".to_string(), schema[idx][name_key].clone());
506 map.insert(
507 "semantic_type".to_string(),
508 schema[idx][column_type_key].clone(),
509 );
510 map.insert(
511 "data_type".to_string(),
512 schema[idx][data_type_key].clone(),
513 );
514 JsonValue::Object(map)
515 })
516 .collect()
517 })
518 .collect();
519
520 let mut result = Map::new();
521 result.insert("schema".to_string(), JsonValue::Array(schema));
522 result.insert("rows".to_string(), JsonValue::Array(rows));
523 result.insert("table_name".to_string(), JsonValue::String(table_name));
524 let result = JsonValue::Object(result);
525 Some(result)
526 } else {
527 None
528 }
529 })
530 .collect();
531 Ok(Json(JsonValue::Array(results)).into_response())
532}
533
534#[derive(Debug, Default, Serialize, Deserialize)]
540pub struct PipelineDryrunParams {
541 pub pipeline_name: Option<String>,
542 pub pipeline_version: Option<String>,
543 pub pipeline: Option<String>,
544 pub data_type: Option<String>,
545 pub data: String,
546}
547
548fn check_pipeline_dryrun_params_valid(payload: &Bytes) -> Option<PipelineDryrunParams> {
552 match serde_json::from_slice::<PipelineDryrunParams>(payload) {
553 Ok(params) if params.pipeline.is_some() || params.pipeline_name.is_some() => Some(params),
555 Ok(_) => None,
557 Err(_) => None,
559 }
560}
561
562fn check_pipeline_name_exists(pipeline_name: Option<String>) -> Result<String> {
564 pipeline_name.context(InvalidParameterSnafu {
565 reason: "pipeline_name is required",
566 })
567}
568
569fn check_data_valid(data_len: usize) -> Result<()> {
571 ensure!(
572 data_len <= 10,
573 InvalidParameterSnafu {
574 reason: "data is required",
575 }
576 );
577 Ok(())
578}
579
580fn add_step_info_for_pipeline_dryrun_error(step_msg: &str, e: Error) -> Response {
581 let body = Json(json!({
582 "error": format!("{}: {}", step_msg,e.output_msg()),
583 }));
584
585 (status_code_to_http_status(&e.status_code()), body).into_response()
586}
587
588fn parse_dryrun_data(data_type: String, data: String) -> Result<Vec<VrlValue>> {
592 if let Ok(content_type) = ContentType::from_str(&data_type) {
593 extract_pipeline_value_by_content_type(content_type, Bytes::from(data), false)
594 } else {
595 InvalidParameterSnafu {
596 reason: format!(
597 "invalid content type: {}, expected: one of {}",
598 data_type,
599 EventPayloadResolver::support_content_type_list().join(", ")
600 ),
601 }
602 .fail()
603 }
604}
605
606#[axum_macros::debug_handler]
607pub async fn pipeline_dryrun(
608 State(log_state): State<LogState>,
609 Query(query_params): Query<LogIngesterQueryParams>,
610 Extension(mut query_ctx): Extension<QueryContext>,
611 TypedHeader(content_type): TypedHeader<ContentType>,
612 payload: Bytes,
613) -> Result<Response> {
614 let handler = log_state.log_handler;
615
616 query_ctx.set_channel(Channel::Log);
617 let query_ctx = Arc::new(query_ctx);
618 handler.check_pipeline_query_permission(&query_ctx)?;
619
620 match check_pipeline_dryrun_params_valid(&payload) {
621 Some(params) => {
622 let data = parse_dryrun_data(
623 params.data_type.unwrap_or("application/json".to_string()),
624 params.data,
625 )?;
626
627 check_data_valid(data.len())?;
628
629 match params.pipeline {
630 None => {
631 let version = to_pipeline_version(params.pipeline_version.as_deref())
632 .context(PipelineSnafu)?;
633 let pipeline_name = check_pipeline_name_exists(params.pipeline_name)?;
634 let pipeline = handler
635 .get_pipeline(&pipeline_name, version, query_ctx.clone())
636 .await?;
637 dryrun_pipeline_inner(data, pipeline, handler, &query_ctx).await
638 }
639 Some(pipeline) => {
640 let pipeline = handler.build_pipeline(&pipeline);
641 match pipeline {
642 Ok(pipeline) => {
643 match dryrun_pipeline_inner(
644 data,
645 Arc::new(pipeline),
646 handler,
647 &query_ctx,
648 )
649 .await
650 {
651 Ok(response) => Ok(response),
652 Err(e) => Ok(add_step_info_for_pipeline_dryrun_error(
653 "Failed to exec pipeline",
654 e,
655 )),
656 }
657 }
658 Err(e) => Ok(add_step_info_for_pipeline_dryrun_error(
659 "Failed to build pipeline",
660 e,
661 )),
662 }
663 }
664 }
665 }
666 None => {
667 let pipeline_name = check_pipeline_name_exists(query_params.pipeline_name)?;
671
672 let version =
673 to_pipeline_version(query_params.version.as_deref()).context(PipelineSnafu)?;
674
675 let ignore_errors = query_params.ignore_errors.unwrap_or(false);
676
677 let value =
678 extract_pipeline_value_by_content_type(content_type, payload, ignore_errors)?;
679
680 check_data_valid(value.len())?;
681
682 let pipeline = handler
683 .get_pipeline(&pipeline_name, version, query_ctx.clone())
684 .await?;
685
686 dryrun_pipeline_inner(value, pipeline, handler, &query_ctx).await
687 }
688 }
689}
690
691pub(crate) fn extract_pipeline_params_map_from_headers(
692 headers: &HeaderMap,
693) -> ahash::HashMap<String, String> {
694 GreptimePipelineParams::parse_header_str_to_map(
695 headers
696 .get(GREPTIME_PIPELINE_PARAMS_HEADER)
697 .and_then(|v| v.to_str().ok()),
698 )
699}
700
701fn pipeline_name_from_headers(headers: &HeaderMap) -> Option<String> {
709 [
710 GREPTIME_PIPELINE_NAME_HEADER_NAME,
711 GREPTIME_LOG_PIPELINE_NAME_HEADER_NAME,
712 ]
713 .iter()
714 .find_map(|name| {
715 headers
716 .get(*name)
717 .and_then(|value| value.to_str().ok())
718 .map(str::trim)
719 .filter(|value| !value.is_empty())
720 .map(ToString::to_string)
721 })
722}
723
724#[axum_macros::debug_handler]
725pub async fn log_ingester(
726 State(log_state): State<LogState>,
727 Query(query_params): Query<LogIngesterQueryParams>,
728 Extension(mut query_ctx): Extension<QueryContext>,
729 TypedHeader(content_type): TypedHeader<ContentType>,
730 headers: HeaderMap,
731 payload: Bytes,
732) -> Result<HttpResponse> {
733 let source = query_params.source.as_deref();
735 let response = match &log_state.log_validator {
736 Some(validator) => validator.validate(source, &payload).await,
737 None => None,
738 };
739 if let Some(response) = response {
740 return response;
741 }
742
743 let handler = log_state.log_handler;
744
745 let table_name = query_params.table.context(InvalidParameterSnafu {
746 reason: "table is required",
747 })?;
748
749 let ignore_errors = query_params.ignore_errors.unwrap_or(false);
750
751 let pipeline_name = pipeline_name_from_headers(&headers)
755 .or(query_params.pipeline_name)
756 .context(InvalidParameterSnafu {
757 reason: "pipeline_name is required",
758 })?;
759 let skip_error = query_params.skip_error.unwrap_or(false);
760 let version = to_pipeline_version(query_params.version.as_deref()).context(PipelineSnafu)?;
761 let pipeline = PipelineDefinition::from_name(
762 &pipeline_name,
763 version,
764 query_params.custom_time_index.map(|s| (s, ignore_errors)),
765 )
766 .context(PipelineSnafu)?;
767
768 let value = extract_pipeline_value_by_content_type(content_type, payload, ignore_errors)?;
769
770 query_ctx.set_channel(Channel::Log);
771 let query_ctx = Arc::new(query_ctx);
772
773 let value = log_state
774 .ingest_interceptor
775 .as_ref()
776 .pre_pipeline(value, query_ctx.clone())?;
777
778 let mut pipeline_params_map = extract_pipeline_params_map_from_headers(&headers);
779 if !pipeline_params_map.contains_key(GREPTIME_PIPELINE_SKIP_ERROR_KEY) && skip_error {
780 pipeline_params_map.insert(GREPTIME_PIPELINE_SKIP_ERROR_KEY.to_string(), "true".into());
781 }
782 let pipeline_params = GreptimePipelineParams::from_map(pipeline_params_map);
783
784 ingest_logs_inner(
785 handler,
786 pipeline,
787 vec![PipelineIngestRequest {
788 table: table_name,
789 values: value,
790 }],
791 query_ctx,
792 pipeline_params,
793 )
794 .await
795}
796
797#[derive(Debug, EnumIter)]
798enum EventPayloadResolverInner {
799 Json,
800 Ndjson,
801 Text,
802}
803
804impl Display for EventPayloadResolverInner {
805 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
806 match self {
807 EventPayloadResolverInner::Json => write!(f, "{}", *JSON_CONTENT_TYPE),
808 EventPayloadResolverInner::Ndjson => write!(f, "{}", *NDJSON_CONTENT_TYPE),
809 EventPayloadResolverInner::Text => write!(f, "{}", *TEXT_CONTENT_TYPE),
810 }
811 }
812}
813
814impl TryFrom<&ContentType> for EventPayloadResolverInner {
815 type Error = Error;
816
817 fn try_from(content_type: &ContentType) -> Result<Self> {
818 let mime: mime_guess::Mime = content_type.clone().into();
819 match (mime.type_(), mime.subtype()) {
820 (mime::APPLICATION, mime::JSON) => Ok(EventPayloadResolverInner::Json),
821 (mime::APPLICATION, subtype) if subtype == CONTENT_TYPE_NDJSON_SUBTYPE_STR => {
822 Ok(EventPayloadResolverInner::Ndjson)
823 }
824 (mime::TEXT, mime::PLAIN) => Ok(EventPayloadResolverInner::Text),
825 _ => InvalidParameterSnafu {
826 reason: format!(
827 "invalid content type: {}, expected: one of {}",
828 content_type,
829 EventPayloadResolver::support_content_type_list().join(", ")
830 ),
831 }
832 .fail(),
833 }
834 }
835}
836
837#[derive(Debug)]
838struct EventPayloadResolver<'a> {
839 inner: EventPayloadResolverInner,
840 #[allow(dead_code)]
843 content_type: &'a ContentType,
844}
845
846impl EventPayloadResolver<'_> {
847 pub(super) fn support_content_type_list() -> Vec<String> {
848 EventPayloadResolverInner::iter()
849 .map(|x| x.to_string())
850 .collect()
851 }
852}
853
854impl<'a> TryFrom<&'a ContentType> for EventPayloadResolver<'a> {
855 type Error = Error;
856
857 fn try_from(content_type: &'a ContentType) -> Result<Self> {
858 let inner = EventPayloadResolverInner::try_from(content_type)?;
859 Ok(EventPayloadResolver {
860 inner,
861 content_type,
862 })
863 }
864}
865
866impl EventPayloadResolver<'_> {
867 fn parse_payload(&self, payload: Bytes, ignore_errors: bool) -> Result<Vec<VrlValue>> {
868 match self.inner {
869 EventPayloadResolverInner::Json => transform_ndjson_array_factory(
870 Deserializer::from_slice(&payload).into_iter(),
871 ignore_errors,
872 ),
873 EventPayloadResolverInner::Ndjson => {
874 let mut result = Vec::with_capacity(1000);
875 let mut buffer = Buffers::new(1000);
876 for (index, line) in payload.lines().enumerate() {
877 let mut line = match line {
878 Ok(line) if !line.is_empty() => line,
879 Ok(_) => continue, Err(_) if ignore_errors => continue,
881 Err(e) => {
882 warn!(e; "invalid string at index: {}", index);
883 return InvalidParameterSnafu {
884 reason: format!("invalid line at index: {}", index),
885 }
886 .fail();
887 }
888 };
889
890 if let Ok(v) = simd_json::serde::from_slice_with_buffers(
893 unsafe { line.as_bytes_mut() },
894 &mut buffer,
895 ) {
896 result.push(v);
897 } else if !ignore_errors {
898 warn!("invalid JSON at index: {}, content: {:?}", index, line);
899 return InvalidParameterSnafu {
900 reason: format!("invalid JSON at index: {}", index),
901 }
902 .fail();
903 }
904 }
905 Ok(result)
906 }
907 EventPayloadResolverInner::Text => {
908 let result = payload
909 .lines()
910 .filter_map(|line| line.ok().filter(|line| !line.is_empty()))
911 .map(|line| {
912 let mut map = BTreeMap::new();
913 map.insert(
914 KeyString::from("message"),
915 VrlValue::Bytes(Bytes::from(line)),
916 );
917 VrlValue::Object(map)
918 })
919 .collect::<Vec<_>>();
920 Ok(result)
921 }
922 }
923 }
924}
925
926fn extract_pipeline_value_by_content_type(
927 content_type: ContentType,
928 payload: Bytes,
929 ignore_errors: bool,
930) -> Result<Vec<VrlValue>> {
931 EventPayloadResolver::try_from(&content_type).and_then(|resolver| {
932 resolver
933 .parse_payload(payload, ignore_errors)
934 .map_err(|e| match &e {
935 Error::InvalidParameter { reason, .. } if content_type == *JSON_CONTENT_TYPE => {
936 if reason.contains("invalid item:") {
937 InvalidParameterSnafu {
938 reason: "json format error, please check the date is valid JSON.",
939 }
940 .build()
941 } else {
942 e
943 }
944 }
945 _ => e,
946 })
947 })
948}
949
950pub(crate) async fn ingest_logs_inner(
951 handler: PipelineHandlerRef,
952 pipeline: PipelineDefinition,
953 log_ingest_requests: Vec<PipelineIngestRequest>,
954 query_ctx: QueryContextRef,
955 pipeline_params: GreptimePipelineParams,
956) -> Result<HttpResponse> {
957 let exec_timer = Instant::now();
960 let mut req = ContextReq::default();
961
962 let pipeline_ctx = PipelineContext::new(&pipeline, &pipeline_params, query_ctx.channel());
963 for pipeline_req in log_ingest_requests {
964 let requests =
965 run_pipeline(&handler, &pipeline_ctx, pipeline_req, &query_ctx, true).await?;
966
967 req.merge(requests);
968 }
969
970 execute_log_context_req(
971 handler,
972 req,
973 query_ctx,
974 exec_timer,
975 &METRIC_HTTP_LOGS_INGESTION_COUNTER,
976 &METRIC_HTTP_LOGS_INGESTION_ELAPSED,
977 )
978 .await
979}
980
981pub(crate) async fn execute_log_context_req(
982 handler: PipelineHandlerRef,
983 ctx_req: ContextReq,
984 query_ctx: QueryContextRef,
985 exec_timer: Instant,
986 counter: &IntCounterVec,
987 elapsed: &HistogramVec,
988) -> Result<HttpResponse> {
989 let db = query_ctx.get_db_string();
990
991 let mut total_rows: u64 = 0;
992 let mut fail = false;
993 let batches = ctx_req.as_req_iter(query_ctx).collect::<Vec<_>>();
994 let outputs = handler.insert_all(batches).await?;
995 for output in &outputs {
996 if let Ok(Output {
997 data: OutputData::AffectedRows(rows),
998 meta: _,
999 }) = &output
1000 {
1001 total_rows += *rows as u64;
1002 } else {
1003 fail = true;
1004 }
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}