1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
use std::collections::HashMap;

use fastly::Request;
use serde::Serialize;
use time::OffsetDateTime;

#[derive(Serialize)]
pub struct EventPayload {
    pub event_id: String,
    #[serde(rename = "type")]
    pub event_type: String,
    pub timestamp: OffsetDateTime,
    pub platform: Platform,
    pub level: Level,
    pub transaction: Option<String>,
    pub server_name: Option<String>,
    pub release: Option<String>,
    pub environment: Option<String>,
    pub exception: Vec<Exception>,
    pub request: Option<RequestMetadata>,
}

#[derive(Serialize)]
pub struct Exception {
    #[serde(rename = "type")]
    name: String,
    value: String,
}

#[derive(Serialize)]
pub struct RequestMetadata {
    method: String,
    url: String,
    headers: HashMap<String, String>,
    env: HashMap<String, String>,
}

#[derive(Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Platform {
    C,
    Native,
    Other,
}

#[derive(Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Level {
    Fatal,
    Error,
    Warning,
    Info,
    Debug,
}

impl Default for EventPayload {
    fn default() -> Self {
        EventPayload {
            event_id: uuid::Uuid::new_v4().to_string(),
            event_type: "event".to_string(),
            timestamp: OffsetDateTime::now_utc(),
            platform: Platform::Other,
            level: Level::Fatal,
            transaction: None,
            server_name: Some(std::env::var("FASTLY_HOSTNAME").unwrap()),
            release: Some(std::env::var("FASTLY_SERVICE_VERSION").unwrap()),
            environment: Some(std::env::var("FASTLY_SERVICE_ID").unwrap()),
            exception: Vec::new(),
            request: None,
        }
    }
}

impl<T: std::error::Error> From<T> for EventPayload {
    fn from(error: T) -> Self {
        EventPayload {
            exception: vec![Exception {
                name: format!("{:?}", error)
                    .chars()
                    .take_while(|&ch| ch != '(' && ch != ' ')
                    .collect::<String>(),
                value: error.to_string(),
            }],
            ..Default::default()
        }
    }
}

impl From<&Request> for RequestMetadata {
    fn from(request: &Request) -> Self {
        let mut headers = HashMap::new();

        request.get_header_names().for_each(|k| {
            headers.insert(
                k.to_string(),
                request.get_header(k).unwrap().to_str().unwrap().to_string(),
            );
        });

        let mut env = HashMap::new();

        if let Some(addr) = request.get_client_ip_addr() {
            env.insert("REMOTE_ADDR".to_string(), addr.to_string());
        }

        RequestMetadata {
            method: request.get_method().to_string(),
            url: request.get_url().to_string(),
            headers,
            env,
        }
    }
}