fullstack-build / tslog

πŸ“ tslog - Universal Logger for TypeScript and JavaScript
https://tslog.js.org
MIT License
1.34k stars 63 forks source link

Question: Can tslog format logs from a tranport? #205

Open LiamLamb opened 1 year ago

LiamLamb commented 1 year ago

Hi πŸ‘‹,

I am using your logging tool to develop an interval vscode extension in my organisation. The extension has multiple output channels for each type of HTTP client talking to different internal services.

What I like is that I can logically split my logs across each client's output channel, making extension logging organised, so I can look at client logs in isolation based on their client type, providing me with a developer experience akin to Serilog in .NET.

When running the extension in vscode, as a user looking at the logs in the output channel, I wouldn't need to see the structured log object, but rather a nicely formatted message (although the structured log itself would be stored to file/pushed to a remote server) in the transport callback.

Here's a simplified shot of what I am doing:

// loggerBuilder.ts
transport(func: (payload: ILogObj & ILogObjMeta) => void): ILoggerBuilder {
  this._transports.push(func);

  return this;
}

build() {
   ...
   this._transports.forEach((t) => logger.attachTransport(t));
   ...
}

// client
...
loggerBuilder
  .transport((log) => {
      // Log to file
  } )
  .transport((log) => {
    // Logging as structured log for now, but ideally formatted here
    outputChannel.appendLine(JSON.stringify(logObj));
  });

...

It would be convenient to be able to format the JSON object from the transport, in some way.

Could you provide some insight into whether this is an already-supported feature and if I am missing something?

Thanks πŸ™‚

terehov commented 1 year ago

It sounds like you're suggesting the ability to choose between a json or pretty format for logs, depending on the attached transport, correct? Alternatively to expose a helper that would transform an ILogObj into a prettystring, right?

LiamLamb commented 1 year ago

Yes, that's correct - either approach would work.

I can see how this might not be a common use case, as in most cases logging to console would be sufficient, but given that vscode has a notion of output channels, this would be extremely useful.

Let me know what your thoughts are

Thanks

JustinGrote commented 1 year ago

This is what I do as a workaround. EDIT: This no longer works in v4+

const log = new Logger<DefaultLog>({
    name: 'default',
    type: 'pretty',
    prettyErrorLoggerNameDelimiter: "-",
    prettyErrorParentNamesSeparator: "-",
    stylePrettyLogs: true,
    argumentsArrayName: "args",
    overwrite: {
        transportFormatted: () => { return }        // We want pretty formatting but no default output and will attach a transport later
    }
})

If I do this then my attached transport still has the formatted message as args[0] and I can output it accordingly.

    /** Wire this up to Logger.AttachTransport
     *
     * @example
     * ```
     * logger.attachTransport((new VSCodeLogOutputChannelTransport('myExtensionName')).transport)
     * ```
     */
    public transport = <T extends DefaultLog & ILogObjMeta>(log: T) => {
        const message = typeof log.args[0] === "string"
            ? log.args[0]
            : JSON.stringify(log.args[0])
        const args = log.args.slice(1)
        switch (log._meta.logLevelName as DefaultTSLogLevel) {
            case 'SILLY': this.channel.trace(message, ...args); break
            case 'TRACE': this.channel.trace(message, ...args); break
            case 'DEBUG': this.channel.debug(message, ...args); break
            case 'INFO': this.channel.info(message, ...args); break
            case 'WARN': this.channel.warn(message, ...args); break
            case 'ERROR': this.channel.error(message, ...args); break
            case 'FATAL': this.channel.error(message, ...args); break
            default: throw new Error(`Unknown log level: ${log._meta.logLevelName}`)
        }
    }
JustinGrote commented 1 year ago

@LiamLamb I vote for a simple exported function that runs the "pretty" process for the current logger on any string, that's going to be the most flexible and least breaking vs. adding a property on ILogObj, etc.

logger.formatPretty(string: string)

JustinGrote commented 1 year ago

@terehov one question I have is why pretty and json are "special" and have their own special steps. Why are they not just implemented as additional transports or maybe transports that are "pre-attached" unless "hidden" is specified.

My main issue is the parameters available in the formattedTransport override is more extensive (including settings) than is available in the attachTransport param which is just the log object. I think it should be the same function signature for both, with pretty and json just being two implementations of that signature.