relay_event_schema/protocol/contexts/
otel.rs

1use relay_protocol::{Annotated, Empty, FromValue, IntoValue, Object, Value};
2
3use crate::processor::ProcessValue;
4
5/// OpenTelemetry Context
6///
7/// If an event has this context, it was generated from an OpenTelemetry signal (trace, metric, log).
8#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
9pub struct OtelContext {
10    /// Attributes of the OpenTelemetry span that maps to a Sentry event.
11    ///
12    /// <https://github.com/open-telemetry/opentelemetry-proto/blob/724e427879e3d2bae2edc0218fff06e37b9eb46e/opentelemetry/proto/trace/v1/trace.proto#L174-L186>
13    #[metastructure(pii = "maybe", max_depth = 7, max_bytes = 8192)]
14    attributes: Annotated<Object<Value>>,
15
16    /// Information about an OpenTelemetry resource.
17    ///
18    /// <https://github.com/open-telemetry/opentelemetry-proto/blob/724e427879e3d2bae2edc0218fff06e37b9eb46e/opentelemetry/proto/resource/v1/resource.proto>
19    #[metastructure(pii = "maybe", max_depth = 7, max_bytes = 8192)]
20    resource: Annotated<Object<Value>>,
21
22    /// Additional arbitrary fields for forwards compatibility.
23    #[metastructure(additional_properties, retain = true, pii = "maybe")]
24    pub other: Object<Value>,
25}
26
27impl super::DefaultContext for OtelContext {
28    fn default_key() -> &'static str {
29        "otel"
30    }
31
32    fn from_context(context: super::Context) -> Option<Self> {
33        match context {
34            super::Context::Otel(c) => Some(*c),
35            _ => None,
36        }
37    }
38
39    fn cast(context: &super::Context) -> Option<&Self> {
40        match context {
41            super::Context::Otel(c) => Some(c),
42            _ => None,
43        }
44    }
45
46    fn cast_mut(context: &mut super::Context) -> Option<&mut Self> {
47        match context {
48            super::Context::Otel(c) => Some(c),
49            _ => None,
50        }
51    }
52
53    fn into_context(self) -> super::Context {
54        super::Context::Otel(Box::new(self))
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61    use crate::protocol::Context;
62
63    #[test]
64    fn test_otel_context_roundtrip() {
65        let json = r#"{
66  "attributes": {
67    "app.payment.amount": 394.25,
68    "rpc.grpc.status_code": "1",
69    "rpc.method": "Charge",
70    "rpc.service": "hipstershop.PaymentService",
71    "rpc.system": "grpc"
72  },
73  "resource": {
74    "process.command": "/usr/src/app/index.js",
75    "process.command_line": "/usr/local/bin/node /usr/src/app/index.js",
76    "process.executable.name": "node",
77    "process.pid": 1,
78    "process.runtime.description": "Node.js",
79    "process.runtime.name": "nodejs",
80    "process.runtime.version": "16.18.0",
81    "service.name": "paymentservice",
82    "telemetry.sdk.language": "nodejs",
83    "telemetry.sdk.name": "opentelemetry",
84    "telemetry.sdk.version": "1.7.0"
85  },
86  "other": "value",
87  "type": "otel"
88}"#;
89        let context = Annotated::new(Context::Otel(Box::new(OtelContext {
90            attributes: Annotated::new(Object::from([
91                (
92                    "app.payment.amount".to_string(),
93                    Annotated::new(Value::F64(394.25)),
94                ),
95                (
96                    "rpc.grpc.status_code".to_string(),
97                    Annotated::new(Value::String("1".to_string())),
98                ),
99                (
100                    "rpc.method".to_string(),
101                    Annotated::new(Value::String("Charge".to_string())),
102                ),
103                (
104                    "rpc.service".to_string(),
105                    Annotated::new(Value::String("hipstershop.PaymentService".to_string())),
106                ),
107                (
108                    "rpc.system".to_string(),
109                    Annotated::new(Value::String("grpc".to_string())),
110                ),
111            ])),
112            resource: Annotated::new(Object::from([
113                (
114                    "process.command".to_string(),
115                    Annotated::new(Value::String("/usr/src/app/index.js".to_string())),
116                ),
117                (
118                    "process.command_line".to_string(),
119                    Annotated::new(Value::String(
120                        "/usr/local/bin/node /usr/src/app/index.js".to_string(),
121                    )),
122                ),
123                (
124                    "process.executable.name".to_string(),
125                    Annotated::new(Value::String("node".to_string())),
126                ),
127                ("process.pid".to_string(), Annotated::new(Value::I64(1))),
128                (
129                    "process.runtime.description".to_string(),
130                    Annotated::new(Value::String("Node.js".to_string())),
131                ),
132                (
133                    "process.runtime.name".to_string(),
134                    Annotated::new(Value::String("nodejs".to_string())),
135                ),
136                (
137                    "process.runtime.version".to_string(),
138                    Annotated::new(Value::String("16.18.0".to_string())),
139                ),
140                (
141                    "service.name".to_string(),
142                    Annotated::new(Value::String("paymentservice".to_string())),
143                ),
144                (
145                    "telemetry.sdk.language".to_string(),
146                    Annotated::new(Value::String("nodejs".to_string())),
147                ),
148                (
149                    "telemetry.sdk.name".to_string(),
150                    Annotated::new(Value::String("opentelemetry".to_string())),
151                ),
152                (
153                    "telemetry.sdk.version".to_string(),
154                    Annotated::new(Value::String("1.7.0".to_string())),
155                ),
156            ])),
157            other: {
158                let mut map = Object::new();
159                map.insert(
160                    "other".to_string(),
161                    Annotated::new(Value::String("value".to_string())),
162                );
163                map
164            },
165        })));
166
167        assert_eq!(context, Annotated::from_json(json).unwrap());
168        assert_eq!(json, context.to_json_pretty().unwrap());
169    }
170}