0.6.8 • Published 10 months ago

@hypericon/axe v0.6.8

Weekly downloads
-
License
MIT
Repository
-
Last release
10 months ago

Axe

npm (scoped)

A simple tool for logging. (Ha ha.)

Includes multiple built-in log sinks, with a simple API for adding arbitrary log handers

Includes fine-grained filtering of logs by log level

  • Set minimum log level in each logger instance
  • Set minimum log level handled by each log sink
  • Override sink filter settings for individual logger instances
  • Create separate manager instances with their own sinks for even more options

Installation

Install with NPM:

npm install @hypericon/axe

Includes type definitions.

Example

Example Typescript usage:

import { logMgr, ConsoleSink, CONSOLE_SINK, LogLevels, Logger, LogManager } from "@hypericon/axe";

const logger = new Logger("MyLogger");

logger.log("a log");
logger.warn("a warning");
logger.error("an error");
logger.log("log primitives and objects", [1, 2, 3], { an: "object" }, undefined);

// Additional loggers can be created with separate contexts
const anotherLogger = new Logger("Another Logger");
anotherLogger.log("This logger has the context 'Another Logger'");

// Update the log level filter for the default console sink.
// Sinks are identified by their unique "name", the default console sink's name is exported
// from the package.
logMgr.setSinkFilter(CONSOLE_SINK, LogLevels.verbose);
logMgr.setSinkFilter("Console", "verbose"); // <- this is equivalent, but less robust to change
logger.verbose("verbose logs are ignored by default, but this is displayed.");

// Additional sinks can be added.
// (there is no good reason to have two console sinks, this is just an example)
const newConsoleSink = "Console2";
logMgr.addSink(new ConsoleSink({
  name: newConsoleSink,
  logLevel: LogLevels.log,
}));

// Separate sinks can have different log filters:
logMgr.setSinkFilter(newConsoleSink, LogLevels.warn);
// The current sink  log level filters can be read:
logMgr.readSinkFilters(); // { 'Console': 'verbose', 'Console2': 'warn' }
// This is useful for editing which log levels are logged where at runtime

// Separate manager instances can be created,
// with their own separate sink instances and log level filters
const newManager = new LogManager({ withDefaultConsoleSink: true });
const logger2 = newManager.newLogger("Logger 2");
logger2.log("This logger's manager 'newManager' is separate from 'logMgr' above.");
logger2.log("This allows them to configure their sinks and common filters separately.");

// Note: the "logMgr" import from the package is simply a prebuilt instance of
// `LogManager` with the default console sink.

Usage

LogLevel

The type LogLevel and const LogLevels define the log levels used in Axe.

import { LogLevel, LogLevels } from "@hypericon/axe";

// LogLevel is defined like this:
type LogLevel = "error" | "warn" | "log" | "debug" | "verbose" | "none";

// LogLevels is defined like this:
const LogLevels = {
  error: "error",
  warn: "warn",
  log: "log",
  debug: "debug",
  verbose: "verbose",
  none: "none",
} as const;

Logger

Logger is the main point of contact for the library.

import { Logger, LogLevel } from "@hypericon/axe";

// Create a new Logger with the given context
const logger = new Logger(context?: string);

// Log anything with one of the 5 log levels
logger.error(...msgs: any[]);
logger.warn(...msgs: any[]);
logger.log(...msgs: any[]);
logger.debug(...msgs: any[]);
logger.verbose(...msgs: any[]);

// Logger instances can be configured to override the logFilter settings of `LogSink`s in their manager

// Read all of the logger's filter settings (may be empty)
logger.sinkFilter.read(): { [sinkName: string]: LogLevel };
// Get the logger's filter set on the named sink (may be undefined)
logger.sinkFilter.get(sinkName: string): LogLevel | undefined;
// Set a log filter on the named sink
logger.sinkFilter.set(sinkName: string, logLevel: LogLevel): void;
// Remove the log filter set on the named sink
logger.sinkFilter.remove(sinkName: string): void;
// Remove all log filters set on the logger
logger.sinkFilter.clear(): void;

LogManager

LogManager stores instances of Logger and LogSink, and handles formatting and filtering logged messages.

Every Logger is associated with one LogManager.

A default instance named logMgr is exported from the library. Loggers created using the new Logger(...) constructor are associated with this instance.

import { logMgr, LogManager, LogSink } from "@hypericon/axe";

// `logMgr` is the default `LogManager` instance.
const logger1 = new Logger("1"); // manager -> `logMgr`

// Create a separate manager instance
const anotherManager = new LogManager();
const logger2 = anotherManager.createLogger("2"); // manager -> `anotherManager`

// Alternatively, create a Logger with the default manager, then move it to another manager
const logger3 = new Logger("3");
anotherManager.addLogger(logger3); // moves the logger from the default `logMgr` to the new manager

// `LogSink`s are managed in `LogManager` instances separately
logMgr.addSink(...);
anotherManager.addSink(...);

// Find, add, and remove `LogSink`s and filters
logMgr.findSinkByName(name: string): LogSink | undefined;
logMgr.findSink<T extends LogSink>(sinkClass: Class<T>): T | undefined;
logMgr.addSink(sink: LogSink): void;
logMgr.removeSinkByName(name: string): void;
logMgr.removeSink(sink: LogSink): void;
logMgr.removeAllSinks(): void;

logMgr.readSinkFilters(): { [name: string]: LogLevel };
logMgr.setSinkFilter(sinkName: string, logLevel: LogLevel): void;

LogSink

LogSink is an interface which all sinks implement.

To make a custom sink, implement the interface then add the object to a LogManager.

import { LogSink, logMgr, Logger, LogLevels } from "@hypericon/axe";

