tslog icon indicating copy to clipboard operation
tslog copied to clipboard

Feature Request: Destructured object params for `getMeta()`

Open prescience-data opened this issue 3 years ago • 3 comments

Description / Use Case for a Feature

https://github.com/fullstack-build/tslog/blob/5e21e7392a0426c22c99fa7a039e7a029b890c9c/src/runtime/nodejs/index.ts#L29-L36

Currently the getMeta() function is stacked with 4 required and 2 trailing positional args, which means the order will shuffle if a new required param is needed, causing a breaking change.

Would be super nice if this could be implemented as a desctructured object instead:

export interface IMetaConfig
  extends Pick<IMeta, "logLevelId" | "logLevelName" | "name" | "parentNames"> {
  stackDepthLevel: number
  hideLogPositionForPerformance: boolean
}

export function getMeta({
  logLevelId,
  logLevelName,
  stackDepthLevel,
  hideLogPositionForPerformance,
  name,
  parentNames
}: IMetaConfig): IMeta {
  return Object.assign({}, meta, {
    name,
    parentNames,
    date: new Date(),
    logLevelId,
    logLevelName,
    path: !hideLogPositionForPerformance
      ? getCallerStackFrame(stackDepthLevel)
      : undefined
  })
}

prescience-data avatar Dec 27 '22 23:12 prescience-data

I see your point, yet it is an internal method and not supposed to be used outside tslog, or are you referring to overwriting it with this.settings.overwrite.addMeta? I am not a big fan of object parameters, since they are not as explicit as parameters and could break at runtime without proper validation and any additional validation is just additional code that has to be shipped and executed.

terehov avatar Dec 28 '22 08:12 terehov

Perhaps there is a better way to solve my current use case that you might be able to see.

Currently I have an abstract Transport class that handles pushing each log level to a batched handler for transmission with fetch to various destinations (one per implementation of the base class).

This method applies a filter provided at construction which limits the log level, so the protected _meta property from the incoming log object is read.


export interface LogObject<T extends unknown = unknown> extends ILogObj {
  line: T[]
  _meta: LogMeta
}

export abstract class Transport<O extends LogObject> {
  
  // ...rest of class
  
  public push(line: O): void {
    if (line._meta.logLevelId >= this._minLevel) {
      this.#lines.push(line) 
    }
  }

}

However, in one edge-case - the transport may receive a LogObject manually (not via the main tslog library), but I would prefer to keep the existing shape and _meta context from tslog as it avoid defining a second base Transport class for this one case. So to achieve this, I must generate the data for _meta.


public log<E extends MetricsEventName>(
    event: E,
    data: MetricsEventData<E>
  ): void {
    this.push({
      line: [this.#parse(event, data)],
      _meta: getMeta({
        logLevelId: 2,
        logLevelName: "debug",
        name: "metrics"
      })
    })
  }

Prior to logging this issue I was intending to import the getMeta() function from it's deeply nested tslog/dist/types/runtime/nodejs - however I noticed the /types/ path component only after actually attempting to build.

The function does not export with types from /esm/ or /cjs/:

// works, but cannot build as only the declaration is imported:
import { getMeta } from "tslog/dist/types/runtime/nodejs" 
// fails with TS7016:
import { getMeta } from "tslog/dist/esm/runtime/nodejs" 
TS7016: Could not find a declaration file for module 'tslog/dist/esm/runtime/nodejs'.

Current solution is implement locally, but would be neat to be able to mirror the internals if that ever becomes exposed.


export const getMeta = ({
  logLevelId,
  logLevelName,
  name,
  parentNames
}: MetaConfig): LogMeta => ({
  date: new Date(),
  hostname: hostname(),
  logLevelId,
  logLevelName,
  name,
  parentNames,
  path: {},
  runtime: "Nodejs",
  runtimeVersion: process.version
})

prescience-data avatar Dec 28 '22 08:12 prescience-data

Separately (and semi-unrelated), this caught my attention -

I am not a big fan of object parameters, since they are not as explicit as parameters and could break at runtime without proper validation

My prior impression was that an untyped (ie JS) set of positional function arguments would not require any less validation than a destructured object given that raw JS would let you pass anything in to each of those parameters in the same way anything could be passed in as object properties?

Definitely disregard/ignore if this is just a bikeshedding/semantics/style difference, but genuinely curious if there is something fundamental I need to research here that I might be missing.

prescience-data avatar Dec 28 '22 09:12 prescience-data