Skip to content

Commit 73a03a7

Browse files
authored
feat(opentelemetry source): add instrumentation scope to logs (#21407)
* feat(opentelemetry source): add instrumentation scope to logs This introduces an optional `scope` object to logs, representing the InstrumentationScope object from the OTel protocol. This object may include: * `name` (if nonempty) * `version` (if nonempty) * `attributes` (if nonempty) * `dropped_attributes_count` (if nonzero) This is particularly important for transmitting the logger name. This PR only addresses instrumentation scope for logs. resolves #21404 * Fix formatting * Fix docs * Fix website docs (round 2)
1 parent d4015f2 commit 73a03a7

File tree

4 files changed

+151
-9
lines changed

4 files changed

+151
-9
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Adds `scope` information to logs received via the `opentelemetry` source
2+
3+
authors: srstrickland

lib/opentelemetry-proto/src/convert.rs

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use vrl::{
1414
};
1515

1616
use super::proto::{
17-
common::v1::{any_value::Value as PBValue, KeyValue},
17+
common::v1::{any_value::Value as PBValue, InstrumentationScope, KeyValue},
1818
logs::v1::{LogRecord, ResourceLogs, SeverityNumber},
1919
resource::v1::Resource,
2020
trace::v1::{
@@ -27,6 +27,9 @@ const SOURCE_NAME: &str = "opentelemetry";
2727

2828
pub const RESOURCE_KEY: &str = "resources";
2929
pub const ATTRIBUTES_KEY: &str = "attributes";
30+
pub const SCOPE_KEY: &str = "scope";
31+
pub const NAME_KEY: &str = "name";
32+
pub const VERSION_KEY: &str = "version";
3033
pub const TRACE_ID_KEY: &str = "trace_id";
3134
pub const SPAN_ID_KEY: &str = "span_id";
3235
pub const SEVERITY_TEXT_KEY: &str = "severity_text";
@@ -37,19 +40,20 @@ pub const FLAGS_KEY: &str = "flags";
3740

3841
impl ResourceLogs {
3942
pub fn into_event_iter(self, log_namespace: LogNamespace) -> impl Iterator<Item = Event> {
40-
let resource = self.resource;
4143
let now = Utc::now();
4244

43-
self.scope_logs
44-
.into_iter()
45-
.flat_map(|scope_log| scope_log.log_records)
46-
.map(move |log_record| {
45+
self.scope_logs.into_iter().flat_map(move |scope_log| {
46+
let scope = scope_log.scope;
47+
let resource = self.resource.clone();
48+
scope_log.log_records.into_iter().map(move |log_record| {
4749
ResourceLog {
4850
resource: resource.clone(),
51+
scope: scope.clone(),
4952
log_record,
5053
}
5154
.into_event(log_namespace, now)
5255
})
56+
})
5357
}
5458
}
5559

@@ -92,6 +96,7 @@ impl From<PBValue> for Value {
9296

9397
struct ResourceLog {
9498
resource: Option<Resource>,
99+
scope: Option<InstrumentationScope>,
95100
log_record: LogRecord,
96101
}
97102

@@ -212,6 +217,49 @@ impl ResourceLog {
212217
}
213218
};
214219

220+
// Insert instrumentation scope (scope name, version, and attributes)
221+
if let Some(scope) = self.scope {
222+
if !scope.name.is_empty() {
223+
log_namespace.insert_source_metadata(
224+
SOURCE_NAME,
225+
&mut log,
226+
Some(LegacyKey::Overwrite(path!(SCOPE_KEY, NAME_KEY))),
227+
path!(SCOPE_KEY, NAME_KEY),
228+
scope.name,
229+
);
230+
}
231+
if !scope.version.is_empty() {
232+
log_namespace.insert_source_metadata(
233+
SOURCE_NAME,
234+
&mut log,
235+
Some(LegacyKey::Overwrite(path!(SCOPE_KEY, VERSION_KEY))),
236+
path!(SCOPE_KEY, VERSION_KEY),
237+
scope.version,
238+
);
239+
}
240+
if !scope.attributes.is_empty() {
241+
log_namespace.insert_source_metadata(
242+
SOURCE_NAME,
243+
&mut log,
244+
Some(LegacyKey::Overwrite(path!(SCOPE_KEY, ATTRIBUTES_KEY))),
245+
path!(SCOPE_KEY, ATTRIBUTES_KEY),
246+
kv_list_into_value(scope.attributes),
247+
);
248+
}
249+
if scope.dropped_attributes_count > 0 {
250+
log_namespace.insert_source_metadata(
251+
SOURCE_NAME,
252+
&mut log,
253+
Some(LegacyKey::Overwrite(path!(
254+
SCOPE_KEY,
255+
DROPPED_ATTRIBUTES_COUNT_KEY
256+
))),
257+
path!(SCOPE_KEY, DROPPED_ATTRIBUTES_COUNT_KEY),
258+
scope.dropped_attributes_count,
259+
);
260+
}
261+
}
262+
215263
// Optional fields
216264
if let Some(resource) = self.resource {
217265
if !resource.attributes.is_empty() {

src/sources/opentelemetry/tests.rs

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use vector_lib::config::LogNamespace;
99
use vector_lib::lookup::path;
1010
use vector_lib::opentelemetry::proto::{
1111
collector::logs::v1::{logs_service_client::LogsServiceClient, ExportLogsServiceRequest},
12-
common::v1::{any_value, AnyValue, KeyValue},
12+
common::v1::{any_value, AnyValue, InstrumentationScope, KeyValue},
1313
logs::v1::{LogRecord, ResourceLogs, ScopeLogs},
1414
resource::v1::Resource as OtelResource,
1515
};
@@ -81,7 +81,17 @@ async fn receive_grpc_logs_vector_namespace() {
8181
dropped_attributes_count: 0,
8282
}),
8383
scope_logs: vec![ScopeLogs {
84-
scope: None,
84+
scope: Some(InstrumentationScope {
85+
name: "some.scope.name".into(),
86+
version: "1.2.3".into(),
87+
attributes: vec![KeyValue {
88+
key: "scope_attr".into(),
89+
value: Some(AnyValue {
90+
value: Some(any_value::Value::StringValue("scope_val".into())),
91+
}),
92+
}],
93+
dropped_attributes_count: 7,
94+
}),
8595
log_records: vec![LogRecord {
8696
time_unix_nano: 1,
8797
observed_time_unix_nano: 2,
@@ -133,6 +143,25 @@ async fn receive_grpc_logs_vector_namespace() {
133143
meta.get(path!("opentelemetry", "attributes")).unwrap(),
134144
&value!({attr_key: "attr_val"})
135145
);
146+
assert_eq!(
147+
meta.get(path!("opentelemetry", "scope", "name")).unwrap(),
148+
&value!("some.scope.name")
149+
);
150+
assert_eq!(
151+
meta.get(path!("opentelemetry", "scope", "version"))
152+
.unwrap(),
153+
&value!("1.2.3")
154+
);
155+
assert_eq!(
156+
meta.get(path!("opentelemetry", "scope", "attributes"))
157+
.unwrap(),
158+
&value!({scope_attr: "scope_val"})
159+
);
160+
assert_eq!(
161+
meta.get(path!("opentelemetry", "scope", "dropped_attributes_count"))
162+
.unwrap(),
163+
&value!(7)
164+
);
136165
assert_eq!(
137166
meta.get(path!("opentelemetry", "trace_id")).unwrap(),
138167
&value!("4ac52aadf321c2e531db005df08792f5")
@@ -219,7 +248,17 @@ async fn receive_grpc_logs_legacy_namespace() {
219248
dropped_attributes_count: 0,
220249
}),
221250
scope_logs: vec![ScopeLogs {
222-
scope: None,
251+
scope: Some(InstrumentationScope {
252+
name: "some.scope.name".into(),
253+
version: "1.2.3".into(),
254+
attributes: vec![KeyValue {
255+
key: "scope_attr".into(),
256+
value: Some(AnyValue {
257+
value: Some(any_value::Value::StringValue("scope_val".into())),
258+
}),
259+
}],
260+
dropped_attributes_count: 7,
261+
}),
223262
log_records: vec![LogRecord {
224263
time_unix_nano: 1,
225264
observed_time_unix_nano: 2,
@@ -262,6 +301,18 @@ async fn receive_grpc_logs_legacy_namespace() {
262301
"resources",
263302
Value::Object(vec_into_btmap(vec![("res_key", "res_val".into())])),
264303
),
304+
(
305+
"scope",
306+
Value::Object(vec_into_btmap(vec![
307+
("name", "some.scope.name".into()),
308+
("version", "1.2.3".into()),
309+
(
310+
"attributes",
311+
Value::Object(vec_into_btmap(vec![("scope_attr", "scope_val".into())])),
312+
),
313+
("dropped_attributes_count", 7.into()),
314+
])),
315+
),
265316
("message", "log body".into()),
266317
("trace_id", "4ac52aadf321c2e531db005df08792f5".into()),
267318
("span_id", "0b9e4bda2a55530d".into()),

website/cue/reference/components/sources/opentelemetry.cue

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,46 @@ components: sources: opentelemetry: {
112112
]
113113
}
114114
}
115+
"scope.name": {
116+
description: "Instrumentation scope name (often logger name)."
117+
required: false
118+
common: true
119+
type: string: {
120+
default: null
121+
examples: ["some.module.name"]
122+
}
123+
}
124+
"scope.version": {
125+
description: "Instrumentation scope version."
126+
required: false
127+
common: false
128+
type: string: {
129+
default: null
130+
examples: ["1.2.3"]
131+
}
132+
}
133+
"scope.attributes": {
134+
description: "Set of attributes that belong to the instrumentation scope."
135+
required: false
136+
common: false
137+
type: object: {
138+
examples: [
139+
{
140+
"attr1": "value1"
141+
"attr2": "value2"
142+
"attr3": "value3"
143+
},
144+
]
145+
}
146+
}
147+
"scope.dropped_attributes_count": {
148+
description: "Number of attributes dropped from the instrumentation scope (if not zero)."
149+
required: false
150+
common: false
151+
type: uint: {
152+
unit: null
153+
}
154+
}
115155
message: {
116156
description: "Contains the body of the log record."
117157
required: false

0 commit comments

Comments
 (0)