Note

This is a proposed design and has not yet been fully implemented in the code.

Design

Interface

Python

tracker.register(name, description, field_descriptions)
name:A unique identification string for this type of event
description:A description of the event and the conditions under which it is emitted
field_descriptions:
 A dictionary mapping field names to a long form description

The documentation for each field is saved and used to generate navigable documentation that is shipped with the event log so that users can have some context about the various parameters in the event. Calling this method is optional, and any events emitted without first registering the event type will simply not include a reference to the event metadata.

Note

Field values can be set to any serializable object of arbitrary complexity, however, they must be documented in the documentation text for the field that will contain the object.

Any events emitted with that event type after the registration will contain a reference back to the data generated by the last call to tracking.register() for that event type.

Example:

from eventtracking import tracker

tracker.register(
    'edx.navigation.request',
    'A user visited a page',
    {
        'url': 'The url of the page visited.',
        'method': 'The HTTP method for the request, can be GET, POST, PUT, DELETE etc.',
        'user_agent': 'The user agent string provided by the user's browser.'
        'parameters': 'All GET and POST parameters.  Note this excludes passwords.'
    }
)
tracker.emit(name, field_values)
name:A unique identification string for an event that has already been registered.
field_values:A dictionary mapping field names to the value to include in the event. Note that all values provided must be serializable.

Regardless of previous state or configuration, the data will always be logged, however, in the following conditions will cause a warning to be logged:

  • the event type is unregistered
  • the data contains a field that was not included in the registered event type
  • the data is missing a field that was included in the registered event type
  • the field_values are not serializable
  • the estimated serialized event size is greater than the maximum supported
tracker.enter_context(name, context, description, field_descriptions)
context:A dictionary of key-value pairs that will be included in every event emitted after this call. Values defined in this dictionary will override any previous calls to push_context with maps that contain the same key.
name:A unique identification string for this type of context.
description:A clear description of the conditions under which this context is included.
field_descriptions:
 A dictionary mapping field names to a long form description.

Pushes a new context on to the context stack. This context will be applied to every event emitted after this call.

tracker.exit_context(name)

Removes the named context from the stack.

Javascript

Tracker.emit(name, field_values)
name:A unique identification string for an event that has already been registered.
field_values:An object mapping field names to the value to include in the event. Note that all values provided must be serializable.

See the documentation for the Python API.

Additionally, the behaviour of this function can be customized to direct events to arbitrary back-ends, and/or pre-process them before transmission to the server.

Event Type Metadata

The metadata for all registered event types is persisted along with a unique identifier.  After registering metadata for an event type, all events emitted with that event type will contain a reference to the metadata that corresponds to that registration of the event type.

Note

The same event type may be registered multiple times with different metadata in the normal case due to revisions to the schema.  This use case is supported and a new metadata record will be created for the new schema and linked to all future events of that type, while the old metadata will remain available for reference.

Nested Context Stack

The context stack is designed to simplify the process of including context in your events without having to have that context available at every location where the event might be emitted. It is rather cumbersome to have to pass around an HTTP request object for the sole purpose of gathering context out of it when emitting events. To aide this process you can define nested scopes which add information to the context when entered and remove information from the context when exited.

Example Scopes:

  • Process
  • Request
  • View

Conceptually this is accomplished using a stack of dictionaries to hold all of the contexts. Contexts can be pushed on to and popped off of the stack. When an event is emitted the values for each key are included in the event metadata. Note that if multiple dictionaries on the stack contain the same key, the value from the most recently pushed context is used and the remaining values are ignored.

Example:

from eventtracking import tracker

tracker.enter_context('request', {'user_id': 10938})
tracker.emit('navigation.request', {'url': 'http://www.edx.org/some/path/1'})

tracker.enter_context('session', {'user_id': 11111, 'session_id': '2987lkjdyoioey'})
tracker.emit('navigation.request', {'url': 'http://www.edx.org/some/path/2'})
tracker.exit_context('session')

tracker.emit('navigation.request', {'url': 'http://www.edx.org/some/path/3'})

