Source: services/LoggingService.js

/**
 * Copyright (C) 2005-2016 Alfresco Software Limited.
 *
 * This file is part of Alfresco
 *
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * This service can be included on a page to handle log messages generated through calls to the
 * [alfLog]{@link module:alfresco/core/Core#alfLog} function. It can either be configured (either
 * through user preferences or directly) to filter the messages that displayed in the browser
 * console.
 *
 * @example <caption>Example configuration to enable all logs</caption>
 * {
 *   name: "alfresco/services/LoggingService",
 *   config: {
 *     loggingPreferences: {
 *       enabled: true,
 *       all: true
 *     }
 *   }
 * }
 *
 * @example <caption>Example configuration to show only warnings and errors</caption>
 * {
 *   name: "alfresco/services/LoggingService",
 *   config: {
 *     loggingPreferences: {
 *       enabled: true,
 *       all: false,
 *       warn: true,
 *       error: true
 *     }
 *   }
 * }
 *
 * @example <caption>Example configuration to show only publication and subscription messages</caption>
 * {
 *   name: "alfresco/services/LoggingService",
 *   config: {
 *     loggingPreferences: {
 *       enabled: true,
 *       all: false,
 *       pubSub: true
 *     }
 *   }
 * }
 *
 * @example <caption>Example configuration to show all logs that match a custom Regular Expression filter</caption>
 * {
 *   name: "alfresco/services/LoggingService",
 *   config: {
 *     loggingPreferences: {
 *       enabled: true,
 *       all: true,
 *       filter: "afresco/core/(.*)"
 *     }
 *   }
 * }
 * 
 * @module alfresco/services/LoggingService
 * @extends module:alfresco/services/BaseService
 * @mixes module:alfresco/services/_PreferenceServiceTopicMixin
 * @author Dave Draper
 */
