0.1.0 • Published 2 months ago

@enhanced-dom/logging v0.1.0

Weekly downloads
-
License
MIT
Repository
github
Last release
2 months ago

TL;DR;

Helpers for structured logging.

What?

In many occasions we like to 'log' to multiple streams (e.g. console, analytics server, error server, etc.). Some of these streams are interested in some messages, others in completely different messages. This code has a log collector (fancy name for a list of messages) that can be flushed in 1 go to send logs to various streams. By default a console stream is implemented

Why?

There seems to be a gap between a 'common' logging interface and the ability to inject consumers and 'collect' logs over a certain scope while staying within the app. Ideally, we'd like to:

  • add different consumers, with the ability to filter the messages to send each consumer
  • categorise the messages (by various tags)
  • retain an entire messages 'session' (regardless of their 'importance') and capture it completely on e.g. an error
  • be able to do some aggregations (wrt to perf) - similar to what Logstash can do (without having to use external apps ofc)
  • expose a contextualized logger for inner context (i.e. function F1 calls F2, and F2 logs a message. F1 would like to 'set some context = tags' on the message logged from F2 without F2 knowing)

How does this look like in practice?

src/paymentHandlers.ts

import { CollectorLogger, LogTagType, LogLevel } from '@enhanced-dom/logging'
const logger = new CollectorLogger('my unique id for a list of logs', { [LogTagType.Area]: 'payments' })
const acceptBalanceInquiry = (balanceInquiry: SimpleBalanceInquiry | ComplicatedBalanceInquiry) => {
  enhancedLogger = logger.withTags({ [LogTagType.Procedure]: 'balance inquiry flow' })
  logger.log()
  if (validateBalanceInquiry(paymentInquiry, enhancedLogger)) {
    processBalanceInquiry(paymentInquiry, enhancedLogger)
  }
}

const validateBalanceInquiry = (balanceInquiry: SimpleBalanceInquiry | ComplicatedBalanceInquiry, enhancedLogger?: ILogger) => {
  const validationLogger = enhancedLogger?.withTags({
    [LogTagType.Step]: 'validation',
    [LogTagType.Entity]: balanceInquiry.type,
    [LogTagType.Id]: balanceInquiry.id,
  })
  validationLogger?.log({ stage: 'start' }, { [LogTagType.Level]: LogLevel.Perf }, 'Starting validation')
  /// validation code
  validationLogger?.log({ stage: 'end' }, { [LogTagType.Level]: LogLevel.Perf }, 'Ended validation')
  return true
}

const processBalanceInquiry = (balanceInquiry: SimpleBalanceInquiry | ComplicatedBalanceInquiry, enhancedLogger?: ILogger) => {
  const processLogger = enhancedLogger?.withTags({
    [LogTagType.Step]: 'process',
    [LogTagType.Entity]: balanceInquiry.type,
    [type: LogTagType.Id]: balanceInquiry.id
})
  validationLogger?.log({ stage: 'start' }, { [LogTagType.Level]: LogLevel.Perf }, 'Starting processing')
  /// process
  validationLogger?.log({ stage: 'end' }, { [LogTagType.Level]: LogLevel.Perf }, 'Ended processing')
}

Of course, this 'passing of logger through props' is not so ideal. One could do a service locator-like pattern where the right logger instance for a 'session id' is returned, but this fails short as soon as the tags need to be changed for the 'child' scope. In a tree-like context, one can access the parent contexts (e.g. dom nodes) to figure out which logger instance to use. This is much harder without binding the current execution to a global tree-like structure.

0.1.0

2 months ago

0.0.1

10 months ago