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