define(["dojo/_base/declare",
        "alfresco/services/BaseService",
        "alfresco/services/_PreferenceServiceTopicMixin",
        "dojo/_base/lang",
        "dojo/dom-class",
        "dojo/_base/window",
        "dijit/registry",
        "alfresco/dialogs/AlfDialog"],
        function(declare, BaseService, _PreferenceServiceTopicMixin, lang, domClass, win, registry, AlfDialog) {
        /*jshint devel:true*/


   return declare([BaseService, _PreferenceServiceTopicMixin], {

      /**
       * An array of the i18n files to use with this widget.
       *
       * @instance
       * @type {object[]}
       * @default [{i18nFile: "./i18n/LoggingService.properties"}]
       */
      i18nRequirements: [{i18nFile: "./i18n/LoggingService.properties"}],

      /**
       * @instance
       * @type {string}
       * @default
       */
      loggingPreferencesId: "org.alfresco.share.logging",

      /**
       * This will hold a reference to the subscription to log events. It will be null unless logging is enabled.
       *
       * @instance
       * @type {object}
       * @default
       */
      logSubscriptionHandle: null,

      /**
       * Indicates whether or not the user preferences for logging should be suppressed. If this is set to true then
       * a request will not be made to retrieve the user preferences for logging.
       *
       * @instance
       * @type {boolean}
       * @default
       * @since 1.0.36
       */
      suppressUserPreferences: false,

      /**
       * Sets up the subscriptions for the LoggingService
       *
       * @instance
       * @listens ALF_LOGGING_STATUS_CHANGE
       * @listens ALF_UPDATE_LOGGING_PREFERENCES
       * @listens ALF_SHOW_PUBSUB_LOG
       * @listens ALF_SHOW_DATA_MODEL
       * @listens ALF_TOGGLE_DEVELOPER_MODE
       *
       * @fires module:alfresco/core/topics#GET_PREFERENCE
       * @since 1.0.32
       */
      registerSubscriptions: function alfresco_services_LoggingService__registerSubscriptions() {
         this.alfSubscribe("ALF_LOGGING_STATUS_CHANGE", lang.hitch(this, this.onLoggingStatusChange));
         this.alfSubscribe("ALF_UPDATE_LOGGING_PREFERENCES", lang.hitch(this, this.onDetailsDialog));
         this.alfSubscribe("ALF_SHOW_PUBSUB_LOG", lang.hitch(this, this.showPubSubLog));
         this.alfSubscribe("ALF_SHOW_DATA_MODEL", lang.hitch(this, this.showDataModel));
         this.alfSubscribe("ALF_TOGGLE_DEVELOPER_MODE", lang.hitch(this, this.toggleDeveloperMode));

         if (this.loggingPreferences !== null && typeof this.loggingPreferences !== "undefined")
         {
            // Set up subscriptions if the LoggingService has been instantiated with default
            // preferences...
            this.handleSubscription();
         }

         if (!this.suppressUserPreferences)
         {
            this.alfPublish(this.getPreferenceTopic, {
               preference: this.loggingPreferencesId,
               callback: this.setLoggingStatus,
               callbackScope: this
            });
         }
      },

      /**
       * Switches into the developer mode that gives an exploded view of the widgets on the page.
       * 
       * @instance
       */
      toggleDeveloperMode: function alfresco_services_LoggingService__toggleDeveloperMode() {
         domClass.toggle(win.body(), "alfresco-developer-mode-Enabled");
      },

      /**
       *
       * @instance
       * @param {object} payload
       */
      showPubSubLog: function alfresco_services_LoggingService__showPubSubLog(/*jshint unused:false*/ payload) {
         if (!this.pubSubLog)
         {
            this.pubSubLog = new AlfDialog({
               title: this.message("logging.pubSubLog.title"),
               fixedWidth: true,
               handleOverflow: true,
               widgetsContent: [
                  {
                     name: "alfresco/logging/DebugLog"
                  }
               ]
            });
         }
         this.pubSubLog.show();
      },

      /**
       * This displays a dialog that displays the current data model built up through the use of the
       * [CoreData singleton]{@link module:alfresco/core/CoreData}.
       *
       * @instance
       * @param {object} payload The request payload
       */
      showDataModel: function alfresco_services_LoggingService__showDataModel(payload) {
         /*jshint unused:false*/
         var dialog = new AlfDialog({
            pubSubScope: this.pubSubScope,
            title: this.message("logging.dataModel.title"),
            widgetsContent: [
               {
                  name: "alfresco/debug/CoreDataDebugger"
               }
            ]
         });
         dialog.show();
      },

      /**
       * Handles requests to change the current logging status.
       *
       * @instance
       * @param {object} payload
       *
       * @fires setPreferenceTopic
       */
      onLoggingStatusChange: function alfresco_services_LoggingService__onLoggingStatusChange(payload) {
         if (lang.exists("selected", payload) && lang.exists("value", payload))
         {
            this.alfPublish(this.setPreferenceTopic, {
               preference: this.loggingPreferencesId + "." + payload.value,
               value: (payload.selected === true)
            });
            if (!this.loggingPreferences)
            {
               this.loggingPreferences = {};
            }
            this.loggingPreferences[payload.value] = (payload.selected === true);
            this.handleSubscription();
         }
      },

      /**
       * @instance
       * @param {boolean} value Indicates whether or not to enable or disable logging.
       */
      setLoggingStatus: function alfresco_services_LoggingService__setLoggingStatus(value) {
         this.loggingPreferences = value || {};
         this.handleSubscription();
      },

      /**
       * Updates the subscription to the logging topic.
       * Checks each subscription individually and only subscribes if one doesn't already exist.
       *
       * @instance
       *
       * @listens alfLoggingTopic
       */
      handleSubscription: function alfresco_services_LoggingService__handleSubscription() {
         // jshint maxcomplexity:false,maxstatements:false
         if (this.loggingPreferences.enabled)
         {
            if (!this.logSubscriptionHandle)
            {
               this.logSubscriptionHandle = this.alfSubscribe(this.alfLoggingTopic, lang.hitch(this, this.onLogRequest));
            }

            // Only log pub/sub activity if either all logging is enabled or PUBSUB logging has been
            // explicitly requested...
            if (this.loggingPreferences.all === true ||
                this.loggingPreferences.pubSub === true)
            {
               if (!this.subLogSubscriptionHandle)
               {
                  this.subLogSubscriptionHandle = this.alfSubscribe("ALF_LOG_SUBSCRIPTION_ACTIVITY", lang.hitch(this, this.onPubSubLogRequest, "SUBSCRIPTION"));
               }

               if (!this.pubLogSubscriptionHandle)
               {
                  this.pubLogSubscriptionHandle = this.alfSubscribe("ALF_LOG_PUBLICATION_ACTIVITY", lang.hitch(this, this.onPubSubLogRequest, "PUBLICATION"));
               }

               if (!this.unsubLogSubscriptionHandle)
               {
                  this.unsubLogSubscriptionHandle = this.alfSubscribe("ALF_LOG_UNSUBSCRIPTION_ACTIVITY", lang.hitch(this, this.onPubSubLogRequest, "UNSUBSCRIPTION"));
               }
            }
            else
            {
               if (this.subLogSubscriptionHandle)
               {
                  this.alfUnsubscribe(this.subLogSubscriptionHandle);
                  this.subLogSubscriptionHandle = null;
               }
               if (this.pubLogSubscriptionHandle)
               {
                  this.alfUnsubscribe(this.pubLogSubscriptionHandle);
                  this.pubLogSubscriptionHandle = null;
               }
               if (this.unsubLogSubscriptionHandle)
               {
                  this.alfUnsubscribe(this.unsubLogSubscriptionHandle);
                  this.unsubLogSubscriptionHandle = null;
               }
            }
         }
         else if (!this.loggingPreferences.enabled && this.logSubscriptionHandle)
         {
            if (this.logSubscriptionHandle)
            {
               this.alfUnsubscribe(this.logSubscriptionHandle);
               this.logSubscriptionHandle = null;
            }
            if (this.subLogSubscriptionHandle)
            {
               this.alfUnsubscribe(this.subLogSubscriptionHandle);
               this.subLogSubscriptionHandle = null;
            }
            if (this.pubLogSubscriptionHandle)
            {
               this.alfUnsubscribe(this.pubLogSubscriptionHandle);
               this.pubLogSubscriptionHandle = null;
            }
            if (this.unsubLogSubscriptionHandle)
            {
               this.alfUnsubscribe(this.unsubLogSubscriptionHandle);
               this.unsubLogSubscriptionHandle = null;
            }
         }
      },

      /**
       * This attribute is used to hold a reference to a [dialog]{@link module:alfresco/dialogs/AlfDialog} that can be
       * used to set more granular logging information (e.g. wildcard style matches for widgets and functions). It is set
       * on the first call to the [onDetailsDialog function]{@link module:alfresco/services/LoggingService#onDetailsDialog}.
       *
       * @instance
       * @type {object}
       * @default
       */
      detailsDialog: null,

      /**
       * This is the topic used to subscribe to requests to save logging preferences updated by the
       * [preferences dialog]{@link module:alfresco/services/LoggingService#detailsDialog}.
       *
       * @instance
       * @event
       * @type {string}
       * @default
       */
      saveLoggingPrefsUpdateTopic: "ALF_SAVE_LOGGING_PREFERNCES_UPDATE",

      /**
       * This is the topic used to subscribe to requests to cancel logging preferences updates set in the
       * [preferences dialog]{@link module:alfresco/services/LoggingService#detailsDialog}.
       *
       * @instance
       * @event
       * @type {string}
       * @default
       */
      cancelLoggingPrefsUpdateTopic: "ALF_CANCEL_LOGGING_PREFERNCES_UPDATE",

      /**
       * @instance
       * @param {object} payload
       */
      onDetailsDialog: function alfresco_services_LoggingService__onDetailsDialog(payload) {
         if (!this.detailsDialog)
         {
            this.alfSubscribe(this.saveLoggingPrefsUpdateTopic, lang.hitch(this, "onPrefsUpdateSave"));
            this.alfSubscribe(this.cancelLoggingPrefsUpdateTopic, lang.hitch(this, "onPrefsUpdateCancel"));
            this.detailsDialog = new AlfDialog({
               title: this.message("logging.preferences.title"),
               widgetsContent: [
                  {
                     name: "alfresco/forms/controls/DojoValidationTextBox",
                     config: {
                        id: this.id + "_LOGGING_FILTER",
                        name: "filter",
                        label: this.message("filter.label"),
                        description: this.message("filter.description"),
                        value: this.loggingPreferences.filter || ""
                     }
                  }
               ],
               widgetsButtons: [
                  {
                     name: "alfresco/buttons/AlfButton",
                     config: {
                        label: this.message("button.save-logging-prefs"),
                        publishTopic: this.saveLoggingPrefsUpdateTopic,
                        publishPayload: payload
                     }
                  },
                  {
                     name: "alfresco/buttons/AlfButton",
                     config: {
                        label: this.message("button.cancel-logging-prefs-update"),
                        publishTopic: this.cancelLoggingPrefsUpdateTopic,
                        publishPayload: payload
                     }
                  }
               ]
            });
         }
         this.detailsDialog.show();
      },

      /**
       * @instance
       * @param {object} payload
       */
      onPrefsUpdateSave: function alfresco_services_LoggingService__onPrefsUpdateSave(payload) {
         /*jshint unused:false*/
         var filterWidget = registry.byId(this.id + "_LOGGING_FILTER");
         if (filterWidget)
         {
            var filterValue = filterWidget.getValue();
            this.alfPublish(this.setPreferenceTopic, {
               preference: this.loggingPreferencesId + ".filter",
               value: filterValue
            });
            this.loggingPreferences.filter = filterValue;
         }
      },

      /**
       * @instance
       * @param {object} payload
       */
      onPrefsUpdateCancel: function alfresco_services_LoggingService__onPrefsUpdateCancel(payload) {
         /*jshint unused:false*/
         var filterWidget = registry.byId(this.id + "_LOGGING_FILTER");
         if (filterWidget)
         {
            filterWidget.setValue(this.loggingPreferences.filter || "");
         }
      },

      /**
       * The local copy of logging preferences.
       *
       * @instance
       * @type {object}
       * @default
       */
      loggingPreferences: null,

      /**
       * This logging is explicitly for publication, subscription and unsubscription events.
       *
       * @param  {string} type This is the PubSub activity type (e.g. "SUBSCRIPTION", "PUBLICATION", etc).
       * @param  {object} payload The details of the PubSub activity
       * @param  {object} [caller] A reference to the object that published the event
       */
      onPubSubLogRequest: function alfresco_services_LoggingService__onPubSubLogRequest(type, payload, caller) {
         if (this.passesLoggingFilter(payload.alfCallerName))
         {
            var event = {
               type: type
            };

            // Build a custom object type depending on the log type
            switch (type)
            {
               case "PUBLICATION":
                  event.topic = payload.alfTopic;
                  event.payload = payload;

                  // If the widget that triggered the publish has been passed, pass that through, highlighting the id.
                  if (caller)
                  {
                     if (caller.id)
                     {
                        event.publisherId = caller.id;
                     }
                     event.publisher = caller;
                  }

                  break;
               case "SUBSCRIPTION":
                  event.topic = payload.subscribedTopic;
                  event.subscriberId = payload.subscriber.id;
                  event.subscriber = payload.subscriber;
                  break;
               case "UNSUBSCRIPTION":
                  event.topic = payload.unsubscribedTopic;
                  event.subscriberId = payload.subscriber.id;
                  event.subscriber = payload.subscriber;
                  break;
            }
            console.log(event);
         }
      },

      /**
       *
       *
       * @typedef {Object} logPayload
       * @property severity {string}
       * @property callerName {string}
       * @property messageArgs {Object[]}
       */

      /**
       * Checks to see whether or not the current log request caller passes the logging filter.
       *
       * @instance
       * @param {string} callerName The name of the function requesting logging.
       * @return {boolean} true if the filter passes and false otherwise
       */
      passesLoggingFilter: function alfresco_services_LoggingService__passesLoggingFilter(callerName) {
         var matchesFilter = true;
         if (callerName && callerName !== "")
         {
            var fIndex = callerName.lastIndexOf("__"),
                re1 = /([^_])_/g;
            if (fIndex !== -1)
            {
               var mName = callerName.substring(0, fIndex);
               var fName = callerName.substring(fIndex + 2);
               callerName = mName.replace(re1, "$1/") + "[" + fName + "] >> ";
            }
            else
            {
               callerName = callerName + " >> ";
            }
         }
         else
         {
            callerName = "";
         }

         // Check to see whether or not there is a log filter and if so, whether or
         // not the current caller passes the filter...
         if (this.loggingPreferences.filter)
         {
            var test = new RegExp(this.loggingPreferences.filter);
            matchesFilter = test.test(callerName);
         }
         return matchesFilter;
      },

      /**onLogRequest
       * @instance
       * @param {logPayload} payload
       */
      onLogRequest: function alfresco_services_LoggingService__onLogRequest(payload) {
         /*jshint maxcomplexity:false*/
         if (payload &&
             payload.severity &&
             payload.messageArgs &&
             (this.loggingPreferences.all === true ||
              this.loggingPreferences[payload.severity] === true))
         {
            // If the filter is mapped (or if there is no filter) output the log...
            if (this.passesLoggingFilter(payload.callerName))
            {
               // Decorate the message with the source
               payload.messageArgs[0] = (payload.callerName || payload.alfCallerName || "") + " >> " + payload.messageArgs[0];

               // Make sure we can use the requested console method
               if (typeof console[payload.severity].apply !== "function") {

                  // Run through the message arguments (because apply doesn't work)
                  var message;
                  for (var i = 0, j = payload.messageArgs.length; i < j; i++) {

                     // Get the next message
                     message = payload.messageArgs[i];

                     // If message is an object, try and be helpful and JSONify it
                     if (typeof message !== "string") {
                        try {
                           message = JSON.stringify(message);
                        } catch (e) {
                           message = "Unable to JSONify: '" + message + "'";
                        }
                     }

                     // Decorate the message according to its position in the array
                     var prefix = (j === 1) ? "" : "[" + (i + 1) + "/" + j + "] ";

                     // Log the message
                     if (i === 0) {
                        console.log("");
                     }
                     console.log(prefix + message);
                     if (i + 1 === j) {
                        console.log("");
                     }
                  }
               } else {

                  // All good here, carry on ...
                  console[payload.severity].apply(console, payload.messageArgs);
               }
            }
         }
      }
   });
});