How to log properly in Sanctuary / Fluture?

291 views Asked by At

Background

I have a function, called logInfoAsync. Let's consider this function sends some information to a log server over the network. For the purposes of this question let's assume the function is implemented as follows:

const logInfoAsync = logger => message =>
    new Promise( ( resolve, reject ) => {
        setTimeout( () => {
            //random fail reason.
            if( message.length > 10 ){ 
                reject( "We have angered the Gods. Message is too big!" );
                return;
            }
            logger(message);
            resolve();
        }, 2000 );
    })

Usage:

const myLog= logInfoAsync( console.log );
myLog("Hello world!") // Rejected: "We have angered the Gods. Message is too big!"
myLog("Hello") // Resolved: "Hello"

Problem

So far so good. We have a standard logger that sometimes works and sometimes it angers the Gods.

Now, let's assume that we have a series sequential async computations:

const { Future } = require("Fluture");

const myLogF= Future.encaseP( logInfoAsync( console.log ) );

//request.get returns a Future and so does saveDB
request.get("http://mywebsite.com")
    .chain( response => myLogF(`res is: ${res}`) )
    .chain( saveDB )
    .chain( ( ) => myLogF("Saved!") )
    .fork(
        err => console.log(`Something failed badly!: ${err}`),
        ( ) => console.log("Data saved to Olympus with great success!")
    );

In this example, what happens if the logger angers the Gods? Well, We fail to save the data! Maybe the request went fine, and maybe the data was valid, but because the logger failed, we got screwed!

Research

Now, a possible solution to this would be to use Fluture.bimap after every log. This would be horrible.

I don't want my logs to be more invasive than they already are and I most definitely do not what to litter my code with Promise styled try/catchs.

Unfortunately for me, this is the only thing I can think off ... I think that the best option would be a backup logger, for example, console.error that is used should the myLogF fail, but I ideally I would want this to be invisible.

The application should not be aware it is being logged at all!

Questions

So, given this snippet, I have the following questions:

  1. How would you keep the chain going if a log fails?
  2. How would you make log failure and recovery invisible to the application ( without littering it with the equivalent of ( try/catch )?
  3. What is the most commonly used pattern for logs?
1

There are 1 answers

2
Avaq On BEST ANSWER

I would make a tap function specifically for Futures, eg:

const always = x => y => x
const tapF = f => x => f(x).fold(always(x), always(x))

This implementation of tapF expects f to return a Future, and it will force it to resolve with the original input value.

It can then be used for logging, for example:

request.get("http://mywebsite.com")
.chain( tapF(res => myLogF(`res is: ${res}`)) )
.chain( saveDB )
.chain( tapF(() => myLogF("Saved!")) )

Now the result of this expression is completely independent from what happens inside the tapF functions.

I believe that should answer your first two questions. The last; "What is the most commonly used pattern for logs?", I'm not sure about. There are a few patterns out there, and two I can think of:

  • Logging as a side-effect inside a Monad that represents the side effect. This is what we've done with tapF.
  • Collecting the logs in memory using the Writer Monad, and writing them at the edge of the program. I have no experience with this approach.