Skip to main content

tracing_loki/
builder.rs

1use super::event_channel;
2use super::BackgroundTask;
3use super::BackgroundTaskController;
4use super::Error;
5use super::ErrorI;
6use super::FormattedLabels;
7use super::Layer;
8use std::collections::hash_map;
9use std::collections::HashMap;
10use url::Url;
11
12/// Create a [`Builder`] for constructing a [`Layer`] and its corresponding
13/// [`BackgroundTask`].
14///
15/// See the crate's root documentation for an example.
16pub fn builder() -> Builder {
17    let mut http_headers = reqwest::header::HeaderMap::new();
18    http_headers.insert(
19        reqwest::header::CONTENT_TYPE,
20        reqwest::header::HeaderValue::from_static("application/x-protobuf"),
21    );
22    http_headers.insert(
23        reqwest::header::CONTENT_ENCODING,
24        reqwest::header::HeaderValue::from_static("snappy"),
25    );
26    Builder {
27        labels: FormattedLabels::new(),
28        extra_fields: HashMap::new(),
29        http_headers,
30    }
31}
32
33/// Builder for constructing a [`Layer`] and its corresponding
34/// [`BackgroundTask`].
35///
36/// See the crate's root documentation for an example.
37#[derive(Clone)]
38pub struct Builder {
39    labels: FormattedLabels,
40    extra_fields: HashMap<String, String>,
41    http_headers: reqwest::header::HeaderMap,
42}
43
44impl Builder {
45    /// Add a label to the logs sent to Loki through the built `Layer`.
46    ///
47    /// Labels are supposed to be closed categories with few possible values.
48    /// For example, `"environment"` with values `"ci"`, `"development"`,
49    /// `"staging"` or `"production"` would work well.
50    ///
51    /// For open categories, extra fields are a better fit. See
52    /// [`Builder::extra_field`].
53    ///
54    /// No two labels can share the same name, and the key `"level"` is
55    /// reserved for the log level.
56    ///
57    /// # Errors
58    ///
59    /// This function will return an error if a key is a duplicate or when the
60    /// key is `"level"`.
61    ///
62    /// # Example
63    ///
64    /// ```
65    /// # use tracing_loki::Error;
66    /// # fn main() -> Result<(), Error> {
67    /// let builder = tracing_loki::builder()
68    ///     .label("environment", "production")?;
69    /// # Ok(())
70    /// # }
71    /// ```
72    pub fn label<S: Into<String>, T: AsRef<str>>(
73        mut self,
74        key: S,
75        value: T,
76    ) -> Result<Builder, Error> {
77        self.labels.add(key.into(), value.as_ref())?;
78        Ok(self)
79    }
80    /// Set an extra field that is sent with all log records sent to Loki
81    /// through the built layer.
82    ///
83    /// Fields are meant to be used for open categories or closed categories
84    /// with many options. For example, `"run_id"` with randomly generated
85    /// [UUIDv4](https://en.wikipedia.org/w/index.php?title=Universally_unique_identifier&oldid=1105876960#Version_4_(random))s
86    /// would be a good fit for these extra fields.
87    ///
88    /// # Example
89    ///
90    /// ```
91    /// # use tracing_loki::Error;
92    /// # fn main() -> Result<(), Error> {
93    /// let builder = tracing_loki::builder()
94    ///     .extra_field("run_id", "5b6aedb4-e2c1-4ad9-b8a7-3ef92b5c8120")?;
95    /// # Ok(())
96    /// # }
97    /// ```
98    pub fn extra_field<S: Into<String>, T: Into<String>>(
99        mut self,
100        key: S,
101        value: T,
102    ) -> Result<Builder, Error> {
103        match self.extra_fields.entry(key.into()) {
104            hash_map::Entry::Occupied(o) => {
105                return Err(Error(ErrorI::DuplicateExtraField(o.key().clone())));
106            }
107            hash_map::Entry::Vacant(v) => {
108                v.insert(value.into());
109            }
110        }
111        Ok(self)
112    }
113    /// Set an extra HTTP header to be sent with all requests sent to Loki.
114    ///
115    /// This can be useful to set the `X-Scope-OrgID` header which Loki
116    /// processes as the tenant ID in a multi-tenant setup.
117    ///
118    /// # Example
119    ///
120    /// ```
121    /// # use tracing_loki::Error;
122    /// # fn main() -> Result<(), Error> {
123    /// let builder = tracing_loki::builder()
124    ///     // Set the tenant ID for Loki.
125    ///     .http_header("X-Scope-OrgID", "7662a206-fa0f-407f-abe9-261d652c750b")?;
126    /// # Ok(())
127    /// # }
128    /// ```
129    pub fn http_header<S: AsRef<str>, T: AsRef<str>>(
130        mut self,
131        key: S,
132        value: T,
133    ) -> Result<Builder, Error> {
134        let key = key.as_ref();
135        let value = value.as_ref();
136        if self
137            .http_headers
138            .insert(
139                reqwest::header::HeaderName::from_bytes(key.as_bytes())
140                    .map_err(|_| Error(ErrorI::InvalidHttpHeaderName(key.into())))?,
141                reqwest::header::HeaderValue::from_str(value)
142                    .map_err(|_| Error(ErrorI::InvalidHttpHeaderValue(key.into())))?,
143            )
144            .is_some()
145        {
146            return Err(Error(ErrorI::DuplicateHttpHeader(key.into())));
147        }
148        Ok(self)
149    }
150    /// Build the tracing [`Layer`] and its corresponding [`BackgroundTask`].
151    ///
152    /// The `loki_url` is the URL of the Loki server, like
153    /// `https://127.0.0.1:3100`.
154    ///
155    /// The [`Layer`] needs to be registered with a
156    /// [`tracing_subscriber::Registry`], and the [`BackgroundTask`] needs to
157    /// be [`tokio::spawn`]ed.
158    ///
159    /// **Note** that unlike the [`layer`](`crate::layer`) function, this
160    /// function **does not strip off** the path component of `loki_url` before
161    /// appending `/loki/api/v1/push`.
162    ///
163    /// See the crate's root documentation for an example.
164    pub fn build_url(self, loki_url: Url) -> Result<(Layer, BackgroundTask), Error> {
165        let (sender, receiver) = event_channel();
166        Ok((
167            Layer {
168                sender,
169                extra_fields: self.extra_fields,
170            },
171            BackgroundTask::new(loki_url, self.http_headers, receiver, &self.labels)?,
172        ))
173    }
174    /// Build the tracing [`Layer`], [`BackgroundTask`] and its
175    /// [`BackgroundTaskController`].
176    ///
177    /// The [`BackgroundTaskController`] can be used to signal the background
178    /// task to shut down.
179    ///
180    /// The `loki_url` is the URL of the Loki server, like
181    /// `https://127.0.0.1:3100`.
182    ///
183    /// The [`Layer`] needs to be registered with a
184    /// [`tracing_subscriber::Registry`], and the [`BackgroundTask`] needs to
185    /// be [`tokio::spawn`]ed.
186    ///
187    /// **Note** that unlike the [`layer`](`crate::layer`) function, this
188    /// function **does not strip off** the path component of `loki_url` before
189    /// appending `/loki/api/v1/push`.
190    ///
191    /// See the crate's root documentation for an example.
192    pub fn build_controller_url(
193        self,
194        loki_url: Url,
195    ) -> Result<(Layer, BackgroundTaskController, BackgroundTask), Error> {
196        let (sender, receiver) = event_channel();
197        Ok((
198            Layer {
199                sender: sender.clone(),
200                extra_fields: self.extra_fields,
201            },
202            BackgroundTaskController { sender },
203            BackgroundTask::new(loki_url, self.http_headers, receiver, &self.labels)?,
204        ))
205    }
206}