@@ -24,16 +24,19 @@ use actix_web::{HttpRequest, HttpResponse, http::header::ContentType};
2424use arrow_array:: RecordBatch ;
2525use bytes:: Bytes ;
2626use chrono:: Utc ;
27+ use tokio:: sync:: oneshot;
2728use tracing:: error;
2829
2930use crate :: event:: error:: EventError ;
3031use crate :: event:: format:: known_schema:: { self , KNOWN_SCHEMA_LIST } ;
3132use crate :: event:: format:: { self , EventFormat , LogSource , LogSourceEntry } ;
3233use crate :: event:: { self , FORMAT_KEY , USER_AGENT_KEY } ;
33- use crate :: handlers:: http:: modal:: utils:: ingest_utils:: validate_stream_for_ingestion;
34+ use crate :: handlers:: http:: modal:: utils:: ingest_utils:: {
35+ ingest_helper, process_otel_content, validate_stream_for_ingestion,
36+ } ;
3437use crate :: handlers:: {
35- CONTENT_TYPE_JSON , CONTENT_TYPE_PROTOBUF , DatasetTag , EXTRACT_LOG_KEY , LOG_SOURCE_KEY ,
36- STREAM_NAME_HEADER_KEY , TELEMETRY_TYPE_KEY , TelemetryType ,
38+ DatasetTag , EXTRACT_LOG_KEY , LOG_SOURCE_KEY , STREAM_NAME_HEADER_KEY , TELEMETRY_TYPE_KEY ,
39+ TelemetryType ,
3740} ;
3841use crate :: metadata:: SchemaVersion ;
3942use crate :: metastore:: MetastoreError ;
@@ -81,10 +84,11 @@ pub async fn ingest(
8184 . and_then ( |h| h. to_str ( ) . ok ( ) )
8285 . map_or ( TelemetryType :: default ( ) , TelemetryType :: from) ;
8386
84- let extract_log = req
85- . headers ( )
86- . get ( EXTRACT_LOG_KEY )
87- . and_then ( |h| h. to_str ( ) . ok ( ) ) ;
87+ let extract_log = req. headers ( ) . get ( EXTRACT_LOG_KEY ) . and_then ( |h| {
88+ h. to_str ( )
89+ . ok ( )
90+ . map_or_else ( || None , |h| Some ( h. to_string ( ) ) )
91+ } ) ;
8892
8993 if matches ! (
9094 log_source,
@@ -97,15 +101,14 @@ pub async fn ingest(
97101 }
98102
99103 let mut p_custom_fields = get_custom_fields_from_header ( & req) ;
100-
101104 let mut json = json. into_inner ( ) ;
102105
103106 let fields = match & log_source {
104107 LogSource :: Custom ( src) => KNOWN_SCHEMA_LIST . extract_from_inline_log (
105108 & mut json,
106109 & mut p_custom_fields,
107110 src,
108- extract_log,
111+ extract_log. as_deref ( ) ,
109112 ) ?,
110113 _ => HashSet :: new ( ) ,
111114 } ;
@@ -129,12 +132,13 @@ pub async fn ingest(
129132 e
130133 } ) ?;
131134
132- //if stream exists, fetch the stream log source
133- //return error if the stream log source is otel traces or otel metrics or otel logs
135+ // if stream exists, fetch the stream log source
136+ // return error if the stream log source is otel traces or otel metrics
134137 validate_stream_for_ingestion ( & stream_name, & log_source, & tenant_id) . map_err ( |e| {
135138 error ! ( "Ingestion failed for stream {stream_name}: {e}" ) ;
136139 e
137140 } ) ?;
141+ let stream = PARSEABLE . get_stream ( & stream_name, & tenant_id) ?;
138142
139143 PARSEABLE
140144 . add_update_log_source ( & stream_name, log_source_entry, & tenant_id)
@@ -144,22 +148,27 @@ pub async fn ingest(
144148 e
145149 } ) ?;
146150
147- if let Err ( e) = flatten_and_push_logs (
148- json,
149- & stream_name,
150- & log_source,
151- & p_custom_fields,
152- None ,
153- telemetry_type,
154- & tenant_id,
155- )
156- . await
157- {
158- error ! ( "Ingestion failed for stream {stream_name}: {e}" ) ;
159- return Err ( e) ;
160- }
151+ let time_partition = stream. get_time_partition ( ) ;
161152
162- Ok ( HttpResponse :: Ok ( ) . finish ( ) )
153+ let ( s, r) = oneshot:: channel ( ) ;
154+ rayon:: spawn ( move || {
155+ let res = ingest_helper (
156+ stream_name,
157+ tenant_id,
158+ log_source,
159+ telemetry_type,
160+ p_custom_fields,
161+ json,
162+ time_partition,
163+ ) ;
164+ let _ = s. send ( res) ;
165+ } ) ;
166+
167+ if let Err ( e) = r. await . map_err ( |e| PostError :: CustomError ( e. to_string ( ) ) ) ? {
168+ Err ( e)
169+ } else {
170+ Ok ( HttpResponse :: Ok ( ) . finish ( ) )
171+ }
163172}
164173
165174pub async fn ingest_internal_stream (
@@ -285,76 +294,6 @@ pub async fn setup_otel_stream(
285294 Ok ( ( stream_name, log_source, log_source_entry, time_partition) )
286295}
287296
288- // Common content processing for OTEL ingestion
289- async fn process_otel_content (
290- req : & HttpRequest ,
291- body : web:: Bytes ,
292- stream_name : & str ,
293- log_source : & LogSource ,
294- telemetry_type : TelemetryType ,
295- ) -> Result < ( ) , PostError > {
296- let p_custom_fields = get_custom_fields_from_header ( req) ;
297-
298- match req
299- . headers ( )
300- . get ( "Content-Type" )
301- . and_then ( |h| h. to_str ( ) . ok ( ) )
302- {
303- Some ( content_type) => {
304- let tenant_id = get_tenant_id_from_request ( req) ;
305- if content_type == CONTENT_TYPE_JSON {
306- let json: serde_json:: Value = match serde_json:: from_slice ( & body) {
307- Ok ( v) => v,
308- Err ( e) => {
309- error ! (
310- "Ingestion failed for stream {stream_name}: malformed JSON in request body"
311- ) ;
312- return Err ( PostError :: SerdeError ( e) ) ;
313- }
314- } ;
315- if let Err ( e) = flatten_and_push_logs (
316- json,
317- stream_name,
318- log_source,
319- & p_custom_fields,
320- None ,
321- telemetry_type,
322- & tenant_id,
323- )
324- . await
325- {
326- error ! ( "Ingestion failed for stream {stream_name}: {e}" ) ;
327- return Err ( e) ;
328- }
329- } else if content_type == CONTENT_TYPE_PROTOBUF {
330- error ! (
331- "Ingestion failed for stream {stream_name}: Protobuf ingestion is not supported in Parseable OSS"
332- ) ;
333- return Err ( PostError :: Invalid ( anyhow:: anyhow!(
334- "Ingestion failed for stream {stream_name}: Protobuf ingestion is not supported in Parseable OSS"
335- ) ) ) ;
336- } else {
337- error ! (
338- "Ingestion failed for stream {stream_name}: Unsupported Content-Type: {content_type}. Expected application/json or application/x-protobuf"
339- ) ;
340- return Err ( PostError :: Invalid ( anyhow:: anyhow!(
341- "Ingestion failed for stream {stream_name}: Unsupported Content-Type: {content_type}. Expected application/json or application/x-protobuf"
342- ) ) ) ;
343- }
344- }
345- None => {
346- error ! (
347- "Ingestion failed for stream {stream_name}: Missing Content-Type header. Expected application/json or application/x-protobuf"
348- ) ;
349- return Err ( PostError :: Invalid ( anyhow:: anyhow!(
350- "Ingestion failed for stream {stream_name}: Missing Content-Type header. Expected application/json or application/x-protobuf"
351- ) ) ) ;
352- }
353- }
354-
355- Ok ( ( ) )
356- }
357-
358297// Handler for POST /v1/logs to ingest OTEL logs
359298// ingests events by extracting stream name from header
360299// creates if stream does not exist
@@ -519,9 +458,7 @@ pub async fn post_event(
519458 None ,
520459 TelemetryType :: Logs ,
521460 & tenant_id,
522- )
523- . await
524- {
461+ ) {
525462 error ! ( "Ingestion failed for stream {stream_name}: {e}" ) ;
526463 return Err ( e) ;
527464 }
0 commit comments