vector/
template.rs

1//! Functionality for managing template fields used by Vector's sinks.
2use std::{borrow::Cow, convert::TryFrom, fmt, hash::Hash, path::PathBuf, sync::LazyLock};
3
4use bytes::Bytes;
5use chrono::{
6    format::{strftime::StrftimeItems, Item},
7    FixedOffset, Utc,
8};
9use regex::Regex;
10use snafu::Snafu;
11use vector_lib::configurable::{
12    configurable_component, ConfigurableNumber, ConfigurableString, NumberClass,
13};
14use vector_lib::lookup::lookup_v2::parse_target_path;
15
16use crate::{
17    config::log_schema,
18    event::{EventRef, Metric, Value},
19};
20
21static RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\{\{(?P<key>[^\}]+)\}\}").unwrap());
22
23/// Errors raised whilst parsing a Template field.
24#[allow(missing_docs)]
25#[derive(Clone, Debug, Eq, PartialEq, Snafu)]
26pub enum TemplateParseError {
27    #[snafu(display("Invalid strftime item"))]
28    StrftimeError,
29    #[snafu(display("Invalid field path in template {:?} (see https://vector.dev/docs/reference/configuration/template-syntax/)", path))]
30    InvalidPathSyntax { path: String },
31    #[snafu(display("Invalid numeric template"))]
32    InvalidNumericTemplate { template: String },
33}
34
35/// Errors raised whilst rendering a Template.
36#[allow(missing_docs)]
37#[derive(Clone, Debug, Eq, PartialEq, Snafu)]
38pub enum TemplateRenderingError {
39    #[snafu(display("Missing fields on event: {:?}", missing_keys))]
40    MissingKeys { missing_keys: Vec<String> },
41    #[snafu(display("Not numeric: {:?}", input))]
42    NotNumeric { input: String },
43}
44
45/// A templated field.
46///
47/// In many cases, components can be configured so that part of the component's functionality can be
48/// customized on a per-event basis. For example, you have a sink that writes events to a file and you want to
49/// specify which file an event should go to by using an event field as part of the
50/// input to the filename used.
51///
52/// By using `Template`, users can specify either fixed strings or templated strings. Templated strings use a common syntax to
53/// refer to fields in an event that is used as the input data when rendering the template. An example of a fixed string
54/// is `my-file.log`. An example of a template string is `my-file-{{key}}.log`, where `{{key}}`
55/// is the key's value when the template is rendered into a string.
56#[configurable_component]
57#[configurable(metadata(docs::templateable))]
58#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
59#[serde(try_from = "String", into = "String")]
60pub struct Template {
61    src: String,
62
63    #[serde(skip)]
64    parts: Vec<Part>,
65
66    #[serde(skip)]
67    is_static: bool,
68
69    #[serde(skip)]
70    reserve_size: usize,
71
72    #[serde(skip)]
73    tz_offset: Option<FixedOffset>,
74}
75
76impl TryFrom<&str> for Template {
77    type Error = TemplateParseError;
78
79    fn try_from(src: &str) -> Result<Self, Self::Error> {
80        Template::try_from(Cow::Borrowed(src))
81    }
82}
83
84impl TryFrom<String> for Template {
85    type Error = TemplateParseError;
86
87    fn try_from(src: String) -> Result<Self, Self::Error> {
88        Template::try_from(Cow::Owned(src))
89    }
90}
91
92impl TryFrom<PathBuf> for Template {
93    type Error = TemplateParseError;
94
95    fn try_from(p: PathBuf) -> Result<Self, Self::Error> {
96        Template::try_from(p.to_string_lossy().into_owned())
97    }
98}
99
100impl TryFrom<Cow<'_, str>> for Template {
101    type Error = TemplateParseError;
102
103    fn try_from(src: Cow<'_, str>) -> Result<Self, Self::Error> {
104        parse_template(&src).map(|parts| {
105            let is_static =
106                parts.is_empty() || (parts.len() == 1 && matches!(parts[0], Part::Literal(..)));
107
108            // Calculate a minimum size to reserve for rendered string. This doesn't have to be
109            // exact, and can't be because of references and time format specifiers. We just want a
110            // better starting number than 0 to avoid the first reallocations if possible.
111            let reserve_size = parts
112                .iter()
113                .map(|part| match part {
114                    Part::Literal(lit) => lit.len(),
115                    // We can't really put a useful number here, assume at least one byte will come
116                    // from the input event.
117                    Part::Reference(_path) => 1,
118                    Part::Strftime(parsed) => parsed.reserve_size(),
119                })
120                .sum();
121
122            Template {
123                parts,
124                src: src.into_owned(),
125                is_static,
126                reserve_size,
127                tz_offset: None,
128            }
129        })
130    }
131}
132
133impl From<Template> for String {
134    fn from(template: Template) -> String {
135        template.src
136    }
137}
138
139impl fmt::Display for Template {
140    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141        self.src.fmt(f)
142    }
143}
144
145// This is safe because we literally defer to `String` for the schema of `Template`.
146impl ConfigurableString for Template {}
147
148impl Template {
149    /// set tz offset
150    pub const fn with_tz_offset(mut self, tz_offset: Option<FixedOffset>) -> Self {
151        self.tz_offset = tz_offset;
152        self
153    }
154    /// Renders the given template with data from the event.
155    pub fn render<'a>(
156        &self,
157        event: impl Into<EventRef<'a>>,
158    ) -> Result<Bytes, TemplateRenderingError> {
159        self.render_string(event.into()).map(Into::into)
160    }
161
162    /// Renders the given template with data from the event.
163    pub fn render_string<'a>(
164        &self,
165        event: impl Into<EventRef<'a>>,
166    ) -> Result<String, TemplateRenderingError> {
167        if self.is_static {
168            Ok(self.src.clone())
169        } else {
170            self.render_event(event.into())
171        }
172    }
173
174    fn render_event(&self, event: EventRef<'_>) -> Result<String, TemplateRenderingError> {
175        let mut missing_keys = Vec::new();
176        let mut out = String::with_capacity(self.reserve_size);
177        for part in &self.parts {
178            match part {
179                Part::Literal(lit) => out.push_str(lit),
180                Part::Strftime(items) => {
181                    out.push_str(&render_timestamp(items, event, self.tz_offset))
182                }
183                Part::Reference(key) => {
184                    out.push_str(
185                        &match event {
186                            EventRef::Log(log) => log
187                                .parse_path_and_get_value(key)
188                                .ok()
189                                .and_then(|v| v.map(Value::to_string_lossy)),
190                            EventRef::Metric(metric) => {
191                                render_metric_field(key, metric).map(Cow::Borrowed)
192                            }
193                            EventRef::Trace(trace) => trace
194                                .parse_path_and_get_value(key)
195                                .ok()
196                                .and_then(|v| v.map(Value::to_string_lossy)),
197                        }
198                        .unwrap_or_else(|| {
199                            missing_keys.push(key.to_owned());
200                            Cow::Borrowed("")
201                        }),
202                    );
203                }
204            }
205        }
206        if missing_keys.is_empty() {
207            Ok(out)
208        } else {
209            Err(TemplateRenderingError::MissingKeys { missing_keys })
210        }
211    }
212
213    /// Returns the names of the fields that are rendered in this template.
214    pub fn get_fields(&self) -> Option<Vec<String>> {
215        let parts: Vec<_> = self
216            .parts
217            .iter()
218            .filter_map(|part| {
219                if let Part::Reference(r) = part {
220                    Some(r.to_owned())
221                } else {
222                    None
223                }
224            })
225            .collect();
226        (!parts.is_empty()).then_some(parts)
227    }
228
229    #[allow(clippy::missing_const_for_fn)] // Adding `const` results in https://doc.rust-lang.org/error_codes/E0015.html
230    /// Returns a reference to the template string.
231    pub fn get_ref(&self) -> &str {
232        &self.src
233    }
234
235    /// Returns `true` if this template string has a length of zero, and `false` otherwise.
236    pub fn is_empty(&self) -> bool {
237        self.src.is_empty()
238    }
239
240    /// A dynamic template string contains sections that depend on the input event or time.
241    pub const fn is_dynamic(&self) -> bool {
242        !self.is_static
243    }
244}
245
246/// The source of a `uint` template. May be a constant numeric value or a template string.
247#[derive(Clone, Debug, Eq, Hash, PartialEq)]
248#[configurable_component]
249#[serde(untagged)]
250enum UnsignedIntTemplateSource {
251    /// A static unsigned number.
252    Number(u64),
253    /// A string, which may be a template.
254    String(String),
255}
256
257impl Default for UnsignedIntTemplateSource {
258    fn default() -> Self {
259        Self::Number(Default::default())
260    }
261}
262
263impl fmt::Display for UnsignedIntTemplateSource {
264    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
265        match self {
266            Self::Number(i) => i.fmt(f),
267            Self::String(s) => s.fmt(f),
268        }
269    }
270}
271
272/// Unsigned integer template.
273#[configurable_component]
274#[configurable(metadata(docs::templateable))]
275#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
276#[serde(
277    try_from = "UnsignedIntTemplateSource",
278    into = "UnsignedIntTemplateSource"
279)]
280pub struct UnsignedIntTemplate {
281    src: UnsignedIntTemplateSource,
282
283    #[serde(skip)]
284    parts: Vec<Part>,
285
286    #[serde(skip)]
287    tz_offset: Option<FixedOffset>,
288}
289
290impl TryFrom<UnsignedIntTemplateSource> for UnsignedIntTemplate {
291    type Error = TemplateParseError;
292
293    fn try_from(src: UnsignedIntTemplateSource) -> Result<Self, Self::Error> {
294        match src {
295            UnsignedIntTemplateSource::Number(num) => Ok(UnsignedIntTemplate {
296                src: UnsignedIntTemplateSource::Number(num),
297                parts: Vec::new(),
298                tz_offset: None,
299            }),
300            UnsignedIntTemplateSource::String(s) => UnsignedIntTemplate::try_from(s),
301        }
302    }
303}
304
305impl From<UnsignedIntTemplate> for UnsignedIntTemplateSource {
306    fn from(template: UnsignedIntTemplate) -> UnsignedIntTemplateSource {
307        template.src
308    }
309}
310
311impl TryFrom<&str> for UnsignedIntTemplate {
312    type Error = TemplateParseError;
313
314    fn try_from(src: &str) -> Result<Self, Self::Error> {
315        UnsignedIntTemplate::try_from(Cow::Borrowed(src))
316    }
317}
318
319impl TryFrom<String> for UnsignedIntTemplate {
320    type Error = TemplateParseError;
321
322    fn try_from(src: String) -> Result<Self, Self::Error> {
323        UnsignedIntTemplate::try_from(Cow::Owned(src))
324    }
325}
326
327impl From<u64> for UnsignedIntTemplate {
328    fn from(num: u64) -> UnsignedIntTemplate {
329        UnsignedIntTemplate {
330            src: UnsignedIntTemplateSource::Number(num),
331            parts: Vec::new(),
332            tz_offset: None,
333        }
334    }
335}
336
337impl TryFrom<Cow<'_, str>> for UnsignedIntTemplate {
338    type Error = TemplateParseError;
339
340    fn try_from(src: Cow<'_, str>) -> Result<Self, Self::Error> {
341        parse_template(&src).and_then(|parts| {
342            let is_static =
343                parts.is_empty() || (parts.len() == 1 && matches!(parts[0], Part::Literal(..)));
344
345            if is_static {
346                match src.parse::<u64>() {
347                    Ok(num) => Ok(UnsignedIntTemplate {
348                        src: UnsignedIntTemplateSource::Number(num),
349                        parts,
350                        tz_offset: None,
351                    }),
352                    Err(_) => Err(TemplateParseError::InvalidNumericTemplate {
353                        template: src.into_owned(),
354                    }),
355                }
356            } else {
357                Ok(UnsignedIntTemplate {
358                    parts,
359                    src: UnsignedIntTemplateSource::String(src.into_owned()),
360                    tz_offset: None,
361                })
362            }
363        })
364    }
365}
366
367impl From<UnsignedIntTemplate> for String {
368    fn from(template: UnsignedIntTemplate) -> String {
369        template.src.to_string()
370    }
371}
372
373impl fmt::Display for UnsignedIntTemplate {
374    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
375        self.src.fmt(f)
376    }
377}
378
379impl ConfigurableString for UnsignedIntTemplate {}
380impl ConfigurableNumber for UnsignedIntTemplate {
381    type Numeric = u64;
382
383    fn class() -> NumberClass {
384        NumberClass::Unsigned
385    }
386}
387
388impl UnsignedIntTemplate {
389    /// Renders the given template with data from the event.
390    pub fn render<'a>(
391        &self,
392        event: impl Into<EventRef<'a>>,
393    ) -> Result<u64, TemplateRenderingError> {
394        match self.src {
395            UnsignedIntTemplateSource::Number(num) => Ok(num),
396            UnsignedIntTemplateSource::String(_) => self.render_event(event.into()),
397        }
398    }
399
400    /// set tz offset
401    pub const fn with_tz_offset(mut self, tz_offset: Option<FixedOffset>) -> Self {
402        self.tz_offset = tz_offset;
403        self
404    }
405
406    fn render_event(&self, event: EventRef<'_>) -> Result<u64, TemplateRenderingError> {
407        let mut missing_keys = Vec::new();
408        let mut out = String::with_capacity(20);
409        for part in &self.parts {
410            match part {
411                Part::Literal(lit) => out.push_str(lit),
412                Part::Reference(key) => {
413                    out.push_str(
414                        &match event {
415                            EventRef::Log(log) => log
416                                .parse_path_and_get_value(key)
417                                .ok()
418                                .and_then(|v| v.map(Value::to_string_lossy)),
419                            EventRef::Metric(metric) => {
420                                render_metric_field(key, metric).map(Cow::Borrowed)
421                            }
422                            EventRef::Trace(trace) => trace
423                                .parse_path_and_get_value(key)
424                                .ok()
425                                .and_then(|v| v.map(Value::to_string_lossy)),
426                        }
427                        .unwrap_or_else(|| {
428                            missing_keys.push(key.to_owned());
429                            Cow::Borrowed("")
430                        }),
431                    );
432                }
433                Part::Strftime(items) => {
434                    out.push_str(&render_timestamp(items, event, self.tz_offset))
435                }
436            }
437        }
438        if missing_keys.is_empty() {
439            out.parse::<u64>()
440                .map_err(|_| TemplateRenderingError::NotNumeric { input: out })
441        } else {
442            Err(TemplateRenderingError::MissingKeys { missing_keys })
443        }
444    }
445
446    /// Returns the names of the fields that are rendered in this template.
447    pub fn get_fields(&self) -> Option<Vec<String>> {
448        let parts: Vec<_> = self
449            .parts
450            .iter()
451            .filter_map(|part| {
452                if let Part::Reference(r) = part {
453                    Some(r.to_owned())
454                } else {
455                    None
456                }
457            })
458            .collect();
459        (!parts.is_empty()).then_some(parts)
460    }
461}
462
463/// One part of the template string after parsing.
464#[derive(Clone, Debug, Eq, Hash, PartialEq)]
465enum Part {
466    /// A literal piece of text to be copied verbatim into the output.
467    Literal(String),
468    /// A literal piece of text containing a time format string.
469    Strftime(ParsedStrftime),
470    /// A reference to the source event, to be copied from the relevant field or tag.
471    Reference(String),
472}
473
474// Wrap the parsed time formatter in order to provide `impl Hash` and some convenience functions.
475#[derive(Clone, Debug, Eq, Hash, PartialEq)]
476struct ParsedStrftime(Box<[Item<'static>]>);
477
478impl ParsedStrftime {
479    fn parse(fmt: &str) -> Result<Self, TemplateParseError> {
480        Ok(Self(
481            StrftimeItems::new(fmt)
482                .map(|item| match item {
483                    // Box the references so they outlive the reference
484                    Item::Space(space) => Item::OwnedSpace(space.into()),
485                    Item::Literal(lit) => Item::OwnedLiteral(lit.into()),
486                    // And copy all the others
487                    Item::Fixed(f) => Item::Fixed(f),
488                    Item::Numeric(num, pad) => Item::Numeric(num, pad),
489                    Item::Error => Item::Error,
490                    Item::OwnedSpace(space) => Item::OwnedSpace(space),
491                    Item::OwnedLiteral(lit) => Item::OwnedLiteral(lit),
492                })
493                .map(|item| {
494                    matches!(item, Item::Error)
495                        .then(|| Err(TemplateParseError::StrftimeError))
496                        .unwrap_or(Ok(item))
497                })
498                .collect::<Result<Vec<_>, _>>()?
499                .into(),
500        ))
501    }
502
503    fn is_dynamic(&self) -> bool {
504        self.0.iter().any(|item| match item {
505            Item::Fixed(_) => true,
506            Item::Numeric(_, _) => true,
507            Item::Error
508            | Item::Space(_)
509            | Item::OwnedSpace(_)
510            | Item::Literal(_)
511            | Item::OwnedLiteral(_) => false,
512        })
513    }
514
515    fn as_items(&self) -> impl Iterator<Item = &Item<'static>> + Clone {
516        self.0.iter()
517    }
518
519    fn reserve_size(&self) -> usize {
520        self.0
521            .iter()
522            .map(|item| match item {
523                Item::Literal(lit) => lit.len(),
524                Item::OwnedLiteral(lit) => lit.len(),
525                Item::Space(space) => space.len(),
526                Item::OwnedSpace(space) => space.len(),
527                Item::Error => 0,
528                Item::Numeric(_, _) => 2,
529                Item::Fixed(_) => 2,
530            })
531            .sum()
532    }
533}
534
535fn parse_literal(src: &str) -> Result<Part, TemplateParseError> {
536    let parsed = ParsedStrftime::parse(src)?;
537    Ok(if parsed.is_dynamic() {
538        Part::Strftime(parsed)
539    } else {
540        Part::Literal(src.to_string())
541    })
542}
543
544// Pre-parse the template string into a series of parts to be filled in at render time.
545fn parse_template(src: &str) -> Result<Vec<Part>, TemplateParseError> {
546    let mut last_end = 0;
547    let mut parts = Vec::new();
548    for cap in RE.captures_iter(src) {
549        let all = cap.get(0).expect("Capture 0 is always defined");
550        if all.start() > last_end {
551            parts.push(parse_literal(&src[last_end..all.start()])?);
552        }
553
554        let path = cap[1].trim().to_owned();
555
556        // This checks the syntax, but doesn't yet store it for use later
557        // see: https://github.com/vectordotdev/vector/issues/14864
558        if parse_target_path(&path).is_err() {
559            return Err(TemplateParseError::InvalidPathSyntax { path });
560        }
561
562        parts.push(Part::Reference(path));
563        last_end = all.end();
564    }
565    if src.len() > last_end {
566        parts.push(parse_literal(&src[last_end..])?);
567    }
568
569    Ok(parts)
570}
571
572fn render_metric_field<'a>(key: &str, metric: &'a Metric) -> Option<&'a str> {
573    match key {
574        "name" => Some(metric.name()),
575        "namespace" => metric.namespace(),
576        _ if key.starts_with("tags.") => metric.tags().and_then(|tags| tags.get(&key[5..])),
577        _ => None,
578    }
579}
580
581fn render_timestamp(
582    items: &ParsedStrftime,
583    event: EventRef<'_>,
584    tz_offset: Option<FixedOffset>,
585) -> String {
586    let timestamp = match event {
587        EventRef::Log(log) => log.get_timestamp().and_then(Value::as_timestamp).copied(),
588        EventRef::Metric(metric) => metric.timestamp(),
589        EventRef::Trace(trace) => {
590            log_schema()
591                .timestamp_key_target_path()
592                .and_then(|timestamp_key| {
593                    trace
594                        .get(timestamp_key)
595                        .and_then(Value::as_timestamp)
596                        .copied()
597                })
598        }
599    }
600    .unwrap_or_else(Utc::now);
601
602    match tz_offset {
603        Some(offset) => timestamp
604            .with_timezone(&offset)
605            .format_with_items(items.as_items())
606            .to_string(),
607        None => timestamp
608            .with_timezone(&chrono::Utc)
609            .format_with_items(items.as_items())
610            .to_string(),
611    }
612}
613
614#[cfg(test)]
615mod tests {
616    use chrono::{Offset, TimeZone, Utc};
617    use chrono_tz::Tz;
618    use vector_lib::config::LogNamespace;
619    use vector_lib::lookup::{metadata_path, PathPrefix};
620    use vector_lib::metric_tags;
621    use vrl::event_path;
622
623    use super::*;
624    use crate::event::{Event, LogEvent, MetricKind, MetricValue};
625
626    #[test]
627    fn get_fields() {
628        let f1 = Template::try_from("{{ foo }}")
629            .unwrap()
630            .get_fields()
631            .unwrap();
632        let f2 = Template::try_from("{{ foo }}-{{ bar }}")
633            .unwrap()
634            .get_fields()
635            .unwrap();
636        let f3 = Template::try_from("nofield").unwrap().get_fields();
637        let f4 = Template::try_from("%F").unwrap().get_fields();
638        let f5 = UnsignedIntTemplate::try_from("{{ foo }}-{{ bar }}")
639            .unwrap()
640            .get_fields()
641            .unwrap();
642        let f6 = UnsignedIntTemplate::from(123u64).get_fields();
643        let f7 = UnsignedIntTemplate::try_from("%s").unwrap().get_fields();
644
645        assert_eq!(f1, vec!["foo"]);
646        assert_eq!(f2, vec!["foo", "bar"]);
647        assert_eq!(f3, None);
648        assert_eq!(f4, None);
649        assert_eq!(f5, vec!["foo", "bar"]);
650        assert_eq!(f6, None);
651        assert_eq!(f7, None);
652    }
653
654    #[test]
655    fn is_dynamic() {
656        assert!(Template::try_from("/kube-demo/%F").unwrap().is_dynamic());
657        assert!(!Template::try_from("/kube-demo/echo").unwrap().is_dynamic());
658        assert!(Template::try_from("/kube-demo/{{ foo }}")
659            .unwrap()
660            .is_dynamic());
661        assert!(Template::try_from("/kube-demo/{{ foo }}/%F")
662            .unwrap()
663            .is_dynamic());
664    }
665
666    #[test]
667    fn render_log_static() {
668        let event = Event::Log(LogEvent::from("hello world"));
669        let template = Template::try_from("foo").unwrap();
670
671        assert_eq!(Ok(Bytes::from("foo")), template.render(&event))
672    }
673
674    #[test]
675    fn render_log_unsigned_number() {
676        let event = Event::Log(LogEvent::from("hello world"));
677        let template = UnsignedIntTemplate::from(123);
678
679        assert_eq!(Ok(123), template.render(&event))
680    }
681
682    #[test]
683    fn render_log_unsigned_number_dynamic() {
684        let mut event = Event::Log(LogEvent::from("hello world"));
685        event.as_mut_log().insert("foo", 123);
686
687        let template = UnsignedIntTemplate::try_from("{{ foo }}").unwrap();
688        assert_eq!(Ok(123), template.render(&event))
689    }
690
691    #[test]
692    fn render_log_dynamic() {
693        let mut event = Event::Log(LogEvent::from("hello world"));
694        event.as_mut_log().insert("log_stream", "stream");
695        let template = Template::try_from("{{log_stream}}").unwrap();
696
697        assert_eq!(Ok(Bytes::from("stream")), template.render(&event))
698    }
699
700    #[test]
701    fn render_log_metadata() {
702        let mut event = Event::Log(LogEvent::from("hello world"));
703        event
704            .as_mut_log()
705            .insert(metadata_path!("metadata_key"), "metadata_value");
706        let template = Template::try_from("{{%metadata_key}}").unwrap();
707
708        assert_eq!(Ok(Bytes::from("metadata_value")), template.render(&event))
709    }
710
711    #[test]
712    fn render_log_dynamic_with_prefix() {
713        let mut event = Event::Log(LogEvent::from("hello world"));
714        event.as_mut_log().insert("log_stream", "stream");
715        let template = Template::try_from("abcd-{{log_stream}}").unwrap();
716
717        assert_eq!(Ok(Bytes::from("abcd-stream")), template.render(&event))
718    }
719
720    #[test]
721    fn render_log_dynamic_with_postfix() {
722        let mut event = Event::Log(LogEvent::from("hello world"));
723        event.as_mut_log().insert("log_stream", "stream");
724        let template = Template::try_from("{{log_stream}}-abcd").unwrap();
725
726        assert_eq!(Ok(Bytes::from("stream-abcd")), template.render(&event))
727    }
728
729    #[test]
730    fn render_log_dynamic_missing_key() {
731        let event = Event::Log(LogEvent::from("hello world"));
732        let template = Template::try_from("{{log_stream}}-{{foo}}").unwrap();
733
734        assert_eq!(
735            Err(TemplateRenderingError::MissingKeys {
736                missing_keys: vec!["log_stream".to_string(), "foo".to_string()]
737            }),
738            template.render(&event)
739        );
740    }
741
742    #[test]
743    fn render_log_dynamic_multiple_keys() {
744        let mut event = Event::Log(LogEvent::from("hello world"));
745        event.as_mut_log().insert("foo", "bar");
746        event.as_mut_log().insert("baz", "quux");
747        let template = Template::try_from("stream-{{foo}}-{{baz}}.log").unwrap();
748
749        assert_eq!(
750            Ok(Bytes::from("stream-bar-quux.log")),
751            template.render(&event)
752        )
753    }
754
755    #[test]
756    fn render_log_dynamic_weird_junk() {
757        let mut event = Event::Log(LogEvent::from("hello world"));
758        event.as_mut_log().insert("foo", "bar");
759        event.as_mut_log().insert("baz", "quux");
760        let template = Template::try_from(r"{stream}{\{{}}}-{{foo}}-{{baz}}.log").unwrap();
761
762        assert_eq!(
763            Ok(Bytes::from(r"{stream}{\{{}}}-bar-quux.log")),
764            template.render(&event)
765        )
766    }
767
768    #[test]
769    fn render_log_timestamp_strftime_style() {
770        let ts = Utc
771            .with_ymd_and_hms(2001, 2, 3, 4, 5, 6)
772            .single()
773            .expect("invalid timestamp");
774
775        let mut event = Event::Log(LogEvent::from("hello world"));
776        event
777            .as_mut_log()
778            .insert(log_schema().timestamp_key_target_path().unwrap(), ts);
779
780        let template = Template::try_from("abcd-%F").unwrap();
781
782        assert_eq!(Ok(Bytes::from("abcd-2001-02-03")), template.render(&event))
783    }
784
785    #[test]
786    fn render_log_timestamp_strftime_style_namespace() {
787        let ts = Utc
788            .with_ymd_and_hms(2001, 2, 3, 4, 5, 6)
789            .single()
790            .expect("invalid timestamp");
791
792        let mut event = Event::Log(LogEvent::from("hello world"));
793        event.as_mut_log().insert("@timestamp", ts);
794        // use Vector namespace instead of legacy
795        LogNamespace::Vector.insert_vector_metadata(event.as_mut_log(), Some("foo"), "foo", "bar");
796        let new_schema = event
797            .as_mut_log()
798            .metadata()
799            .schema_definition()
800            .as_ref()
801            .clone()
802            .with_meaning(parse_target_path("@timestamp").unwrap(), "timestamp");
803        event
804            .as_mut_log()
805            .metadata_mut()
806            .set_schema_definition(&std::sync::Arc::new(new_schema));
807
808        let template = Template::try_from("abcd-%F").unwrap();
809
810        assert_eq!(Ok(Bytes::from("abcd-2001-02-03")), template.render(&event))
811    }
812
813    #[test]
814    fn render_log_timestamp_multiple_strftime_style() {
815        let ts = Utc
816            .with_ymd_and_hms(2001, 2, 3, 4, 5, 6)
817            .single()
818            .expect("invalid timestamp");
819
820        let mut event = Event::Log(LogEvent::from("hello world"));
821        event
822            .as_mut_log()
823            .insert(log_schema().timestamp_key_target_path().unwrap(), ts);
824
825        let template = Template::try_from("abcd-%F_%T").unwrap();
826
827        assert_eq!(
828            Ok(Bytes::from("abcd-2001-02-03_04:05:06")),
829            template.render(&event)
830        )
831    }
832
833    #[test]
834    fn render_log_dynamic_with_strftime() {
835        let ts = Utc
836            .with_ymd_and_hms(2001, 2, 3, 4, 5, 6)
837            .single()
838            .expect("invalid timestamp");
839
840        let mut event = Event::Log(LogEvent::from("hello world"));
841        event.as_mut_log().insert("foo", "butts");
842        event.as_mut_log().insert(
843            (PathPrefix::Event, log_schema().timestamp_key().unwrap()),
844            ts,
845        );
846
847        let template = Template::try_from("{{ foo }}-%F_%T").unwrap();
848
849        assert_eq!(
850            Ok(Bytes::from("butts-2001-02-03_04:05:06")),
851            template.render(&event)
852        )
853    }
854
855    #[test]
856    fn render_log_dynamic_with_nested_strftime() {
857        let ts = Utc
858            .with_ymd_and_hms(2001, 2, 3, 4, 5, 6)
859            .single()
860            .expect("invalid timestamp");
861
862        let mut event = Event::Log(LogEvent::from("hello world"));
863        event.as_mut_log().insert("format", "%F");
864        event.as_mut_log().insert(
865            (PathPrefix::Event, log_schema().timestamp_key().unwrap()),
866            ts,
867        );
868
869        let template = Template::try_from("nested {{ format }} %T").unwrap();
870
871        assert_eq!(
872            Ok(Bytes::from("nested %F 04:05:06")),
873            template.render(&event)
874        )
875    }
876
877    #[test]
878    fn render_log_dynamic_with_reverse_nested_strftime() {
879        let ts = Utc
880            .with_ymd_and_hms(2001, 2, 3, 4, 5, 6)
881            .single()
882            .expect("invalid timestamp");
883
884        let mut event = Event::Log(LogEvent::from("hello world"));
885        event.as_mut_log().insert("\"%F\"", "foo");
886        event.as_mut_log().insert(
887            (PathPrefix::Event, log_schema().timestamp_key().unwrap()),
888            ts,
889        );
890
891        let template = Template::try_from("nested {{ \"%F\" }} %T").unwrap();
892
893        assert_eq!(
894            Ok(Bytes::from("nested foo 04:05:06")),
895            template.render(&event)
896        )
897    }
898
899    #[test]
900    fn render_metric_timestamp() {
901        let template = Template::try_from("timestamp %F %T").unwrap();
902
903        assert_eq!(
904            Ok(Bytes::from("timestamp 2002-03-04 05:06:07")),
905            template.render(&sample_metric())
906        );
907    }
908
909    #[test]
910    fn render_metric_with_tags() {
911        let template = Template::try_from("name={{name}} component={{tags.component}}").unwrap();
912        let metric = sample_metric().with_tags(Some(metric_tags!(
913            "test" => "true",
914            "component" => "template",
915        )));
916        assert_eq!(
917            Ok(Bytes::from("name=a-counter component=template")),
918            template.render(&metric)
919        );
920    }
921
922    #[test]
923    fn render_metric_without_tags() {
924        let template = Template::try_from("name={{name}} component={{tags.component}}").unwrap();
925        assert_eq!(
926            Err(TemplateRenderingError::MissingKeys {
927                missing_keys: vec!["tags.component".into()]
928            }),
929            template.render(&sample_metric())
930        );
931    }
932
933    #[test]
934    fn render_metric_with_namespace() {
935        let template = Template::try_from("namespace={{namespace}} name={{name}}").unwrap();
936        let metric = sample_metric().with_namespace(Some("vector-test"));
937        assert_eq!(
938            Ok(Bytes::from("namespace=vector-test name=a-counter")),
939            template.render(&metric)
940        );
941    }
942
943    #[test]
944    fn render_metric_without_namespace() {
945        let template = Template::try_from("namespace={{namespace}} name={{name}}").unwrap();
946        let metric = sample_metric();
947        assert_eq!(
948            Err(TemplateRenderingError::MissingKeys {
949                missing_keys: vec!["namespace".into()]
950            }),
951            template.render(&metric)
952        );
953    }
954
955    #[test]
956    fn render_log_with_timezone() {
957        let ts = Utc.with_ymd_and_hms(2001, 2, 3, 4, 5, 6).unwrap();
958
959        let template = Template::try_from("vector-%Y-%m-%d-%H.log").unwrap();
960        let mut event = Event::Log(LogEvent::from("hello world"));
961        event.as_mut_log().insert(
962            (PathPrefix::Event, log_schema().timestamp_key().unwrap()),
963            ts,
964        );
965
966        let tz: Tz = "Asia/Singapore".parse().unwrap();
967        let offset = Some(Utc::now().with_timezone(&tz).offset().fix());
968        assert_eq!(
969            Ok(Bytes::from("vector-2001-02-03-12.log")),
970            template.with_tz_offset(offset).render(&event)
971        );
972    }
973
974    #[test]
975    fn render_log_unsigned_int_with_timezone() {
976        let ts = Utc.with_ymd_and_hms(2001, 2, 3, 4, 5, 6).unwrap();
977
978        let template = UnsignedIntTemplate::try_from("%Y%m%d%H").unwrap();
979        let mut event = Event::Log(LogEvent::from("hello world"));
980        event.as_mut_log().insert(event_path!("timestamp"), ts);
981
982        let tz: Tz = "Asia/Singapore".parse().unwrap();
983        let offset = Some(Utc::now().with_timezone(&tz).offset().fix());
984
985        assert_eq!(
986            Ok(2001020312),
987            template.with_tz_offset(offset).render(&event)
988        );
989    }
990
991    fn sample_metric() -> Metric {
992        Metric::new(
993            "a-counter",
994            MetricKind::Absolute,
995            MetricValue::Counter { value: 1.1 },
996        )
997        .with_timestamp(Some(
998            Utc.with_ymd_and_hms(2002, 3, 4, 5, 6, 7)
999                .single()
1000                .expect("invalid timestamp"),
1001        ))
1002    }
1003
1004    #[test]
1005    fn strftime_error() {
1006        assert_eq!(
1007            Template::try_from("%E").unwrap_err(),
1008            TemplateParseError::StrftimeError
1009        );
1010    }
1011
1012    #[test]
1013    fn strftime_non_int_result() {
1014        let template = UnsignedIntTemplate::try_from("a-%s").unwrap();
1015        let ts = Utc.with_ymd_and_hms(2001, 2, 3, 4, 5, 6).unwrap();
1016
1017        let mut event = Event::Log(LogEvent::from("hello world"));
1018        event.as_mut_log().insert(event_path!("timestamp"), ts);
1019
1020        assert_eq!(
1021            Err(TemplateRenderingError::NotNumeric {
1022                input: "a-981173106".to_owned()
1023            }),
1024            template.render(&event)
1025        );
1026    }
1027}