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 Some(Value::StringValueStrindex(_)) | None => buf.push_str("null"),
185 }
186}
187
188fn any_value_to_string(v: Option<&AnyValue>) -> Option<String> {
190 use opentelemetry_proto::tonic::common::v1::any_value::Value;
191 v.and_then(|av| match &av.value {
192 Some(Value::StringValue(s)) => Some(s.clone()),
193 Some(Value::IntValue(i)) => Some(i.to_string()),
194 Some(Value::BoolValue(b)) => Some(b.to_string()),
195 Some(Value::DoubleValue(d)) => Some(d.to_string()),
196 _ => None,
197 })
198}
199
200#[allow(clippy::too_many_lines)] pub fn trace_request_to_batch(
207 req: &ExportTraceServiceRequest,
208 schema: &SchemaRef,
209 received_at_nanos: i64,
210) -> Result<Option<RecordBatch>, arrow_schema::ArrowError> {
211 let total_spans: usize = req
213 .resource_spans
214 .iter()
215 .flat_map(|rs| &rs.scope_spans)
216 .map(|ss| ss.spans.len())
217 .sum();
218
219 if total_spans == 0 {
220 return Ok(None);
221 }
222
223 let mut trace_ids = FixedSizeBinaryBuilder::with_capacity(total_spans, 16);
225 let mut span_ids = FixedSizeBinaryBuilder::with_capacity(total_spans, 8);
226 let mut parent_span_ids = FixedSizeBinaryBuilder::with_capacity(total_spans, 8);
227 let mut trace_states = StringBuilder::with_capacity(total_spans, total_spans * 8);
228 let mut names = StringBuilder::with_capacity(total_spans, total_spans * 32);
229 let mut kinds = Int32Builder::with_capacity(total_spans);
230 let mut start_times = Int64Builder::with_capacity(total_spans);
231 let mut end_times = Int64Builder::with_capacity(total_spans);
232 let mut durations = Int64Builder::with_capacity(total_spans);
233 let mut status_codes = Int32Builder::with_capacity(total_spans);
234 let mut status_messages = StringBuilder::with_capacity(total_spans, total_spans * 16);
235 let mut res_service_names = StringBuilder::with_capacity(total_spans, total_spans * 16);
236 let mut res_service_versions = StringBuilder::with_capacity(total_spans, total_spans * 8);
237 let mut res_attributes = StringBuilder::with_capacity(total_spans, total_spans * 64);
238 let mut scope_names = StringBuilder::with_capacity(total_spans, total_spans * 16);
239 let mut scope_versions = StringBuilder::with_capacity(total_spans, total_spans * 8);
240 let mut span_attrs = StringBuilder::with_capacity(total_spans, total_spans * 64);
241 let mut events_counts = Int32Builder::with_capacity(total_spans);
242 let mut links_counts = Int32Builder::with_capacity(total_spans);
243 let mut received_at = TimestampNanosecondBuilder::with_capacity(total_spans);
244
245 for resource_spans in &req.resource_spans {
246 let resource = resource_spans.resource.as_ref();
247 let svc_name = extract_resource_attr(resource, "service.name");
248 let svc_version = extract_resource_attr(resource, "service.version");
249 let res_attrs_json = resource_attributes_json(resource);
250
251 for scope_spans in &resource_spans.scope_spans {
252 let scope = scope_spans.scope.as_ref();
253 let scope_n = scope.map(|s| s.name.as_str());
254 let scope_v = scope.map(|s| s.version.as_str());
255
256 for span in &scope_spans.spans {
257 let tid = fixed_bytes(&span.trace_id, 16);
259 trace_ids.append_value(&tid)?;
260
261 let sid = fixed_bytes(&span.span_id, 8);
263 span_ids.append_value(&sid)?;
264
265 let psid = fixed_bytes(&span.parent_span_id, 8);
267 if span.parent_span_id.is_empty() || is_all_zeros(&psid) {
268 parent_span_ids.append_null();
269 } else {
270 parent_span_ids.append_value(&psid)?;
271 }
272
273 if span.trace_state.is_empty() {
275 trace_states.append_null();
276 } else {
277 trace_states.append_value(&span.trace_state);
278 }
279
280 names.append_value(&span.name);
282
283 kinds.append_value(span.kind);
285
286 #[allow(clippy::cast_possible_wrap)]
288 let start_ns = span.start_time_unix_nano as i64;
289 #[allow(clippy::cast_possible_wrap)]
290 let end_ns = span.end_time_unix_nano as i64;
291 start_times.append_value(start_ns);
292 end_times.append_value(end_ns);
293 durations.append_value(end_ns.saturating_sub(start_ns));
294
295 if let Some(status) = &span.status {
297 status_codes.append_value(status.code);
298 if status.message.is_empty() {
299 status_messages.append_null();
300 } else {
301 status_messages.append_value(&status.message);
302 }
303 } else {
304 status_codes.append_value(0); status_messages.append_null();
306 }
307
308 append_context_fields(
310 svc_name.as_deref(),
311 res_attrs_json.as_deref(),
312 scope_n,
313 &span.attributes,
314 received_at_nanos,
315 &mut res_service_names,
316 &mut res_attributes,
317 &mut scope_names,
318 &mut span_attrs,
319 &mut received_at,
320 );
321
322 append_nullable_opt(&mut res_service_versions, svc_version.as_deref());
324 append_nullable_opt(&mut scope_versions, scope_v);
325
326 #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
328 let ec = span.events.len() as i32;
329 #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
330 let lc = span.links.len() as i32;
331 events_counts.append_value(ec);
332 links_counts.append_value(lc);
333 }
334 }
335 }
336
337 let batch = RecordBatch::try_new(
338 Arc::clone(schema),
339 vec![
340 Arc::new(trace_ids.finish()),
341 Arc::new(span_ids.finish()),
342 Arc::new(parent_span_ids.finish()),
343 Arc::new(trace_states.finish()),
344 Arc::new(names.finish()),
345 Arc::new(kinds.finish()),
346 Arc::new(start_times.finish()),
347 Arc::new(end_times.finish()),
348 Arc::new(durations.finish()),
349 Arc::new(status_codes.finish()),
350 Arc::new(status_messages.finish()),
351 Arc::new(res_service_names.finish()),
352 Arc::new(res_service_versions.finish()),
353 Arc::new(res_attributes.finish()),
354 Arc::new(scope_names.finish()),
355 Arc::new(scope_versions.finish()),
356 Arc::new(span_attrs.finish()),
357 Arc::new(events_counts.finish()),
358 Arc::new(links_counts.finish()),
359 Arc::new(received_at.finish()),
360 ],
361 )?;
362
363 Ok(Some(batch))
364}
365
366fn any_value_to_body_string(v: Option<&AnyValue>) -> Option<String> {
369 use opentelemetry_proto::tonic::common::v1::any_value::Value;
370 v.and_then(|av| match &av.value {
371 Some(Value::StringValue(s)) => Some(s.clone()),
372 Some(Value::IntValue(i)) => Some(i.to_string()),
373 Some(Value::BoolValue(b)) => Some(b.to_string()),
374 Some(Value::DoubleValue(d)) => Some(d.to_string()),
375 Some(_) => {
376 let mut buf = String::new();
377 write_any_value_json(&mut buf, av);
378 Some(buf)
379 }
380 None => None,
381 })
382}
383
384struct MetricBuilders {
387 names: StringBuilder,
388 descs: StringBuilder,
389 units: StringBuilder,
390 types: Int32Builder,
391 timestamps: Int64Builder,
392 value_doubles: Float64Builder,
393 value_ints: Int64Builder,
394 hist_counts: UInt64Builder,
395 hist_sums: Float64Builder,
396 res_svc_names: StringBuilder,
397 res_attrs: StringBuilder,
398 scope_names: StringBuilder,
399 attrs: StringBuilder,
400 received_at: TimestampNanosecondBuilder,
401}
402
403struct MetricContext<'a> {
404 name: &'a str,
405 desc: &'a str,
406 unit: &'a str,
407 metric_type: i32,
408 svc_name: Option<&'a str>,
409 res_json: Option<&'a str>,
410 scope_name: Option<&'a str>,
411 received_at_nanos: i64,
412}
413
414impl MetricBuilders {
415 fn with_capacity(n: usize) -> Self {
416 Self {
417 names: StringBuilder::with_capacity(n, n * 32),
418 descs: StringBuilder::with_capacity(n, n * 32),
419 units: StringBuilder::with_capacity(n, n * 8),
420 types: Int32Builder::with_capacity(n),
421 timestamps: Int64Builder::with_capacity(n),
422 value_doubles: Float64Builder::with_capacity(n),
423 value_ints: Int64Builder::with_capacity(n),
424 hist_counts: UInt64Builder::with_capacity(n),
425 hist_sums: Float64Builder::with_capacity(n),
426 res_svc_names: StringBuilder::with_capacity(n, n * 16),
427 res_attrs: StringBuilder::with_capacity(n, n * 64),
428 scope_names: StringBuilder::with_capacity(n, n * 16),
429 attrs: StringBuilder::with_capacity(n, n * 64),
430 received_at: TimestampNanosecondBuilder::with_capacity(n),
431 }
432 }
433
434 fn append_common(&mut self, ctx: &MetricContext, time_unix_nano: u64, dp_attrs: &[KeyValue]) {
435 self.names.append_value(ctx.name);
436 append_nullable_str(&mut self.descs, ctx.desc);
437 append_nullable_str(&mut self.units, ctx.unit);
438 self.types.append_value(ctx.metric_type);
439 #[allow(clippy::cast_possible_wrap)]
440 self.timestamps.append_value(time_unix_nano as i64);
441
442 append_context_fields(
443 ctx.svc_name,
444 ctx.res_json,
445 ctx.scope_name,
446 dp_attrs,
447 ctx.received_at_nanos,
448 &mut self.res_svc_names,
449 &mut self.res_attrs,
450 &mut self.scope_names,
451 &mut self.attrs,
452 &mut self.received_at,
453 );
454 }
455
456 fn append_number_dp(
457 &mut self,
458 ctx: &MetricContext,
459 time_unix_nano: u64,
460 value: Option<&number_data_point::Value>,
461 dp_attrs: &[KeyValue],
462 ) {
463 self.append_common(ctx, time_unix_nano, dp_attrs);
464 match value {
465 Some(number_data_point::Value::AsDouble(d)) => {
466 self.value_doubles.append_value(*d);
467 self.value_ints.append_null();
468 }
469 Some(number_data_point::Value::AsInt(i)) => {
470 self.value_doubles.append_null();
471 self.value_ints.append_value(*i);
472 }
473 None => {
474 self.value_doubles.append_null();
475 self.value_ints.append_null();
476 }
477 }
478 self.hist_counts.append_null();
479 self.hist_sums.append_null();
480 }
481
482 fn append_histogram_dp(
483 &mut self,
484 ctx: &MetricContext,
485 time_unix_nano: u64,
486 count: u64,
487 sum: Option<f64>,
488 dp_attrs: &[KeyValue],
489 ) {
490 self.append_common(ctx, time_unix_nano, dp_attrs);
491 self.value_doubles.append_null();
492 self.value_ints.append_null();
493 self.hist_counts.append_value(count);
494 match sum {
495 Some(s) => self.hist_sums.append_value(s),
496 None => self.hist_sums.append_null(),
497 }
498 }
499
500 fn finish(mut self, schema: &SchemaRef) -> Result<RecordBatch, arrow_schema::ArrowError> {
501 RecordBatch::try_new(
502 Arc::clone(schema),
503 vec![
504 Arc::new(self.names.finish()),
505 Arc::new(self.descs.finish()),
506 Arc::new(self.units.finish()),
507 Arc::new(self.types.finish()),
508 Arc::new(self.timestamps.finish()),
509 Arc::new(self.value_doubles.finish()),
510 Arc::new(self.value_ints.finish()),
511 Arc::new(self.hist_counts.finish()),
512 Arc::new(self.hist_sums.finish()),
513 Arc::new(self.res_svc_names.finish()),
514 Arc::new(self.res_attrs.finish()),
515 Arc::new(self.scope_names.finish()),
516 Arc::new(self.attrs.finish()),
517 Arc::new(self.received_at.finish()),
518 ],
519 )
520 }
521}
522
523#[allow(clippy::too_many_lines)] pub fn metrics_request_to_batch(
530 req: &ExportMetricsServiceRequest,
531 schema: &SchemaRef,
532 received_at_nanos: i64,
533) -> Result<Option<RecordBatch>, arrow_schema::ArrowError> {
534 let total_points: usize = req
535 .resource_metrics
536 .iter()
537 .flat_map(|rm| &rm.scope_metrics)
538 .flat_map(|sm| &sm.metrics)
539 .map(count_metric_points)
540 .sum();
541
542 if total_points == 0 {
543 return Ok(None);
544 }
545
546 let mut b = MetricBuilders::with_capacity(total_points);
547
548 for rm in &req.resource_metrics {
549 let resource = rm.resource.as_ref();
550 let svc_name = extract_resource_attr(resource, "service.name");
551 let res_json = resource_attributes_json(resource);
552
553 for sm in &rm.scope_metrics {
554 let scope_name = sm.scope.as_ref().map(|s| s.name.as_str());
555
556 for metric in &sm.metrics {
557 let Some(data) = &metric.data else { continue };
558 let base = MetricContext {
559 name: &metric.name,
560 desc: &metric.description,
561 unit: &metric.unit,
562 metric_type: 0,
563 svc_name: svc_name.as_deref(),
564 res_json: res_json.as_deref(),
565 scope_name,
566 received_at_nanos,
567 };
568
569 match data {
570 metric::Data::Gauge(g) => {
571 for dp in &g.data_points {
572 b.append_number_dp(
573 &base,
574 dp.time_unix_nano,
575 dp.value.as_ref(),
576 &dp.attributes,
577 );
578 }
579 }
580 metric::Data::Sum(s) => {
581 for dp in &s.data_points {
582 let ctx = MetricContext {
583 metric_type: 1,
584 ..base
585 };
586 b.append_number_dp(
587 &ctx,
588 dp.time_unix_nano,
589 dp.value.as_ref(),
590 &dp.attributes,
591 );
592 }
593 }
594 metric::Data::Histogram(h) => {
595 for dp in &h.data_points {
596 let ctx = MetricContext {
597 metric_type: 2,
598 ..base
599 };
600 b.append_histogram_dp(
601 &ctx,
602 dp.time_unix_nano,
603 dp.count,
604 dp.sum,
605 &dp.attributes,
606 );
607 }
608 }
609 metric::Data::ExponentialHistogram(eh) => {
610 for dp in &eh.data_points {
611 let ctx = MetricContext {
612 metric_type: 3,
613 ..base
614 };
615 b.append_histogram_dp(
616 &ctx,
617 dp.time_unix_nano,
618 dp.count,
619 dp.sum,
620 &dp.attributes,
621 );
622 }
623 }
624 metric::Data::Summary(s) => {
625 for dp in &s.data_points {
626 let ctx = MetricContext {
627 metric_type: 4,
628 ..base
629 };
630 b.append_histogram_dp(
631 &ctx,
632 dp.time_unix_nano,
633 dp.count,
634 Some(dp.sum),
635 &dp.attributes,
636 );
637 }
638 }
639 }
640 }
641 }
642 }
643
644 Ok(Some(b.finish(schema)?))
645}
646
647fn count_metric_points(m: &opentelemetry_proto::tonic::metrics::v1::Metric) -> usize {
648 match &m.data {
649 Some(metric::Data::Gauge(g)) => g.data_points.len(),
650 Some(metric::Data::Sum(s)) => s.data_points.len(),
651 Some(metric::Data::Histogram(h)) => h.data_points.len(),
652 Some(metric::Data::ExponentialHistogram(eh)) => eh.data_points.len(),
653 Some(metric::Data::Summary(s)) => s.data_points.len(),
654 None => 0,
655 }
656}
657
658#[allow(clippy::too_many_arguments)]
660fn append_context_fields(
661 svc_name: Option<&str>,
662 res_json: Option<&str>,
663 scope_name: Option<&str>,
664 attrs: &[KeyValue],
665 received_at_nanos: i64,
666 res_svc_names: &mut StringBuilder,
667 res_attrs: &mut StringBuilder,
668 scope_names_col: &mut StringBuilder,
669 attrs_col: &mut StringBuilder,
670 received_at: &mut TimestampNanosecondBuilder,
671) {
672 match svc_name {
673 Some(s) => res_svc_names.append_value(s),
674 None => res_svc_names.append_null(),
675 }
676 match res_json {
677 Some(s) => res_attrs.append_value(s),
678 None => res_attrs.append_null(),
679 }
680 match scope_name {
681 Some(s) if !s.is_empty() => scope_names_col.append_value(s),
682 _ => scope_names_col.append_null(),
683 }
684 match kv_to_json(attrs) {
685 Some(s) => attrs_col.append_value(&s),
686 None => attrs_col.append_null(),
687 }
688 received_at.append_value(received_at_nanos);
689}
690
691fn append_nullable_str(builder: &mut StringBuilder, s: &str) {
693 if s.is_empty() {
694 builder.append_null();
695 } else {
696 builder.append_value(s);
697 }
698}
699
700fn append_nullable_opt(builder: &mut StringBuilder, s: Option<&str>) {
702 match s {
703 Some(v) if !v.is_empty() => builder.append_value(v),
704 _ => builder.append_null(),
705 }
706}
707
708pub fn logs_request_to_batch(
716 req: &ExportLogsServiceRequest,
717 schema: &SchemaRef,
718 received_at_nanos: i64,
719) -> Result<Option<RecordBatch>, arrow_schema::ArrowError> {
720 let total_records: usize = req
721 .resource_logs
722 .iter()
723 .flat_map(|rl| &rl.scope_logs)
724 .map(|sl| sl.log_records.len())
725 .sum();
726
727 if total_records == 0 {
728 return Ok(None);
729 }
730
731 let mut ts = Int64Builder::with_capacity(total_records);
732 let mut observed_ts = Int64Builder::with_capacity(total_records);
733 let mut sev_nums = Int32Builder::with_capacity(total_records);
734 let mut sev_texts = StringBuilder::with_capacity(total_records, total_records * 8);
735 let mut bodies = StringBuilder::with_capacity(total_records, total_records * 64);
736 let mut trace_ids = FixedSizeBinaryBuilder::with_capacity(total_records, 16);
737 let mut span_ids = FixedSizeBinaryBuilder::with_capacity(total_records, 8);
738 let mut res_svc_names = StringBuilder::with_capacity(total_records, total_records * 16);
739 let mut res_attrs = StringBuilder::with_capacity(total_records, total_records * 64);
740 let mut scope_names_col = StringBuilder::with_capacity(total_records, total_records * 16);
741 let mut attrs_col = StringBuilder::with_capacity(total_records, total_records * 64);
742 let mut received_at = TimestampNanosecondBuilder::with_capacity(total_records);
743
744 for rl in &req.resource_logs {
745 let resource = rl.resource.as_ref();
746 let svc_name = extract_resource_attr(resource, "service.name");
747 let res_json = resource_attributes_json(resource);
748
749 for sl in &rl.scope_logs {
750 let scope_name = sl.scope.as_ref().map(|s| s.name.as_str());
751
752 for log in &sl.log_records {
753 #[allow(clippy::cast_possible_wrap)]
754 ts.append_value(log.time_unix_nano as i64);
755
756 if log.observed_time_unix_nano == 0 {
757 observed_ts.append_null();
758 } else {
759 #[allow(clippy::cast_possible_wrap)]
760 observed_ts.append_value(log.observed_time_unix_nano as i64);
761 }
762
763 sev_nums.append_value(log.severity_number);
764
765 if log.severity_text.is_empty() {
766 sev_texts.append_null();
767 } else {
768 sev_texts.append_value(&log.severity_text);
769 }
770
771 match any_value_to_body_string(log.body.as_ref()) {
772 Some(s) => bodies.append_value(&s),
773 None => bodies.append_null(),
774 }
775
776 let tid = fixed_bytes(&log.trace_id, 16);
778 if log.trace_id.is_empty() || is_all_zeros(&tid) {
779 trace_ids.append_null();
780 } else {
781 trace_ids.append_value(&tid)?;
782 }
783
784 let sid = fixed_bytes(&log.span_id, 8);
786 if log.span_id.is_empty() || is_all_zeros(&sid) {
787 span_ids.append_null();
788 } else {
789 span_ids.append_value(&sid)?;
790 }
791
792 append_context_fields(
793 svc_name.as_deref(),
794 res_json.as_deref(),
795 scope_name,
796 &log.attributes,
797 received_at_nanos,
798 &mut res_svc_names,
799 &mut res_attrs,
800 &mut scope_names_col,
801 &mut attrs_col,
802 &mut received_at,
803 );
804 }
805 }
806 }
807
808 let batch = RecordBatch::try_new(
809 Arc::clone(schema),
810 vec![
811 Arc::new(ts.finish()),
812 Arc::new(observed_ts.finish()),
813 Arc::new(sev_nums.finish()),
814 Arc::new(sev_texts.finish()),
815 Arc::new(bodies.finish()),
816 Arc::new(trace_ids.finish()),
817 Arc::new(span_ids.finish()),
818 Arc::new(res_svc_names.finish()),
819 Arc::new(res_attrs.finish()),
820 Arc::new(scope_names_col.finish()),
821 Arc::new(attrs_col.finish()),
822 Arc::new(received_at.finish()),
823 ],
824 )?;
825
826 Ok(Some(batch))
827}
828
829#[cfg(test)]
830mod tests;