Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include metadata that can be emitted along with events #81

Closed
Tracked by #810
lukekarrys opened this issue Apr 14, 2024 · 1 comment · Fixed by #84
Closed
Tracked by #810

Include metadata that can be emitted along with events #81

lukekarrys opened this issue Apr 14, 2024 · 1 comment · Fixed by #84

Comments

@lukekarrys
Copy link
Contributor

lukekarrys commented Apr 14, 2024

proc-log is designed to be Just an Event Emitter ™️ with some well known event names.

This means it passes along all arguments when emitting to consumers. This is a good approach because there are rarely changes that proc-log needs to make outside of adding new methods. If a producer sends arguments then it is the consumers responsibility to consume them somehow.

Our primary use case is displaying things in a terminal. So all arguments are passed to util.format which can take anything as input. We might be displaying a string, number, plain function or custom class with a [util.inspect.custom] method.

BUT most of the examples I wrote out in npm/statusboard#810 rely on some kind of well-known metadata to be useful. This is why I think proc-log should have some way to indicate "this event contains an argument that is special and belongs to proc-log. It will have data in it that you should look at differently than the rest"

Possible Solutions

Producers

proc-log itself should have some sort of ergonomic API to send this metadata.

meta() function on each method

const { output, log } = require('proc-log')

// Each function gets an additional `meta` function which indicates
// the first parameter is special
output.standard.meta(
  { colors: false, template: './arbitrary' },
  'anything', 'else', new You(), { want: new Set() }
)

// could also be the last parameter
output.standard.meta('anything', 'else', new You(), { want: new Set() }, {
  colors: false,
  template: './arbitrary'
})

top level meta() function

const { output, log, meta } = require('proc-log')

// Wrap an object in the meta function which will somehow mark it as special
output.standard(
  meta({ colors: false, template: './arbitrary' }),
  'anything', 'else', new You(), { want: new Set() }
)

// Can't enforce which location its used in, which seems bad
output.standard('anything', 'else', new You(), { want: new Set() }, meta({
  colors: false,
  template: './arbitrary'
}))

use a symbol to set a key

const { output, log, META } = require('proc-log')

// Still need to figure out which position the argument goes in
output.standard('anything', 'else', new You(), { want: new Set() }, {
  [META]: true,
  colors: false,
  template: './arbitrary'
}))

Consumers

Consumers listening with process.on(eventName) should have some ergonomic way to know that an argument is metadata.

level argument is metadata too

const { output, meta } = require('proc-log')

// This would be a breaking change
process.on('output', ({ level, ...metadata }, ...args) => 
  if (level === 'standard' && metadata.templates) {
    // It is a template
  }
})

helper to find metadata in args

const { output, meta } = require('proc-log')

process.on('output', (level, ...rawArgs) => {
  const [args, metadata] = meta.getMeta(rawArgs)
  if (level === 'standard' && metadata.templates) {
    // It is a template
  }
})
@wraithgar
Copy link
Member

The only thing I have even approaching the level of an opinion is that proc-log should export a meta symbol that consumers can use. I don't even know if I like having proc-log look for that symbol. There's an argument that could be made that the consumer is responsible for all of that. The symbol is what's needed to have some "thing" that all proc-log consumers can use as a signal so that we don't have to go back down the path of sharing some stateful object w/ everything that needs to log or output.

This brings us to the nature of the symbol. If it's Symbol('proc-log.META') then if somehow in the future folks have duplicated versions of proc-log in their tree, the consumer could get confused if it has to compare it. If it's `Symbol.for('proc-log.META') then we are free of that particular issue

> x = Symbol.for('test')
Symbol(test)
> y = Symbol.for('test')
Symbol(test)
> x === y
true

I don't know if this is more an argument for Symbol.for or if it's evidence that proc-log DOES need to be looking for that symbol in a well-known place. Sorry, I've more questions than answers at this phase.

lukekarrys added a commit that referenced this issue Apr 16, 2024
Two things to note:

- The key is the string `'META'`
- The value is `Symbol('proc-log.meta')`

So it could be used like the following. All of the following is up to
producers/consumers to implement with shared conventions. All `proc-log`
is doing to allowing them to share a unique `Symbol`.

```js
const { output, META } = require('../')

// An example of how consumers would see if a META object
// was the last argument from an event
const getMeta = (args) => {
  let meta = {}
  const last = args.at(-1)
  if (last && typeof last === 'object' && Object.hasOwn(last, META)) {
    meta = args.pop()
  }
  return [meta, ...args]
}

process.on('output', (level, ...rawArgs) => {
  const [{ force = false }, ...args] = getMeta(rawArgs)
  console.log(level, { force, args })
})

// in this implementation the value does not matter, just the key
output.standard('arg1', 'arg2', { [META]: null, force: true })
output.standard('arg1', 'arg2')
output.standard('arg1', null)
output.standard(null)
output.standard()

// Will log the following:
// standard { force: true, args: [ 'arg1', 'arg2' ] }
// standard { force: false, args: [ 'arg1', 'arg2' ] }
// standard { force: false, args: [ 'arg1', null ] }
// standard { force: false, args: [ null ] }
// standard { force: false, args: [] }
```

Closes: #81
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants