Skip to content

Commit 4c1e228

Browse files
authored
Handle nested OTLP values in attributes and log bodies (#5485)
* Handle nested OTLP values in attributes and log bodies * Fix Python install
1 parent f5da576 commit 4c1e228

File tree

3 files changed

+95
-48
lines changed

3 files changed

+95
-48
lines changed

.github/workflows/ci.yml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ jobs:
4747
steps:
4848
- uses: actions/checkout@v4
4949
- name: Install Ubuntu packages
50-
run: sudo apt-get -y install protobuf-compiler python3 python3-pip
50+
run: sudo apt-get -y install protobuf-compiler python3
5151
- uses: dorny/paths-filter@v3
5252
id: modified
5353
with:
@@ -86,11 +86,12 @@ jobs:
8686
run: cargo build --features=postgres --bin quickwit
8787
working-directory: ./quickwit
8888
- name: Install python packages
89-
run: sudo pip3 install pyaml requests
9089
if: always() && steps.modified.outputs.rust_src == 'true'
91-
- name: run REST API tests
90+
run: python3 -m venv venv && source venv/bin/activate && pip install pyaml requests
91+
working-directory: ./quickwit/rest-api-tests
92+
- name: Run REST API tests
9293
if: always() && steps.modified.outputs.rust_src == 'true'
93-
run: python3 ./run_tests.py --binary ../target/debug/quickwit
94+
run: source venv/bin/activate && python3 ./run_tests.py --binary ../target/debug/quickwit
9495
working-directory: ./quickwit/rest-api-tests
9596

9697
lints:

quickwit/quickwit-opentelemetry/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ quickwit-proto = { workspace = true }
3232
[dev-dependencies]
3333
quickwit-common = { workspace = true, features = ["testsuite"] }
3434
quickwit-metastore = { workspace = true, features = ["testsuite"] }
35+
quickwit-proto = { workspace = true, features = ["testsuite"] }
3536

3637
[features]
3738
testsuite = []

quickwit/quickwit-opentelemetry/src/otlp/mod.rs

Lines changed: 89 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
use std::collections::HashMap;
2121

22+
use quickwit_common::rate_limited_warn;
2223
use quickwit_config::{validate_identifier, validate_index_id_pattern, INGEST_V2_SOURCE_ID};
2324
use quickwit_ingest::{CommitType, IngestServiceError};
2425
use quickwit_proto::ingest::router::{
@@ -119,46 +120,54 @@ pub(crate) fn extract_attributes(attributes: Vec<OtlpKeyValue>) -> HashMap<Strin
119120
if let Some(value) = attribute
120121
.value
121122
.and_then(|any_value| any_value.value)
122-
.and_then(to_json_value)
123+
.and_then(oltp_value_to_json_value)
123124
{
124125
attrs.insert(attribute.key, value);
125126
}
126127
}
127128
attrs
128129
}
129130

