Skip to main content

vector/sources/
internal_logs.rs

1use chrono::Utc;
2use futures::{StreamExt, stream};
3use vector_lib::{
4    codecs::BytesDeserializerConfig,
5    config::{LegacyKey, LogNamespace, log_schema},
6    configurable::configurable_component,
7    lookup::{OwnedValuePath, lookup_v2::OptionalValuePath, owned_value_path, path},
8    schema::Definition,
9};
10use vrl::value::Kind;
11
12use crate::{
13    SourceSender,
14    config::{DataType, SourceConfig, SourceContext, SourceOutput},
15    event::{EstimatedJsonEncodedSizeOf, Event},
16    internal_events::{InternalLogsBytesReceived, InternalLogsEventsReceived, StreamClosedError},
17    shutdown::ShutdownSignal,
18    trace::TraceSubscription,
19};
20
21/// Configuration for the `internal_logs` source.
22#[configurable_component(source(
23    "internal_logs",
24    "Expose internal log messages emitted by the running Vector instance."
25))]
26#[derive(Clone, Debug)]
27#[serde(deny_unknown_fields)]
28pub struct InternalLogsConfig {
29    /// Overrides the name of the log field used to add the current hostname to each event.
30    ///
31    /// By default, the [global `log_schema.host_key` option][global_host_key] is used.
32    ///
33    /// Set to `""` to suppress this key.
34    ///
35    /// [global_host_key]: https://vector.dev/docs/reference/configuration/global-options/#log_schema.host_key
36    host_key: Option<OptionalValuePath>,
37
38    /// Overrides the name of the log field used to add the current process ID to each event.
39    ///
40    /// By default, `"pid"` is used.
41    ///
42    /// Set to `""` to suppress this key.
43    #[serde(default = "default_pid_key")]
44    pid_key: OptionalValuePath,
45
46    /// The namespace to use for logs. This overrides the global setting.
47    #[configurable(metadata(docs::hidden))]
48    #[serde(default)]
49    log_namespace: Option<bool>,
50}
51
52fn default_pid_key() -> OptionalValuePath {
53    OptionalValuePath::from(owned_value_path!("pid"))
54}
55
56impl_generate_config_from_default!(InternalLogsConfig);
57
58impl Default for InternalLogsConfig {
59    fn default() -> InternalLogsConfig {
60        InternalLogsConfig {
61            host_key: None,
62            pid_key: default_pid_key(),
63            log_namespace: None,
64        }
65    }
66}
67
68impl InternalLogsConfig {
69    /// Generates the `schema::Definition` for this component.
70    fn schema_definition(&self, log_namespace: LogNamespace) -> Definition {
71        let host_key = self
72            .host_key
73            .clone()
74            .unwrap_or(log_schema().host_key().cloned().into())
75            .path
76            .map(LegacyKey::Overwrite);
77        let pid_key = self.pid_key.clone().path.map(LegacyKey::Overwrite);
78
79        // There is a global and per-source `log_namespace` config.
80        // The source config overrides the global setting and is merged here.
81        BytesDeserializerConfig
82            .schema_definition(log_namespace)
83            .with_standard_vector_source_metadata()
84            .with_source_metadata(
85                InternalLogsConfig::NAME,
86                host_key,
87                &owned_value_path!("host"),
88                Kind::bytes().or_undefined(),
89                Some("host"),
90            )
91            .with_source_metadata(
92                InternalLogsConfig::NAME,
93                pid_key,
94                &owned_value_path!("pid"),
95                Kind::integer(),
96                None,
97            )
98    }
99}
100
101#[async_trait::async_trait]
102#[typetag::serde(name = "internal_logs")]
103impl SourceConfig for InternalLogsConfig {
104    async fn build(&self, cx: SourceContext) -> crate::Result<super::Source> {
105        let host_key = self
106            .host_key
107            .clone()
108            .unwrap_or(log_schema().host_key().cloned().into())
109            .path;
110        let pid_key = self.pid_key.clone().path;
111
112        let subscription = TraceSubscription::subscribe();
113
114        let log_namespace = cx.log_namespace(self.log_namespace);
115
116        Ok(Box::pin(run(
117            host_key,
118            pid_key,
119            subscription,
120            cx.out,
121            cx.shutdown,
122            log_namespace,
123        )))
124    }
125
126    fn outputs(&self, global_log_namespace: LogNamespace) -> Vec<SourceOutput> {
127        let schema_definition =
128            self.schema_definition(global_log_namespace.merge(self.log_namespace));
129
130        vec![SourceOutput::new_maybe_logs(
131            DataType::Log,
132            schema_definition,
133        )]
134    }
135
136    fn can_acknowledge(&self) -> bool {
137        false
138    }
139}
140
141async fn run(
142    host_key: Option<OwnedValuePath>,
143    pid_key: Option<OwnedValuePath>,
144    mut subscription: TraceSubscription,
145    mut out: SourceSender,
146    shutdown: ShutdownSignal,
147    log_namespace: LogNamespace,
148) -> Result<(), ()> {
149    let hostname = crate::get_hostname();
150    let pid = std::process::id();
151
152    // Chain any log events that were captured during early buffering to the front,
153    // and then continue with the normal stream of internal log events.
154    let buffered_events = subscription.buffered_events().await;
155    let mut rx = stream::iter(buffered_events.into_iter().flatten())
156        .chain(subscription.into_stream())
157        .take_until(shutdown);
158
159    // Note: This loop, or anything called within it, MUST NOT generate
160    // any logs that don't break the loop, as that could cause an
161    // infinite loop since it receives all such logs.
162    while let Some(mut log) = rx.next().await {
163        // TODO: Should this actually be in memory size?
164        let byte_size = log.estimated_json_encoded_size_of().get();
165        let json_byte_size = log.estimated_json_encoded_size_of();
166        // This event doesn't emit any log
167        emit!(InternalLogsBytesReceived { byte_size });
168        emit!(InternalLogsEventsReceived {
169            count: 1,
170            byte_size: json_byte_size,
171        });
172
173        if let Ok(hostname) = &hostname {
174            let legacy_host_key = host_key.as_ref().map(LegacyKey::Overwrite);
175            log_namespace.insert_source_metadata(
176                InternalLogsConfig::NAME,
177                &mut log,
178                legacy_host_key,
179                path!("host"),
180                hostname.to_owned(),
181            );
182        }
183
184        let legacy_pid_key = pid_key.as_ref().map(LegacyKey::Overwrite);
185        log_namespace.insert_source_metadata(
186            InternalLogsConfig::NAME,
187            &mut log,
188            legacy_pid_key,
189            path!("pid"),
190            pid,
191        );
192
193        log_namespace.insert_standard_vector_source_metadata(
194            &mut log,
195            InternalLogsConfig::NAME,
196            Utc::now(),
197        );
198
199        if (out.send_event(Event::from(log)).await).is_err() {
200            // this wont trigger any infinite loop considering it stops the component
201            emit!(StreamClosedError { count: 1 });
202            return Err(());
203        }
204    }
205
206    Ok(())
207}
208
209#[cfg(test)]
210mod tests {
211    use futures::Stream;
212    use tokio::time::{Duration, sleep};
213    use vector_lib::{SpanField, event::Value, lookup::OwnedTargetPath};
214    use vrl::value::kind::Collection;
215
216    use serial_test::serial;
217    use vrl::event_path;
218
219    use super::*;
220    use crate::{
221        event::Event,
222        test_util::{
223            collect_ready,
224            components::{SOURCE_TAGS, assert_source_compliance},
225        },
226        trace,
227    };
228
229    #[test]
230    fn generates_config() {
231        crate::test_util::test_generate_config::<InternalLogsConfig>();
232    }
233
234    // This test is fairly overloaded with different cases.
235    //
236    // Unfortunately, this can't be easily split out into separate test
237    // cases because `consume_early_buffer` (called within the
238    // `start_source` helper) panics when called more than once.
239    #[tokio::test]
240    #[serial]
241    async fn receives_logs() {
242        trace::init(false, false, "debug", 10, None);
243        trace::reset_early_buffer();
244
245        assert_source_compliance(&SOURCE_TAGS, run_test()).await;
246    }
247
248    // Register test-specific span fields so they appear in the SPAN_FIELDS allowlist.
249    inventory::submit!(SpanField("component_new_field"));
250    inventory::submit!(SpanField("component_numerical_field"));
251
252    async fn run_test() {
253        let test_id: u8 = rand::random();
254        let start = chrono::Utc::now();
255
256        error!(message = "Before source started without span.", %test_id);
257
258        let span = error_span!(
259            "source",
260            component_kind = "source",
261            component_id = "foo",
262            component_type = "internal_logs",
263        );
264        let enter = span.enter();
265
266        error!(message = "Before source started.", %test_id);
267
268        drop(enter); // don't hold the span guard across an await point
269
270        let rx = start_source().await;
271
272        let enter = span.enter();
273
274        error!(message = "After source started.", %test_id);
275
276        {
277            let nested_span = error_span!(
278                "nested span",
279                component_kind = "bar",
280                component_new_field = "baz",
281                component_numerical_field = 1,
282                ignored_field = "foobarbaz",
283            );
284            let _enter = nested_span.enter();
285            error!(message = "In a nested span.", %test_id);
286        }
287
288        drop(enter);
289
290        sleep(Duration::from_millis(1)).await;
291        let mut events = collect_ready(rx).await;
292        let test_id = Value::from(test_id.to_string());
293        events.retain(|event| event.as_log().get(event_path!("test_id")) == Some(&test_id));
294
295        let end = chrono::Utc::now();
296
297        assert_eq!(events.len(), 4);
298
299        assert_eq!(
300            events[0].as_log()["message"],
301            "Before source started without span.".into()
302        );
303        assert_eq!(
304            events[1].as_log()["message"],
305            "Before source started.".into()
306        );
307        assert_eq!(
308            events[2].as_log()["message"],
309            "After source started.".into()
310        );
311        assert_eq!(events[3].as_log()["message"], "In a nested span.".into());
312
313        for (i, event) in events.iter().enumerate() {
314            let log = event.as_log();
315            let timestamp = *log["timestamp"]
316                .as_timestamp()
317                .expect("timestamp isn't a timestamp");
318            assert!(timestamp >= start);
319            assert!(timestamp <= end);
320            assert_eq!(log["metadata.kind"], "event".into());
321            assert_eq!(log["metadata.level"], "ERROR".into());
322            // The first log event occurs outside our custom span
323            if i == 0 {
324                assert!(log.get(event_path!("vector", "component_id")).is_none());
325                assert!(log.get(event_path!("vector", "component_kind")).is_none());
326                assert!(log.get(event_path!("vector", "component_type")).is_none());
327            } else if i < 3 {
328                assert_eq!(log["vector.component_id"], "foo".into());
329                assert_eq!(log["vector.component_kind"], "source".into());
330                assert_eq!(log["vector.component_type"], "internal_logs".into());
331            } else {
332                // The last event occurs in a nested span. Here, we expect
333                // parent fields to be preserved (unless overwritten), new
334                // fields to be added, and filtered fields to not exist.
335                assert_eq!(log["vector.component_id"], "foo".into());
336                assert_eq!(log["vector.component_kind"], "bar".into());
337                assert_eq!(log["vector.component_type"], "internal_logs".into());
338                assert_eq!(log["vector.component_new_field"], "baz".into());
339                assert_eq!(log["vector.component_numerical_field"], 1.into());
340                assert!(log.get(event_path!("vector", "ignored_field")).is_none());
341            }
342        }
343    }
344
345    async fn start_source() -> impl Stream<Item = Event> + Unpin {
346        let (tx, rx) = SourceSender::new_test();
347
348        let source = InternalLogsConfig::default()
349            .build(SourceContext::new_test(tx, None))
350            .await
351            .unwrap();
352        tokio::spawn(source);
353        sleep(Duration::from_millis(1)).await;
354        trace::stop_early_buffering();
355        rx
356    }
357
358    // Register a span field through the same macro downstream crates would use, then verify
359    // that emitting a log inside a span carrying that field captures it onto the log event.
360    // This is the regression check for `register_extra_span_field!` extending the
361    // `SpanFields::record` allowlist beyond the built-in `component_*` prefix.
362    vector_lib::register_extra_span_field!("internal_logs_test_extra_field");
363
364    #[tokio::test]
365    #[serial]
366    async fn registered_extra_span_field_is_captured() {
367        trace::init(false, false, "info", 10, None);
368        trace::reset_early_buffer();
369
370        let test_id: u8 = rand::random();
371        let rx = start_source().await;
372
373        {
374            let span = error_span!(
375                "extras",
376                component_id = "foo",
377                internal_logs_test_extra_field = "captured",
378                some_other_field = "dropped",
379            );
380            let _enter = span.enter();
381            error!(message = "With extra field.", %test_id);
382        }
383
384        sleep(Duration::from_millis(1)).await;
385        let mut events = collect_ready(rx).await;
386        let test_id_value = Value::from(test_id.to_string());
387        events.retain(|event| event.as_log().get(event_path!("test_id")) == Some(&test_id_value));
388
389        assert_eq!(events.len(), 1);
390        let log = events[0].as_log();
391        assert_eq!(
392            log["vector.internal_logs_test_extra_field"],
393            "captured".into()
394        );
395        // The unregistered span field is still filtered out.
396        assert!(log.get(event_path!("vector", "some_other_field")).is_none());
397    }
398
399    // NOTE: This test requires #[serial] because it directly interacts with global tracing state.
400    // This is a pre-existing limitation around tracing initialization in tests.
401    #[tokio::test]
402    #[serial]
403    async fn repeated_logs_are_not_rate_limited() {
404        trace::init(false, false, "info", 10, None);
405        trace::reset_early_buffer();
406
407        let rx = start_source().await;
408
409        // Generate 20 identical log messages with the same component_id
410        for _ in 0..20 {
411            info!(component_id = "test", "Repeated test message.");
412        }
413
414        sleep(Duration::from_millis(50)).await;
415        let events = collect_ready(rx).await;
416
417        // Filter to only our test messages
418        let test_events: Vec<_> = events
419            .iter()
420            .filter(|e| {
421                e.as_log()
422                    .get(event_path!("message"))
423                    .map(|m| m.to_string_lossy() == "Repeated test message.")
424                    .unwrap_or(false)
425            })
426            .collect();
427
428        // We should receive all 20 messages, no rate limiting.
429        assert_eq!(
430            test_events.len(),
431            20,
432            "internal_logs source should capture all repeated messages without rate limiting"
433        );
434    }
435
436    #[test]
437    fn output_schema_definition_vector_namespace() {
438        let config = InternalLogsConfig::default();
439
440        let definitions = config
441            .outputs(LogNamespace::Vector)
442            .remove(0)
443            .schema_definition(true);
444
445        let expected_definition =
446            Definition::new_with_default_metadata(Kind::bytes(), [LogNamespace::Vector])
447                .with_meaning(OwnedTargetPath::event_root(), "message")
448                .with_metadata_field(
449                    &owned_value_path!("vector", "source_type"),
450                    Kind::bytes(),
451                    None,
452                )
453                .with_metadata_field(
454                    &owned_value_path!(InternalLogsConfig::NAME, "pid"),
455                    Kind::integer(),
456                    None,
457                )
458                .with_metadata_field(
459                    &owned_value_path!("vector", "ingest_timestamp"),
460                    Kind::timestamp(),
461                    None,
462                )
463                .with_metadata_field(
464                    &owned_value_path!(InternalLogsConfig::NAME, "host"),
465                    Kind::bytes().or_undefined(),
466                    Some("host"),
467                );
468
469        assert_eq!(definitions, Some(expected_definition))
470    }
471
472    #[test]
473    fn output_schema_definition_legacy_namespace() {
474        let mut config = InternalLogsConfig::default();
475
476        let pid_key = "pid_a_pid_a_pid_pid_pid";
477
478        config.pid_key = OptionalValuePath::from(owned_value_path!(pid_key));
479
480        let definitions = config
481            .outputs(LogNamespace::Legacy)
482            .remove(0)
483            .schema_definition(true);
484
485        let expected_definition = Definition::new_with_default_metadata(
486            Kind::object(Collection::empty()),
487            [LogNamespace::Legacy],
488        )
489        .with_event_field(
490            &owned_value_path!("message"),
491            Kind::bytes(),
492            Some("message"),
493        )
494        .with_event_field(&owned_value_path!("source_type"), Kind::bytes(), None)
495        .with_event_field(&owned_value_path!(pid_key), Kind::integer(), None)
496        .with_event_field(&owned_value_path!("timestamp"), Kind::timestamp(), None)
497        .with_event_field(
498            &owned_value_path!("host"),
499            Kind::bytes().or_undefined(),
500            Some("host"),
501        );
502
503        assert_eq!(definitions, Some(expected_definition))
504    }
505}