@@ -29,8 +29,12 @@ use crate::event::error::EventError;
29
29
use crate :: event:: format:: known_schema:: { self , KNOWN_SCHEMA_LIST } ;
30
30
use crate :: event:: format:: { self , EventFormat , LogSource , LogSourceEntry } ;
31
31
use crate :: event:: { self , FORMAT_KEY , USER_AGENT_KEY } ;
32
+ use crate :: handlers:: http:: MAX_EVENT_PAYLOAD_SIZE ;
32
33
use crate :: handlers:: http:: modal:: utils:: ingest_utils:: push_logs;
33
- use crate :: handlers:: { EXTRACT_LOG_KEY , LOG_SOURCE_KEY , STREAM_NAME_HEADER_KEY } ;
34
+ use crate :: handlers:: {
35
+ CONTENT_TYPE_JSON , CONTENT_TYPE_PROTOBUF , EXTRACT_LOG_KEY , LOG_SOURCE_KEY ,
36
+ STREAM_NAME_HEADER_KEY ,
37
+ } ;
34
38
use crate :: metadata:: SchemaVersion ;
35
39
use crate :: option:: Mode ;
36
40
use crate :: otel:: logs:: { OTEL_LOG_KNOWN_FIELD_LIST , flatten_otel_protobuf} ;
@@ -157,34 +161,32 @@ pub async fn ingest_internal_stream(stream_name: String, body: Bytes) -> Result<
157
161
Ok ( ( ) )
158
162
}
159
163
160
- // Handler for POST /v1/logs to ingest OTEL logs
161
- // ingests events by extracting stream name from header
162
- // creates if stream does not exist
163
- pub async fn handle_otel_logs_ingestion (
164
- req : HttpRequest ,
165
- body : web:: Bytes ,
166
- ) -> Result < HttpResponse , PostError > {
164
+ // Common validation and setup for OTEL ingestion
165
+ async fn setup_otel_stream (
166
+ req : & HttpRequest ,
167
+ expected_log_source : LogSource ,
168
+ known_fields : & [ & str ] ,
169
+ ) -> Result < ( String , LogSource , LogSourceEntry ) , PostError > {
167
170
let Some ( stream_name) = req. headers ( ) . get ( STREAM_NAME_HEADER_KEY ) else {
168
171
return Err ( PostError :: Header ( ParseHeaderError :: MissingStreamName ) ) ;
169
172
} ;
170
173
171
174
let Some ( log_source) = req. headers ( ) . get ( LOG_SOURCE_KEY ) else {
172
175
return Err ( PostError :: Header ( ParseHeaderError :: MissingLogSource ) ) ;
173
176
} ;
177
+
174
178
let log_source = LogSource :: from ( log_source. to_str ( ) . unwrap ( ) ) ;
175
- if log_source != LogSource :: OtelLogs {
176
- return Err ( PostError :: IncorrectLogSource ( LogSource :: OtelLogs ) ) ;
179
+ if log_source != expected_log_source {
180
+ return Err ( PostError :: IncorrectLogSource ( expected_log_source ) ) ;
177
181
}
178
182
179
183
let stream_name = stream_name. to_str ( ) . unwrap ( ) . to_owned ( ) ;
180
184
181
185
let log_source_entry = LogSourceEntry :: new (
182
186
log_source. clone ( ) ,
183
- OTEL_LOG_KNOWN_FIELD_LIST
184
- . iter ( )
185
- . map ( |& s| s. to_string ( ) )
186
- . collect ( ) ,
187
+ known_fields. iter ( ) . map ( |& s| s. to_string ( ) ) . collect ( ) ,
187
188
) ;
189
+
188
190
PARSEABLE
189
191
. create_stream_if_not_exists (
190
192
& stream_name,
@@ -194,49 +196,83 @@ pub async fn handle_otel_logs_ingestion(
194
196
)
195
197
. await ?;
196
198
197
- //if stream exists, fetch the stream log source
198
- //return error if the stream log source is otel traces or otel metrics
199
+ // Validate stream compatibility
199
200
if let Ok ( stream) = PARSEABLE . get_stream ( & stream_name) {
200
- stream
201
- . get_log_source ( )
202
- . iter ( )
203
- . find ( |& stream_log_source_entry| {
204
- stream_log_source_entry. log_source_format != LogSource :: OtelTraces
205
- && stream_log_source_entry. log_source_format != LogSource :: OtelMetrics
206
- } )
207
- . ok_or ( PostError :: IncorrectLogFormat ( stream_name. clone ( ) ) ) ?;
201
+ match log_source {
202
+ LogSource :: OtelLogs => {
203
+ // For logs, reject if stream is metrics or traces
204
+ stream
205
+ . get_log_source ( )
206
+ . iter ( )
207
+ . find ( |& stream_log_source_entry| {
208
+ stream_log_source_entry. log_source_format != LogSource :: OtelTraces
209
+ && stream_log_source_entry. log_source_format != LogSource :: OtelMetrics
210
+ } )
211
+ . ok_or ( PostError :: IncorrectLogFormat ( stream_name. clone ( ) ) ) ?;
212
+ }
213
+ LogSource :: OtelMetrics | LogSource :: OtelTraces => {
214
+ // For metrics/traces, only allow same type
215
+ stream
216
+ . get_log_source ( )
217
+ . iter ( )
218
+ . find ( |& stream_log_source_entry| {
219
+ stream_log_source_entry. log_source_format == log_source
220
+ } )
221
+ . ok_or ( PostError :: IncorrectLogFormat ( stream_name. clone ( ) ) ) ?;
222
+ }
223
+ _ => { }
224
+ }
208
225
}
209
226
210
227
PARSEABLE
211
- . add_update_log_source ( & stream_name, log_source_entry)
228
+ . add_update_log_source ( & stream_name, log_source_entry. clone ( ) )
212
229
. await ?;
213
230
214
- let p_custom_fields = get_custom_fields_from_header ( & req) ;
215
- match req. headers ( ) . get ( "Content-Type" ) {
231
+ Ok ( ( stream_name, log_source, log_source_entry) )
232
+ }
233
+
234
+ // Common content processing for OTEL ingestion
235
+ async fn process_otel_content < T , F > (
236
+ req : & HttpRequest ,
237
+ body : web:: Bytes ,
238
+ stream_name : & str ,
239
+ log_source : & LogSource ,
240
+ decode_protobuf : F ,
241
+ flatten_protobuf : fn ( & T ) -> Vec < serde_json:: Value > ,
242
+ ) -> Result < ( ) , PostError >
243
+ where
244
+ T : prost:: Message + Default ,
245
+ F : FnOnce ( web:: Bytes ) -> Result < T , prost:: DecodeError > ,
246
+ {
247
+ let p_custom_fields = get_custom_fields_from_header ( req) ;
248
+
249
+ match req
250
+ . headers ( )
251
+ . get ( "Content-Type" )
252
+ . and_then ( |h| h. to_str ( ) . ok ( ) )
253
+ {
216
254
Some ( content_type) => {
217
- if content_type == "application/json" {
255
+ if content_type == CONTENT_TYPE_JSON {
218
256
flatten_and_push_logs (
219
257
serde_json:: from_slice ( & body) ?,
220
- & stream_name,
221
- & log_source,
258
+ stream_name,
259
+ log_source,
222
260
& p_custom_fields,
223
261
)
224
262
. await ?;
225
- }
226
-
227
- if content_type == "application/x-protobuf" {
228
- const MAX_PROTOBUF_SIZE : usize = 10 * 1024 * 1024 ; // 10MB limit
229
- if body. len ( ) > MAX_PROTOBUF_SIZE {
263
+ } else if content_type == CONTENT_TYPE_PROTOBUF {
264
+ // 10MB limit
265
+ if body. len ( ) > MAX_EVENT_PAYLOAD_SIZE {
230
266
return Err ( PostError :: Invalid ( anyhow:: anyhow!(
231
267
"Protobuf message size {} exceeds maximum allowed size of {} bytes" ,
232
268
body. len( ) ,
233
- MAX_PROTOBUF_SIZE
269
+ MAX_EVENT_PAYLOAD_SIZE
234
270
) ) ) ;
235
271
}
236
- match ExportLogsServiceRequest :: decode ( body) {
237
- Ok ( json ) => {
238
- for record in flatten_otel_protobuf ( & json ) {
239
- push_logs ( & stream_name, record, & log_source, & p_custom_fields) . await ?;
272
+ match decode_protobuf ( body) {
273
+ Ok ( decoded ) => {
274
+ for record in flatten_protobuf ( & decoded ) {
275
+ push_logs ( stream_name, record, log_source, & p_custom_fields) . await ?;
240
276
}
241
277
}
242
278
Err ( e) => {
@@ -253,6 +289,29 @@ pub async fn handle_otel_logs_ingestion(
253
289
}
254
290
}
255
291
292
+ Ok ( ( ) )
293
+ }
294
+
295
+ // Handler for POST /v1/logs to ingest OTEL logs
296
+ // ingests events by extracting stream name from header
297
+ // creates if stream does not exist
298
+ pub async fn handle_otel_logs_ingestion (
299
+ req : HttpRequest ,
300
+ body : web:: Bytes ,
301
+ ) -> Result < HttpResponse , PostError > {
302
+ let ( stream_name, log_source, _) =
303
+ setup_otel_stream ( & req, LogSource :: OtelLogs , & OTEL_LOG_KNOWN_FIELD_LIST ) . await ?;
304
+
305
+ process_otel_content (
306
+ & req,
307
+ body,
308
+ & stream_name,
309
+ & log_source,
310
+ ExportLogsServiceRequest :: decode,
311
+ flatten_otel_protobuf,
312
+ )
313
+ . await ?;
314
+
256
315
Ok ( HttpResponse :: Ok ( ) . finish ( ) )
257
316
}
258
317
@@ -263,82 +322,18 @@ pub async fn handle_otel_metrics_ingestion(
263
322
req : HttpRequest ,
264
323
body : web:: Bytes ,
265
324
) -> Result < HttpResponse , PostError > {
266
- let Some ( stream_name) = req. headers ( ) . get ( STREAM_NAME_HEADER_KEY ) else {
267
- return Err ( PostError :: Header ( ParseHeaderError :: MissingStreamName ) ) ;
268
- } ;
269
- let Some ( log_source) = req. headers ( ) . get ( LOG_SOURCE_KEY ) else {
270
- return Err ( PostError :: Header ( ParseHeaderError :: MissingLogSource ) ) ;
271
- } ;
272
- let log_source = LogSource :: from ( log_source. to_str ( ) . unwrap ( ) ) ;
273
- if log_source != LogSource :: OtelMetrics {
274
- return Err ( PostError :: IncorrectLogSource ( LogSource :: OtelMetrics ) ) ;
275
- }
276
-
277
- let stream_name = stream_name. to_str ( ) . unwrap ( ) . to_owned ( ) ;
278
-
279
- let log_source_entry = LogSourceEntry :: new (
280
- log_source. clone ( ) ,
281
- OTEL_METRICS_KNOWN_FIELD_LIST
282
- . iter ( )
283
- . map ( |& s| s. to_string ( ) )
284
- . collect ( ) ,
285
- ) ;
286
- PARSEABLE
287
- . create_stream_if_not_exists (
288
- & stream_name,
289
- StreamType :: UserDefined ,
290
- None ,
291
- vec ! [ log_source_entry. clone( ) ] ,
292
- )
293
- . await ?;
294
-
295
- //if stream exists, fetch the stream log source
296
- //return error if the stream log source is not otel metrics
297
- if let Ok ( stream) = PARSEABLE . get_stream ( & stream_name) {
298
- stream
299
- . get_log_source ( )
300
- . iter ( )
301
- . find ( |& stream_log_source_entry| {
302
- stream_log_source_entry. log_source_format == log_source. clone ( )
303
- } )
304
- . ok_or ( PostError :: IncorrectLogFormat ( stream_name. clone ( ) ) ) ?;
305
- }
306
-
307
- PARSEABLE
308
- . add_update_log_source ( & stream_name, log_source_entry)
309
- . await ?;
310
-
311
- let p_custom_fields = get_custom_fields_from_header ( & req) ;
312
-
313
- match req. headers ( ) . get ( "Content-Type" ) {
314
- Some ( content_type) => {
315
- if content_type == "application/json" {
316
- flatten_and_push_logs (
317
- serde_json:: from_slice ( & body) ?,
318
- & stream_name,
319
- & log_source,
320
- & p_custom_fields,
321
- )
322
- . await ?;
323
- }
324
-
325
- if content_type == "application/x-protobuf" {
326
- match ExportMetricsServiceRequest :: decode ( body) {
327
- Ok ( json) => {
328
- for record in flatten_otel_metrics_protobuf ( & json) {
329
- push_logs ( & stream_name, record, & log_source, & p_custom_fields) . await ?;
330
- }
331
- }
332
- Err ( e) => {
333
- return Err ( PostError :: Invalid ( e. into ( ) ) ) ;
334
- }
335
- }
336
- }
337
- }
338
- None => {
339
- return Err ( PostError :: Header ( ParseHeaderError :: InvalidValue ) ) ;
340
- }
341
- }
325
+ let ( stream_name, log_source, _) =
326
+ setup_otel_stream ( & req, LogSource :: OtelMetrics , & OTEL_METRICS_KNOWN_FIELD_LIST ) . await ?;
327
+
328
+ process_otel_content (
329
+ & req,
330
+ body,
331
+ & stream_name,
332
+ & log_source,
333
+ ExportMetricsServiceRequest :: decode,
334
+ flatten_otel_metrics_protobuf,
335
+ )
336
+ . await ?;
342
337
343
338
Ok ( HttpResponse :: Ok ( ) . finish ( ) )
344
339
}
@@ -350,83 +345,18 @@ pub async fn handle_otel_traces_ingestion(
350
345
req : HttpRequest ,
351
346
body : web:: Bytes ,
352
347
) -> Result < HttpResponse , PostError > {
353
- let Some ( stream_name) = req. headers ( ) . get ( STREAM_NAME_HEADER_KEY ) else {
354
- return Err ( PostError :: Header ( ParseHeaderError :: MissingStreamName ) ) ;
355
- } ;
356
-
357
- let Some ( log_source) = req. headers ( ) . get ( LOG_SOURCE_KEY ) else {
358
- return Err ( PostError :: Header ( ParseHeaderError :: MissingLogSource ) ) ;
359
- } ;
360
- let log_source = LogSource :: from ( log_source. to_str ( ) . unwrap ( ) ) ;
361
- if log_source != LogSource :: OtelTraces {
362
- return Err ( PostError :: IncorrectLogSource ( LogSource :: OtelTraces ) ) ;
363
- }
364
- let stream_name = stream_name. to_str ( ) . unwrap ( ) . to_owned ( ) ;
365
-
366
- let log_source_entry = LogSourceEntry :: new (
367
- log_source. clone ( ) ,
368
- OTEL_TRACES_KNOWN_FIELD_LIST
369
- . iter ( )
370
- . map ( |& s| s. to_string ( ) )
371
- . collect ( ) ,
372
- ) ;
373
-
374
- PARSEABLE
375
- . create_stream_if_not_exists (
376
- & stream_name,
377
- StreamType :: UserDefined ,
378
- None ,
379
- vec ! [ log_source_entry. clone( ) ] ,
380
- )
381
- . await ?;
382
-
383
- //if stream exists, fetch the stream log source
384
- //return error if the stream log source is not otel traces
385
- if let Ok ( stream) = PARSEABLE . get_stream ( & stream_name) {
386
- stream
387
- . get_log_source ( )
388
- . iter ( )
389
- . find ( |& stream_log_source_entry| {
390
- stream_log_source_entry. log_source_format == log_source. clone ( )
391
- } )
392
- . ok_or ( PostError :: IncorrectLogFormat ( stream_name. clone ( ) ) ) ?;
393
- }
394
-
395
- PARSEABLE
396
- . add_update_log_source ( & stream_name, log_source_entry)
397
- . await ?;
398
-
399
- let p_custom_fields = get_custom_fields_from_header ( & req) ;
400
-
401
- match req. headers ( ) . get ( "Content-Type" ) {
402
- Some ( content_type) => {
403
- if content_type == "application/json" {
404
- flatten_and_push_logs (
405
- serde_json:: from_slice ( & body) ?,
406
- & stream_name,
407
- & log_source,
408
- & p_custom_fields,
409
- )
410
- . await ?;
411
- }
412
-
413
- if content_type == "application/x-protobuf" {
414
- match ExportTraceServiceRequest :: decode ( body) {
415
- Ok ( json) => {
416
- for record in flatten_otel_traces_protobuf ( & json) {
417
- push_logs ( & stream_name, record, & log_source, & p_custom_fields) . await ?;
418
- }
419
- }
420
- Err ( e) => {
421
- return Err ( PostError :: Invalid ( e. into ( ) ) ) ;
422
- }
423
- }
424
- }
425
- }
426
- None => {
427
- return Err ( PostError :: Header ( ParseHeaderError :: InvalidValue ) ) ;
428
- }
429
- }
348
+ let ( stream_name, log_source, _) =
349
+ setup_otel_stream ( & req, LogSource :: OtelTraces , & OTEL_TRACES_KNOWN_FIELD_LIST ) . await ?;
350
+
351
+ process_otel_content (
352
+ & req,
353
+ body,
354
+ & stream_name,
355
+ & log_source,
356
+ ExportTraceServiceRequest :: decode,
357
+ flatten_otel_traces_protobuf,
358
+ )
359
+ . await ?;
430
360
431
361
Ok ( HttpResponse :: Ok ( ) . finish ( ) )
432
362
}
0 commit comments