@hypericon/axe
v0.6.8
Published
A simple tool for logging
Downloads
7
Readme
Axe
A simple tool for logging. (Ha ha.)
Includes multiple built-in log sinks, with a simple API for adding arbitrary log handers
- Console (usage)
- File (usage)
- Hypertable (usage) (website)
- RxJS observable (usage)
- Webhook (usage)
- Custom sink (usage)
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. Logger
s 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");