worker_cf_sentry/
client.rs

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
114
115
116
117
118
119
120
121
122
123
use crate::toucan_sys;
use worker::js_sys;

macro_rules! set {
    ($obj:expr, $k:expr, $v:expr) => {
        js_sys::Reflect::set(&$obj, &$k.into(), &$v.into()).unwrap();
    };
}

/// Sentry configuration
pub struct SentryConfig {
    dsn: String,
    environment: Option<String>,
    cf_access_client_id: String,
    cf_access_client_secret: String,
}

impl SentryConfig {
    /// Build the Sentry configuration from Worker environment.
    ///
    /// It assumes you have the following vars:
    /// - SENTRY_DSN
    /// - SENTRY_CF_ACCESS_CLIENT_ID
    /// - SENTRY_ENVIRONMENT (optional)
    ///
    /// and secrets:
    /// - SENTRY_CF_ACCESS_CLIENT_SECRET
    pub fn from_env(env: &worker::Env) -> Result<Self, worker::Error> {
        let dsn = env.var("SENTRY_DSN")?.to_string();
        let environment = env.var("SENTRY_ENVIRONMENT").ok().map(|v| v.to_string());
        let cf_access_client_id = env.var("SENTRY_CF_ACCESS_CLIENT_ID")?.to_string();
        let cf_access_client_secret = env.secret("SENTRY_CF_ACCESS_CLIENT_SECRET")?.to_string();

        Ok(Self {
            dsn,
            environment,
            cf_access_client_id,
            cf_access_client_secret,
        })
    }
}

/// Sentry client
pub struct SentryClient {
    inner: toucan_sys::Toucan,
}

impl SentryClient {
    /// Create a Sentry client
    pub fn new(
        config: SentryConfig,
        request: &worker::Request,
        context: &worker::Context,
    ) -> SentryClient {
        let transport_options_headers = js_sys::Object::new();
        set!(
            transport_options_headers,
            "CF-Access-Client-ID",
            config.cf_access_client_id
        );
        set!(
            transport_options_headers,
            "CF-Access-Client-Secret",
            config.cf_access_client_secret
        );

        let transport_options = js_sys::Object::new();
        set!(transport_options, "headers", transport_options_headers);

        let args = js_sys::Object::new();
        set!(args, "dsn", config.dsn);
        set!(args, "transportOptions", transport_options);

        let allowed_headers = js_sys::Array::new();
        allowed_headers.push(&"user-agent".into());
        allowed_headers.push(&"accept-encoding".into());
        allowed_headers.push(&"accept-language".into());
        allowed_headers.push(&"cf-ray".into());
        allowed_headers.push(&"content-length".into());
        allowed_headers.push(&"content-type".into());
        allowed_headers.push(&"x-real-ip".into());
        allowed_headers.push(&"host".into());
        set!(args, "allowedHeaders", allowed_headers);

        js_sys::Reflect::set(&args, &"request".into(), request.inner()).unwrap();
        js_sys::Reflect::set(&args, &"context".into(), context.as_ref()).unwrap();
        set!(args, "debug", false);

        if let Some(ref environment) = config.environment {
            set!(args, "environment", environment);
        }

        let toucan = toucan_sys::Toucan::new(args);

        SentryClient { inner: toucan }
    }

    /// See Sentry's setTag documentation
    pub fn set_tag(&self, key: &str, value: &str) {
        self.inner.set_tag(key, value);
    }

    /// See Sentry's setContext documentation
    pub fn set_context(&self, name: &str, value: js_sys::Object) {
        self.inner.set_context(name, value);
    }

    /// See Sentry's captureMessage documentation
    pub fn capture_message(&self, message: &str) {
        self.inner.capture_message(message);
    }

    /// See Sentry's captureException documentation
    pub fn capture_exception<T: std::error::Error + ?Sized>(&self, error: &T) {
        let js_error = js_sys::Error::new(&error.to_string());
        self.inner.capture_exception(js_error);
    }

    /// Same as `capture_exception` but with a `js_sys::Error`.
    pub fn capture_js_error(&self, js_error: js_sys::Error) {
        self.inner.capture_exception(js_error);
    }
}