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