Collecting data with Trackers and Webhooks

  1. Home
  2. Docs
  3. Collecting data with Trackers and Webhooks
  4. Trackers – collecting data from your own applications
  5. C++ Tracker
  6. Emitters

Emitters

Emitters are responsible for sending events to the collector. Each tracker is given a single emitter. Once the emitter receives an event from the Tracker a few things start to happen:

  • The event is added to a local SQLite3 database or custom event store (blocking execution).
  • A long running daemon thread is started which will continue to send events as long as they can be found in the database (asynchronous).
  • The emitter loop will grab a range of events from the database up until the batch_size passed to it as configuration.
  • The emitter will send all of these events as determined by the Request, Protocol and ByteLimits.
    • Each request is sent in its thread.
  • Once sent, it will process the results of all the requests sent and will remove all successfully sent events from the database. If the request failed, the events will be retried after a retry delay (see below).

In Initialisation, we discussed how to create a tracker with an emitter configured using EmitterConfiguration or by instantiating an Emitter instance directly. Both of these options provide the same configuration functionality (e.g., storage options, byte limits, setting custom HTTP clients) that were discussed previously. This page will go into more detail on some of the configurable emitter properties.

Event store

The event store is used to store an event queue with events scheduled to be sent. Events are added to the event store when they are tracked and removed when they are successfuly emitted or when emitting fails without any scheduled retries.

The tracker provides the SqliteStorage class that can be used as the event store. It uses SQLite to store the event queue. By default it will create the required files wherever the application is being run from.

You may also provide a custom event store implementation. To do so, define a class that inherits from the EventStore struct:

struct EventStore { virtual void add_event(const Payload &payload) = 0; virtual void get_event_rows_batch(list<EventRow> *event_list, int number_to_get) = 0; virtual void delete_event_rows_with_ids(const list<int> &id_list) = 0; };
Code language: C++ (cpp)

The EventStore struct defines functions to insert, retrieve, and remove events from the queue. Events are represented using their Payload instance which is persisted in a EventRow wrapper that also assigns IDs to each stored event (these event row IDs are different from event IDs used in the event payloads). There are three supported operations:

FunctionDescription
add_eventInsert event payload into event queue.
get_event_rows_batchRetrieve event rows from event queue up to the given limit.
delete_event_rows_with_idsRemove event rows with the given event row IDs.

Emitter request callback

The emitter enables you to set a callback function to be called after events are attempted to be sent to the Collector. This callback is fired after HTTP requests are made and you can subscribe for specific emit statuses. The following statuses can be subscribed to:

  • EmitStatus::SUCCESS – events were successfuly sent to the Collector.
  • EmitStatus::FAILED_WILL_RETRY – events failed to be sent to the Collector but will be retried later.
  • EmitStatus::FAILED_WONT_RETRY – events failed to be sent to the Collector and won’t be retried later (they will be dropped).

The callback is given two arguments – list of event IDs, and their emit status. You can only set one callback at once but you can subscribe to multiple emit statuses using binary operations. The following example shows how to set a callback that is called for all three emit statuses and prints to standard output.

emitter_configuration.set_request_callback( [](list<string> event_ids, EmitStatus emit_status) { switch (emit_status) { case EmitStatus::SUCCESS: printf("Successfuly sent %lu events.\n", event_ids.size()); break; case EmitStatus::FAILED_WILL_RETRY: printf("Failed to send %lu events, but will retry.\n", event_ids.size()); break; case EmitStatus::FAILED_WONT_RETRY: printf("Failed to send %lu events and won't retry.\n", event_ids.size()); break; } }, EmitStatus::SUCCESS | EmitStatus::FAILED_WILL_RETRY | EmitStatus::FAILED_WONT_RETRY);
Code language: C++ (cpp)

The callback is executed in a new thread. The set_request_callback function can’t be called when the Emitter is running.

HTTP request retry behavior

The Emitter has a retry functionality that repeatedly sends the same events to the Collector in case HTTP requests fail to get through. Requests are retried in case the connection to the Collector fails to be estabilished. They are also retried for all 3xx and 5xx HTTP status codes in server response and most 4xx status codes with the following exceptions – 400, 401, 403, 410, and 422. These status codes signal a rejection of the events being sent to the Collector and, therefore, the events in the requests are dropped and not sent again.

The Emitter and EmitterConfiguration provide an option to set custom retry behavior for 3xx, 4xx and 5xx HTTP status codes. You can call the set_retry_for_status_code function on the Emitter or EmitterConfiguration instance to define whether to retry or not for a given HTTP status code. Here is an example that overrides the default behavior and enables retries on 422 status code:

emitter_configuration.set_retry_for_status_code(422, true);
Code language: C++ (cpp)

Request retry delay (back-off)

Failed requests are retried with exponentially increasing delays between subsequent retry requests. This ensures that the Collector is not overwhelmed with too many requests and also saves resources on the client by reducing the number of requests during failures.

The retry delay calculation is an exponential function with multiplicative factor 2. To prevent spikes of traffic, small amount of randomness is added to the delay (at most 10% of the total value). Finally, it is limited to be no larger than around 2 minutes. The following is a sample sequence of the retry delays given 5 failed requests: 0.101s, 0.198s, 0.404s, 0.791s, 1.603s.

Manual flushing

You may want to force an emitter to send all events in its buffer, even if the buffer is not full. The Tracker class has a flush() method which flushes its emitter.

Example:

tracker->flush();
Code language: C++ (cpp)

Using a custom HTTP Client

You may optionally configure the HTTP client to be used to make HTTP requests to the collector. This is done by passing a unique pointer to a class inheriting from HttpClient that the Emitter will take ownership of. If not configured, the Emitter will use the built-in HttpClientWindows on Windows, HttpClientApple on Apple operating systems, and HttpClientCurl on other Unix systems.

If you’d like to learn more about Snowplow BDP you can book a demo with our team, or if you’d prefer, you can try Snowplow technology for yourself quickly and easily.