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