1use std::sync::Arc;
6
7use arrow_array::builder::{
8 FixedSizeBinaryBuilder, Float64Builder, Int32Builder, Int64Builder, StringBuilder,
9 TimestampNanosecondBuilder, UInt64Builder,
10};
11use arrow_array::RecordBatch;
12use arrow_schema::SchemaRef;
13
14use opentelemetry_proto::tonic::collector::logs::v1::ExportLogsServiceRequest;
15use opentelemetry_proto::tonic::collector::metrics::v1::ExportMetricsServiceRequest;
16use opentelemetry_proto::tonic::collector::trace::v1::ExportTraceServiceRequest;
17use opentelemetry_proto::tonic::common::v1::{AnyValue, KeyValue};
18use opentelemetry_proto::tonic::metrics::v1::{metric, number_data_point};
19use opentelemetry_proto::tonic::resource::v1::Resource;
20
21fn fixed_bytes(src: &[u8], len: usize) -> std::borrow::Cow<'_, [u8]> {
24 if src.len() == len {
25 std::borrow::Cow::Borrowed(src)
26 } else {
27 let mut out = vec![0u8; len];
28 let copy_len = src.len().min(len);
29 out[..copy_len].copy_from_slice(&src[..copy_len]);
30 std::borrow::Cow::Owned(out)
31 }
32}
33
34fn is_all_zeros(bytes: &[u8]) -> bool {
36 bytes.iter().all(|&b| b == 0)
37}
38
39fn extract_resource_attr(resource: Option<&Resource>, key: &str) -> Option<String> {
41 resource.and_then(|r| {
42 r.attributes.iter().find_map(|kv| {
43 if kv.key == key {
44 any_value_to_string(kv.value.as_ref())
45 } else {
46 None
47 }
48 })
49 })
50}
51
52fn resource_attributes_json(resource: Option<&Resource>) -> Option<String> {
54 let attrs: Vec<&KeyValue> = resource
55 .map(|r| {
56 r.attributes
57 .iter()
58 .filter(|kv| kv.key != "service.name" && kv.key != "service.version")
59 .collect()
60 })
61 .unwrap_or_default();
62
63 if attrs.is_empty() {
64 return None;
65 }
66
67 Some(key_values_to_json(&attrs))
68}
69
70fn kv_to_json(attrs: &[KeyValue]) -> Option<String> {
72 if attrs.is_empty() {
73 return None;
74 }
75 let refs: Vec<&KeyValue> = attrs.iter().collect();
76 Some(key_values_to_json(&refs))
77}
78
79fn key_values_to_json(kvs: &[&KeyValue]) -> String {
81 let mut buf = String::with_capacity(kvs.len() * 32);
82 buf.push('{');
83 for (i, kv) in kvs.iter().enumerate() {
84 if i > 0 {
85 buf.push(',');
86 }
87 write_json_string(&mut buf, &kv.key);
89 buf.push(':');
90 if let Some(v) = &kv.value {
92 write_any_value_json(&mut buf, v);
93 } else {
94 buf.push_str("null");
95 }
96 }
97 buf.push('}');
98 buf
99}
100
101fn write_json_string(buf: &mut String, s: &str) {
103 buf.push('"');
104 for c in s.chars() {
105 match c {
106 '"' => buf.push_str("\\\""),
107 '\\' => buf.push_str("\\\\"),
108 '\n' => buf.push_str("\\n"),
109 '\r' => buf.push_str("\\r"),
110 '\t' => buf.push_str("\\t"),
111 c if c.is_control() => {
112 use std::fmt::Write;
113 let _ = write!(buf, "\\u{:04x}", c as u32);
114 }
115 c => buf.push(c),
116 }
117 }
118 buf.push('"');
119}
120
121const MAX_JSON_DEPTH: usize = 32;
124
125fn write_any_value_json(buf: &mut String, v: &AnyValue) {
127 write_any_value_json_depth(buf, v, 0);
128}
129
130fn write_any_value_json_depth(buf: &mut String, v: &AnyValue, depth: usize) {
131 use opentelemetry_proto::tonic::common::v1::any_value::Value;
132 if depth >= MAX_JSON_DEPTH {
133 buf.push_str("null");
134 return;
135 }
136 match &v.value {
137 Some(Value::StringValue(s)) => write_json_string(buf, s),
138 Some(Value::BoolValue(b)) => buf.push_str(if *b { "true" } else { "false" }),
139 Some(Value::IntValue(i)) => {
140 use std::fmt::Write;
141 let _ = write!(buf, "{i}");
142 }
143 Some(Value::DoubleValue(d)) => {
144 if d.is_finite() {
145 use std::fmt::Write;
146 let _ = write!(buf, "{d}");
147 } else {
148 buf.push_str("null");
149 }
150 }
151 Some(Value::ArrayValue(arr)) => {
152 buf.push('[');
153 for (i, val) in arr.values.iter().enumerate() {
154 if i > 0 {
155 buf.push(',');
156 }
157 write_any_value_json_depth(buf, val, depth + 1);
158 }
159 buf.push(']');
160 }
161 Some(Value::KvlistValue(kvl)) => {
162 buf.push('{');
163 for (i, kv) in kvl.values.iter().enumerate() {
164 if i > 0 {
165 buf.push(',');
166 }
167 write_json_string(buf, &kv.key);
168 buf.push(':');
169 if let Some(val) = &kv.value {
170 write_any_value_json_depth(buf, val, depth + 1);
171 } else {
172 buf.push_str("null");
173 }
174 }
175 buf.push('}');
176 }
177 Some(Value::BytesValue(b)) => {
178 use base64::Engine;
179 buf.push('"');
180 buf.push_str(&base64::engine::general_purpose::STANDARD.encode(b));
181 buf.push('"');
182 }
183 None => buf.push_str("null"),
184 }
185}
186
187fn any_value_to_string(v: Option<&AnyValue>) -> Option<String> {
189 use opentelemetry_proto::tonic::common::v1::any_value::Value;
190 v.and_then(|av| match &av.value {
191 Some(Value::StringValue(s)) => Some(s.clone()),
192 Some(Value::IntValue(i)) => Some(i.to_string()),
193 Some(Value::BoolValue(b)) => Some(b.to_string()),
194 Some(Value::DoubleValue(d)) => Some(d.to_string()),
195 _ => None,
196 })
197}
198
199#[allow(clippy::too_many_lines)] pub fn trace_request_to_batch(
206 req: &ExportTraceServiceRequest,
207 schema: &SchemaRef,
208 received_at_nanos: i64,
209) -> Result<Option<RecordBatch>, arrow_schema::ArrowError> {
210 let total_spans: usize = req
212 .resource_spans
213 .iter()
214 .flat_map(|rs| &rs.scope_spans)
215 .map(|ss| ss.spans.len())
216 .sum();
217
218 if total_spans == 0 {
219 return Ok(None);
220 }
221
222 let mut trace_ids = FixedSizeBinaryBuilder::with_capacity(total_spans, 16);
224 let mut span_ids = FixedSizeBinaryBuilder::with_capacity(total_spans, 8);
225 let mut parent_span_ids = FixedSizeBinaryBuilder::with_capacity(total_spans, 8);
226 let mut trace_states = StringBuilder::with_capacity(total_spans, total_spans * 8);
227 let mut names = StringBuilder::with_capacity(total_spans, total_spans * 32);
228 let mut kinds = Int32Builder::with_capacity(total_spans);
229 let mut start_times = Int64Builder::with_capacity(total_spans);
230 let mut end_times = Int64Builder::with_capacity(total_spans);
231 let mut durations = Int64Builder::with_capacity(total_spans);
232 let mut status_codes = Int32Builder::with_capacity(total_spans);
233 let mut status_messages = StringBuilder::with_capacity(total_spans, total_spans * 16);
234 let mut res_service_names = StringBuilder::with_capacity(total_spans, total_spans * 16);
235 let mut res_service_versions = StringBuilder::with_capacity(total_spans, total_spans * 8);
236 let mut res_attributes = StringBuilder::with_capacity(total_spans, total_spans * 64);
237 let mut scope_names = StringBuilder::with_capacity(total_spans, total_spans * 16);
238 let mut scope_versions = StringBuilder::with_capacity(total_spans, total_spans * 8);
239 let mut span_attrs = StringBuilder::with_capacity(total_spans, total_spans * 64);
240 let mut events_counts = Int32Builder::with_capacity(total_spans);
241 let mut links_counts = Int32Builder::with_capacity(total_spans);
242 let mut received_at = TimestampNanosecondBuilder::with_capacity(total_spans);
243
244 for resource_spans in &req.resource_spans {
245 let resource = resource_spans.resource.as_ref();
246 let svc_name = extract_resource_attr(resource, "service.name");
247 let svc_version = extract_resource_attr(resource, "service.version");
248 let res_attrs_json = resource_attributes_json(resource);
249
250 for scope_spans in &resource_spans.scope_spans {
251 let scope = scope_spans.scope.as_ref();
252 let scope_n = scope.map(|s| s.name.as_str());
253 let scope_v = scope.map(|s| s.version.as_str());
254
255 for span in &scope_spans.spans {
256 let tid = fixed_bytes(&span.trace_id, 16);
258 trace_ids.append_value(&tid)?;
259
260 let sid = fixed_bytes(&span.span_id, 8);
262 span_ids.append_value(&sid)?;
263
264 let psid = fixed_bytes(&span.parent_span_id, 8);
266 if span.parent_span_id.is_empty() || is_all_zeros(&psid) {
267 parent_span_ids.append_null();
268 } else {
269 parent_span_ids.append_value(&psid)?;
270 }
271
272 if span.trace_state.is_empty() {
274 trace_states.append_null();
275 } else {
276 trace_states.append_value(&span.trace_state);
277 }
278
279 names.append_value(&span.name);
281
282 kinds.append_value(span.kind);
284
285 #[allow(clippy::cast_possible_wrap)]
287 let start_ns = span.start_time_unix_nano as i64;
288 #[allow(clippy::cast_possible_wrap)]
289 let end_ns = span.end_time_unix_nano as i64;
290 start_times.append_value(start_ns);
291 end_times.append_value(end_ns);
292 durations.append_value(end_ns.saturating_sub(start_ns));
293
294 if let Some(status) = &span.status {
296 status_codes.append_value(status.code);
297 if status.message.is_empty() {
298 status_messages.append_null();
299 } else {
300 status_messages.append_value(&status.message);
301 }
302 } else {
303 status_codes.append_value(0); status_messages.append_null();
305 }
306
307 append_context_fields(
309 svc_name.as_deref(),
310 res_attrs_json.as_deref(),
311 scope_n,
312 &span.attributes,
313 received_at_nanos,
314 &mut res_service_names,
315 &mut res_attributes,
316 &mut scope_names,
317 &mut span_attrs,
318 &mut received_at,
319 );
320
321 append_nullable_opt(&mut res_service_versions, svc_version.as_deref());
323 append_nullable_opt(&mut scope_versions, scope_v);
324
325 #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
327 let ec = span.events.len() as i32;
328 #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
329 let lc = span.links.len() as i32;
330 events_counts.append_value(ec);
331 links_counts.append_value(lc);
332 }
333 }
334 }
335
336 let batch = RecordBatch::try_new(
337 Arc::clone(schema),
338 vec![
339 Arc::new(trace_ids.finish()),
340 Arc::new(span_ids.finish()),
341 Arc::new(parent_span_ids.finish()),
342 Arc::new(trace_states.finish()),
343 Arc::new(names.finish()),
344 Arc::new(kinds.finish()),
345 Arc::new(start_times.finish()),
346 Arc::new(end_times.finish()),
347 Arc::new(durations.finish()),
348 Arc::new(status_codes.finish()),
349 Arc::new(status_messages.finish()),
350 Arc::new(res_service_names.finish()),
351 Arc::new(res_service_versions.finish()),
352 Arc::new(res_attributes.finish()),
353 Arc::new(scope_names.finish()),
354 Arc::new(scope_versions.finish()),
355 Arc::new(span_attrs.finish()),
356 Arc::new(events_counts.finish()),
357 Arc::new(links_counts.finish()),
358 Arc::new(received_at.finish()),
359 ],
360 )?;
361
362 Ok(Some(batch))
363}
364
365fn any_value_to_body_string(v: Option<&AnyValue>) -> Option<String> {
368 use opentelemetry_proto::tonic::common::v1::any_value::Value;
369 v.and_then(|av| match &av.value {
370 Some(Value::StringValue(s)) => Some(s.clone()),
371 Some(Value::IntValue(i)) => Some(i.to_string()),
372 Some(Value::BoolValue(b)) => Some(b.to_string()),
373 Some(Value::DoubleValue(d)) => Some(d.to_string()),
374 Some(_) => {
375 let mut buf = String::new();
376 write_any_value_json(&mut buf, av);
377 Some(buf)
378 }
379 None => None,
380 })
381}
382
383struct MetricBuilders {
386 names: StringBuilder,
387 descs: StringBuilder,
388 units: StringBuilder,
389 types: Int32Builder,
390 timestamps: Int64Builder,
391 value_doubles: Float64Builder,
392 value_ints: Int64Builder,
393 hist_counts: UInt64Builder,
394 hist_sums: Float64Builder,
395 res_svc_names: StringBuilder,
396 res_attrs: StringBuilder,
397 scope_names: StringBuilder,
398 attrs: StringBuilder,
399 received_at: TimestampNanosecondBuilder,
400}
401
402struct MetricContext<'a> {
403 name: &'a str,
404 desc: &'a str,
405 unit: &'a str,
406 metric_type: i32,
407 svc_name: Option<&'a str>,
408 res_json: Option<&'a str>,
409 scope_name: Option<&'a str>,
410 received_at_nanos: i64,
411}
412
413impl MetricBuilders {
414 fn with_capacity(n: usize) -> Self {
415 Self {
416 names: StringBuilder::with_capacity(n, n * 32),
417 descs: StringBuilder::with_capacity(n, n * 32),
418 units: StringBuilder::with_capacity(n, n * 8),
419 types: Int32Builder::with_capacity(n),
420 timestamps: Int64Builder::with_capacity(n),
421 value_doubles: Float64Builder::with_capacity(n),
422 value_ints: Int64Builder::with_capacity(n),
423 hist_counts: UInt64Builder::with_capacity(n),
424 hist_sums: Float64Builder::with_capacity(n),
425 res_svc_names: StringBuilder::with_capacity(n, n * 16),
426 res_attrs: StringBuilder::with_capacity(n, n * 64),
427 scope_names: StringBuilder::with_capacity(n, n * 16),
428 attrs: StringBuilder::with_capacity(n, n * 64),
429 received_at: TimestampNanosecondBuilder::with_capacity(n),
430 }
431 }
432
433 fn append_common(&mut self, ctx: &MetricContext, time_unix_nano: u64, dp_attrs: &[KeyValue]) {
434 self.names.append_value(ctx.name);
435 append_nullable_str(&mut self.descs, ctx.desc);
436 append_nullable_str(&mut self.units, ctx.unit);
437 self.types.append_value(ctx.metric_type);
438 #[allow(clippy::cast_possible_wrap)]
439 self.timestamps.append_value(time_unix_nano as i64);
440
441 append_context_fields(
442 ctx.svc_name,
443 ctx.res_json,
444 ctx.scope_name,
445 dp_attrs,
446 ctx.received_at_nanos,
447 &mut self.res_svc_names,
448 &mut self.res_attrs,
449 &mut self.scope_names,
450 &mut self.attrs,
451 &mut self.received_at,
452 );
453 }
454
455 fn append_number_dp(
456 &mut self,
457 ctx: &MetricContext,
458 time_unix_nano: u64,
459 value: Option<&number_data_point::Value>,
460 dp_attrs: &[KeyValue],
461 ) {
462 self.append_common(ctx, time_unix_nano, dp_attrs);
463 match value {
464 Some(number_data_point::Value::AsDouble(d)) => {
465 self.value_doubles.append_value(*d);
466 self.value_ints.append_null();
467 }
468 Some(number_data_point::Value::AsInt(i)) => {
469 self.value_doubles.append_null();
470 self.value_ints.append_value(*i);
471 }
472 None => {
473 self.value_doubles.append_null();
474 self.value_ints.append_null();
475 }
476 }
477 self.hist_counts.append_null();
478 self.hist_sums.append_null();
479 }
480
481 fn append_histogram_dp(
482 &mut self,
483 ctx: &MetricContext,
484 time_unix_nano: u64,
485 count: u64,
486 sum: Option<f64>,
487 dp_attrs: &[KeyValue],
488 ) {
489 self.append_common(ctx, time_unix_nano, dp_attrs);
490 self.value_doubles.append_null();
491 self.value_ints.append_null();
492 self.hist_counts.append_value(count);
493 match sum {
494 Some(s) => self.hist_sums.append_value(s),
495 None => self.hist_sums.append_null(),
496 }
497 }
498
499 fn finish(mut self, schema: &SchemaRef) -> Result<RecordBatch, arrow_schema::ArrowError> {
500 RecordBatch::try_new(
501 Arc::clone(schema),
502 vec![
503 Arc::new(self.names.finish()),
504 Arc::new(self.descs.finish()),
505 Arc::new(self.units.finish()),
506 Arc::new(self.types.finish()),
507 Arc::new(self.timestamps.finish()),
508 Arc::new(self.value_doubles.finish()),
509 Arc::new(self.value_ints.finish()),
510 Arc::new(self.hist_counts.finish()),
511 Arc::new(self.hist_sums.finish()),
512 Arc::new(self.res_svc_names.finish()),
513 Arc::new(self.res_attrs.finish()),
514 Arc::new(self.scope_names.finish()),
515 Arc::new(self.attrs.finish()),
516 Arc::new(self.received_at.finish()),
517 ],
518 )
519 }
520}
521
522#[allow(clippy::too_many_lines)] pub fn metrics_request_to_batch(
529 req: &ExportMetricsServiceRequest,
530 schema: &SchemaRef,
531 received_at_nanos: i64,
532) -> Result<Option<RecordBatch>, arrow_schema::ArrowError> {
533 let total_points: usize = req
534 .resource_metrics
535 .iter()
536 .flat_map(|rm| &rm.scope_metrics)
537 .flat_map(|sm| &sm.metrics)
538 .map(count_metric_points)
539 .sum();
540
541 if total_points == 0 {
542 return Ok(None);
543 }
544
545 let mut b = MetricBuilders::with_capacity(total_points);
546
547 for rm in &req.resource_metrics {
548 let resource = rm.resource.as_ref();
549 let svc_name = extract_resource_attr(resource, "service.name");
550 let res_json = resource_attributes_json(resource);
551
552 for sm in &rm.scope_metrics {
553 let scope_name = sm.scope.as_ref().map(|s| s.name.as_str());
554
555 for metric in &sm.metrics {
556 let Some(data) = &metric.data else { continue };
557 let base = MetricContext {
558 name: &metric.name,
559 desc: &metric.description,
560 unit: &metric.unit,
561 metric_type: 0,
562 svc_name: svc_name.as_deref(),
563 res_json: res_json.as_deref(),
564 scope_name,
565 received_at_nanos,
566 };
567
568 match data {
569 metric::Data::Gauge(g) => {
570 for dp in &g.data_points {
571 b.append_number_dp(
572 &base,
573 dp.time_unix_nano,
574 dp.value.as_ref(),
575 &dp.attributes,
576 );
577 }
578 }
579 metric::Data::Sum(s) => {
580 for dp in &s.data_points {
581 let ctx = MetricContext {
582 metric_type: 1,
583 ..base
584 };
585 b.append_number_dp(
586 &ctx,
587 dp.time_unix_nano,
588 dp.value.as_ref(),
589 &dp.attributes,
590 );
591 }
592 }
593 metric::Data::Histogram(h) => {
594 for dp in &h.data_points {
595 let ctx = MetricContext {
596 metric_type: 2,
597 ..base
598 };
599 b.append_histogram_dp(
600 &ctx,
601 dp.time_unix_nano,
602 dp.count,
603 dp.sum,
604 &dp.attributes,
605 );
606 }
607 }
608 metric::Data::ExponentialHistogram(eh) => {
609 for dp in &eh.data_points {
610 let ctx = MetricContext {
611 metric_type: 3,
612 ..base
613 };
614 b.append_histogram_dp(
615 &ctx,
616 dp.time_unix_nano,
617 dp.count,
618 dp.sum,
619 &dp.attributes,
620 );
621 }
622 }
623 metric::Data::Summary(s) => {
624 for dp in &s.data_points {
625 let ctx = MetricContext {
626 metric_type: 4,
627 ..base
628 };
629 b.append_histogram_dp(
630 &ctx,
631 dp.time_unix_nano,
632 dp.count,
633 Some(dp.sum),
634 &dp.attributes,
635 );
636 }
637 }
638 }
639 }
640 }
641 }
642
643 Ok(Some(b.finish(schema)?))
644}
645
646fn count_metric_points(m: &opentelemetry_proto::tonic::metrics::v1::Metric) -> usize {
647 match &m.data {
648 Some(metric::Data::Gauge(g)) => g.data_points.len(),
649 Some(metric::Data::Sum(s)) => s.data_points.len(),
650 Some(metric::Data::Histogram(h)) => h.data_points.len(),
651 Some(metric::Data::ExponentialHistogram(eh)) => eh.data_points.len(),
652 Some(metric::Data::Summary(s)) => s.data_points.len(),
653 None => 0,
654 }
655}
656
657#[allow(clippy::too_many_arguments)]
659fn append_context_fields(
660 svc_name: Option<&str>,
661 res_json: Option<&str>,
662 scope_name: Option<&str>,
663 attrs: &[KeyValue],
664 received_at_nanos: i64,
665 res_svc_names: &mut StringBuilder,
666 res_attrs: &mut StringBuilder,
667 scope_names_col: &mut StringBuilder,
668 attrs_col: &mut StringBuilder,
669 received_at: &mut TimestampNanosecondBuilder,
670) {
671 match svc_name {
672 Some(s) => res_svc_names.append_value(s),
673 None => res_svc_names.append_null(),
674 }
675 match res_json {
676 Some(s) => res_attrs.append_value(s),
677 None => res_attrs.append_null(),
678 }
679 match scope_name {
680 Some(s) if !s.is_empty() => scope_names_col.append_value(s),
681 _ => scope_names_col.append_null(),
682 }
683 match kv_to_json(attrs) {
684 Some(s) => attrs_col.append_value(&s),
685 None => attrs_col.append_null(),
686 }
687 received_at.append_value(received_at_nanos);
688}
689
690fn append_nullable_str(builder: &mut StringBuilder, s: &str) {
692 if s.is_empty() {
693 builder.append_null();
694 } else {
695 builder.append_value(s);
696 }
697}
698
699fn append_nullable_opt(builder: &mut StringBuilder, s: Option<&str>) {
701 match s {
702 Some(v) if !v.is_empty() => builder.append_value(v),
703 _ => builder.append_null(),
704 }
705}
706
707pub fn logs_request_to_batch(
715 req: &ExportLogsServiceRequest,
716 schema: &SchemaRef,
717 received_at_nanos: i64,
718) -> Result<Option<RecordBatch>, arrow_schema::ArrowError> {
719 let total_records: usize = req
720 .resource_logs
721 .iter()
722 .flat_map(|rl| &rl.scope_logs)
723 .map(|sl| sl.log_records.len())
724 .sum();
725
726 if total_records == 0 {
727 return Ok(None);
728 }
729
730 let mut ts = Int64Builder::with_capacity(total_records);
731 let mut observed_ts = Int64Builder::with_capacity(total_records);
732 let mut sev_nums = Int32Builder::with_capacity(total_records);
733 let mut sev_texts = StringBuilder::with_capacity(total_records, total_records * 8);
734 let mut bodies = StringBuilder::with_capacity(total_records, total_records * 64);
735 let mut trace_ids = FixedSizeBinaryBuilder::with_capacity(total_records, 16);
736 let mut span_ids = FixedSizeBinaryBuilder::with_capacity(total_records, 8);
737 let mut res_svc_names = StringBuilder::with_capacity(total_records, total_records * 16);
738 let mut res_attrs = StringBuilder::with_capacity(total_records, total_records * 64);
739 let mut scope_names_col = StringBuilder::with_capacity(total_records, total_records * 16);
740 let mut attrs_col = StringBuilder::with_capacity(total_records, total_records * 64);
741 let mut received_at = TimestampNanosecondBuilder::with_capacity(total_records);
742
743 for rl in &req.resource_logs {
744 let resource = rl.resource.as_ref();
745 let svc_name = extract_resource_attr(resource, "service.name");
746 let res_json = resource_attributes_json(resource);
747
748 for sl in &rl.scope_logs {
749 let scope_name = sl.scope.as_ref().map(|s| s.name.as_str());
750
751 for log in &sl.log_records {
752 #[allow(clippy::cast_possible_wrap)]
753 ts.append_value(log.time_unix_nano as i64);
754
755 if log.observed_time_unix_nano == 0 {
756 observed_ts.append_null();
757 } else {
758 #[allow(clippy::cast_possible_wrap)]
759 observed_ts.append_value(log.observed_time_unix_nano as i64);
760 }
761
762 sev_nums.append_value(log.severity_number);
763
764 if log.severity_text.is_empty() {
765 sev_texts.append_null();
766 } else {
767 sev_texts.append_value(&log.severity_text);
768 }
769
770 match any_value_to_body_string(log.body.as_ref()) {
771 Some(s) => bodies.append_value(&s),
772 None => bodies.append_null(),
773 }
774
775 let tid = fixed_bytes(&log.trace_id, 16);
777 if log.trace_id.is_empty() || is_all_zeros(&tid) {
778 trace_ids.append_null();
779 } else {
780 trace_ids.append_value(&tid)?;
781 }
782
783 let sid = fixed_bytes(&log.span_id, 8);
785 if log.span_id.is_empty() || is_all_zeros(&sid) {
786 span_ids.append_null();
787 } else {
788 span_ids.append_value(&sid)?;
789 }
790
791 append_context_fields(
792 svc_name.as_deref(),
793 res_json.as_deref(),
794 scope_name,
795 &log.attributes,
796 received_at_nanos,
797 &mut res_svc_names,
798 &mut res_attrs,
799 &mut scope_names_col,
800 &mut attrs_col,
801 &mut received_at,
802 );
803 }
804 }
805 }
806
807 let batch = RecordBatch::try_new(
808 Arc::clone(schema),
809 vec![
810 Arc::new(ts.finish()),
811 Arc::new(observed_ts.finish()),
812 Arc::new(sev_nums.finish()),
813 Arc::new(sev_texts.finish()),
814 Arc::new(bodies.finish()),
815 Arc::new(trace_ids.finish()),
816 Arc::new(span_ids.finish()),
817 Arc::new(res_svc_names.finish()),
818 Arc::new(res_attrs.finish()),
819 Arc::new(scope_names_col.finish()),
820 Arc::new(attrs_col.finish()),
821 Arc::new(received_at.finish()),
822 ],
823 )?;
824
825 Ok(Some(batch))
826}
827
828#[cfg(test)]
829mod tests;