/**
 * @license
 * Copyright The Closure Library Authors.
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @fileoverview This class sends logging messages over a message channel to a
 * server on the main page that prints them using standard logging mechanisms.
 */

goog.provide('goog.messaging.LoggerClient');

goog.require('goog.Disposable');
goog.require('goog.debug');
goog.require('goog.log');
goog.requireType('goog.messaging.MessageChannel');


/**
 * Creates a logger client that sends messages along a message channel for the
 * remote end to log. The remote end of the channel should use a
 * {goog.messaging.LoggerServer} with the same service name.
 *
 * @param {!goog.messaging.MessageChannel} channel The channel that on which to
 *     send the log messages.
 * @param {string} serviceName The name of the logging service to use.
 * @constructor
 * @extends {goog.Disposable}
 * @final
 */
goog.messaging.LoggerClient = function(channel, serviceName) {
  'use strict';
  if (goog.messaging.LoggerClient.instance_) {
    return goog.messaging.LoggerClient.instance_;
  }

  goog.messaging.LoggerClient.base(this, 'constructor');

  /**
   * The channel on which to send the log messages.
   * @type {!goog.messaging.MessageChannel}
   * @private
   */
  this.channel_ = channel;

  /**
   * The name of the logging service to use.
   * @type {string}
   * @private
   */
  this.serviceName_ = serviceName;

  /**
   * The bound handler function for handling log messages. This is kept in a
   * variable so that it can be deregistered when the logger client is disposed.
   * @type {!Function}
   * @private
   */
  this.publishHandler_ = goog.bind(this.sendLog_, this);
  goog.log.addHandler(goog.log.getRootLogger(), this.publishHandler_);

  goog.messaging.LoggerClient.instance_ = this;
};
goog.inherits(goog.messaging.LoggerClient, goog.Disposable);


/**
 * The singleton instance, if any.
 * @type {?goog.messaging.LoggerClient}
 * @private
 */
goog.messaging.LoggerClient.instance_ = null;


/**
 * Sends a log message through the channel.
 * @param {!goog.log.LogRecord} logRecord The log message.
 * @private
 */
goog.messaging.LoggerClient.prototype.sendLog_ = function(logRecord) {
  'use strict';
  var name = logRecord.getLoggerName();
  var level = logRecord.getLevel();
  var msg = logRecord.getMessage();
  var originalException = logRecord.getException();

  var exception;
  if (originalException !== undefined) {
    var normalizedException =
        goog.debug.normalizeErrorObject(originalException);
    /** @suppress {strictMissingProperties} Added to tighten compiler checks */
    exception = {
      'name': normalizedException.name,
      'message': normalizedException.message,
      'lineNumber': normalizedException.lineNumber,
      'fileName': normalizedException.fileName,
      // Normalized exceptions without a stack have 'stack' set to 'Not
      // available', so we check for the existence of 'stack' on the original
      // exception instead.
      'stack': originalException.stack || goog.debug.getStacktrace(goog.log.log)
    };

    if (goog.isObject(originalException)) {
      // Add messageN to the exception in case it was added using
      // goog.debug.enhanceError.
      for (var i = 0; 'message' + i in originalException; i++) {
        exception['message' + i] = String(originalException['message' + i]);
      }
    }
  }
  this.channel_.send(this.serviceName_, {
    'name': name,
    'level': level.value,
    'message': msg,
    'exception': exception
  });
};


/** @override */
goog.messaging.LoggerClient.prototype.disposeInternal = function() {
  'use strict';
  goog.messaging.LoggerClient.base(this, 'disposeInternal');
  goog.log.removeHandler(goog.log.getRootLogger(), this.publishHandler_);
  delete this.channel_;
  goog.messaging.LoggerClient.instance_ = null;
};
