libhoney/
event.rs

1use std::collections::HashMap;
2
3use chrono::prelude::{DateTime, Utc};
4use log::info;
5use rand::Rng;
6
7use crate::client;
8use crate::errors::{Error, Result};
9use crate::fields::FieldHolder;
10use crate::sender::Sender;
11use crate::Value;
12
13/// `Metadata` is a type alias for an optional json serialisable value
14pub type Metadata = Option<Value>;
15
16/// `Event` is used to hold data that can be sent to Honeycomb. It can also specify
17/// overrides of the config settings (`client::Options`).
18#[derive(Debug, Clone)]
19pub struct Event {
20    pub(crate) options: client::Options,
21    pub(crate) timestamp: DateTime<Utc>,
22    pub(crate) fields: HashMap<String, Value>,
23    pub(crate) metadata: Metadata,
24    sent: bool,
25}
26
27impl FieldHolder for Event {
28    fn add(&mut self, data: HashMap<String, Value>) {
29        if !self.sent {
30            self.fields.extend(data);
31        }
32    }
33
34    /// add_field adds a field to the current (event/builder) fields
35    fn add_field(&mut self, name: &str, value: Value) {
36        if !self.sent {
37            self.fields.insert(name.to_string(), value);
38        }
39    }
40
41    /// add_func iterates over the results from func (until Err) and adds the results to
42    /// the event/builder fields
43    fn add_func<F>(&mut self, func: F)
44    where
45        F: Fn() -> Result<(String, Value)>,
46    {
47        if !self.sent {
48            while let Ok((name, value)) = func() {
49                self.add_field(&name, value);
50            }
51        }
52    }
53}
54
55impl Event {
56    /// new creates a new event with the passed ClientOptions
57    pub fn new(options: &client::Options) -> Self {
58        Self {
59            options: options.clone(),
60            timestamp: Utc::now(),
61            fields: HashMap::new(),
62            metadata: None,
63            sent: false,
64        }
65    }
66
67    /// send dispatches the event to be sent to Honeycomb, sampling if necessary.
68    ///
69    /// If you have sampling enabled (i.e. sample_rate >1), send will only actually
70    /// transmit data with a probability of 1/sample_rate. No error is returned whether or
71    /// not traffic is sampled, however, the Response sent down the response channel will
72    /// indicate the event was sampled in the errors Err field.
73    ///
74    /// Send inherits the values of required fields from ClientOptions. If any required
75    /// fields are specified in neither ClientOptions nor the Event, send will return an
76    /// error. Required fields are api_host, api_key, and dataset. Values specified in an
77    /// Event override ClientOptions.
78    ///
79    /// Once you send an event, any addition calls to add data to that event will return
80    /// without doing anything. Once the event is sent, it becomes immutable.
81    pub fn send<T: Sender>(&mut self, client: &mut client::Client<T>) -> Result<()> {
82        if self.should_drop() {
83            info!("dropping event due to sampling");
84            return Ok(());
85        }
86        self.send_presampled(client)
87    }
88
89    /// `send_presampled` dispatches the event to be sent to Honeycomb.
90    ///
91    /// Sampling is assumed to have already happened. `send_presampled` will dispatch
92    /// every event handed to it, and pass along the sample rate. Use this instead of
93    /// `send()` when the calling function handles the logic around which events to drop
94    /// when sampling.
95    ///
96    /// `send_presampled` inherits the values of required fields from `Config`. If any
97    /// required fields are specified in neither `Config` nor the `Event`, `send` will
98    /// return an error.  Required fields are `api_host`, `api_key`, and `dataset`. Values
99    /// specified in an `Event` override `Config`.
100    ///
101    /// Once you `send` an event, any addition calls to add data to that event will return
102    /// without doing anything. Once the event is sent, it becomes immutable.
103    pub fn send_presampled<T: Sender>(&mut self, client: &mut client::Client<T>) -> Result<()> {
104        if self.fields.is_empty() {
105            return Err(Error::missing_event_fields());
106        }
107
108        if self.options.api_host.is_empty() {
109            return Err(Error::missing_option("api_host", "can't send to Honeycomb"));
110        }
111
112        if self.options.api_key.is_empty() {
113            return Err(Error::missing_option("api_key", "can't send to Honeycomb"));
114        }
115
116        if self.options.dataset.is_empty() {
117            return Err(Error::missing_option("dataset", "can't send to Honeycomb"));
118        }
119
120        self.sent = true;
121        client.transmission.send(self.clone());
122        Ok(())
123    }
124
125    /// Set options sample_rate on the event
126    pub fn set_sample_rate(&mut self, sample_rate: usize) {
127        self.options.sample_rate = sample_rate;
128    }
129
130    /// Set timestamp on the event
131    pub fn set_timestamp(&mut self, timestamp: DateTime<Utc>) {
132        self.timestamp = timestamp;
133    }
134
135    /// Set metadata on the event
136    pub fn set_metadata(&mut self, metadata: Metadata) {
137        self.metadata = metadata;
138    }
139
140    /// Get event metadata
141    pub fn metadata(&self) -> Metadata {
142        self.metadata.clone()
143    }
144
145    /// Get event fields
146    pub fn fields(&self) -> HashMap<String, Value> {
147        self.fields.clone()
148    }
149
150    /// Get event fields (mutable)
151    pub fn get_fields_mut(&mut self) -> &mut HashMap<String, Value> {
152        &mut self.fields
153    }
154
155    fn should_drop(&self) -> bool {
156        if self.options.sample_rate <= 1 {
157            return false;
158        }
159        rand::thread_rng().gen_range(0..self.options.sample_rate) != 0
160    }
161
162    pub(crate) fn stop_event() -> Self {
163        let mut h: HashMap<String, Value> = HashMap::new();
164        h.insert("internal_stop_event".to_string(), Value::Null);
165
166        Self {
167            options: client::Options::default(),
168            timestamp: Utc::now(),
169            fields: h,
170            metadata: None,
171            sent: false,
172        }
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use reqwest::StatusCode;
179
180    use super::*;
181    use crate::client;
182
183    #[test]
184    fn test_add() {
185        let mut e = Event::new(&client::Options {
186            api_key: "some_api_key".to_string(),
187            ..client::Options::default()
188        });
189        let now = Value::String(Utc::now().to_rfc3339());
190        e.add_field("my_timestamp", now.clone());
191
192        assert_eq!(e.options.api_key, "some_api_key");
193        assert_eq!(e.fields["my_timestamp"], now);
194    }
195
196    #[test]
197    fn test_send() {
198        use crate::transmission;
199
200        let api_host = &mockito::server_url();
201        let _m = mockito::mock(
202            "POST",
203            mockito::Matcher::Regex(r"/1/batch/(.*)$".to_string()),
204        )
205        .with_status(200)
206        .with_header("content-type", "application/json")
207        .with_body("[{ \"status\": 200 }]")
208        .create();
209
210        let options = client::Options {
211            api_key: "some api key".to_string(),
212            api_host: api_host.to_string(),
213            ..client::Options::default()
214        };
215
216        let mut client = client::Client::new(
217            options.clone(),
218            transmission::Transmission::new(transmission::Options {
219                max_batch_size: 1,
220                ..transmission::Options::default()
221            })
222            .unwrap(),
223        );
224
225        let mut e = Event::new(&options);
226        e.add_field("field_name", Value::String("field_value".to_string()));
227        e.send(&mut client).unwrap();
228
229        if let Some(only) = client.transmission.responses().iter().next() {
230            assert_eq!(only.status_code, Some(StatusCode::OK));
231        }
232        client.close().unwrap();
233    }
234
235    #[test]
236    fn test_empty() {
237        use crate::errors::ErrorKind;
238        use crate::transmission;
239
240        let api_host = &mockito::server_url();
241        let _m = mockito::mock(
242            "POST",
243            mockito::Matcher::Regex(r"/1/batch/(.*)$".to_string()),
244        )
245        .with_status(200)
246        .with_header("content-type", "application/json")
247        .with_body("[{ \"status\": 200 }]")
248        .create();
249
250        let mut client = client::Client::new(
251            client::Options {
252                api_key: "some api key".to_string(),
253                api_host: api_host.to_string(),
254                ..client::Options::default()
255            },
256            transmission::Transmission::new(transmission::Options {
257                max_batch_size: 1,
258                ..transmission::Options::default()
259            })
260            .unwrap(),
261        );
262
263        let mut e = client.new_event();
264        assert_eq!(
265            e.send(&mut client).err().unwrap().kind,
266            ErrorKind::MissingEventFields
267        );
268        client.close().unwrap();
269    }
270}