@livy/contracts

Conceptual types and structures for the Livy logger

Usage no npm install needed!

<script type="module">
  import livyContracts from 'https://cdn.skypack.dev/@livy/contracts';
</script>

README

@livy/contracts

This package (primarily) contains types representing the core concepts of the Livy logger. Those are especially useful when you're building Livy components using TypeScript. For this reason, code blocks in this documentation are written in TypeScript.


Table of Contents:


LogLevel

This module contains the available log levels and their corresponding severities:

  • LogLevel is a type representing any valid RFC 5424 log level while logLevels is an array containing all those level strings, sorted from least (debug) to most serious (emergency):

    import { LogLevel, logLevels } from '@livy/contracts'
    
    function printLevel(level: LogLevel) {
      if (!logLevels.includes(level)) {
        throw Error('Invalidlog log level')
      }
    
      console.log(level)
    }
    
  • The SeverityLevel type represents log level severity as an integer from 0 through 7. The SeverityMap object maps log levels to their corresponding severity (debug → 7, ..., emergency → 0):

    import { LogLevel, SeverityMap } from '@livy/contracts'
    
    /**
     * Sort a list of log levels by severity ("debug" first, "emergency" last)
     */
    function sortBySeverity(levels: LogLevel[]) {
      return [...levels].sort(
        (levelA, levelB) => SeverityMap[levelB] - SeverityMap[levelA]
      )
    }
    

LogRecord

Exposes the LogRecord interface which describes the structure of a Livy log record. This is useful for writing, for example, a custom formatter in TypeScript:

import { FormatterInterface } from '@livy/contracts'
import { LogRecord } from '@livy/contracts'

class QueryStringFormatter implements FormatterInterface {
  /*
   * Format properties of a record as level=debug&severity=7&message=...
   */
  public format(record: LogRecord) {
    return new URLSearchParams(Object.entries(record)).toString()
  }

  /*
   * Format properties of multiple records as level[]=debug&severity[]=7&message[]=...
   */
  public formatBatch(records: LogRecord[]) {
    return new URLSearchParams(
      records.reduce(
        (carry, record) => [
          ...carry,
          ...Object.entries(record).map(([key, value]) => [`${key}[]`, value])
        ],
        []
      )
    )
      .toString()
      .replace(/%5B%5D/g, '[]')
  }
}

LoggerInterface

Describes the abstract structure of a Livy logger instance (LoggerInterface) as well as two variations, AsyncLoggerInterface and SyncLoggerInterface, which extend LoggerInterface and additionally define concrete return types (instead of unknown) for the log methods.

import {
  LoggerInterface,
  AsyncLoggerInterface,
  SyncLoggerInterface
} from '@livy/contracts'

HandlerInterface

Describes the minimum viable structure of a Livy handler as HandlerInterface. Additionally, a SyncHandlerInterface is provided which represents a handler that has synchronous logging methods in addition to the asynchronous ones.

import { HandlerInterface, SyncHandlerInterface } from '@livy/contracts'

ResettableInterface

Describes the structure of a resettable handler or processor — a component able to reset its internal state (e.g. the ArrayHandler which can clear its own buffer).

import { ResettableInterface } from '@livy/contracts'

Handlers implementing this interface expose a reset method to trigger the reset:

const resettableHandler = new SomeResettableHandler()

resettableHandler.reset()

Calling this method on a handler should not only reset its own state but also the state of all components attached to it (e.g. processors or wrapped handlers).

FormattableHandlerInterface

Describes the structure of a handler which has a configurable formatter.

import { FormattableHandlerInterface } from '@livy/contracts'

ProcessableHandlerInterface

Describes the structure of a handler with support for processors.

import { ProcessableHandlerInterface } from '@livy/contracts'

Handlers implementing this interface expose a Set of processors as their processors property:

const processableHandler = new SomeProcessableHandler()

processableHandler.processors
  .add(new SomeProcessor())
  .add(new OtherProcessor())

ClosableHandlerInterface

Describes the structure of a handler which wants to do cleanup tasks when the process exits.

If you want to implement such a handler, please be aware that there is often no spare time left when the close() method is invoked (because it is triggered when the process exits/the web page is left), i.e. asynchronous cleanup actions usually won't work.

import { ClosableHandlerInterface } from '@livy/contracts'

FormatterInterface

Describes the structure of a formatter.

import { FormatterInterface } from '@livy/contracts'

ProcessorInterface/ProcessorFunction

Describes the structure of a processor.

import { ProcessorInterfaceOrFunction } from '@livy/contracts'

A processor can either be stateless or stateful:

Stateless Processors

A stateless processor is just a bare function taking a log record and returning a log record.

import { ProcessorFunction } from '@livy/contracts'

Stateful Processors

A stateful processor is a class with a process method. That method then does the job of accepting and returning a log record.

import { ProcessorInterface } from '@livy/contracts'

Writing a processor in a stateful is usually done...

  • ...if it needs to take options (which are usually taken to the processor's constructor).
  • ...if it needs to retain state between several processed records.