fp-ts wrapper to use winston logging library inside a pipe.
The index define an object for each supported fp-ts monad (Option, Either, Task and TaskEither) implementing log functions for it (log, info, warn and error). A log function could be used directly in the pipe, without output changes.
import { pipe } from "fp-ts/lib/function";
import * as TE from "fp-ts/TaskEither";
import { defaultLog, useWinston, withConsole } from "@pagopa/winston-ts";
useWinston(withConsole()); // configure the default winston tranport
pipe(
{ name: "Queen" },
TE.right,
defaultLog.taskEither.info("INFO!"), // log an info if the taskEither is right
defaultLog.taskEither.debug(i => `DEBUG ${i.name}`), // log a debug if the taskEither is right using data conteined in the monad (rigth)
TE.chain(i => TE.left(Error(i.name))),
defaultLog.taskEither.debugLeft(i => `DEBUG ${i}`) // log a debug if the taskEither is left using data contained in the monad (left)
);
winston-ts will allow you to use a configured logger within an fp-ts pipe without altering its input/output flow.
To log a message you need to specify:
- which logger you want to use, using a LoggerId
- which fp-ts monad you are working with
- the logger params (see below)
The syntax used is <logger id>.<monad type>.<log function>(<logger params>)
defaultLog.task.info("Don't STOP me NOW!"); // log an info with message 'Don't STOP me NOW!' on the 'default' logger when the promise pointed in the task is resolved
pipe(
"NOW",
T.of,
defaultLog.task.info(a -> `Don't STOP me ${a}!`), // log an info with message 'Don't STOP me NOW!' on the 'default' logger when the promise pointed in the task is resolved
);
The special monad type peek
allow you to log directly using an object instead of a monad.
This is useful when we are still constructing our input.
pipe(
"NOW",
defaultLog.peek.info(`Don't STOP me ${a}!`), // log an info with message 'Don't STOP me NOW!' on the 'default' logger
);
Each logger function support differents inputs:
- string
- tuple of 2 elements (a string and an unknown object)
- a function with arity 1 and returning a string
- a function with arity 1 and rutirning a tuple of 2 elements (a string and an unknown object)
A logger with a string
input, will direct used as message log.
The string will be provide to the transport as message
parameter.
defaultLog.task.info("Don't STOP me NOW!");
// Output
// {message: "Don't STOP me NOW!"}
A logger with a tuple [string, unknown]
as input, will use the string as message and the unknown object fields will be used as meta parameters.
The transport will receive the string as message
, the unknown object fields deconstructed as meta
.
defaultLog.task.info(["Don't STOP me NOW!", {galileo: "magnificooooo ", under: "pressure"}]);
// Output
// {message: "Don't STOP me NOW!", galileo: "magnificooooo ", under: "pressure"}
A logger with a function (a) -> string
as input, will execute the function providing the value wrapped by the monad as input, and use the output string as explained above (String input).
pipe(
"NOW",
T.of,
defaultLog.task.info(a -> `Don't STOP me ${a}!`),
);
// Output
// {message: "Don't STOP me NOW!"}
A logger with a function (a) -> [string, unknown]
as input, will execute the function providing the value wrapped by the monad as input, and use the output tuple as explained above (Tupla of 2 elements).
pipe(
"NOW",
T.of,
defaultLog.task.info(a -> [`Don't STOP me ${a}!`, {galileo: "magnificooooo ", under: "pressure"]),
);
// Output
// {message: "Don't STOP me NOW!", galileo: "magnificooooo ", under: "pressure"}
winston-ts use the winston Container to configure one or more logger.
The utility useWinstonFor() allow you to configure and map it to be used with a specific logger type.
NB: Each logger must be mapped with a LoggerId: this will limit the maximum number of configured logger at time.
useWinstonFor({
loggerId: LoggerId.default, // the type of the logger
transports: withConsole() // the transports used to logging
});
...
pipe("", defaultLog.peek.info("Don't STOP me NOW!"));
You can configure multiple transports within a single logger: in this way, when calling a log function on the mapped loggerId, the message will be processed by all the transports.
useWinstonFor({
loggerId: LoggerId.event,
transports: [
withConsole(),
withConsole()
]
});
useWinstonFor({
loggerId: LoggerId.default,
transports: [withConsole()]
});
...
pipe("", defaultLog.peek.info("Don't STOP me NOW! on default logger"));
// Output:
// {message: "Don't STOP me NOW! on default logger"}
pipe("", eventLog.peek.info("Don't STOP me NOW! on event logger"));
// Output:
// {message: "Don't STOP me NOW! on event logger"}
// {message: "Don't STOP me NOW! on event logger"}
A winston logger is backed by a node stream. Each log call will be an asynchronous 'fire and forget': no further error will be throw by winston transport.
This logger could be used with:
- Azure Application Insight using the ApplicationInsightTransport.
NB: this transport will log in the Applicaiton Insight properties all the parameters given to logging function (not only the message string).
export const run = (context: Context) => {
...
useWinston(withApplicationInsight(telemetryClient)); // configure the default winston tranport
...
}
- Azure function context using the AzureContextTransport.
NB: if you use a Function App, the transport logging level must be set to FINEST_LEVEL. The final logging level will be the one set in the function host.json.
export const run = (context: Context) => {
...
useWinston(new AzureContextTransport(() => context.log, { level: FINEST_LEVEL })); // configure the default winston tranport
...
}