// LogSink is defined like this:
interface LogSink {
  /**
   * The unique name of the sink.
   * The name only needs to be unique within a single manager.
   */
  name: string,
  /**
   * The default minimum log level that a received message must have for it to be handled.
   * If the log level of a received message is lower than this, the message is ignored.
   * 
   * This can be overridden by individual logger instances.
   */
  logFilter: LogLevel,
  /**
   * Handle a logged message. The message does not need to be filtered again.
   * @param logMessage 
   */
  handleMessage(logMessage: LogMessage): any,
  /**
   * Gracefully destroy the sink. It cannot be used again.
   * (Method may be empty depending on the sink implementation)
   */
  destroy(): any,
}

// Create a custom sink, and register it with the default manager after removing the default console sink:

class MySink implements LogSink {
  name: "MySink",
  logLevel: LogLevels.log,

  constructor(settings: { /* ... */ }) {
    // ...
  }

  handleMessage(logMessage: LogMessage) {
    // ...
  }
  destroy() {
    // ...
  }
}

logMgr.removeAllSinks();
logMgr.addSink(new MySink({ /* ... */ }));

const logger = new Logger("Context");
logger.log("A message");

ConsoleSink

Log messages to the console.

A single ConsoleSink is included by in the default logMgr instance exported from the library.

Example:

import { Logger, logMgr, LogLevels, ConsoleSink } from "@hypericon/axe";

logMgr.removeAllSinks();
logMgr.addSink(new ConsoleSink({
  name: "ConsoleSink",
  logLevel: LogLevels.log,
}));

const logger = new Logger("Example");
logger.log("A message logged to the console");

FileSink

Log messages to a file.

Example:

import { Logger, logMgr, LogLevels, FileSink } from "@hypericon/axe";

logMgr.removeAllSinks();
const sink = new FileSink({
  name: "FileSink",
  logLevel: LogLevels.log,

  /**
   * Full path to the dir containing the log files
   * @default join(process.cwd(), "logs")
   */
  logDirPath?: string,
  /**
   * Function to build the filenames for new log files.
   * 
   * @default
   * () => {
   *   const logDate = new Date().toISOString()
   *     .replace(/:/g, "-")
   *     .replace(/\./g, "_");
   *   const logFileName = `${logDate}.txt`;
   *   return logFileName;
   *   // example: "2023-01-31T10-35-12_345Z.txt"
   * }
   */
  logFilenameFn?: () => string,
});
logMgr.addSink(sink);

const logger = new Logger("Example");
logger.log("A message logged to a file");

// Open a new log file.
// This can be useful to run on a schedule, so log files don't get too large,
// and so that logs are stored in a file with a relevant filename.
// For example, run this every midnight with the default `logFilenameFn` to
// (more or less) store all logs in a files named with the midnight timestamp.
sink.openNewFile();

// Interact with log files

// List existing log files
sink.listLogFiles(): Promise<{ logFiles: LogFileInfo[] }>;

// Read a particular log file
sink.readLogFile(filename: string): Promise<{ filename: string, contents: string }>;

HypertableSink

Log messages to Hypertable. For each new message, a new Record is created within a target Collection, with the data from the logged message written to the new Record.

Example:

import { Logger, logMgr, LogLevels, HypertableSink } from "@hypericon/axe";

logMgr.removeAllSinks();
logMgr.addSink(new HypertableSink({
  name: "HypertableSink",
  logLevel: LogLevels.log,

  /** Hypertable API key with permission to create a Record */
  apiKey: string,
  /** The ID of the Project in which to create the new Record */
  projectId: string,
  /** The ID of the Collection in which to create the new Record */
  collectionId: string,
  /** Optionally override the Hypertable base URL */
  baseUrl?: string,
  /** Optionally override the field keys within the created Record */
  fieldKeys?: Partial<RecordFieldKeys>,
}));

const logger = new Logger("Example");
logger.log("A message logged to a new Hypertable record");

ObservableSink

Log messages to an RxJS observable, so they can be easily consumed elsewhere in the application.

Note: RxJS is a peer dependency of Axe, and must be installed separately.

Example:

import { Logger, logMgr, LogLevels, ObservableSink } from "@hypericon/axe";

logMgr.removeAllSinks();
const sink = new ObservableSink({
  name: "ObservableSink",
  logLevel: LogLevels.log,
});
logMgr.addSink(sink);

// This would be consumed by the application after the desired log messages had been aggregated
sink.logMessage$.subscribe(msg => {
  // ...
});

const logger = new Logger("Example");
logger.log("A message logged to Observable");

WebhookSink

Log messages to a webhook.

Example:

import { Logger, logMgr, LogLevels, WebhookSink } from "@hypericon/axe";

logMgr.removeAllSinks();
logMgr.addSink(new WebhookSink({
  name: "WebhookSink",
  logLevel: LogLevels.warn,

  /** The URL to which to send the request */
  url: "https://some.url/api/123-456-789",
  /** Specify a different HTTP method */
  method?: "POST",
  /** Any additional headers to include with the request */
  headers?: {
    "Authorization": "...",
  },
  /** Optionally override the function building the request body */
  buildBody?: (logMessage: LogMessage) => {
    return { /* ... */ }
  },
}));

const logger = new Logger("Example");
logger.warn("A warning logged to the webhook");
0.6.7

10 months ago

0.6.6

10 months ago

0.6.8

10 months ago

0.6.3

10 months ago

0.6.2

10 months ago

0.6.5

10 months ago

0.6.4

10 months ago

0.6.1

10 months ago

0.6.0

10 months ago

0.5.4

12 months ago

0.5.3

12 months ago

0.5.2

12 months ago

0.5.1

12 months ago

0.5.0

12 months ago

0.4.0

12 months ago

0.3.0

12 months ago

0.2.0

12 months ago

0.1.1

12 months ago

0.1.0

12 months ago