# The following list shows the contexts and data for the three events that are emitted
#  "context": { "user_id": 10938 }, "data": { "url": "http://www.edx.org/some/path/1" }
#  "context": { "user_id": 11111, "session_id": "2987lkjdyoioey" }, "data": { "url": "http://www.edx.org/some/path/2" }
#  "context": { "user_id": 10938 }, "data": { "url": "http://www.edx.org/some/path/3" }

Best Practices

  • It is recommended that event types are namespaced using dot notation to avoid naming collisions, similar to DNS names. For example: edx.video.stop, mit.audio.stop
  • Avoid using event type names that may cause collisions. The burden is on the analyst to decide whether your event is equivalent to another and should be grouped accordingly etc.
  • Do not emit events that you don’t own. This could negatively impact the analysis of the event stream. If you suspect your event is equivalent to another, say so in your documenation, and the analyst can decide whether or not to group them.

Sample Usage

Emitting an unregistered event:

tracker.emit('edx.problem.show_answer', {'problem_id': 'i4x://MITx/6.00x/problem/L15:L15_Problem_2'})

Emitting a registered event:

tracker.register('edx.problem.show_answer', 'An answer was shown for a problem', {'problem_id': 'A unique problem identifier'})
tracker.emit('edx.problem.show_answer', {'problem_id': 'i4x://MITx/6.00x/problem/L15:L15_Problem_2'})

Emitting an event with context:

tracker.enter_context('request', {'user_id': '1234'})
try:
    tracker.emit('edx.problem.show_answer', {'problem_id': 'i4x://MITx/6.00x/problem/L15:L15_Problem_2'})
finally:
    tracker.exit_context('request')

Sample Events

Show Answer:

{
    "name": "edx.problem.show_answer",
    "timestamp": "2013-09-12T12:55:00.12345+00:00",
    "name_id": "10ac28",
    "context_type_id": "11bd88",
    "context": {
        "course_id":"",
        "user_id": "",
        "session_id": "",
        "org_id": "",
        "origin": "client"
    }
    "data": {
        "problem_id": "i4x://MITx/6.00x/problem/L15:L15_Problem_2"
    }
}

Sample Event Type Metadata

For the edx.problem.show_answer event type.

schema_id name description timestamp stack_trace
10ac28 edx.problem.show_answer An answer was shown for a problem 2013-09-12T12:05:00-00:00
11bd88 edX context   2013-09-12T12:05:01-00:00
schema_field_id schema_id name description
25 10ac28 problem_id A unique problem identifier
26 11bd88 course_id A unique course identifier
11bd88
40 11bd88 origin client || server

Sample Event Schema

Events can be serialized into any format. Here is an example JSON serialization format that could be used to store events.

Event Schema:

{
    "type":"object",
    "$schema": "http://json-schema.org/draft-03/schema",
    "id": "http://edx.org/event",
    "required":true,
    "title": "Event",
    "description": "An event emitted from the edx platform.",

    "properties":{
        "name": {
            "type": "string",
            "id": "http://edx.org/event/name",
            "description": "A unique identifier for this type of event.",
            "required": true
        },
        "timestamp": {
            "type": "string",
            "id": "http://edx.org/event/timestamp",
            "description": "The UTC time the event was emitted in RFC-3339 format.",
            "required": true
        }
        "name_id": {
            "type": "string",
            "id": "http://edx.org/event/name_id",
            "description": "A unique reference to the metadata for this event type.",
            "required": false
        },
        "context_type_id": {
            "type": "string",
            "id": "http://edx.org/event/context_type_id",
            "description": "A unique reference to the metadata for this context.",
            "required": false
        },
        "context": {
            "type": "object",
            "id": "http://edx.org/event/context",
            "description": "Context for the event that was not explicitly provided during emission.",
            "required": false,
            "additionalProperties":true
        },
        "data": {
            "type":"object",
            "id": "http://edx.org/event/data",
            "description": "All custom fields and values provided during emission."
            "required": false,
            "additionalProperties": true
        },
    }
}