4

I'm trying to setup logging across my typescript program, using log4javascript.

However I have no idea how to retrieve the function names using reflection (rather than typed manually).

Ideally I want to emulate what I do in C#:

public class Foo 
{
    private static readonly log4net.ILog logger = log4net.LogManager.GetLogger(typeof(Foo));

    public Foo()
    {
    }

    public FooMethod()
    {
        try {
            logger.Logger.Log(this.GetType(), log4net.Core.Level.Trace, "Entering" + MethodBase.GetCurrentMethod().Name, null);
            // code
        }
        catch (e) {
            logger.Logger.Log(this.GetType(), log4net.Core.Level.Debug, ex.Message, null);
        }
        finally {
            logger.Logger.Log(this.GetType(), log4net.Core.Level.Trace, "Exiting" + MethodBase.GetCurrentMethod().Name, null);
        }
    }
}

How can I do this in Typescript? All I can do is get the class name.

class Foo {
    private static logger: log4javascript.Logger = log4javascript.getLogger(getName(Foo));

    constructor() {
    }

    FooFunction() {
        try {
            SymDataSource.logger.trace("Entering: " + getName(Foo.prototype.FooFunction));
            // code
        } catch (e) {
            SymDataSource.logger.debug("Exception: " + getName(Foo.prototype.FooFunction), e);
        } finally {
            SymDataSource.logger.trace("Exiting: " + getName(Foo.prototype.FooFunction));
        }
    }
}

function getName(obj: any): string {
   if (obj.name) {
       return obj.name;
   }
   var funcNameRegex = /function (.{1,})\(/;
   var results = (funcNameRegex).exec((<any> obj).constructor.toString());
   return (results && results.length > 1) ? results[1] : "";
}

The class name returns correctly, but the functions return as "Function".

Tim
  • 2,968
  • 5
  • 29
  • 55
  • possible duplicate of [How do I get the name of an object's type in JavaScript?](http://stackoverflow.com/questions/332422/how-do-i-get-the-name-of-an-objects-type-in-javascript) or [Get an objects Class name at runtime in TypeScript](http://stackoverflow.com/questions/13613524/get-an-objects-class-name-at-runtime-in-typescript) – Fenton Oct 01 '13 at 14:50
  • Are the definitelytyped typings out for log4javascript?? I've been waiting for those! – Allen Rice Nov 22 '13 at 18:52
  • I've written them myself. Would upload to the repository but haven't had the time to write the required test classes. – Tim Nov 25 '13 at 09:40
  • If you want my copies or want to do that then I'll email it to you if you wish. – Tim Nov 25 '13 at 09:41
  • Tim, would love to get hold of the typings for log4javascript. Do you have them available? Couldn't see an email address in your profile. – Dunco May 02 '14 at 06:27
  • @Tim can you upload your log4javascript typescript definitions somewhere? – Brandon Jul 07 '14 at 23:40
  • Sorry I've left the company I made them for, and not getting any response when I ask if they would be willing to release them for the web. – Tim Jul 08 '14 at 11:31

2 Answers2

6

For member functions it is actually not possible to use the simple toString parsing approach because of the way prototype functions are created e.g. :

Foo.prototype.FooFunction = function () {
    return getName(this.FooFunction);
};

Here toString would give you :

function () {
    return getName(this.FooFunction);
};

And you can see that the function name is not present between function () {.

What you can do is create an inline function (using the awesome fat arrow e.g. return getName(()=>this.FooFunction);) and parse this local function body to find the function name. Notice that in this way it is still TypeSafe and if you do a rename refactoring there are no magic strings that will go out of sync. So the complete implementation becomes :

class Foo { 
    static className = getName(Foo);    
    constructor() {
    }
    FooFunction() {        
            return getName(()=>this.FooFunction);            
    }
}

function getName(obj: any): string {    
    if (obj.name) {
        return obj.name;
    }

    var funcNameRegex = /function (.{1,})\(/;   
    var results = (funcNameRegex).exec(obj.toString());
    var result = results && results.length > 1 && results[1];

    // Check to see custom implementation
    if(!result){
        funcNameRegex = /return _this.(.*);/;
        results = (funcNameRegex).exec(obj.toString());
        result = results && results.length > 1 && results[1];
    }
    return result || "";
}

console.log(Foo.className);
var foo = new Foo();
console.log(foo.FooFunction());

which will output :

Foo
FooFunction
basarat
  • 261,912
  • 58
  • 460
  • 511
  • Works. Not really thrilled about using an expression to sneak it out though. I've been trying to use the stack trace to get the previous function (so have a GetCurrentName() function), creating an error and pulling the top of the call stack. http://www.codeovertones.com/2011/08/how-to-print-stack-trace-anywhere-in.html – Tim Oct 01 '13 at 12:49
  • Probably my solution isn't much better though, and no idea if it wastes resource. – Tim Oct 01 '13 at 12:49
2

I've ended up making two functions. 1 for current top method in stack, other for the type name.

  export function getName(): string {
     var result: string = "";
     var error: any = new Error("getStack");
     var stack: string[] = error.stack
        .replace(/^[^\(]+?[\n$]/gm, '') // Remove first useless line
        .replace(/^\s+at\s+/gm, '') // Remove 'at'
        .replace(/^Object.<anonymous>\s*\(/gm, '{anonymous}()@') // Replace Object.<anon> with {anon}
        .replace(/\s\(.*:[0-9]+:[0-9]+\)/gm, '') // Remove filename, col and row
        .split('\n');
     return stack[1]; // Previous method
  }

  export function getType(obj: any): string {
     if (obj.name) {
        return obj.name;
     }
     var results = /function (.{1,})\(/.exec(obj.constructor.toString());
     return (results && results.length > 1) ? results[1] : "";
  }
Tim
  • 2,968
  • 5
  • 29
  • 55