Source: lib/logging.js

// Copyright (c) 2018 Intel Corporation. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

'use strict';

const path = require('path');
const rclnodejs = require('bindings')('rclnodejs');

/**
 * Enum for LoggingSeverity
 * @readonly
 * @enum {number}
 */
let LoggingSeverity = {
  /** @member {number} */
  UNSET: 0,
  /** @member {number} */
  DEBUG: 10,
  /** @member {number} */
  INFO: 20,
  /** @member {number} */
  WARN: 30,
  /** @member {number} */
  ERROR: 40,
  /** @member {number} */
  FATAL: 50,
};

class Caller {
  constructor() {
    this._info = {
      functionName: 'unknown',
      fileName: 'unknown',
      lineNumber: 'unknown',
    };

    const stackLines = new Error().stack.split('\n');

    // Adjust the index (usually 3 or 4) to correctly point to the caller frame.
    const callerFrame = stackLines[4] || stackLines[3];
    // Match both named and anonymous function stack frames.
    const frameRegex = /^\s*at\s+(?:(.+)\s+\()?(.+):(\d+):(\d+)\)?$/;
    const match = callerFrame.match(frameRegex);
    if (match && match.length === 5) {
      this._info.functionName = match[1] || '(anonymous)';
      this._info.fileName = path.basename(match[2]);
      this._info.lineNumber = match[3];
    } else {
      // Handle anonymous functions or different stack formats.
      const altMatch = callerFrame.match(/at\s+(.*):(\d+):(\d+)/i);
      if (altMatch && altMatch.length >= 4) {
        this._info.functionName = '(anonymous)';
        this._info.fileName = path.basename(altMatch[1]);
        this._info.lineNumber = altMatch[2];
      }
    }
  }

  get functionName() {
    return this._info.functionName;
  }

  get lineNumber() {
    return this._info.lineNumber;
  }

  get fileName() {
    return this._info.fileName;
  }
}

/**
 * @class - Class representing logger in ROS
 * @hideconstructor
 */

class Logging {
  constructor(name) {
    this._name = name;
  }

  /**
   * Set the logging severity level.
   * @param {LoggingSeverity} level - The logging severity level.
   * @function
   * @return {undefined}
   */
  setLoggerLevel(level) {
    if (typeof level !== 'number') {
      throw new TypeError('Invalid argument');
    }
    rclnodejs.setLoggerLevel(this._name, level);
  }

  /**
   * Get the logging severity level.
   * @function
   * @return {LoggingSeverity} - The severity level of the logger.
   */
  get loggerEffectiveLevel() {
    return rclnodejs.getLoggerEffectiveLevel(this._name);
  }

  /**
   * Log a message with the DEBUG severity.
   * @param {string} message - message to be logged.
   * @function
   * @return {bool} Return true if the message has been logged.
   */
  debug(message) {
    return this._log(message, LoggingSeverity.DEBUG);
  }

  /**
   * Log a message with the INFO severity.
   * @param {string} message - message to be logged.
   * @function
   * @return {bool} Return true if the message has been logged.
   */
  info(message) {
    return this._log(message, LoggingSeverity.INFO);
  }

  /**
   * Log a message with the WARN severity.
   * @param {string} message - message to be logged.
   * @function
   * @return {bool} Return true if the message has been logged.
   */
  warn(message) {
    return this._log(message, LoggingSeverity.WARN);
  }

  /**
   * Log a message with the ERROR severity.
   * @param {string} message - message to be logged.
   * @function
   * @return {bool} Return true if the message has been logged.
   */
  error(message) {
    return this._log(message, LoggingSeverity.ERROR);
  }

  /**
   * Log a message with the FATAL severity.
   * @param {string} message - message to be logged.
   * @function
   * @return {bool} Return true if the message has been logged.
   */
  fatal(message) {
    return this._log(message, LoggingSeverity.FATAL);
  }

  _log(message, severity) {
    if (typeof message !== 'string') {
      throw new TypeError('Invalid argument');
    }

    let caller = new Caller();
    return rclnodejs.log(
      this._name,
      severity,
      message,
      caller.functionName,
      parseInt(caller.lineNumber, 10),
      caller.fileName
    );
  }

  /**
   * Get LoggingSeverity enum.
   * @function
   * @return {LoggingSeverity} Return LoggingSeverity enum.
   */
  get LoggingSeverity() {
    return LoggingSeverity;
  }

  /**
   * Get name of the logger.
   * @function
   * @return {string} logger's name.
   */
  get name() {
    return this._name;
  }

  /**
   * Create a logger by name.
   * @param {string} name - name of the logger.
   * @function
   * @return {Logging} Return the logger object.
   */
  static getLogger(name) {
    if (typeof name !== 'string') {
      throw new TypeError('Invalid argument');
    }
    return new Logging(name);
  }
}

module.exports = Logging;