130-
fn to_json_value(value: OtlpValue) -> Option<JsonValue> {
131+
fn oltp_value_to_json_value(value: OtlpValue) -> Option<JsonValue> {
131132
match value {
132133
OtlpValue::ArrayValue(OtlpArrayValue { values }) => Some(
133134
values
134135
.into_iter()
135-
.flat_map(to_json_value_from_primitive_any_value)
136+
.filter_map(|value| match value.value {
137+
Some(value) => oltp_value_to_json_value(value),
138+
None => None,
139+
})
136140
.collect(),
137141
),
138-
OtlpValue::BoolValue(value) => Some(JsonValue::Bool(value)),
139-
OtlpValue::DoubleValue(value) => JsonNumber::from_f64(value).map(JsonValue::Number),
140-
OtlpValue::IntValue(value) => Some(JsonValue::Number(JsonNumber::from(value))),
141-
OtlpValue::StringValue(value) => Some(JsonValue::String(value)),
142-
OtlpValue::BytesValue(_) | OtlpValue::KvlistValue(_) => {
143-
// These attribute types are not supported for attributes according to the OpenTelemetry
144-
// specification.
142+
OtlpValue::BoolValue(bool_value) => Some(JsonValue::Bool(bool_value)),
143+
OtlpValue::DoubleValue(double_value) => {
144+
JsonNumber::from_f64(double_value).map(JsonValue::Number)
145+
}
146+
OtlpValue::IntValue(int_value) => Some(JsonValue::Number(JsonNumber::from(int_value))),
147+
OtlpValue::KvlistValue(key_values) => {
148+
let mut map = serde_json::Map::with_capacity(key_values.values.len());
149+
150+
for key_value in key_values.values {
151+
if let Some(value) = key_value
152+
.value
153+
.and_then(|any_value| any_value.value)
154+
.and_then(oltp_value_to_json_value)
155+
{
156+
map.insert(key_value.key, value);
157+
}
158+
}
159+
Some(JsonValue::Object(map))
160+
}
161+
OtlpValue::StringValue(string_value) => Some(JsonValue::String(string_value)),
162+
OtlpValue::BytesValue(_) => {
163+
rate_limited_warn!(limit_per_min = 10, "ignoring unsupported OTLP bytes value");
145164
None
146165
}
147166
}
148167
}
149168

150-
fn to_json_value_from_primitive_any_value(any_value: OtlpAnyValue) -> Option<JsonValue> {
151-
match any_value.value {
152-
Some(OtlpValue::BoolValue(value)) => Some(JsonValue::Bool(value)),
153-
Some(OtlpValue::DoubleValue(value)) => JsonNumber::from_f64(value).map(JsonValue::Number),
154-
Some(OtlpValue::IntValue(value)) => Some(JsonValue::Number(JsonNumber::from(value))),
155-
Some(OtlpValue::StringValue(value)) => Some(JsonValue::String(value)),
156-
_ => None,
157-
}
158-
}
159-
160169
pub(crate) fn parse_log_record_body(body: OtlpAnyValue) -> Option<JsonValue> {
161-
body.value.and_then(to_json_value).map(|value| {
170+
body.value.and_then(oltp_value_to_json_value).map(|value| {
162171
if value.is_string() {
163172
let mut map = serde_json::Map::with_capacity(1);
164173
map.insert("message".to_string(), value);
@@ -211,15 +220,13 @@ pub(crate) fn extract_otel_index_id_from_metadata(
211220
.transpose()
212221
.map_err(|error| {
213222
Status::internal(format!(
214-
"failed to extract index ID from request metadata: {}",
215-
error
223+
"failed to extract index ID from request metadata: {error}",
216224
))
217225
})?
218226
.unwrap_or_else(|| otel_signal.default_index_id());
219227
validate_identifier("index_id", index_id).map_err(|error| {
220228
Status::internal(format!(
221-
"invalid index ID pattern in request metadata: {}",
222-
error
229+
"invalid index ID pattern in request metadata: {error}",
223230
))
224231
})?;
225232
Ok(index_id.to_string())
@@ -241,14 +248,11 @@ async fn ingest_doc_batch_v2(
241248
commit_type: commit_type.into(),
242249
subrequests: vec![subrequest],
243250
};
244-
let mut response = ingest_router
245-
.ingest(request)
246-
.await
247-
.map_err(IngestServiceError::from)?;
251+
let mut response = ingest_router.ingest(request).await?;
248252
let num_responses = response.successes.len() + response.failures.len();
249253
if num_responses != 1 {
250254
return Err(IngestServiceError::Internal(format!(
251-
"Expected a single failure/success, got {}.",
255+
"expected a single failure or success, got {}",
252256
num_responses
253257
)));
254258
}
@@ -264,34 +268,75 @@ mod tests {
264268
use quickwit_proto::opentelemetry::proto::common::v1::any_value::{
265269
Value as OtlpValue, Value as OtlpAnyValueValue,
266270
};
267-
use quickwit_proto::opentelemetry::proto::common::v1::ArrayValue as OtlpArrayValue;
271+
use quickwit_proto::opentelemetry::proto::common::v1::{
272+
ArrayValue as OtlpArrayValue, KeyValueList as OtlpKeyValueList,
273+
};
268274
use serde_json::{json, Value as JsonValue};
269275

270276
use super::*;
271-
use crate::otlp::{extract_attributes, parse_log_record_body, to_json_value};
277+
use crate::otlp::{extract_attributes, oltp_value_to_json_value, parse_log_record_body};
272278

273279
#[test]
274-
fn test_to_json_value() {
280+
fn test_oltp_value_to_json_value() {
275281
assert_eq!(
276-
to_json_value(OtlpValue::ArrayValue(OtlpArrayValue { values: Vec::new() })),
282+
oltp_value_to_json_value(OtlpValue::ArrayValue(OtlpArrayValue { values: Vec::new() })),
277283
Some(json!([]))
278284
);
279285
assert_eq!(
280-
to_json_value(OtlpValue::ArrayValue(OtlpArrayValue {
281-
values: vec![OtlpAnyValue {
282-
value: Some(OtlpAnyValueValue::IntValue(1337))
283-
}]
286+
oltp_value_to_json_value(OtlpValue::ArrayValue(OtlpArrayValue {
287+
values: vec![
288+
OtlpAnyValue {
289+
value: Some(OtlpAnyValueValue::IntValue(1337))
290+
},
291+
OtlpAnyValue {
292+
value: Some(OtlpAnyValueValue::StringValue("1337".to_string()))
293+
}
294+
]
284295
})),
285-
Some(json!([1337]))
296+
Some(json!([1337, "1337"]))
297+
);
298+
assert_eq!(
299+
oltp_value_to_json_value(OtlpValue::BoolValue(true)),
300+
Some(json!(true))
286301
);
287-
assert_eq!(to_json_value(OtlpValue::BoolValue(true)), Some(json!(true)));
288302
assert_eq!(
289-
to_json_value(OtlpValue::DoubleValue(12.0)),
303+
oltp_value_to_json_value(OtlpValue::DoubleValue(12.0)),
290304
Some(json!(12.0))
291305
);
292-
assert_eq!(to_json_value(OtlpValue::IntValue(42)), Some(json!(42)));
293306
assert_eq!(
294-
to_json_value(OtlpValue::StringValue("foo".to_string())),
307+
oltp_value_to_json_value(OtlpValue::IntValue(42)),
308+
Some(json!(42))
309+
);
310+
assert_eq!(
311+
oltp_value_to_json_value(OtlpValue::KvlistValue(OtlpKeyValueList {
312+
values: Vec::new()
313+
})),
314+
Some(json!({}))
315+
);
316+
assert_eq!(
317+
oltp_value_to_json_value(OtlpValue::KvlistValue(OtlpKeyValueList {
318+
values: vec![
319+
OtlpKeyValue {
320+
key: "foo".to_string(),
321+
value: Some(OtlpAnyValue {
322+
value: Some(OtlpAnyValueValue::IntValue(1337))
323+
})
324+
},
325+
OtlpKeyValue {
326+
key: "bar".to_string(),
327+
value: Some(OtlpAnyValue {
328+
value: Some(OtlpAnyValueValue::StringValue("1337".to_string()))
329+
})
330+
}
331+
]
332+
})),
333+
Some(json!({
334+
"foo": 1337,
335+
"bar": "1337"
336+
}))
337+
);
338+
assert_eq!(
339+
oltp_value_to_json_value(OtlpValue::StringValue("foo".to_string())),
295340
Some(json!("foo"))
296341
);
297342
}
@@ -358,7 +403,7 @@ mod tests {
358403
}),
359404
},
360405
];
361-
let expected_attributes = std::collections::HashMap::from_iter([
406+
let expected_attributes = HashMap::from_iter([
362407
("array_key".to_string(), json!([1337])),
363408
("bool_key".to_string(), json!(true)),
364409
("double_key".to_string(), json!(12.0)),

0 commit comments

Comments
 (0)