Log mailer for (Node) JS. Aggregate your log/error mail from all over your app and send it when you want.
Tags: showdev, node, javascript, devops
Published: 2019-09-29 11:35:59

Hi, I am Daniel and I want to present to you one of my little minions I developed within the context of my current side project, the [Globallytics Fund No. 1](https://globallytics.de/), which is a machine learning based stock fund (ISIN DE000A2PE1D2).

See also my other minion called "patiently". You can find more info about patiently [here](https://dev.to/mindactuate/patiently-an-api-rate-limit-handler-for-node-js-a-legal-way-to-wait-for-api-rate-limit-resets-51dp) and [here](https://github.com/mindactuate/patiently).

I would be so happy for a Github star on my repo. 😃 😍 I am looking very forward for discussions and comments. Thank you!

## Short description

Think of the following scenario: There are several events in your app and you want to be noticed about them via email. Well, if you have one notification, then you have one email. That's fine. But what if you have lots of notifications over runtime, thrown by functions scattered all over your app? Then you would have to aggregate all those notifications to a (log) file and send it when your app run is finished. Lots of loggers are able to write into files. But what if you want to have a well formatted html email? Then you can use logmailer. :)

## Where to find

[logmailer @ github](https://github.com/mindactuate/logmailer)

[logmailer @ npm](https://www.npmjs.com/package/logmailer)

## The "Why"

In the context of the side project I am responsible for the development and operation of several web scrapers that run a few times a day and load website data, process it and write it into databases. As it is my "side" project I do not have too much time for monitoring and reporting. But: since the apps are an important part of the software behind the ML based fund, a high stability and transparency is required.

> If something goes wrong, the team needs to know about it. Also we need a report per app execution.

So I need some kind of observer to observe the app execution (job) and to notify us if there were any incidents. Also we need a report per job for analytical purposes.

I divide those incidents into serious ("Errors") and less serious incidents ("Warnings"). Errors must be checked immediately by the "DevOps team" which is my colleague and I. For example, if our databases or the web services are down. Warnings do not have to be processed instantly. It is good enough if these are evaluated at a later point in time. For example, if one of many data records is corrupt.

> As it is my "side" project I do not have too much time for monitoring and reporting. So I want to be notified only if there are serious incidents.

Only if serious incidents occur during the job my colleague and I want to be notified directly by e-mail to our private e-mail addresses. The normal report and warnings can be sent to a group address.

Within the context of these scenarios the following requirements can be derived:
- we need a well structured report sent by e-mail to our group e-mail address
- we need to be notified via e-mail at our private e-mail addresses if there were serious incidents in an app execution while the e-mail also has to be well structured so that the most important information can be captured quickly
- the report needs to be aggregated from several places all over the app

During my research about existing libraries or packages I stumbled across common loggers like Winston or similar. Such loggers are able to log text into files, which can then be sent by e-mail. But that was not what I needed. I needed well formatted and structured e-mails. Another solution I found was to send an e-mail for every single error. There are loggers out there that can be configured this way. But that was not what I needed either, because I need an aggregated e-mail and a report.

> So I decided to develop my own notification service - or better: **logmailer**.

Please see below screenshots of some log mail examples.

## Installing

Using npm:

```bash
$ npm install logmailer
```

## How to use

### Set up the logmailer (initially)

Create a file to create and configure the logmailer (e.g. logmailer.js). Make sure to export the logmailer itself and your chapters.

**logmailer.js**

```javascript
    let { logmailer, Recipient, Chapter, StandardChapters } = require("logmailer");
    // import { logmailer, Recipient, Chapter, StandardChapters } from "logmailer";

    // the order in this object is the order of the chapters in the email
    let chapters = {
        summary: StandardChapters.Summary,
        ffOnly: new Chapter("Firefighter only", false, "DeepPink"),
        managerOnly: new Chapter("Manager only", false, "DarkSlateBlue"),
        errors: StandardChapters.Errors,
        logs: StandardChapters.Logs
    }

    logmailer.create({
        appName: "My App",
        mailAlias: "myapp@mymail.com",
        client: {
            host: "smtp.googlemail.com",
            user: "user",
            password: "password",
            ssl: true
        },
        recipients: [
            "baerbel@gmx.de", // receives everything

            // receives email if the "managerOnly" chapter is not empty
            // receives only the chapter "managerOnly"
            new Recipient("guenther@gmail.com", [chapters.managerOnly], [chapters.managerOnly]),

            // receives email if the "ffOnly" chapter is not empty
            // receives only the chapters "ffOnly" and "errors"
            new Recipient("horst@web.de", [chapters.ffOnly], [chapters.summary, chapters.ffOnly, chapters.errors]),
        ],
        chapters: chapters
    })

    module.exports.logmail = chapters;
    module.exports.logmailer = logmailer;
```

#### Chapter class

`Chapter` is a single chapter object
```javascript
let Chapter: new (name: string, hasCount?: boolean, color?: string) => Chapter
```

Params:
- @param `name` — chapters name e.g. "Summary"
- @param `hasCount` — (optional, default is false) set to true if you want to count how often you added content to the chapter (good for errors or warnings)
- @param `color` — (optional, default is "black") use colors to colorize headlines (you can use hex, rgb, rgba, color codes etc. but it is important that the email client can display the color correctly)

#### Recipient class

`Recipient` a single recipient object
```javascript
let Recipient: new (emailAddress: string, getsEmailOnlyIfChaptersNotEmpty?: Chapter[], canOnlySeeChapters?: Chapter[]) => Recipient
```

Params:
- @param `emailAddress`
- @param `getsEmailOnlyIfChaptersNotEmpty` — (optional) array of chapters e.g. [chapters.errors], the recipient will get the email only if there is at least 1 logged error
- @param `canOnlySeeChapters` — (optional) array of chapters e.g. [chapters.summary, chapters.errors], the recipient can only see the summary and the logged errors

### Use the logmailer

In all your other files you can simply import your chapters and the logmailer and use them.

**myapp.js**

```javascript
    let { logmailer, logmail } = require("./logmailer");
    // import { logmailer, logmail } from "./logmailer";

    logmail.summary.add("Starting time", `Starting app run now: ${new Date().toISOString()}`);

    // ..

    logmail.errors.add("Error heading", "Info about error");
    logmail.errors.add(null, "Further info about error");
    logmail.errors.add(null, "Further info about error");

    // ..

    logmail.managerOnly.add("Info for the manager heading", "Info for the manager");
    logmail.managerOnly.add(null, "Further info for the manager");
    logmail.managerOnly.add(null, "Further info for the manager");

    // ..

    logmail.ffOnly.add("Info for the firefighter heading", "Instructions for the firefighter");
    logmail.ffOnly.add(null, "Further instructions");
    logmail.ffOnly.add(null, "Further instructions");
```

### Send the mail

```javascript
    logmailer.sendMail(err => {
        if (err) {
            console.log("error while sending", err);
        } else {
            console.log("mail sent successfully");
        }
    })
```

### Reset your chapters

```javascript
    logmail.errors.reset();
    logmail.warnings.reset();
```

### Format objects or arrays of objects as html tables

```javascript
    let object = {
        "row1, col1": "row1, col2",
        "row2, col1": "row2, col2",
        "row3, col1": {
            "row3.1, col2.1": "row3.1, col2.2",
            "row3.2, col2.1": "row3.2, col2.2"
        }
    }

    logmail.logs.add("My object as a html table", logmailer.convertObjectToHTMLTable(object));

    let arrayOfObjects = [object, object];

    logmail.logs.add("My object array as a html table", logmailer.convertObjectArrayToHTMLTable(arrayOfObjects));
```

### Nice solution for Node JS

**index.js or server.js**

```javascript
    process.on('uncaughtException', function (err) {
        logmail.errors.add("Uncaught exception", `&#9658; Error message: ${err.message}<br/>Error stack: ${err.stack}`);
    });

    process.on('unhandledRejection', function (err) {
        logmail.errors.add("Unhandled rejection", `&#9658; Error message: ${err.message}<br/>Error stack: ${err.stack}`);
        unplannedExit("rejection error");
    })

    function unplannedExit(info) {
        logmail.errors.add("Unnormal exit:", `&#9658; Info: ${info}`);
        logmail.summary.add("Ending time", `Ending app run now: ${new Date().toISOString()}`);
        logmailer.sendMail(err => {
            if (err) {
                console.log("error while sending", err);
            } else {
                console.log("mail sent successfully");
            }
            process.exit();
        });
    }

    process.on('beforeExit', function (exitCode) {
        unplannedExit(exitCode);
    })

    process.on("SIGTERM", function (signal) {
        unplannedExit(signal);
    })

    process.on("SIGINT", function (signal) {
        unplannedExit(signal);
    })

    // ..
```

## Screenshots

### Managers view
<img src="https://github.com/mindactuate/logmailer/blob/master/Screenshot_20190727-155020.png?raw=true" width="400">

### Firefighters view
<img src="https://github.com/mindactuate/logmailer/blob/master/Screenshot_20190727-155105.png?raw=true" width="400">

### Full view (all chapters)
<img src="https://github.com/mindactuate/logmailer/blob/master/Screenshot_20190727-155117.png?raw=true" width="400">
<img src="https://github.com/mindactuate/logmailer/blob/master/Screenshot_20190727-155127.png?raw=true" width="400">

## License

MIT