@pabra/logger
v1.2.0
Published
A small and simple but extendable logger for typescript/javascript in browser and Node.js.
Downloads
236
Maintainers
Readme
@pabra/logger
What
A JavaScript/TypeScript logger that implements Syslog severitiy levels.
goals are:
- be lightweight/small
- can be used in browser and node.js
- have as few as possible dependencies (currently just 1)
- (almost) ready to use if you just want to use
console.log
and do not want to log debug messages in production - easily extendable
- functional code and immutable data
A Logger consists of 3 parts:
- Filter (optional) - should a message be logged at all
- Formatter - how to format log entries
- Transporter - where to trasport log entries to
These are packed together into a Handler.
Install
npm install --save @pabra/logger
# or
yarn add @pabra/logger
Getting Started
Just Log
This works in both, browser and node.js environments.
// import
import getLogger from '@pabra/logger';
// init and use root logger
const rootLogger = getLogger('myProject');
rootLogger.info("I'm using a simple logger now!");
Results in the following console output:
2020-08-13T13:55:32.327Z [myProject] INFORMATIONAL - I'm using a simple logger now!
Logging Data
Pass any additional data after the log message.
rootLogger.warning(
'something unexpected happened',
{ some: ['data', true] },
'23',
42,
);
Results in the following console output:
2020-09-06T07:29:05.356Z [myProject] WARNING - something unexpected happened { some: [ 'data', true ] } 23 42
Child Logger
Call getLogger
on your rootLogger to get a child logger.
// import
import getLogger from '@pabra/logger';
// init root logger
const rootLogger = getLogger('myProject');
// init and use child logger in your modules/components/etc.
const moduleLogger = rootLogger.getLogger('myModule');
moduleLogger.info('Logging from within a module!');
Results in the following console output:
2020-09-06T07:39:08.677Z [myProject.myModule] INFORMATIONAL - Logging from within a module!
Selectively Logging for Dev / Prod
Set up a custom Handler to only show log messages starting at 'warning' level in production:
import getLogger, { handlers } from '@pabra/logger';
const logLevel = process.env.NODE_ENV === 'development' ? undefined : 'warning';
const logHandler = handlers.getConsoleRawDataHandler(logLevel);
const rootLogger = getLogger('myProject', logHandler);
// in some module
const moduleLogger = rootLogger.getLogger('myModule');
Then, any log messages that are lower than "warning" will be ignored.
rootLogger.info("I'm using a simple logger now!");
moduleLogger.notice("I'm using a simple module logger now!");
rootLogger.err('No such table in db.');
moduleLogger.warning('User entered invalid user name.');
Will only show messages eqal or higher than 'warning' level:
2020-09-06T07:53:40.896Z [myProject] ERROR - No such table in db.
2020-09-06T07:53:40.896Z [myProject.myModule] WARNING - User entered invalid user name.
You should take care that process.env.NODE_ENV
is properly set. This might
also differ if you use it in node.js or browser (there is no global process
in
the browser - webpack
EnvironmentPlugin might
help with that).
Usage
Logger
What is it
type Logger = {
emerg: (message: string, ...data: any[]) => void;
alert: (message: string, ...data: any[]) => void;
crit: (message: string, ...data: any[]) => void;
err: (message: string, ...data: any[]) => void;
warning: (message: string, ...data: any[]) => void;
notice: (message: string, ...data: any[]) => void;
info: (message: string, ...data: any[]) => void;
debug: (message: string, ...data: any[]) => void;
getLogger: GetLogger;
getHandlers: () => Handlers;
};
How to get it
import getLogger from '@pabra/logger';
// get a main/root logger
const mainLogger = getLogger(loggerName); // default handler will be used
// or
const mainLogger = getLogger(loggerName, handler);
// or
const mainLogger = getLogger(loggerName, handlers);
// get a child/module logger
const moduleLogger = mainLogger.getLogger(loggerName); // parent's handlers will be used
// or
const moduleLogger = mainLogger.getLogger(loggerName, handler);
// or
const moduleLogger = mainLogger.getLogger(loggerName, handlers);
| object | type | required | description |
| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------: | ------------------------------- |
| loggerName
| string | yes | name of your logger |
| handler
| type Handler = { readonly filter?: Filter | undefined; readonly formatter: Formatter; readonly transporter: Transporter;} | no | a single Handler
|
| handlers
| Handler[] | no | multiple Handler
s |
How to use it
moduleLogger.info(message, ...data);
| object | type | required | description |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------: | ------------------------------------- |
| moduleLogger
| type Logger = { emerg: (message: string, ...data: any[]) => void; alert: (message: string, ...data: any[]) => void; crit: (message: string, ...data: any[]) => void; err: (message: string, ...data: any[]) => void; warning: (message: string, ...data: any[]) => void; notice: (message: string, ...data: any[]) => void; info: (message: string, ...data: any[]) => void; debug: (message: string, ...data: any[]) => void; getLogger: GetLogger; getHandlers: () => Handlers;}; | | the actual Logger
Object |
| message
| string | yes | a message to log |
| data
| any | no | some kind of data to log |
For each call of a log function the Logger
will pass the message
and data to each of it's Handler
s.
Handler
What is it
type Handler = {
readonly filter?: Filter;
readonly formatter: Formatter;
readonly transporter: Transporter;
};
A Handler
keeps all 3 parts together that are needed to handle a
log entry - hence the name. Whereas the filter is optional.
How to get it
import { handlers, Handler } from '@pabra/logger';
const myHandler: Handler = handlers.getConsoleTextHandler(logLevelName);
const myHandler: Handler = handlers.getConsoleRawDataHandler(logLevelName);
const myHandler: Handler = handlers.getConsoleJsonHandler(logLevelName);
| object | type | required | description |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| handlers
| { getConsoleTextHandler, getConsoleRawDataHandler, getConsoleJsonHandler,} as const; | | an object of common handlers |
| logLevelName
| type LogLevelName = | 'emerg' | 'alert' | 'crit' | 'err' | 'warning' | 'notice' | 'info' | 'debug'; | no | The name of the maximal log level to handle (low log levels are more urgent than higher ones). If none is passed (or undefined
) that Hanlder won't filter - means everything get's logged. |
| getConsoleRawDataHandler
| ( level?: LogLevelName | undefined,) => Handler | | This is the default Handler
if you don't pass one to getLogger
. It mostly works like console.log
. It doesn't has a Formatter
and just passes the raw data to console. |
| getConsoleTextHandler
| ( level?: LogLevelName | undefined,) => Handler | | This Handler
will be best for human readability. |
| getConsoleJsonHandler
| ( level?: LogLevelName | undefined,) => Handler | | This Handler
will be best for machine readability as it will be one big strigified JSON line. |
How to make it
import { Handler } from '@pabra/logger';
const myHandler: Handler = {
filter: myFilter,
formatter: myFormatter,
transporter: myTransporter,
};
Filter
What is it
type Filter = (logger: InternalLogger, message: Message) => boolean;
type InternalLogger = {
readonly name: string;
readonly nameChain: string[];
readonly handlers: Handler[];
};
interface Message {
readonly raw: string;
readonly data: any[];
readonly level:
| 'emerg'
| 'alert'
| 'crit'
| 'err'
| 'warning'
| 'notice'
| 'info'
| 'debug';
}
The Filter
function decides if a log entry should be handled at
all. If it returns false
the log entry handling immediately ends for this
handler.
If there is no Filter
provided in a Handler
, every
log entry gets handled. So no Filter
behaves the same as a
Filter
that's always returning true
.
How to get it
import { filters, Filter } from '@pabra/logger';
const myFilter: Filter = filters.getMaxLevelFilter(logLevelName);
| object | type | required | description |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| filters
| { getMaxLevelFilter } as const; | | an object of common filters |
| logLevelName
| type LogLevelName = | 'emerg' | 'alert' | 'crit' | 'err' | 'warning' | 'notice' | 'info' | 'debug'; | yes | The name of the maximal log level to handle (low log levels are more urgent than higher ones). |
| getMaxLevelFilter
| ( level: LogLevelName,) => Filter | | This Filter decides based on the severity of the log entry weather it should be logged/handled or not (low levels are more urgent - see Syslog severitiy levels). |
How to make it
A Filter
is a function that gets the InternalLogger
object and
the Message
object passed as arguments and needs to return a boolean.
If you want to have a Handler
that should only handle error
log
entries, your Filter
could look like this:
import { Filter } from '@pabra/logger';
const myFilter: Filter = (_logger, message) => message.level === 'err';
// or if you only want to handle log entries from your "auth" module
const myFilter: Filter = (logger, _message) => logger.name === 'auth';
Formatter
What is it
type Formatter = (logger: InternalLogger, message: Message) => string;
type InternalLogger = {
readonly name: string;
readonly nameChain: string[];
readonly handlers: Handler[];
};
interface Message {
readonly raw: string;
readonly data: any[];
readonly level:
| 'emerg'
| 'alert'
| 'crit'
| 'err'
| 'warning'
| 'notice'
| 'info'
| 'debug';
}
The Formatter
function produces the formatted message (string
)
that finally appears in your log file/console/etc. It might add a time stamp and
than somehow join the severity level/name, logger name, raw log message and log
data into one string.
How to get it
import { formatters, Formatter } from '@pabra/logger';
const myFormatter: Formatter = formatters.jsonFormatter;
const myFormatter: Formatter = formatters.textFormatter;
const myFormatter: Formatter = formatters.textWithoutDataFormatter;
const myFormatter: Formatter = formatters.getJsonLengthFormatter(maxLength);
const myFormatter: Formatter = formatters.getTextLengthFormatter(maxLength);
| object | type | required | description |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| formatters
| { textWithoutDataFormatter, textFormatter, jsonFormatter, getTextLengthFormatter, getJsonLengthFormatter,} as const; | | an object of common formatters |
| maxLength
| undefined | number | no | The maximum length of your formatted log message. If undefined
or omitted the default is 1024^2
(1 MiB). It is there to prevent you from potentially sending huge data objects over the wire. Notice: if used with jsonFormatter
the stringified data will end up truncated and not parseable anymore. |
| jsonFormatter
| Formatter | | Will return untruncated, stringified JSON like this: { "name": "auth", "nameChain": ["main", "auth"], "time": "2020-08-16T08:23:43.395Z", "level": "debug", "levelValue": 7, "levelServerity": "Debug", "message": "failed to login", "data": [{ "user": "bob" }]} Can handle instances of Error
as data. |
| textFormatter
| Formatter | | Will return untruncated, text like this: 2020-08-16T08:45:08.297Z [main.auth] DEBUG - failed to login {"user":"bob"}
Can handle instances of Error
as data. |
| textWithoutDataFormatter
| Formatter | | This Formatter
will just return the raw message without trying to serialize data. It's used for getConsoleRawDataHandler
to be able to pass arbitrary objects like DOM Nodes or Events to the console which could not be serialized by JSON.stringify otherwise. |
| getJsonLengthFormatter
| ( maxLength?: number | undefined,) => Formatter | | Will return length limited jsonFormatter
. |
| getTextLengthFormatter
| ( maxLength?: number | undefined,) => Formatter | | Will return length limited textFormatter
. |
How to make it
A Formatter
is a function that gets the InternalLogger
object
and the Message
object passed as arguments and needs to return a string.
A very simple Formatter
(for the sake of simplicity ignores
data) could look like this:
import { Formatter } from '@pabra/logger';
const myFormatter: Formatter = (logger, message) =>
`${new Date().toISOString()} [${logger.name}] ${message.level}: ${
message.raw
}`;
Transporter
What is it
type Transporter = (logger: InternalLogger, message: MessageFormatted) => void;
type InternalLogger = {
readonly name: string;
readonly nameChain: string[];
readonly handlers: Handler[];
};
interface MessageFormatted {
readonly raw: string;
readonly data: DataArgs;
readonly level: LogLevelName;
readonly formatted: string;
}
The Transporter
"transports" the formatted message to its
destination. That might be the console
, a file, some http endpoint, etc.
How to get it
import { transporters, Transporter } from '@pabra/logger';
const myTransporter: Transporter = transporters.consoleTransporter;
const myTransporter: Transporter = transporters.consoleWithoutDataTransporter;
| object | type | required | description |
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------- | :------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| transporters
| { consoleTransporter, consoleWithoutDataTransporter,} as const | | an object of common transporters |
| consoleTransporter
| Transporter | | It passes the formated message and data to the console. It's used by the default Handler
(getConsoleRawDataHandler
- used if no Handler
is passed to getLogger
). It can be used if you want to pass arbitrary objects (like DOM Nodes, Events, etc.) to the console without having formatter dealt with them. |
| consoleWithoutDataTransporter
| Transporter | | It passes only the formatted message to the console. A Formatter
should have taken care, that data became part of formatted message. It's used by getConsoleTextHandler
and getConsoleJsonHandler
. |
How to make it
A Transporter
is a function that gets the InternalLogger
object and the MessageFormatted
object passed as arguments and needs to return
nothing (viod
).
A very simple Tranporter
to POST to your logging server might look like:
import { Transporter } from '@pabra/logger';
const myTransporter: Transporter = (_logger, message) =>
void fetch('https://example.com', {
method: 'POST',
body: message.formatted,
});