relay_event_schema/protocol/ourlog/
mod.rs1use relay_protocol::{
2 Annotated, Empty, FromValue, Getter, IntoValue, Object, SkipSerialization, Value,
3};
4use std::collections::BTreeMap;
5use std::fmt::{self, Display};
6
7use serde::{Deserialize, Serialize, Serializer};
8
9use crate::processor::ProcessValue;
10use crate::protocol::{Attributes, SpanId, Timestamp, TraceId};
11
12pub mod container;
13
14#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
15#[metastructure(process_func = "process_ourlog", value_type = "OurLog")]
16pub struct OurLog {
17 #[metastructure(required = true)]
19 pub timestamp: Annotated<Timestamp>,
20
21 #[metastructure(required = true, trim = false)]
23 pub trace_id: Annotated<TraceId>,
24
25 #[metastructure(required = false, trim = false)]
27 pub span_id: Annotated<SpanId>,
28
29 #[metastructure(required = true)]
31 pub level: Annotated<OurLogLevel>,
32
33 #[metastructure(required = true, pii = "maybe", trim = false)]
35 pub body: Annotated<String>,
36
37 #[metastructure(pii = "maybe", trim = false)]
39 pub attributes: Annotated<Attributes>,
40
41 #[metastructure(additional_properties, retain = true, pii = "maybe")]
43 pub other: Object<Value>,
44}
45
46impl Getter for OurLog {
47 fn get_value(&self, path: &str) -> Option<relay_protocol::Val<'_>> {
48 Some(match path.strip_prefix("log.")? {
49 "body" => self.body.as_str()?.into(),
50 path => {
51 let key = path.strip_prefix("attributes.")?;
52 let key = key.strip_suffix(".value")?;
53 self.attributes.value()?.get_value(key)?.into()
54 }
55 })
56 }
57}
58
59#[derive(Clone, Debug, Default, Serialize, Deserialize)]
64pub struct OurLogHeader {
65 pub byte_size: Option<u64>,
70
71 #[serde(flatten)]
73 pub other: BTreeMap<String, Value>,
74}
75
76#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
77pub enum OurLogLevel {
78 Trace,
79 Debug,
80 Info,
81 Warn,
82 Error,
83 Fatal,
84 Unknown(String),
86}
87
88impl OurLogLevel {
89 fn as_str(&self) -> &str {
90 match self {
91 OurLogLevel::Trace => "trace",
92 OurLogLevel::Debug => "debug",
93 OurLogLevel::Info => "info",
94 OurLogLevel::Warn => "warn",
95 OurLogLevel::Error => "error",
96 OurLogLevel::Fatal => "fatal",
97 OurLogLevel::Unknown(s) => s.as_str(),
98 }
99 }
100}
101
102impl Display for OurLogLevel {
103 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104 write!(f, "{}", self.as_str())
105 }
106}
107
108impl From<String> for OurLogLevel {
109 fn from(value: String) -> Self {
110 match value.as_str() {
111 "trace" => OurLogLevel::Trace,
112 "debug" => OurLogLevel::Debug,
113 "info" => OurLogLevel::Info,
114 "warn" => OurLogLevel::Warn,
115 "error" => OurLogLevel::Error,
116 "fatal" => OurLogLevel::Fatal,
117 _ => OurLogLevel::Unknown(value),
118 }
119 }
120}
121
122impl FromValue for OurLogLevel {
123 fn from_value(value: Annotated<Value>) -> Annotated<Self> {
124 match String::from_value(value) {
125 Annotated(Some(value), meta) => Annotated(Some(value.into()), meta),
126 Annotated(None, meta) => Annotated(None, meta),
127 }
128 }
129}
130
131impl IntoValue for OurLogLevel {
132 fn into_value(self) -> Value {
133 Value::String(self.to_string())
134 }
135
136 fn serialize_payload<S>(&self, s: S, _behavior: SkipSerialization) -> Result<S::Ok, S::Error>
137 where
138 Self: Sized,
139 S: Serializer,
140 {
141 Serialize::serialize(self.as_str(), s)
142 }
143}
144
145impl ProcessValue for OurLogLevel {}
146
147impl Empty for OurLogLevel {
148 #[inline]
149 fn is_empty(&self) -> bool {
150 false
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157 use relay_protocol::SerializableAnnotated;
158
159 #[test]
160 fn test_ourlog_serialization() {
161 let json = r#"{
162 "timestamp": 1544719860.0,
163 "trace_id": "5b8efff798038103d269b633813fc60c",
164 "span_id": "eee19b7ec3c1b174",
165 "level": "info",
166 "body": "Example log record",
167 "attributes": {
168 "boolean.attribute": {
169 "value": true,
170 "type": "boolean"
171 },
172 "double.attribute": {
173 "value": 1.23,
174 "type": "double"
175 },
176 "string.attribute": {
177 "value": "some string",
178 "type": "string"
179 },
180 "sentry.severity_text": {
181 "value": "info",
182 "type": "string"
183 },
184 "sentry.severity_number": {
185 "value": "10",
186 "type": "integer"
187 },
188 "sentry.observed_timestamp_nanos": {
189 "value": "1544712660300000000",
190 "type": "integer"
191 }
192 }
193 }"#;
194
195 let data = Annotated::<OurLog>::from_json(json).unwrap();
196
197 insta::assert_json_snapshot!(SerializableAnnotated(&data), @r###"
198 {
199 "timestamp": 1544719860.0,
200 "trace_id": "5b8efff798038103d269b633813fc60c",
201 "span_id": "eee19b7ec3c1b174",
202 "level": "info",
203 "body": "Example log record",
204 "attributes": {
205 "boolean.attribute": {
206 "type": "boolean",
207 "value": true
208 },
209 "double.attribute": {
210 "type": "double",
211 "value": 1.23
212 },
213 "sentry.observed_timestamp_nanos": {
214 "type": "integer",
215 "value": "1544712660300000000"
216 },
217 "sentry.severity_number": {
218 "type": "integer",
219 "value": "10"
220 },
221 "sentry.severity_text": {
222 "type": "string",
223 "value": "info"
224 },
225 "string.attribute": {
226 "type": "string",
227 "value": "some string"
228 }
229 }
230 }
231 "###);
232 }
233
234 #[test]
235 fn test_invalid_int_attribute() {
236 let json = r#"{
237 "timestamp": 1544719860.0,
238 "trace_id": "5b8efff798038103d269b633813fc60c",
239 "span_id": "eee19b7ec3c1b174",
240 "level": "info",
241 "body": "Example log record",
242 "attributes": {
243 "sentry.severity_number": {
244 "value": 10,
245 "type": "integer"
246 }
247 }
248 }"#;
249
250 let data = Annotated::<OurLog>::from_json(json).unwrap();
251
252 insta::assert_json_snapshot!(SerializableAnnotated(&data), @r###"
253 {
254 "timestamp": 1544719860.0,
255 "trace_id": "5b8efff798038103d269b633813fc60c",
256 "span_id": "eee19b7ec3c1b174",
257 "level": "info",
258 "body": "Example log record",
259 "attributes": {
260 "sentry.severity_number": {
261 "type": "integer",
262 "value": 10
263 }
264 }
265 }
266 "###);
267 }
268}