blob: 049d61f4e9c237dfa87e4bb1f68827c91bcc03f9 [file] [log] [blame]
using System;
using System.Collections.Generic;
namespace Activity_Logging_API_Helper
{
/*
* ++++++++++++++++++
* C# Activity Logger
* ++++++++++++++++++
*
* Draper Laboratory, June 2013
* ----------------------------
*
*
* This library is intended for integration into a C# software application which is implementing the Draper
* Activity Logging API. To send activity log messages using this libary, components must:
*
* 1. Instantiate an ``ActivityLogger`` object
* 2. Call ``registerActivityLogger(...)`` to pass in required networking
* and version information.
* 3. Call one of the logging functions:
* * ``logSystemActivity(...)``
* * ``logUserActivity(...)``
* * ``logUILayout(...)``
*
* An example use of this library is included below::
*
* //Instantiate the Activity Logger
* ac = ActivityLogger.ActivityLogger();
*
* //Get the the current UTC time, and store it as an ISO-compliant timestamp string.
* String ISOTime = DateTime.UtcNow.ToString("O");
*
* //Minimally register the logger (DISCOURAGED). In this case, we register our logger with client hostname
* //"3D Viz Tablet 001", which is the name of the hardware device on which this application is running.
* //No other arguments are supplied, so the software component name will be logged as unknownComponent, the
* //component version will be unknown,and the User Session ID will be a random integer.
*
* ac.registerActivityLogger("Viz_Tablet_001");
*
* //Re-register the logger. In this case, we register our logger object with client hostname
* //"Viz_Tablet_001". We specify that the application sending logs is version 34.87 of the application
* //"c-Sharp Test App", and the User Session ID is "AC34523452345".
*
* c.registerActivityLogger("Viz_Tablet_001", "cSharpTestApp", "34.87", "AC34523452345");
*
* //Send a System Activity Message. In this case, we send a System Activity message with the current UTC
* //timestamp and the action description "Pushed query results to GUI"
*
* ac.logSystemActivity(ISOTime, "Pushed query results to GUI");
*
* //Send a System Activity Message with optional metadata included. In this case, we send a System Activity
* //message with the current UTC timestamp, the action description "Pushed query results to GUI" and
* //optional metadata with two key-value pairs of:
* // 'rowsReturned'=314
* // 'queryTime'='422 ms'
*
* Dictionary<String,String> testDict = new Dictionary<String, String>();
* testDict.Add("rowsReturned", "314");
* testDict.Add("queryTime", "422 ms");
* ac.logSystemActivity(ISOTime, "Pushed query results to GUI", testDict);
*
* //Send a User Activity Message. In this case, we send a User Activity message with the current UTC
* //timestamp, the action description "Filtered results using a Histogram view", a developer-defined user
* //action visualFilter_Histogram, and the workflow constant SEARCH, defined in the Draper Activity Logging
* //API.
*
* ac.logUserActivity(ISOTime, "Filtered results using a Histogram view" , "visualFilter_Histogram",
* ActivityLogger.WF.MARSHAL);
*
* //Send a UI Layout Message. In this case, we send a UI Layout message with the current UTC timestamp,
* //action description of "Expand Tree Node". The name of the UI element is "Cluster_Browser_List",
* //visibility=true, meaning SearchWindow A is currently visible. The left, right, top and bottom bounds of
* //the UI element are 200px, 450px, 200px, and 500px from the top right of the screen.
*
* Console.Write(ac.logUILayout(ISOTime, "Expand Tree Node", "Cluster_Browser_List", true, 200, 450, 200,
* 500)) ;
*/
class ActivityLogger
{
/// <summary>
/// The name of the computer or VM on which the software component using this library is runing. In the case of
///a server-side Python component, this should be the host name of the machine on which the Python service is
///running. By default, this field will be populated with the IP address of the machine on which this module is
///executed.
///
///Ideally, this hostname should describe a physical terminal or experimental setup as persistently as possible.
/// </summary>
String clientHostname;
//The name of the software component or application sending log messages from this library. Defaults to
//``unknownComponent``
String componentName = "unknownComponent";
//The version number of the software component or application specified in ``clientHostname`` that is sending log
//messages from this library. Defaults to ``unknown``.
String componentVersion = "unknown";
//The unique session ID used for communication between client and sever-side software components during use of
//this component. Defaults to a random integer.
//
//Ideally, this session ID will identify log messages from all software components used to execute a unique user
//session.
int sessionID;
public ActivityLogger()
{
Random randomNumberGen = new Random();
sessionID = randomNumberGen.Next(1, 10000);
}
/*
INTERNAL CONSTANTS
******************
* These constant define values associated with this specific version of this library, and should not be
* changed by the implementor.
*/
//The version number of the Draper Activity Logging API implemented by this library.
private int apiVersion = 2;
//The workflow coding version used by this Activity Logging API.
private int workflowCodingVersion = 1;
//WORKFLOW CODES
//These constants specify the workflow codes defined in the Draper Activity Logging API version <apiVersion>. One of
//these constants *must* be passed in the parameter ``userWorkflowState`` in the function ``logUserActivity``.
public enum WF
{
OTHER = 0,
PLAN = 1,
SEARCH = 2,
EXAMINE = 3,
MARSHAL = 4,
REASON = 5,
COLLABORATE = 6,
REPORT = 7
}
//The language in which this helper library is implemented
String implementationLanguage = "C#";
// END INTERNAL CONSTANTS
// **********************
/*======================== REGISTRATION ============================
* These variables are assigned by calling the
* <registerActivityLogger> function below. They are persistent until
* a new ActivityLogger object is instantiated, or until modification
* by the <registerActivityLogger> function.
*/
/// <summary>
/// Register this event logger. <registerActivityLogger> MUST be called before log messages can be sent with
/// this library.
/// </summary>
/// <param name="clientHostnameIN">The hostname or IP address of this machine or VM. See documentation for
/// <clientHostname> below. If not provided, defaults to the public IP address of this computer.</param>
/// <param name="componentNameIN">The name of the app or component using this library. See documentation for
/// <componentName> below. If not provided, defaults to the hostname of the web app that loaded this
/// library.</param>
/// <param name="componentVersionIN">The version of this app or component. See documentation for
/// <componentVersion> below. If not provided, defaults to 'unknown'.</param>
/// <param name="sessionIdIN">A unique ID for the current user session. See documentation for <sessionID>
/// below. If not provided, defaults to a random integer.</param>
public void registerActivityLogger(String clientHostnameIN, String componentNameIN = null, String componentVersionIN = null,
int sessionIdIN = -1)
{
if (componentNameIN != null)
{
componentName = componentNameIN;
}
if (componentVersionIN != null)
{
componentVersion = componentVersionIN;
}
if (sessionIdIN != -1)
{
sessionID = sessionIdIN;
}
clientHostname = clientHostnameIN;
}
//========================END REGISTRATION==========================
/*==================ACTIVITY LOGGING FUNCTIONS======================
* The 3 functions in this section are used to send Activity Log Mesages to an Activity Logging Server.
* Seperate functions are used to log System Activity, User Activity, and UI Layout Events. See the Activity
* Logging API by Draper Laboratory for more details about the use of these messages.
*/
/// <summary>
/// Log a System Activity, with nested metadata.
/// </summary>
/// <remarks> <see cref="registerActivityLogger"/> **must** be called before calling this function. Use <code>logSystemActivity</code> to log
/// software actions that are not explicitly invoked by the user. For example, if a software component refreshes a
/// data store after a pre-determined time span, the refresh event should be logged as a system activity. However,
/// if the datastore was refreshed in response to a user clicking a Reshresh UI element, that activity should NOT be
/// logged as a System Activity, but rather as a User Activity, with the method <see cref="logUserActivity"/>.
/// </remarks>
/// <param name="ISOTimestamp">An ISO-compliant Timestamp string, in UTC.</param>
/// <param name="actionDescription">A string describing the System Activity performed by the component. Example:
/// <example>"BankAccountTableView component refreshed datasource"</example></param>
/// <param name="softwareMetadata">Any key/value pairs that will clarify or paramterize this system activity.</param>
/// <returns>A JSON message.</returns>
public String logSystemActivity(String ISOTimestamp, String actionDescription, Dictionary<String, String> softwareMetadata = null)
{
Dictionary<String, object> recastMetaData = null;
if (softwareMetadata != null)
{
recastMetaData = new Dictionary<string, object>();
foreach (String key in softwareMetadata.Keys)
{
recastMetaData.Add(key, softwareMetadata[key]);
}
}
return logSystemActivity<Object>(ISOTimestamp, actionDescription, recastMetaData);
}
/// <summary>
/// Log a System Activity.
/// </summary>
/// <remarks> <see cref="registerActivityLogger"/> **must** be called before calling this function. Use <code>logSystemActivity</code> to log
/// software actions that are not explicitly invoked by the user. For example, if a software component refreshes a
/// data store after a pre-determined time span, the refresh event should be logged as a system activity. However,
/// if the datastore was refreshed in response to a user clicking a Reshresh UI element, that activity should NOT be
/// logged as a System Activity, but rather as a User Activity, with the method <see cref="logUserActivity"/>.
/// </remarks>
/// <param name="ISOTimestamp">An ISO-compliant Timestamp string, in UTC.</param>
/// <param name="actionDescription">A string describing the System Activity performed by the component. Example:
/// <example>"BankAccountTableView component refreshed datasource"</example></param>
/// <param name="softwareMetadata">Any key/value pairs that will clarify or paramterize this system activity.</param>
/// <returns>A JSON message.</returns>
public String logSystemActivity<T>(String ISOTimestamp, String actionDescription, Dictionary<String, T> softwareMetadata = null)
{
Dictionary<string,object> SystemActivityMessage = new Dictionary<string,object>();
writeHeader(SystemActivityMessage);
SystemActivityMessage.Add("timestamp", ISOTimestamp);
SystemActivityMessage.Add("type","SYSACTION");
Dictionary<string, object> parms = new Dictionary<string, object>();
parms.Add("desc", actionDescription);
SystemActivityMessage.Add("parms", parms);
SystemActivityMessage.Add("metadata",softwareMetadata);
return convertToJSON(SystemActivityMessage);
}
/// <summary>
/// Log a User Activity.
/// </summary>
/// <param name="ISOTimestamp"></param>
/// <param name="actionDescription">A string describing the System Activity performed by the component. Example:
/// <example>"BankAccountTableView component refreshed datastore."</example></param>
/// <param name="userActivity">A key word defined by each software component or application indicating which
/// software-centric function is is most likely indicated by the this user activity. See the Activity Logging
/// API for a standard set of user activity key words. </param>
/// <param name="userWorkflowState">
/// This value must be one of the Workflow Codes defined in this library. See the Activity Logging API
/// for definitions of each workflow code. Example:
/// <example>
/// ac = new ActivityLogger();
/// ...
/// userWorkflowState = ac.WF.SEARCH
/// </example>
/// </param>
/// <param name="softwareMetadata">Any key/value pairs that will clarify or paramterize this system activity.</param>
/// <returns>A JSON log message.</returns>
/// <remarks>
/// <see cref="registerActivityLogger"/> MUST be called before calling this function. Use <code>logUserActivity</code>
/// to log actions initiated by an explicit user action. For example, if a software component refreshes a
/// data store when the user clicks a Reshresh UI element, that activity should be logged as a User Activity.
/// However, if the datastore was refreshed automatically after a certain time span, that activity should NOT
/// be logged as a User Activity, but rather as a System Activity.
/// </remarks>
public String logUserActivity(String ISOTimestamp, String actionDescription, String userActivity, WF userWorkflowState, Dictionary<String, String> softwareMetadata = null)
{
Dictionary<String, object> recastMetaData = null;
if (softwareMetadata != null)
{
recastMetaData = new Dictionary<string, object>();
foreach (String key in softwareMetadata.Keys)
{
recastMetaData.Add(key, softwareMetadata[key]);
}
}
return logUserActivity<Object>(ISOTimestamp, actionDescription, userActivity, userWorkflowState, recastMetaData);
}
/// <summary>
/// Log a User Activity, with optionally nested metadata.
/// </summary>
/// <param name="ISOTimestamp"></param>
/// <param name="actionDescription">A string describing the System Activity performed by the component. Example:
/// <example>"BankAccountTableView component refreshed datastore."</example></param>
/// <param name="userActivity">A key word defined by each software component or application indicating which
/// software-centric function is is most likely indicated by the this user activity. See the Activity Logging
/// API for a standard set of user activity key words. </param>
/// <param name="userWorkflowState">
/// This value must be one of the Workflow Codes defined in this library. See the Activity Logging API
/// for definitions of each workflow code. Example:
/// <example>
/// ac = new ActivityLogger();
/// ...
/// userWorkflowState = ac.WF.SEARCH
/// </example>
/// </param>
/// <param name="softwareMetadata">Any key/value pairs that will clarify or paramterize this system activity. These values can be nested.</param>
/// <returns>A JSON log message.</returns>
/// <remarks>
/// <see cref="registerActivityLogger"/> MUST be called before calling this function. Use <code>logUserActivity</code>
/// to log actions initiated by an explicit user action. For example, if a software component refreshes a
/// data store when the user clicks a Reshresh UI element, that activity should be logged as a User Activity.
/// However, if the datastore was refreshed automatically after a certain time span, that activity should NOT
/// be logged as a User Activity, but rather as a System Activity.
/// </remarks>
public String logUserActivity<T>(String ISOTimestamp, String actionDescription, String userActivity, WF userWorkflowState, Dictionary<String, T> softwareMetadata = null)
{
Dictionary<String, Object> UserActivityMessage = new Dictionary<string, object>();
writeHeader(UserActivityMessage);
UserActivityMessage.Add("timestamp", ISOTimestamp);
UserActivityMessage.Add("type", "USERACTION ");
Dictionary<String, Object> parms = new Dictionary<string,object>();
parms.Add("desc", actionDescription);
parms.Add("activity", userActivity);
parms.Add("wf_state", (int)userWorkflowState);
parms.Add("wf_version", workflowCodingVersion);
UserActivityMessage.Add("parms", parms);
UserActivityMessage.Add("metadata", softwareMetadata);
return convertToJSON(UserActivityMessage);
}
/// <summary>
/// Log the Layout of a UI Element.
/// </summary>
/// <param name="ISOTimestamp"></param>
/// <param name="actionDescription">A string describing the System Activity performed by the component. Example:
/// <example>"BankAccountTableView moved in User_Dashboard"</example></param>
/// <param name="uiElementName">The name of the UI component that has changed position or visibility.</param>
/// <param name="visibility"><code>true</code> if the element is currently visibile. <code>false</code> if the element is completely hidden.</param>
/// <param name="leftBound">The absolute position on screen, in pixels, of the leftmost boundary of the UI element.</param>
/// <param name="rightBound">The absolute position on screen, in pixels, of the rightmost boundary of the UI element.</param>
/// <param name="topBound">The absolute position on screen, in pixels of the top boundary of the UI element.</param>
/// <param name="bottomBound">The absolute position on screen, in pixels of the bottom boundary of the UI element.</param>
/// <param name="softwareMetadata">Any key/value pairs that will clarify or paramterize this system activity.</param>
/// <remarks><see cref="registerActivityLogger"/> MUST be called before calling this function. Use <code>logUILayout</code>
/// to record any changes to the position or visibility of User Interface elements on screen.</remarks>
/// <returns>A JSON log message.</returns>
public String logUILayout(String ISOTimestamp, String actionDescription, String uiElementName, bool visibility, int leftBound, int rightBound, int topBound, int bottomBound, Dictionary<String, String> softwareMetadata = null)
{
Dictionary<String, object> recastMetaData = null;
if (softwareMetadata != null)
{
recastMetaData = new Dictionary<string, object>();
foreach (String key in softwareMetadata.Keys)
{
recastMetaData.Add(key, softwareMetadata[key]);
}
}
return logUILayout<Object>(ISOTimestamp, actionDescription, uiElementName, visibility, leftBound, rightBound, topBound, bottomBound, recastMetaData);
}
/// <summary>
/// Log the Layout of a UI Element, with optionally nested metadata.
/// </summary>
/// <param name="ISOTimestamp"></param>
/// <param name="actionDescription">A string describing the System Activity performed by the component. Example:
/// <example>"BankAccountTableView moved in User_Dashboard"</example></param>
/// <param name="uiElementName">The name of the UI component that has changed position or visibility.</param>
/// <param name="visibility"><code>true</code> if the element is currently visibile. <code>false</code> if the element is completely hidden.</param>
/// <param name="leftBound">The absolute position on screen, in pixels, of the leftmost boundary of the UI element.</param>
/// <param name="rightBound">The absolute position on screen, in pixels, of the rightmost boundary of the UI element.</param>
/// <param name="topBound">The absolute position on screen, in pixels of the top boundary of the UI element.</param>
/// <param name="bottomBound">The absolute position on screen, in pixels of the bottom boundary of the UI element.</param>
/// <param name="softwareMetadata">Any key/value pairs that will clarify or paramterize this system activity. These can be nested.</param>
/// <remarks><see cref="registerActivityLogger"/> MUST be called before calling this function. Use <code>logUILayout</code>
/// to record any changes to the position or visibility of User Interface elements on screen.</remarks>
/// <returns>A JSON log message.</returns>
public String logUILayout<T>(String ISOTimestamp, String actionDescription, String uiElementName, bool visibility, int leftBound, int rightBound, int topBound, int bottomBound, Dictionary<String, T> softwareMetadata = null)
{
Dictionary<string, object> UILayoutMessage = new Dictionary<string,object>();
writeHeader(UILayoutMessage);
UILayoutMessage.Add("timestamp", ISOTimestamp);
UILayoutMessage.Add("type", "UILAYOUT");
Dictionary<string, object> parms = new Dictionary<string,object>();
parms.Add("desc", actionDescription);
parms.Add("visibility", visibility);
parms.Add("leftBound", leftBound);
parms.Add("rightBound", rightBound);
parms.Add("topBound", topBound);
parms.Add("bottomBound", bottomBound);
UILayoutMessage.Add("parms", parms);
UILayoutMessage.Add("metadata", softwareMetadata);
return convertToJSON(UILayoutMessage);
}
//=================END ACTIVITY LOGGING FUNCTIONS========================
/*=========================INTERNAL FUNCTIONS============================
* These functions are used internally by the Activity Logger helper
* library to generate JSON log messages.
*/
private void writeHeader(Dictionary<string,object> msg)
{
msg.Add("client", clientHostname);
msg.Add("sessionID", sessionID);
msg.Add("apiVersion", apiVersion);
msg.Add("impLanguage", implementationLanguage);
Dictionary<string, object> component = new Dictionary<string,object>();
component.Add("name", componentName);
component.Add("version", componentVersion);
msg.Add("component", component);
}
//Write the required API version structured data element
private String convertToJSON(Dictionary<string, object> msg)
{
String json = "{";
bool isFirstElement = true;
foreach (String key in msg.Keys)
{
if (msg[key] != null)
{
if (!isFirstElement)
{
json += ", ";
}
else
{
isFirstElement = false;
}
json += "\"" + key + "\":";
if (msg[key] is int || msg[key] is float)
{
json += msg[key];
}
else if (msg[key] is String)
{
json += "\"" + msg[key] + "\"";
}
else if (msg[key] is bool)
{
bool fieldVal = (bool)msg[key];
if (fieldVal)
{
json += "true";
}
else
{
json += "false";
}
}
else if (msg[key] is Dictionary<string, object>)
{
json += convertToJSON((Dictionary<string, object>)msg[key]);
}
else
{
throw new FormatException("Can only serialize numbers, strings, and Dictionary<string, object>s to JSON.");
}
}
}
json += "}";
return json;
}
//Write the required Activity structured data element
// Write the UI Layout structured data element
//Write any metadata included by the software developer
//Internal function to encode a single structured data element
//=======================END INTERNAL FUNCTIONS==========================
}
}