Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Log caller function name and line number #890

Closed
httpdigest opened this issue Jul 15, 2016 · 6 comments
Closed

Log caller function name and line number #890

httpdigest opened this issue Jul 15, 2016 · 6 comments

Comments

@httpdigest
Copy link

httpdigest commented Jul 15, 2016

There does not seem to be a way to log the caller's method name and line number. I know that this has already been requested with #200, but that issue was locked with the reason that such a function would be very slow. Given that a typical application would not excessively log thousands of log statements per second, it does not make a noticeably difference.

As you probably know, JavaScript provides the stack trace in the Error.stack property on construction of a new Error object. However, this is an unstructured string and needs to be parsed.
V8 on the other hand has a feature allowing to retrieve the stack trace in its original structured form (before stringifying). This is described here: https://github.com/v8/v8/wiki/Stack-Trace-API

My current implementation on top of Winston looks like this. (It may not be a good implementation, I'm very new to JavaScript and Node/V8. :) )

// ES2015
const path = require('path');
const winston = require('winston');
function CustomError() {
  // Use V8's feature to get a structured stack trace
  const oldStackTrace = Error.prepareStackTrace;
  const oldLimit = Error.stackTraceLimit;
  try {
    Error.stackTraceLimit = 3; // <- we only need the top 3
    Error.prepareStackTrace = (err, structuredStackTrace) => structuredStackTrace;
    Error.captureStackTrace(this, CustomError);
    this.stack; // <- invoke the getter for 'stack'
  } finally {
    Error.stackTraceLimit = oldLimit;
    Error.prepareStackTrace = oldStackTrace;
  }
}
function getAndFormatStackTraceElement() {
  const stack = new CustomError().stack;
  const CALLER_INDEX = 2; // <- position in stacktrace to find deepest caller
  const element = stack[CALLER_INDEX];
  const fileName = path.basename(element.getFileName());
  return element.getFunctionName() + "(" + fileName + ":" + element.getLineNumber() + ")";
}
module.exports = {
  info(msg, ...vars) {
    winston.info(getAndFormatStackTraceElement() + ': ' + msg, ...vars);
  }
}

Credits go to @addaleax from: nodejs/node#7749 (comment)
The way the string is formatted and the module exports are defined, sure needs reworking, I guess.

This ticket could serve as a discussion on how a good PR would look like for Winston, adding this feature to the library.

@hreimer
Copy link

hreimer commented Mar 20, 2017

hi, I am trying to use your wrapper right now and get this error:

RangeError: Maximum call stack size exceeded
    at CustomError.get stack [as stack] (native)
    at new CustomError (<filename:lineNumber>)
    at getAndFormatStackTraceElement (<filename:lineNumber>)
    at logger.error (<filename:lineNumber>)

Any idea how I could resolve this?
Did it happen to you too? thanks

@abumq
Copy link

abumq commented Mar 28, 2018

Try Easylogging++ binding for node. See original library docs to check the potential of this binding

A simple example

const easyloggingpp = require('easyloggingpp');
const ConfigType = easyloggingpp.ConfigType;
const Level = easyloggingpp.Level;
const logger = easyloggingpp.getLogger('mylogger'); // register logger

easyloggingpp.configureAllLoggers([
    {
        config: ConfigType.Format,
        value: '%levshort %datetime %fbase:%line %msg',  // <-- fbase or file for full path 
    },
    {
        config: ConfigType.Filename,
        value: 'logs/output.log'
    }
]);

logger.info('simple log');
logger.info('array %s', [1, 2, 3]);
var person = { 'name': 'Adam', 'age': 960, }
logger.info('obj %s', person);

logger.error('an error occurred %s', new Error("error msg"));

easyloggingpp.setLogErrorStack(false); // turn stack logging off
logger.fatal('serious error occurred %s', new Error("fatal msg"));
logger.error('an error occurred %s', new Error("error msg"));

Sample output
sample-output

@Shmee007
Copy link

hi, I am trying to use your wrapper right now and get this error:

RangeError: Maximum call stack size exceeded
    at CustomError.get stack [as stack] (native)
    at new CustomError (<filename:lineNumber>)
    at getAndFormatStackTraceElement (<filename:lineNumber>)
    at logger.error (<filename:lineNumber>)

Any idea how I could resolve this?
Did it happen to you too? thanks

In the line
Error.captureStackTrace(this, CustomError)
CustomError is effectively calling itself, so infinite loop and max call stack exceeded.

@Shmee007
Copy link

Shmee007 commented Oct 12, 2018

I used @httpdigest solution to create a Static class for what I needed but you can use it as above with winston
usage

const StackElements = require('./stackElements');
consol.log(StackElements.getFormatedStackTraceElement(1)); // 0 for current level, 1 for parent ...

export


const path = require('path');

class V8StackElements {
    static get stack() {
        return this.customStack;
    }

    constructor(callerIndex) {

        // Use V8's feature to get a structured stack trace
        const oldStackTrace = Error.prepareStackTrace;
        const oldLimit = Error.stackTraceLimit;
        try {
            Error.stackTraceLimit = callerIndex + 1; // <- we only want the top couple of elements
            Error.prepareStackTrace = (err, structuredStackTrace) => structuredStackTrace;
            Error.captureStackTrace(this);
            this.customStack = this.stack; // <- invoke the getter for 'stack'
        } finally {
            Error.stackTraceLimit = oldLimit;
            Error.prepareStackTrace = oldStackTrace;
        }
    }
}

class StackElements {
    static getFormatedStackTraceElement(callerIndex) {
        callerIndex = callerIndex + 2; //ignor v8 level and this level
        const stack = new V8StackElements(callerIndex).stack;
        const element = stack[callerIndex];
        const fileName = path.basename(element.getFileName());
        return element.getFunctionName() + "(" + fileName + ":" + element.getLineNumber() + ")";
    }
}

module.exports = StackElements;

I split it to 2 classes to only expose the intended function on the static class

I ended up here as I got stuck with the

'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them

when trying to use Function.caller

@indexzero
Copy link
Member

Thanks for the suggestion @httpdigest! But as you said this is a duplicate of #200, so going to close it. Will leave the issue unlocked to keep the discussion going this time.

@yeongjet
Copy link

yeongjet commented Feb 8, 2020

please provide a option to enable this feature, like log4js

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants