.NET Tracker 0.1.2 - OXYGEN-MARKET/oxygen-market.github.io GitHub Wiki
HOME > [SNOWPLOW TECHNICAL DOCUMENTATION](Snowplow technical documentation) > Trackers > .NET Tracker
This page refers to version 0.1.2 of the Snowplow .NET Tracker. For the latest version, please see here.
-
- 2.1 Accessing the namespace
- 2.2 Creating a tracker
- 2.2.1
emitters
- 2.2.2
subject
- 2.2.3
namespace
- 2.2.4
appId
- 2.2.5
encodeBase64
- 2.2.1
-
- 3.1
SetPlatform
- 3.2
SetUserId
- 3.3
SetScreenResolution
- 3.4
SetViewport
- 3.5
SetColorDepth
- 3.6
SetTimezone
- 3.7
SetLang
- 3.1
-
- 4.1 Common
- 4.1.1 Custom contexts
- 4.1.2 Optional timestamp argument
- 4.1.3 Tracker method return values
- 4.2
TrackScreenView()
- 4.3
TrackPageView()
- 4.4
TrackEcommerceTransaction()
- 4.5
TrackStructEvent()
- 4.6
TrackUnstructEvent()
- 4.1 Common
-
- 5.1 The basic Emitter class
- 5.2 The AsyncEmitter class
- 5.3 Manual flushing
- 5.4 Periodic flushing
- 5.5 Offline tracking
- 5.6 The RedisEmitter class
- 5.7 Multiple emitters
- 5.8 Custom emitters
- 6 Logging
The Snowplow .NET Tracker allows you to track Snowplow events from your .NET websites and desktop applications.
The tracker should be straightforward to use if you are comfortable with C# development; any prior experience with Snowplow's JavaScript Tracker, Python Tracker or Ruby Tracker, Google Analytics or Mixpanel (which have similar APIs to Snowplow) is helpful but not necessary.
Note that this tracker covers all the same events as the Python and Ruby Trackers.
There are three basic types of object you will create when using the Snowplow .NET Tracker: subjects, emitters, and trackers.
A subject represents a user whose events are tracked. A tracker constructs events and sends them to one or more emitters. Each emitter then sends the event to the endpoint you configure. This will usually be a Snowplow collector, but could also be a Redis database.
## 2 InitializationAssuming you have completed the .NET Tracker Setup for your project, you are now ready to initialize the .NET Tracker.
### 2.1 Accessing the namespaceAdd the .NET Tracker's namespace like so:
using Snowplow.Tracker
That's it - you are now ready to initialize a tracker instance.
### 2.2 Creating a trackerThe simplest tracker initialization only requires you to provide the URI of the collector to which the tracker will log events:
var e = new Emitter("d3rkrsqld9gmqf.cloudfront.net")
var t = new Tracker(e)
There are other optional keyword arguments:
Argument Name | Description | Required? | Default |
---|---|---|---|
emitters |
The emitter to which events are sent | Yes | None |
subject |
The user being tracked | No | new Subject() |
namespace |
The name of the tracker instance | No | null |
appId |
The application ID | No | null |
encodeBase64 |
Whether to enable [base 64 encoding] base64 | No | true |
A more complete example:
var tracker = new Tracker(new Emitter("d3rkrsqld9gmqf.cloudfront.net"), "cf", "my-application-id", false);
This can be a single emitter or a List
of emitters. The tracker will send events to these emitters, which will in turn send them to a collector. See Emitters for more on emitter configuration.
The user which the Tracker will track. This should be an instance of the Subject class. You don't need to set this during Tracker construction; you can use the Tracker.SetSubject
method afterwards. In fact, you don't need to create a subject at all. If you don't, though, your events won't contain user-specific data such as timezone and language.
If provided, the namespace
argument will be attached to every event fired by the new tracker. This allows you to later identify which tracker fired which event if you have multiple trackers running.
The appId
argument lets you set the application ID to any string.
By default, unstructured events and custom contexts are encoded into Base64 to ensure that no data is lost or corrupted. You can turn encoding on or off using the Boolean encodeBase64
argument.
You may have additional information about your application's environment, current user and so on, which you want to send to Snowplow with each event.
Create a subject like this:
var s = new Subject();
The Subject class has a set of set...()
methods to attach extra data relating to the user to all tracked events:
All of these methods return the Subject instance.
If you initialize a Tracker
instance without a subject, a default Subject
instance will be attached to the tracker. You can access that subject by calling the relevant method on the Tracker
instance:
t = new Tracker(myEmitter);
t.subject.SetPlatform(Platform.Pc).SetUserId("user-12345").SetLang("en");
We will discuss each of these in turn below:
#### 3.1 Change the tracker's platform with `SetPlatform`The possible platforms are enumerated in the Platform
enum. The default platform is Platform.Pc
. You can change the platform the subject is using by calling:
s.SetPlatform( {{PLATFORM}} );
For example:
s.SetPlatform(Platform.Srv) // Running on a server
For a full list of supported platforms, please see the Snowplow Tracker Protocol.
### 3.2 Set user ID with `SetUserId`You can set the user ID to any string:
s.SetUserId( "{{USER ID}}" );
Example:
s.SetUserId("alexd");
If your code has access to the device's screen resolution, then you can pass this in to Snowplow too:
s.SetScreenResolution( {{WIDTH}}, {{HEIGHT}} );
Both numbers should be positive integers; note the order is width followed by height. Example:
s.SetScreenResolution(1366, 768);
If your code has access to the viewport dimensions, then you can pass this in to Snowplow too:
s.SetViewport( {{WIDTH}}, {{HEIGHT}} );
Both numbers should be positive integers; note the order is width followed by height. Example:
s.SetViewport(300, 200);
If your code has access to the bit depth of the device's color palette for displaying images, then you can pass this in to Snowplow too:
s.SetColorDepth( {{BITS PER PIXEL}} );
The number should be a positive integer, in bits per pixel. Example:
s.SetColorDepth(32);
This method lets you pass a user's timezone in to Snowplow:
s.timezone( {{TIMEZONE}} );
The timezone should be a string:
s.SetColorDepth("Europe/London");
This method lets you pass a user's language in to Snowplow:
s.SetLang( {{LANGUAGE}} );
The language should be a string:
s.SetLang('en');
You may want to track more than one subject concurrently. To avoid data about one subject being added to events pertaining to another subject, create two subject instances and switch between them using Tracker.SetSubject
:
// Create a simple Emitter which will log events to http://d3rkrsqld9gmqf.cloudfront.net/i
var e = new Emitter("d3rkrsqld9gmqf.cloudfront.net");
// Create a Tracker instance
var t = new Tracker(emitters=e, namespace="cf", appId="CF63A");
// Create a Subject corresponding to a pc user
var s1 = new Subject();
// Set some data for that user
s1.SetPlatform(Platform.Pc);
s1.SetUserId("0a78f2867de");
// Set s1 as the tracker subject
// All events fired will have the information we set about s1 attached
t.SetSubject(s1);
// Track user s1 viewing a page
t.TrackPageView("http://www.example.com");
// Create another Subject instance corresponding to a mobile user
var s2 = new Subject();
// All methods of the Subject class return the Subject instance so methods can be chained:
s2.SetPlatform(Platform.Web).SetUserId("0b08f8be3f1");
// Change the tracker subject from s1 to s2
// All events fired will have instead have information we set about s2 attached
t.SetSubject(s2);
// Track user s2 viewing a page
t.TrackPageView("http://www.example.com");
// Switch back to s1 and track a structured event, this time using method chaining:
t.SetSubject(s1).TrackStructEvent("Ecomm", "add-to-basket", "dog-skateboarding-video", "hd", 13.99);
Snowplow has been built to enable you to track a wide range of events that occur when users interact with your websites and apps. We are constantly growing the range of functions available in order to capture that data more richly.
Tracking methods supported by the .NET Tracker at a glance:
Function | Description |
---|---|
TrackPageView() |
Track and record views of web pages. |
TrackEcommerceTransaction() |
Track an ecommerce transaction |
TrackScreenView() |
Track the user viewing a screen within the application |
TrackStructEvent() |
Track a Snowplow custom structured event |
TrackUnstructEvent() |
Track a Snowplow custom unstructured event |
All events are tracked with specific methods on the tracker instance, of the form TrackXXX()
, where XXX
is the name of the event to track.
In short, custom contexts let you add additional information about the circumstances surrounding an event in the form of a C# dictionary object. Each tracking method accepts an additional optional contexts parameter after all the parameters specific to that method, which should be a list of contexts:
public Tracker TrackPageView(string pageUrl, string pageTitle = null, string referrer = null,
List<Dictionary<String, Object>> context = null, Int64? tstamp = null)
The context
argument should consist of a List<Dictionary<String, Object>>. The format of each dictionary is the same as for an unstructured event.
If a visitor arrives on a page advertising a movie, the dictionary for a single custom context in the list might look like this:
var movieContext = new List<Dictionary<String, Object>>
{
{ "schema", "iglu:com.acme_company/movie_poster/jsonschema/2-1-1" },
{ "data", new Dictionary<String, Object>
{
{ "movie_name", "Solaris" },
{ "poster_country": "JP" }
}
}
};
This is how to fire a page view event with the above custom context:
t.TrackPageView("http://www.films.com", "Homepage", null, new List<Dictionary<String, String>> {movieContext});
Note that even though there is only one custom context attached to the event, it still needs to be placed in an array.
### 4.1.2 Optional timestamp argumentEach Track...()
method supports an optional timestamp as its final argument; this allows you to manually override the timestamp attached to this event. The timestamp should be in milliseconds since the Unix epoch.
If you do not pass this timestamp in as an argument, then the .NET Tracker will use the current time to be the timestamp for the event.
Here is an example tracking a structured event and supplying the optional timestamp argument. We can explicitly supply None
s for the intervening arguments which are empty:
t.TrackStructEvent("some cat", "save action", None, None, None, 1368725287000)
Timestamp is counted in milliseconds since the Unix epoch - the same format as generated by:
Int64 tstamp = (Int64)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalMilliseconds;
All tracker methods will return the tracker instance, allowing tracker methods to be chained:
var e = new Emitter("d3rkrsqld9gmqf.cloudfront.net");
var t = new Tracker(e);
t.TrackPageView("http://www.example.com").TrackScreenView("title screen");
Use TrackScreenView()
to track a user viewing a screen (or equivalent) within your app. Arguments are:
Argument | Description | Required? | Type |
---|---|---|---|
name |
Human-readable name for this screen | No | String |
id |
Unique identifier for this screen | No | String |
context |
Custom context for the event | No | List<Dictionary<String, object>>> |
tstamp |
When the screen was viewed | No | Int64 |
Although name and id are not individually required, at least one must be provided or the event will fail validation.
Example:
t.TrackScreenView("HUD > Save Game", "screen23", null, 1368725287000)
Use TrackPageView()
to track a user viewing a page within your app.
Arguments are:
Argument | Description | Required? | Validation |
---|---|---|---|
pageUrl |
The URL of the page | Yes | String |
pageTitle |
The title of the page | No | String |
referrer |
The address which linked to the page | No | String |
context |
Custom context for the event | No | List<Dictionary<String, Object>>> |
tstamp |
When the pageview occurred | No | Int64 |
Example:
t.TrackPageView("www.example.com", "example", "www.referrer.com")
Use TrackEcommerceTransaction()
to track an ecommerce transaction.
Arguments:
Argument | Description | Required? | Validation |
---|---|---|---|
orderId |
ID of the eCommerce transaction | Yes | String |
totalValuee |
Total transaction value | Yes | Double |
affiliation |
Transaction affiliation | No | String |
taxValue |
Transaction tax value | No | Double |
shipping |
Delivery cost charged | No | Double |
city |
Delivery address city | No | String |
state |
Delivery address state | No | String |
country |
Delivery address country | No | String |
currency |
Transaction currency | No | String |
items |
Items in the transaction | Yes | List |
context |
Custom context for the event | No | List<Dictionary<String, Object>>> |
tstamp |
When the transaction event occurred | No | Int64 |
The items
argument is a List of TransactionItems. TrackEcommerceTransaction
fires multiple events: one transaction event for the transaction as a whole, and one transaction item event for each element of the items
list. Each transaction item event will have the same timestamp, order ID, and currency as the main transaction event.
These are the fields with which a TransactionItem can be created.
Field | Description | Required? | Validation |
---|---|---|---|
"sku" |
Item SKU | Yes | String |
"price" |
Item price | Yes | Double |
"quantity" |
Item quantity | Yes | Int |
"name" |
Item name | No | String |
"category" |
Item category | No | String |
"context" |
Custom context for the event | No | List<Dictionary<String, Object>>> |
Example of tracking a transaction containing two items:
var items = new List<TransactionItem>
{
new TransactionItem("pbz0026", 20, 1),
new TransactionItem("pbz0038", 15, 1, "red hat", "menswear")
};
t.TrackEcommerceTransaction("6a8078be", 35, "some-affiliation", 6.125, 0, "London", null, "UK", currency="GBP", items);
Use TrackStructEvent()
to track a custom event happening in your app which fits the Google Analytics-style structure of having up to five fields (with only the first two required):
Argument | Description | Required? | Validation |
---|---|---|---|
category |
The grouping of structured events which this action belongs to |
Yes | Non-empty string |
action |
Defines the type of user interaction which this event involves | Yes | Non-empty string |
label |
A string to provide additional dimensions to the event data | No | String |
property |
A string describing the object or the action performed on it | No | String |
value |
A value to provide numerical data about the event | No | Int or Float |
context |
Custom context for the event | No | List<Dictionary<String, Object>>> |
tstamp |
When the structured event occurred | No | Int64 |
Example:
t.TrackStructEvent("shop", "add-to-basket", None, "pcs", 2);
Use TrackUnstructEvent()
to track a custom event which consists of a name and an unstructured set of properties. This is useful when:
- You want to track event types which are proprietary/specific to your business (i.e. not already part of Snowplow), or
- You want to track events which have unpredictable or frequently changing properties
The arguments are as follows:
Argument | Description | Required? | Validation |
---|---|---|---|
eventJson |
The properties of the event | Yes | <Dictionary<String, Object >> |
context |
Custom context for the event | No | List<Dictionary<String, Object>>> |
tstamp |
When the unstructured event occurred | No | Int64 |
Example:
var eventData = new Dictionary<String, Object>
{
{ "level", 5 },
{ "saveId", "ju302" },
{ "hardMode", true }
};
var eventJson = new Dictionary<String, Object>
{
{ "schema", "iglu:com.example_company/save-game/jsonschema/1-0-2" },
{ "data", eventData },
};
t.TrackUnstructEvent(eventJson);
The eventJson
must be a Dictionary<String, Object> with two fields: schema
and data
. data
is a flat dictionary containing the properties of the unstructured event. schema
identifies the JSON schema against which data
should be validated.
For more on JSON schema, see the [blog post] self-describing-jsons.
## 5. EmittersTracker instances must be initialized with an emitter. This section will go into more depth about the Emitter class and its subclasses.
### 5.1 The basic Emitter classAt its most basic, the Emitter class only needs a collector URI:
var e = new Emitter("d3rkrsqld9gmqf.cloudfront.net");
This is the signature of the constructor for the base Emitter class:
public Emitter(string endpoint,
HttpProtocol protocol = HttpProtocol.HTTP, int? port = null, HttpMethod = HttpMethod.GET,
int? bufferSize = null, Action<Int> onSuccess = null, Action<Int, List<Dictionary<String, String>>> onFailure = null, offlineModeEnabled = true):
Argument | Description | Required? |
---|---|---|
endpoint |
The collector URI | Yes |
protocol |
Request protocol: HTTP or HTTPS | No |
port |
The port to connect to | No |
method |
The method to use: "get" or "post" | No |
bufferSize |
Number of events to store before flushing | No |
onSuccess |
Callback executed when a flush is successful | No |
onFailure |
Callback executed when a flush is unsuccessful | No |
offlineModeEnabled |
Whether to store events when unable to connect to the server | No |
protocol
must be a member of the HttpProtocol
enum: HttpProtocol.HTTP
or HttpProtocol.HTTPS
.
method
must be a member of the HttpMethod
enum: HttpMethod.GET
or HttpMethod.POST
.
When the emitter receives an event, it adds it to a buffer. When the queue is full, all events in the queue get sent to the collector. The bufferSize
argument allows you to customize the queue size. By default, it is 1 for GET requests and 10 for POST requests. (So in the case of GET requests, each event is fired as soon as the emitter receives it.) If the emitter is configured to send POST requests, then instead of sending one for every event in the buffer, it will send a sing request containing all those events in JSON format.
onSuccess
is an optional callback that will execute whenever the queue is flushed successfully, that is, whenever every request sent has status code 200. It will be passed one argument: the number of events that were sent.
onFailure
is similar to onSuccess
, but executes when the flush is not wholly successful. It will be passed two arguments: the number of events that were successfully sent, and a list of unsent requests.
offlineModeEnabled
determines whether offline tracking is enabled. It defaults to true
.
An example:
unsentEvents = new List<Dictionary<String, String>>();
Action<Int> successCallback = (successCount) =>
{
Console.WriteLine(String.Format("{0} events sent successfully!", successCount.ToString()));
};
Action<Int, List<Dictionary<String, String>>> failureCallback = (successCount, unsentEvents) =>
{
Console.WriteLine(String.Format("{0} events sent successfully!", successCount.ToString()));
Console.WriteLine(String.Format("Failed to send {0} events", unsentEvents.Count.ToString()));
foreach (event e in unsentEvents)
{
unsentEvents.Add(e);
}
};
// Initialize an emitter with a bufferSize of 2 and with onSuccess and onFailure callbacks
var e = new Emitter("d3rkrsqld9gmqf.cloudfront.net", HttpProtocol.HTTP, null, HttpMethod.GET, 2, successCallback, failureCallback);
var t = new Tracker(e);
// This doesn't cause the emitter to send a request because the bufferSize was set to 3, not 1
t.TrackPageView("http://www.example.com");
t.TrackPageView("http://www.example.com/page1");
// This does cause the emitter to try to send all 3 events
t.TrackPageView("http://www.example.com/page2");
// Since the method is GET by default, 3 separate requests are sent
// If any of them are unsuccessful, they will be stored in the unsentEvents variable
**Warning: the AsyncEmitter in version 0.1.0 is susceptible to a race condition. Until a release is published fixing this, the synchronous Emitter class should be used instead.
var e = new AsyncEmitter("d3rkrsqld9gmqf.cloudfront.net");
The AsyncEmitter
class works just like the Emitter class (from which it inherits). It has one advantage, though: HTTP(S) requests are sent asynchronously, using C# Tasks, so the Tracker won't be blocked while the Emitter waits for a response. For this reason, the AsyncEmitter is recommended over the base Emitter
class.
You can flush the emitter manually using the Flush
method of the Tracker
instance which is sending events to the emitter. If you pass it the Boolean value true
, it will be a blocking call which synchronously sends all events in the emitter's buffer. In the case of the AsyncEmitter
, it also forces all running tasks to finish within 10 seconds.
t.Flush(true);
You can use the SetFlushTimer
method to set an interval at which the buffer will be flushed:
e.SetFlushTimer(60000);
It takes one parameter: the number of milliseconds to wait between flushes. The above example will flush every minute.
### 5.5 Offline trackingIf offline tracking is enabled for an emitter, then whenever it attempts to send a request and is unable to connect to the server, it will move all events in that request into an MSMQ queue.
The events stored in the queue will be resent whenever one of two events occurs:
- A NetworkAvailabilityChanged event indicates that the network has become available
- The emitter successfully sends a request
Note that the onFailure callback will be called when the unsent events are moved into the queue.
### 5.6 The RedisEmitter classThe RedisEmitter class can be used to store events in a Redis database. It uses the Sider library. Its constructor takes two optional parameters: an instance of the Sider RedisClient class, representing the database in which events will be stored, and a key under which events will be stored. The RedisClient parameter defaults to new RedisClient()
and the key defaults to "snowplow".
var re = new RedisEmitter(null, "myeventqueue");
In the above example, events will be stored in the default database in a list with key "myeventqueue".
### 5.7 Multiple emittersYou can configure a tracker instance to send events to multiple emitters by passing the Tracker
constructor function an array of emitters instead of a single emitter, or by using the addEmitter
method:
var e1 = new Emitter("collector1.cloudfront.net", HttpProtocol.HTTP, null, HttpMethod.GET);
var e1 = new Emitter("collector2.cloudfront.net", HttpProtocol.HTTP, null, HttpMethod.POST);
var e3 = new Emitter("collector2.cloudfront.net", HttpProtocol.HTTPS, null, HttpMethod.POST);
var t = new Tracker([e1, e2]);
t.addEmitter(e3);
You can create your own custom emitter class, implementing the IEmitter interface. This means it must have the following methods:
void Flush(bool sync);
void Input(Dictionary<String, String> payload);
The Input
method is called whenever the Tracker instance gives the Emitter a dictionary of name-value pairs representing one event.
The Flush
method is called be the Tracker's Flush
method. It need not do anything.
The emitters.rb module has NLog logging enabled to give you information about requests being sent. The logger prints to the console messages about what emitters are doing. By default, only messages with priority "INFO" or higher will be logged.
To change this, use the static SetLogLevel
method of the Log
class:
Log.SetLogLevel(Log.Level.Debug);
The levels are:
Level | Description |
---|---|
Off |
Nothing logged |
Warn |
Notification for requests with status code not equal to 200 |
Info |
Notification for all requests |
Debug |
Contents of all requests |