2

Is there a way to set up an object to dynamically generate values for keys? For example, I want obj.foo to console.log("foo"), obj.bar to console.log("bar"), etc.? I'm trying to make a custom logger where

let logger = new CustomLogger();
logger.error("404"); // "[ERROR] - 404"
logger.info("user signed up"); // "[INFO] - user signed up"

It doesn't have to be instantiated from a class, it's actually better if it's just a plain object because I want to export this for different projects.

  • "*Is there a way to set up an object to dynamically generate values for keys?*" I don't see how this relates to "*I'm trying to make a custom logger where [...]*". Why do you need *dynamic* keys for the logger? Presumably you only have a handful of log levels (e.g., trace, info, warn, error), so I'm not sure where dynamic keys come in. Furthermore "*it's actually better if it's just a plain object because I want to export this for different projects.*" - I'm also not sure how this relates. A simple module will make it reusable anyway. – VLAZ Mar 01 '21 at 08:22

4 Answers4

3

You could wrap the object with a Proxy and print the handed over property.

const
    object = { foo: "hello", bar: "everyone" },
    handler = {
        get: function(target, prop, receiver) {
            console.log(prop, target[prop]);
            return target[prop];
        }
    },
    proxy = new Proxy(object, handler);

console.log(proxy.foo);
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
1

It seems you want a logger where the key determines (part of) what is logged. This is somewhat normal, however the approach here is bad because you lose control over your API. You can have a typo like logger.erro("500") and your code would work but your logs will now come out as [ERRO] - 500. This in turn messes up any log analysis you might do, the simplest of which is just searching for [ERROR].

Instead, the usual approach here is to have a method on the logger that can take the parts of the message and then log it uniformly. In your case, it seems you want a prefix and a message. You can then expose methods with pre-defined prefix part.

Using class syntax

class CustomLogger {
  log(prefix, msg) {
    console.log(`[${prefix}] - ${msg}`)
  }
  
  trace(msg) {
    this.log("TRACE", msg);
  }
  
  info(msg) {
    this.log("INFO", msg);
  }
  
  warn(msg) {
    this.log("WARN", msg);
  }
  
  error(msg) {
    this.log("ERROR", msg);
  }
}

let logger = new CustomLogger();
logger.error("404"); // "[ERROR] - 404"
logger.info("user signed up"); // "[INFO] - user signed up"

Using an object literal

let logger = {
  log(prefix, msg) {
    console.log(`[${prefix}] - ${msg}`)
  },
  
  trace(msg) {
    this.log("TRACE", msg);
  },
  
  info(msg) {
    this.log("INFO", msg);
  },
  
  warn(msg) {
    this.log("WARN", msg);
  },
  
  error(msg) {
    this.log("ERROR", msg);
  },
}

logger.error("404"); // "[ERROR] - 404"
logger.info("user signed up"); // "[INFO] - user signed up"

If the above seems like too much code, then consider the following:

Derive your methods

Using currying to perform partial application.

const log = prefix => msg => 
  console.log(`[${level}] - ${msg}`);
  
let logger = {
  trace: log("TRACE"),
  info : log("INFO"),
  warn : log("WARN"),
  error: log("ERROR"),
}

logger.error("404"); // "[ERROR] - 404"
logger.info("user signed up"); // "[INFO] - user signed up"

Using the approach where you only expose the methods you want makes it easier to change the implementation if needed. The consuming code will not need to be aware of this. Consider if you actually want to have different log levels as well as a prefix and a message part. If you have completely dynamic methods, then you can hardly do this. How are you going to distinguish logger.info from logger.error when they both just call the same code? You could try to do a big conditional chain but then what you end up doing is implementing all possible methods in one place.

Instead here is how the above code can change:

const log = level => prefix => msg =>    //take `level`
  console[level](`[${prefix}] - ${msg}`); //use `level`
  
let logger = {
  trace: log("info")("TRACE"),
  info : log("info")("INFO"),
  warn : log("warn")("WARN"),
  error: log("error")("ERROR")
}

logger.error("404"); // "[ERROR] - 404"
logger.info("user signed up"); // "[INFO] - user signed up"

Minimal change for the consuming code and the logger itself.

VLAZ
  • 26,331
  • 9
  • 49
  • 67
0

You can do this easily like this

const logger = {
  log: (type, val) => console.log(`[${type}] - ${val}`)
}

logger.log("Error", "Foo");
logger.log("Info", "Bar");
Som Shekhar Mukherjee
  • 4,701
  • 1
  • 12
  • 28
0

If you want to get key from the object you can you for in loop for that as follows

const obj={
  foo:"value for foo",
  bar:"value for bar"
}

for (const key in obj) {
  console.log(key);
}
Shashank Padwal
  • 194
  • 2
  • 19