megahertz / electron-log

Simple logging module Electron/Node.js/NW.js application. No dependencies. No complicated configuration.
MIT License
1.3k stars 127 forks source link

Add a `getScope` method to scoped loggers #433

Closed TheOneTheOnlyJJ closed 1 month ago

TheOneTheOnlyJJ commented 2 months ago

I've been using this library for some time now and it's absolutely amazing how good it is when working in a big project. There is however one little need I have that's not covered and I would like to see addressed, if possible.

The project I work on is structured in classes (very Java-like structure). Scoped loggers (they're all of LogFunctions type) are initialised in this big Main class and are passed down to other composed classes to use as constructor parameters. These classes, in their turn, pass down that logger they received to other composed classes in their implementations. I would like these deep-nested classes to have other scopes, separate from the ones the parent class received, but derived from them. I would like to be able to get the scope of an existing scoped logger, so that I can create a new scoped logger with a scope derived from the existing one.

The best way to illustrate my use case is with an example:

class Main {
  // There are many more loggers in my project
  private readonly mainLogger: LogFunctions = log.scope("main")
  private readonly userManagerLogger: LogFunctions = log.scope("user-manager")

  // Pass the relevant scoped logger to the class
  private userManager: UserManager = new UserManager(userManagerLogger /* other parameters */)

  // Rest of implementation...
}

class UserManager {
  private readonly logger: LogFunctions;
  private readonly fileConfigManager: FileConfigManager;

  public constructor(logger: LogFunctions /* other parameters */) {
    this.logger = logger;
    // Class passes logger to it's composed member; It would ideally be in a new scope
    this.fileConfigManager = new FileConfigManager(log.scope(this.logger.getScope() + "-file-config-manager")  /* other parameters */)
    // This is how I would create scoped loggers dynamically ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  }

  // Rest of implementation...
}

class FileConfigManager {
  private readonly logger: LogFunctions;

  // Lots of logging will be done in this class, and I don't want the scope to be that of the UserManager above
  public constructor(logger: LogFunctions /* other parameters */) {
    this.logger = logger;
  }

  // Rest of implementation...
}

While it would be possible to create all required loggers in the Main class and pass them down through all the constructors, it would bloat the constructors of many classes and I'd like to avoid that. The information I need is already in the logger's scope, I just need a way to access it.

Instead of a getScope method, maybe the scope could be exposed through a simple scope property (that could be changed anytime at runtime, just like the transports).

megahertz commented 2 months ago

Currently, I don't want to make the scope API more complex. Anyway, I think your case could be easily solved like that (I haven't tried it):


// ...
private readonly mainLogger: ScopedLogger = createScopedLogger('main')
// ...

function createScopedLogger(scopeName: string): ScopedLogger {
  return {
     ...logger.scope(scopeName),
     scope(name: string): ScopedLogger {
       return createScopedLogger(scopeName + name);
     },
  };
}

interface ScopedLogger extends LogFunctions {
  scope(name: string): ScopedLogger;
}
TheOneTheOnlyJJ commented 1 month ago

Yes, you are right, my proposal is not ideal and my use case can be solved by just passing the log scope as a string directly to my classes (no real point in passing an already initialized logger) or by a solution such as what you highlighted here.