datadog_api_client/datadogV1/api/
api_logs.rs

1// Unless explicitly stated otherwise all files in this repository are licensed under the Apache-2.0 License.
2// This product includes software developed at Datadog (https://www.datadoghq.com/).
3// Copyright 2019-Present Datadog, Inc.
4use crate::datadog;
5use flate2::{
6    write::{GzEncoder, ZlibEncoder},
7    Compression,
8};
9use reqwest::header::{HeaderMap, HeaderValue};
10use serde::{Deserialize, Serialize};
11use std::io::Write;
12
13/// SubmitLogOptionalParams is a struct for passing parameters to the method [`LogsAPI::submit_log`]
14#[non_exhaustive]
15#[derive(Clone, Default, Debug)]
16pub struct SubmitLogOptionalParams {
17    /// HTTP header used to compress the media-type.
18    pub content_encoding: Option<crate::datadogV1::model::ContentEncoding>,
19    /// Log tags can be passed as query parameters with `text/plain` content type.
20    pub ddtags: Option<String>,
21}
22
23impl SubmitLogOptionalParams {
24    /// HTTP header used to compress the media-type.
25    pub fn content_encoding(mut self, value: crate::datadogV1::model::ContentEncoding) -> Self {
26        self.content_encoding = Some(value);
27        self
28    }
29    /// Log tags can be passed as query parameters with `text/plain` content type.
30    pub fn ddtags(mut self, value: String) -> Self {
31        self.ddtags = Some(value);
32        self
33    }
34}
35
36/// ListLogsError is a struct for typed errors of method [`LogsAPI::list_logs`]
37#[derive(Debug, Clone, Serialize, Deserialize)]
38#[serde(untagged)]
39pub enum ListLogsError {
40    LogsAPIErrorResponse(crate::datadogV1::model::LogsAPIErrorResponse),
41    APIErrorResponse(crate::datadogV1::model::APIErrorResponse),
42    UnknownValue(serde_json::Value),
43}
44
45/// SubmitLogError is a struct for typed errors of method [`LogsAPI::submit_log`]
46#[derive(Debug, Clone, Serialize, Deserialize)]
47#[serde(untagged)]
48pub enum SubmitLogError {
49    HTTPLogError(crate::datadogV1::model::HTTPLogError),
50    APIErrorResponse(crate::datadogV1::model::APIErrorResponse),
51    UnknownValue(serde_json::Value),
52}
53
54/// Search your logs and send them to your Datadog platform over HTTP. See the [Log Management page](<https://docs.datadoghq.com/logs/>) for more information.
55#[derive(Debug, Clone)]
56pub struct LogsAPI {
57    config: datadog::Configuration,
58    client: reqwest_middleware::ClientWithMiddleware,
59}
60
61impl Default for LogsAPI {
62    fn default() -> Self {
63        Self::with_config(datadog::Configuration::default())
64    }
65}
66
67impl LogsAPI {
68    pub fn new() -> Self {
69        Self::default()
70    }
71    pub fn with_config(config: datadog::Configuration) -> Self {
72        let mut reqwest_client_builder = reqwest::Client::builder();
73
74        if let Some(proxy_url) = &config.proxy_url {
75            let proxy = reqwest::Proxy::all(proxy_url).expect("Failed to parse proxy URL");
76            reqwest_client_builder = reqwest_client_builder.proxy(proxy);
77        }
78
79        let mut middleware_client_builder =
80            reqwest_middleware::ClientBuilder::new(reqwest_client_builder.build().unwrap());
81
82        if config.enable_retry {
83            struct RetryableStatus;
84            impl reqwest_retry::RetryableStrategy for RetryableStatus {
85                fn handle(
86                    &self,
87                    res: &Result<reqwest::Response, reqwest_middleware::Error>,
88                ) -> Option<reqwest_retry::Retryable> {
89                    match res {
90                        Ok(success) => reqwest_retry::default_on_request_success(success),
91                        Err(_) => None,
92                    }
93                }
94            }
95            let backoff_policy = reqwest_retry::policies::ExponentialBackoff::builder()
96                .build_with_max_retries(config.max_retries);
97
98            let retry_middleware =
99                reqwest_retry::RetryTransientMiddleware::new_with_policy_and_strategy(
100                    backoff_policy,
101                    RetryableStatus,
102                );
103
104            middleware_client_builder = middleware_client_builder.with(retry_middleware);
105        }
106
107        let client = middleware_client_builder.build();
108
109        Self { config, client }
110    }
111
112    pub fn with_client_and_config(
113        config: datadog::Configuration,
114        client: reqwest_middleware::ClientWithMiddleware,
115    ) -> Self {
116        Self { config, client }
117    }
118
119    /// List endpoint returns logs that match a log search query.
120    /// [Results are paginated][1].
121    ///
122    /// **If you are considering archiving logs for your organization,
123    /// consider use of the Datadog archive capabilities instead of the log list API.
124    /// See [Datadog Logs Archive documentation][2].**
125    ///
126    /// [1]: /logs/guide/collect-multiple-logs-with-pagination
127    /// [2]: <https://docs.datadoghq.com/logs/archives>
128    pub async fn list_logs(
129        &self,
130        body: crate::datadogV1::model::LogsListRequest,
131    ) -> Result<crate::datadogV1::model::LogsListResponse, datadog::Error<ListLogsError>> {
132        match self.list_logs_with_http_info(body).await {
133            Ok(response_content) => {
134                if let Some(e) = response_content.entity {
135                    Ok(e)
136                } else {
137                    Err(datadog::Error::Serde(serde::de::Error::custom(
138                        "response content was None",
139                    )))
140                }
141            }
142            Err(err) => Err(err),
143        }
144    }
145
146    /// List endpoint returns logs that match a log search query.
147    /// [Results are paginated][1].
148    ///
149    /// **If you are considering archiving logs for your organization,
150    /// consider use of the Datadog archive capabilities instead of the log list API.
151    /// See [Datadog Logs Archive documentation][2].**
152    ///
153    /// [1]: /logs/guide/collect-multiple-logs-with-pagination
154    /// [2]: <https://docs.datadoghq.com/logs/archives>
155    pub async fn list_logs_with_http_info(
156        &self,
157        body: crate::datadogV1::model::LogsListRequest,
158    ) -> Result<
159        datadog::ResponseContent<crate::datadogV1::model::LogsListResponse>,
160        datadog::Error<ListLogsError>,
161    > {
162        let local_configuration = &self.config;
163        let operation_id = "v1.list_logs";
164
165        let local_client = &self.client;
166
167        let local_uri_str = format!(
168            "{}/api/v1/logs-queries/list",
169            local_configuration.get_operation_host(operation_id)
170        );
171        let mut local_req_builder =
172            local_client.request(reqwest::Method::POST, local_uri_str.as_str());
173
174        // build headers
175        let mut headers = HeaderMap::new();
176        headers.insert("Content-Type", HeaderValue::from_static("application/json"));
177        headers.insert("Accept", HeaderValue::from_static("application/json"));
178
179        // build user agent
180        match HeaderValue::from_str(local_configuration.user_agent.as_str()) {
181            Ok(user_agent) => headers.insert(reqwest::header::USER_AGENT, user_agent),
182            Err(e) => {
183                log::warn!("Failed to parse user agent header: {e}, falling back to default");
184                headers.insert(
185                    reqwest::header::USER_AGENT,
186                    HeaderValue::from_static(datadog::DEFAULT_USER_AGENT.as_str()),
187                )
188            }
189        };
190
191        // build auth
192        if let Some(local_key) = local_configuration.auth_keys.get("apiKeyAuth") {
193            headers.insert(
194                "DD-API-KEY",
195                HeaderValue::from_str(local_key.key.as_str())
196                    .expect("failed to parse DD-API-KEY header"),
197            );
198        };
199        if let Some(local_key) = local_configuration.auth_keys.get("appKeyAuth") {
200            headers.insert(
201                "DD-APPLICATION-KEY",
202                HeaderValue::from_str(local_key.key.as_str())
203                    .expect("failed to parse DD-APPLICATION-KEY header"),
204            );
205        };
206
207        // build body parameters
208        let output = Vec::new();
209        let mut ser = serde_json::Serializer::with_formatter(output, datadog::DDFormatter);
210        if body.serialize(&mut ser).is_ok() {
211            if let Some(content_encoding) = headers.get("Content-Encoding") {
212                match content_encoding.to_str().unwrap_or_default() {
213                    "gzip" => {
214                        let mut enc = GzEncoder::new(Vec::new(), Compression::default());
215                        let _ = enc.write_all(ser.into_inner().as_slice());
216                        match enc.finish() {
217                            Ok(buf) => {
218                                local_req_builder = local_req_builder.body(buf);
219                            }
220                            Err(e) => return Err(datadog::Error::Io(e)),
221                        }
222                    }
223                    "deflate" => {
224                        let mut enc = ZlibEncoder::new(Vec::new(), Compression::default());
225                        let _ = enc.write_all(ser.into_inner().as_slice());
226                        match enc.finish() {
227                            Ok(buf) => {
228                                local_req_builder = local_req_builder.body(buf);
229                            }
230                            Err(e) => return Err(datadog::Error::Io(e)),
231                        }
232                    }
233                    "zstd1" => {
234                        let mut enc = zstd::stream::Encoder::new(Vec::new(), 0).unwrap();
235                        let _ = enc.write_all(ser.into_inner().as_slice());
236                        match enc.finish() {
237                            Ok(buf) => {
238                                local_req_builder = local_req_builder.body(buf);
239                            }
240                            Err(e) => return Err(datadog::Error::Io(e)),
241                        }
242                    }
243                    _ => {
244                        local_req_builder = local_req_builder.body(ser.into_inner());
245                    }
246                }
247            } else {
248                local_req_builder = local_req_builder.body(ser.into_inner());
249            }
250        }
251
252        local_req_builder = local_req_builder.headers(headers);
253        let local_req = local_req_builder.build()?;
254        log::debug!("request content: {:?}", local_req.body());
255        let local_resp = local_client.execute(local_req).await?;
256
257        let local_status = local_resp.status();
258        let local_content = local_resp.text().await?;
259        log::debug!("response content: {}", local_content);
260
261        if !local_status.is_client_error() && !local_status.is_server_error() {
262            match serde_json::from_str::<crate::datadogV1::model::LogsListResponse>(&local_content)
263            {
264                Ok(e) => {
265                    return Ok(datadog::ResponseContent {
266                        status: local_status,
267                        content: local_content,
268                        entity: Some(e),
269                    })
270                }
271                Err(e) => return Err(datadog::Error::Serde(e)),
272            };
273        } else {
274            let local_entity: Option<ListLogsError> = serde_json::from_str(&local_content).ok();
275            let local_error = datadog::ResponseContent {
276                status: local_status,
277                content: local_content,
278                entity: local_entity,
279            };
280            Err(datadog::Error::ResponseError(local_error))
281        }
282    }
283
284    /// Send your logs to your Datadog platform over HTTP. Limits per HTTP request are:
285    ///
286    /// - Maximum content size per payload (uncompressed): 5MB
287    /// - Maximum size for a single log: 1MB
288    /// - Maximum array size if sending multiple logs in an array: 1000 entries
289    ///
290    /// Any log exceeding 1MB is accepted and truncated by Datadog:
291    /// - For a single log request, the API truncates the log at 1MB and returns a 2xx.
292    /// - For a multi-logs request, the API processes all logs, truncates only logs larger than 1MB, and returns a 2xx.
293    ///
294    /// Datadog recommends sending your logs compressed.
295    /// Add the `Content-Encoding: gzip` header to the request when sending compressed logs.
296    ///
297    /// The status codes answered by the HTTP API are:
298    /// - 200: OK
299    /// - 400: Bad request (likely an issue in the payload formatting)
300    /// - 403: Permission issue (likely using an invalid API Key)
301    /// - 413: Payload too large (batch is above 5MB uncompressed)
302    /// - 5xx: Internal error, request should be retried after some time
303    pub async fn submit_log(
304        &self,
305        body: Vec<crate::datadogV1::model::HTTPLogItem>,
306        params: SubmitLogOptionalParams,
307    ) -> Result<std::collections::BTreeMap<String, serde_json::Value>, datadog::Error<SubmitLogError>>
308    {
309        match self.submit_log_with_http_info(body, params).await {
310            Ok(response_content) => {
311                if let Some(e) = response_content.entity {
312                    Ok(e)
313                } else {
314                    Err(datadog::Error::Serde(serde::de::Error::custom(
315                        "response content was None",
316                    )))
317                }
318            }
319            Err(err) => Err(err),
320        }
321    }
322
323    /// Send your logs to your Datadog platform over HTTP. Limits per HTTP request are:
324    ///
325    /// - Maximum content size per payload (uncompressed): 5MB
326    /// - Maximum size for a single log: 1MB
327    /// - Maximum array size if sending multiple logs in an array: 1000 entries
328    ///
329    /// Any log exceeding 1MB is accepted and truncated by Datadog:
330    /// - For a single log request, the API truncates the log at 1MB and returns a 2xx.
331    /// - For a multi-logs request, the API processes all logs, truncates only logs larger than 1MB, and returns a 2xx.
332    ///
333    /// Datadog recommends sending your logs compressed.
334    /// Add the `Content-Encoding: gzip` header to the request when sending compressed logs.
335    ///
336    /// The status codes answered by the HTTP API are:
337    /// - 200: OK
338    /// - 400: Bad request (likely an issue in the payload formatting)
339    /// - 403: Permission issue (likely using an invalid API Key)
340    /// - 413: Payload too large (batch is above 5MB uncompressed)
341    /// - 5xx: Internal error, request should be retried after some time
342    pub async fn submit_log_with_http_info(
343        &self,
344        body: Vec<crate::datadogV1::model::HTTPLogItem>,
345        params: SubmitLogOptionalParams,
346    ) -> Result<
347        datadog::ResponseContent<std::collections::BTreeMap<String, serde_json::Value>>,
348        datadog::Error<SubmitLogError>,
349    > {
350        let local_configuration = &self.config;
351        let operation_id = "v1.submit_log";
352
353        // unbox and build optional parameters
354        let content_encoding = params.content_encoding;
355        let ddtags = params.ddtags;
356
357        let local_client = &self.client;
358
359        let local_uri_str = format!(
360            "{}/v1/input",
361            local_configuration.get_operation_host(operation_id)
362        );
363        let mut local_req_builder =
364            local_client.request(reqwest::Method::POST, local_uri_str.as_str());
365
366        if let Some(ref local_query_param) = ddtags {
367            local_req_builder =
368                local_req_builder.query(&[("ddtags", &local_query_param.to_string())]);
369        };
370
371        // build headers
372        let mut headers = HeaderMap::new();
373        headers.insert("Content-Type", HeaderValue::from_static("application/json"));
374        headers.insert("Accept", HeaderValue::from_static("application/json"));
375
376        if let Some(ref local) = content_encoding {
377            headers.insert(
378                "Content-Encoding",
379                local
380                    .to_string()
381                    .parse()
382                    .expect("failed to parse Content-Encoding header"),
383            );
384        }
385
386        // build user agent
387        match HeaderValue::from_str(local_configuration.user_agent.as_str()) {
388            Ok(user_agent) => headers.insert(reqwest::header::USER_AGENT, user_agent),
389            Err(e) => {
390                log::warn!("Failed to parse user agent header: {e}, falling back to default");
391                headers.insert(
392                    reqwest::header::USER_AGENT,
393                    HeaderValue::from_static(datadog::DEFAULT_USER_AGENT.as_str()),
394                )
395            }
396        };
397
398        // build auth
399        if let Some(local_key) = local_configuration.auth_keys.get("apiKeyAuth") {
400            headers.insert(
401                "DD-API-KEY",
402                HeaderValue::from_str(local_key.key.as_str())
403                    .expect("failed to parse DD-API-KEY header"),
404            );
405        };
406
407        // build body parameters
408        let output = Vec::new();
409        let mut ser = serde_json::Serializer::with_formatter(output, datadog::DDFormatter);
410        if body.serialize(&mut ser).is_ok() {
411            if let Some(content_encoding) = headers.get("Content-Encoding") {
412                match content_encoding.to_str().unwrap_or_default() {
413                    "gzip" => {
414                        let mut enc = GzEncoder::new(Vec::new(), Compression::default());
415                        let _ = enc.write_all(ser.into_inner().as_slice());
416                        match enc.finish() {
417                            Ok(buf) => {
418                                local_req_builder = local_req_builder.body(buf);
419                            }
420                            Err(e) => return Err(datadog::Error::Io(e)),
421                        }
422                    }
423                    "deflate" => {
424                        let mut enc = ZlibEncoder::new(Vec::new(), Compression::default());
425                        let _ = enc.write_all(ser.into_inner().as_slice());
426                        match enc.finish() {
427                            Ok(buf) => {
428                                local_req_builder = local_req_builder.body(buf);
429                            }
430                            Err(e) => return Err(datadog::Error::Io(e)),
431                        }
432                    }
433                    "zstd1" => {
434                        let mut enc = zstd::stream::Encoder::new(Vec::new(), 0).unwrap();
435                        let _ = enc.write_all(ser.into_inner().as_slice());
436                        match enc.finish() {
437                            Ok(buf) => {
438                                local_req_builder = local_req_builder.body(buf);
439                            }
440                            Err(e) => return Err(datadog::Error::Io(e)),
441                        }
442                    }
443                    _ => {
444                        local_req_builder = local_req_builder.body(ser.into_inner());
445                    }
446                }
447            } else {
448                local_req_builder = local_req_builder.body(ser.into_inner());
449            }
450        }
451
452        local_req_builder = local_req_builder.headers(headers);
453        let local_req = local_req_builder.build()?;
454        log::debug!("request content: {:?}", local_req.body());
455        let local_resp = local_client.execute(local_req).await?;
456
457        let local_status = local_resp.status();
458        let local_content = local_resp.text().await?;
459        log::debug!("response content: {}", local_content);
460
461        if !local_status.is_client_error() && !local_status.is_server_error() {
462            match serde_json::from_str::<std::collections::BTreeMap<String, serde_json::Value>>(
463                &local_content,
464            ) {
465                Ok(e) => {
466                    return Ok(datadog::ResponseContent {
467                        status: local_status,
468                        content: local_content,
469                        entity: Some(e),
470                    })
471                }
472                Err(e) => return Err(datadog::Error::Serde(e)),
473            };
474        } else {
475            let local_entity: Option<SubmitLogError> = serde_json::from_str(&local_content).ok();
476            let local_error = datadog::ResponseContent {
477                status: local_status,
478                content: local_content,
479                entity: local_entity,
480            };
481            Err(datadog::Error::ResponseError(local_error))
482        }
483    }
484}