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}