| 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========================== |
| |
| } |
| } |
| |