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