How to get console.log line numbers shown in Nodejs?

后端 未结 5 594
小蘑菇
小蘑菇 2020-12-02 20:54

Got an old application, that prints out quite a lot of messages using console.log, but I just can not find in which files and lines console.log is

相关标签:
5条回答
  • 2020-12-02 21:22

    Having full stack trace for each call is a bit noisy. I've just improved the @noppa's solution to print only the initiator:

    ['log', 'warn', 'error'].forEach((methodName) => {
      const originalMethod = console[methodName];
      console[methodName] = (...args) => {
        let initiator = 'unknown place';
        try {
          throw new Error();
        } catch (e) {
          if (typeof e.stack === 'string') {
            let isFirst = true;
            for (const line of e.stack.split('\n')) {
              const matches = line.match(/^\s+at\s+(.*)/);
              if (matches) {
                if (!isFirst) { // first line - current function
                                // second line - caller (what we are looking for)
                  initiator = matches[1];
                  break;
                }
                isFirst = false;
              }
            }
          }
        }
        originalMethod.apply(console, [...args, '\n', `  at ${initiator}`]);
      };
    });
    

    It also patches other methods (useful for Nodejs, since warn and error don't come with a stack trace as in Chrome).

    So your console would look something like:

    Loading settings.json
       at fs.readdirSync.filter.forEach (.../settings.js:21:13)
    Server is running on http://localhost:3000 or http://127.0.0.1:3000
       at Server.app.listen (.../index.js:67:11)
    
    0 讨论(0)
  • 2020-12-02 21:26

    For a temporary hack to find the log statements that you want to get rid of, it's not too difficult to override console.log yourself.

    var log = console.log;
    console.log = function() {
        log.apply(console, arguments);
        // Print the stack trace
        console.trace();
    };
    
    
    // Somewhere else...
    function foo(){
        console.log('Foobar');
    }
    foo();

    That will print something like

    Foobar
    Trace
    at Console.console.log (index.js:4:13)
    at foo (index.js:10:13)
    at Object.<anonymous> (index.js:12:1)
    ...
    

    A lot of noise in there but the second line in the call stack, at foo (index.js:10:13), should point you to the right place.

    0 讨论(0)
  • 2020-12-02 21:26

    All solutions to this question so far rely on splitting and matching the stack trace as a string, which will break in (the unlikely) case the format of that string is changed in the future. Inspired by this gist on GitHub and the other answers here, I want to provide my own solution:

    'use strict';
    
    const path = require('path');
    
    ['debug', 'log', 'warn', 'error'].forEach((methodName) => {
        const originalLoggingMethod = console[methodName];
        console[methodName] = (firstArgument, ...otherArguments) => {
            const originalPrepareStackTrace = Error.prepareStackTrace;
            Error.prepareStackTrace = (_, stack) => stack;
            const callee = new Error().stack[1];
            Error.prepareStackTrace = originalPrepareStackTrace;
            const relativeFileName = path.relative(process.cwd(), callee.getFileName());
            const prefix = `${relativeFileName}:${callee.getLineNumber()}:`;
            if (typeof firstArgument === 'string') {
                originalLoggingMethod(prefix + ' ' + firstArgument, ...otherArguments);
            } else {
                originalLoggingMethod(prefix, firstArgument, ...otherArguments);
            }
        };
    });
    
    // Tests:
    console.log('%s %d', 'hi', 42);
    console.log({ a: 'foo', b: 'bar'});
    

    Unlike the other solutions, this script

    • outputs no additional lines and
    • handles string substitutions correctly.

    You can color the prefix with chalk or color.js, but I didn't want to introduce dependencies for this here.

    The above script uses the V8 API to customize stack traces. The callee is a CallSite object with the following methods in case you want to customize the prefix:

    • getThis: returns the value of this
    • getTypeName: returns the type of this as a string. This is the name of the function stored in the constructor field of this, if available, otherwise the object’s [[Class]] internal property.
    • getFunction: returns the current function
    • getFunctionName: returns the name of the current function, typically its name property. If a name property is not available an attempt is made to infer a name from the function’s context.
    • getMethodName: returns the name of the property of this or one of its prototypes that holds the current function
    • getFileName: if this function was defined in a script returns the name of the script
    • getLineNumber: if this function was defined in a script returns the current line number
    • getColumnNumber: if this function was defined in a script returns the current column number
    • getEvalOrigin: if this function was created using a call to eval returns a string representing the location where eval was called
    • isToplevel: is this a top-level invocation, that is, is this the global object?
    • isEval: does this call take place in code defined by a call to eval?
    • isNative: is this call in native V8 code?
    • isConstructor: is this a constructor call?
    • isAsync: is this an async call (i.e. await or Promise.all())?
    • isPromiseAll: is this an async call to Promise.all()?
    • getPromiseIndex: returns the index of the promise element that was followed in Promise.all() for async stack traces, or null if the CallSite is not a Promise.all() call.

    This answer is a cross-post of an answer I just gave to a similar question as more people might find this page.

    0 讨论(0)
  • 2020-12-02 21:33

    I found Dmitry Druganov's answer really nice, but I tried it on Windows 10 (with Node 8.9.4) and it didn't work well. It was printing the full path, something like:

    Loading settings.json
       at fs.readdirSync.filter.forEach (D:\Users\Piyin\Projects\test\settings.js:21:13)
    Server is running on http://localhost:3000 or http://127.0.0.1:3000
       at Server.app.listen (D:\Users\Piyin\Projects\test\index.js:67:11)
    

    So I took said answer and made these improvements (from my point of view):

    • Assume the important line of the stack trace is the third one (the first one is the word Error and the second one is where you place this script)
    • Remove the current script folder path (given by __dirname, which in my case is D:\Users\Piyin\Projects\test). Note: For this to work well, the script should be on the project's main Javascript
    • Remove the starting at
    • Place the file information before the actual log
    • Format the information as Class.method at path/to/file:line:column

    Here it is:

    ['log','warn','error'].forEach((methodName) => {
      const originalMethod = console[methodName];
      console[methodName] = (...args) => {
        try {
          throw new Error();
        } catch (error) {
          originalMethod.apply(
            console,
            [
              (
                error
                .stack // Grabs the stack trace
                .split('\n')[2] // Grabs third line
                .trim() // Removes spaces
                .substring(3) // Removes three first characters ("at ")
                .replace(__dirname, '') // Removes script folder path
                .replace(/\s\(./, ' at ') // Removes first parentheses and replaces it with " at "
                .replace(/\)/, '') // Removes last parentheses
              ),
              '\n',
              ...args
            ]
          );
        }
      };
    });
    

    And here's the new output:

    fs.readdirSync.filter.forEach at settings.js:21:13
     Loading settings.json
    Server.app.listen at index.js:67:11
     Server is running on http://localhost:3000 or http://127.0.0.1:3000
    

    Here's the minified-by-hand code (240 bytes):

    ['log','warn','error'].forEach(a=>{let b=console[a];console[a]=(...c)=>{try{throw new Error}catch(d){b.apply(console,[d.stack.split('\n')[2].trim().substring(3).replace(__dirname,'').replace(/\s\(./,' at ').replace(/\)/,''),'\n',...c])}}});
    
    0 讨论(0)
  • 2020-12-02 21:39

    Slightly modified version of noppa's answer, this version will output something like:

    /file/in-which/console/is/called.js:75:23
     The stuff you want to log.
    

    This is clean and convenient (especially for use in VSCode - which will turn the file path into a link).

    const { log } = console;
    function proxiedLog(...args) {
      const line = (((new Error('log'))
        .stack.split('\n')[2] || '…')
        .match(/\(([^)]+)\)/) || [, 'not found'])[1];
      log.call(console, `${line}\n`, ...args);
    }
    console.info = proxiedLog;
    console.log = proxiedLog;
    
    // test
    console.log('Hello!');

    The snippet will only work well in a NodeJS environment…

    0 讨论(0)
提交回复
热议问题