fastrace_datadog/
lib.rs

1// Copyright 2020 TiKV Project Authors. Licensed under Apache-2.0.
2
3#![doc = include_str!("../README.md")]
4
5use std::collections::HashMap;
6use std::net::SocketAddr;
7
8use fastrace::collector::Reporter;
9use fastrace::prelude::*;
10use rmp_serde::Serializer;
11use serde::Serialize;
12
13/// [Datadog](https://docs.datadoghq.com/tracing/) reporter for `fastrace` in msgpack format.
14pub struct DatadogReporter {
15    agent_addr: SocketAddr,
16    service_name: String,
17    resource: String,
18    trace_type: String,
19}
20
21impl DatadogReporter {
22    pub fn new(
23        agent_addr: SocketAddr,
24        service_name: impl Into<String>,
25        resource: impl Into<String>,
26        trace_type: impl Into<String>,
27    ) -> DatadogReporter {
28        DatadogReporter {
29            agent_addr,
30            service_name: service_name.into(),
31            resource: resource.into(),
32            trace_type: trace_type.into(),
33        }
34    }
35
36    fn convert<'a>(&'a self, spans: &'a [SpanRecord]) -> Vec<DatadogSpan<'a>> {
37        spans
38            .iter()
39            .map(move |s| DatadogSpan {
40                name: &s.name,
41                service: &self.service_name,
42                trace_type: &self.trace_type,
43                resource: &self.resource,
44                start: s.begin_time_unix_ns as i64,
45                duration: s.duration_ns as i64,
46                meta: if s.properties.is_empty() {
47                    None
48                } else {
49                    Some(
50                        s.properties
51                            .iter()
52                            .map(|(k, v)| (k.as_ref(), v.as_ref()))
53                            .collect(),
54                    )
55                },
56                error_code: 0,
57                span_id: s.span_id.0,
58                trace_id: s.trace_id.0 as u64,
59                parent_id: s.parent_id.0,
60            })
61            .collect()
62    }
63
64    fn serialize(&self, spans: Vec<DatadogSpan>) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
65        let mut buf = vec![0b10010001];
66        spans.serialize(&mut Serializer::new(&mut buf).with_struct_map())?;
67        Ok(buf)
68    }
69
70    fn try_report(&self, spans: Vec<SpanRecord>) -> Result<(), Box<dyn std::error::Error>> {
71        let datadog_spans = self.convert(&spans);
72        let bytes = self.serialize(datadog_spans)?;
73        let client = reqwest::blocking::Client::new();
74        let _rep = client
75            .post(format!("http://{}/v0.4/traces", self.agent_addr))
76            .header("Datadog-Meta-Tracer-Version", "v1.27.0")
77            .header("Content-Type", "application/msgpack")
78            .body(bytes)
79            .send()?;
80        Ok(())
81    }
82}
83
84impl Reporter for DatadogReporter {
85    fn report(&mut self, spans: Vec<SpanRecord>) {
86        if spans.is_empty() {
87            return;
88        }
89
90        if let Err(err) = self.try_report(spans) {
91            log::error!("report to datadog failed: {err}");
92        }
93    }
94}
95
96#[derive(Serialize)]
97struct DatadogSpan<'a> {
98    name: &'a str,
99    service: &'a str,
100    #[serde(rename = "type")]
101    trace_type: &'a str,
102    resource: &'a str,
103    start: i64,
104    duration: i64,
105    #[serde(skip_serializing_if = "Option::is_none")]
106    meta: Option<HashMap<&'a str, &'a str>>,
107    error_code: i32,
108    span_id: u64,
109    trace_id: u64,
110    parent_id: u64,
111}