Production Troubleshooting using Log infrastructure


#1

Hi All,

I’m soon to deploy my meteor app to production for the first time. I’m new to meteor and web development generally, so I wonder how to troubleshoot production issues.
The systems I built in the past were using a sophisticated log infrastructure which allowed us to reproduce the user’s flow that caused the issue and therefore troubleshoot the problem fast.

However, I saw no reference to logging it in the meteor guide. I find it hard to believe that there is no builtin logging activity at the server side, at least for DDP transportation.

Am I missing something, but if not, how do you troubleshoot production issues? Which logging system do you use?

Best regards,
Eviatar


#2

That depends on two main factors:

  • Lightweight logging, such as exception handling and tracing can be handled on Mongo as a separate collection.
  • Tracebacks, status management, etc (Heavyweight logging), better use a local / cloud file system for this.

There is no pre-built logging solution for Meteor, but also remember Meteor is a NodeJS app and unless you’re working with <1.3 versions then you can make use of NPM packages that best suit your needs.

In my case, I use log4JS and avoid logging into database as that will decrease overall performace and availability.

In general, logging is an architecture design, which means it’s purpose specific.

My recommendation would be to build a Logger controller yourself that you can import into each specific method and is able to monitor Method arguments and state, something like this:

// on Method declaration
method(...args){
    if(!this.userId){  throw new Meteor.Error('Not authorized') }
    const loggerComposite = new Logger('method.name');
    loggerComposite.assignUser(this.userId);    
    loggerComposite.argumentsUsed(...args);
    // For sensitive methods, use a try / catch
    try{
          // Logic goes here
        loggerComposite.success(returnedResult);
    } catch(err) {
        loggerComposite.error(err);
        // Handle error on method as you please
    } finally {
        loggerComposite.terminate();
    }
}

Try catch block is pretty useful, and will only show a performance impact when used on extremely demanding method calls (something like +500 ms per 20’000 calls, you can test it with a loop yourself).

The idea behind is to allow your Logger controller to format things as you need it, either save a JSON file such as:

[{
   status: success / fail;
   date: new Date(),
   method: 'method.name',
   userId: 'USERID',
   arguments: [...args],
   success: null || result,
   error: error.message
}]

Otherwise you can also use a logger to handle all functionality behind the curtain and just replace the usage you have with console.log:

const logger = new LoggerController();
logger.log('debug', this.userId);
logger.log('info', this.userId);
logger.log('error', this.userId);

This last method will emulate a raw log output, compatible with any unstructured file type you want (txt, csv, etc.)
The former has a more report-like approach, which can be latter processed in UNIX / node, etc. as you want.

Just remember, don’t clog your mongo process or your reactivity will be impacted.

Footnote:
There used to be a service called Kadira which logged permormace on Meteor methods, it’s been opensourced and taken down for a number of reasons (see end of transmission). You can set your standalone performance monitor here: https://github.com/kadira-open/kadira-server


#3

Thanks for the detailed response. I need to dive into the details you specified, there is a lot of information there I need to process.
Thanks again.


#4

After some research I saw there are many good feedbacks about the winston package.

Indeed it looks promising (although, for my opinion, missing some basic functionality like email support). However, it doesn’t seem to work properly on shared code of the client and the server. This is very much expected and it may log to a file on the file system.

Anyway, perhaps there is a conceptual gap from my end of how to structure the code, but it looks like there is a problem logging from a shared code (for instance from methods.js), which I think, is a basic problem of this methodology of shared client and server code. I wouldn’t think that scoping each log statement with an “isServer” condition is good programming.

I would love to hear what I am missing, as I’m quite new to Meteor.

Thanks and best regards,
Eviatar


#5

Take a look at meteor’s folder structure, in general, get familiar with:
./root/…

  • client/
  • imports/
  • public/
  • server/
  • both/

There are some particular use cases where a function should be scoped within Meteor.isServer and Meteor.isClient, but I do mean SOME particular cases.

While meteor is very flexible, Methods should be set within their correct folder structure, there isn’t a “standard” approach to this, but commonly:

  • Sensitive methods or server heavy methods should go on /server folder ONLY. This applies to concepts such as:
    • inventory management,
    • password retrievals,
    • permissions assigning,
    • inventory modification,
    • sales creation / checkout,
    • node.child_process,
    • encrypting, decrypting,
    • emails,
    • Important collection modifiers, etc.

What should be set on /both are interface intensive and non-sensitive methods:

  • Post coments
  • Text editing
  • non-sensitive document updates, etc

What I can tell you is that you don’t really need to log everything; however, if you want to avoid the login isServer / isClient issue, a little abstraction can help out.

Make a class function that you export, instead of importing the logger repo itself, this way you can construct the class’ methods (class functions) with isMeteor isClient, and only return an empty echo on isClient and log on isServer.

import * as fs from 'fs';
import * as whatteva from 'bestNPMLoggerMyFam';
const clientFs = {}
exports default class Logger {
    private fileSystem;
    private writeStream;
    constructor(){
        if(Meteor.isClient){
                this.fileSystem = clientFs; 
                this.writeStream = null;
            } else {
                this.fileSystem = fs;
                this.writeStream = this.fileSystem.createWriteStream('log.log');
            }
        }
        
    }
        punyExampleMethod(text){
            if(Meteor.isClient){
                return;
            }
            this.writeSream.write(text);
            // Use your logger api in a similar fashion
        }

    }
}

The example above is a little lame but will give you an idea, what you want to avoid are exceptionErrors from “This wasn’t found” and “fs does not exist in chrome because duh”. But if you were to import this class, the class’ internal isClient/isServer methods should handle it outside your code.

If you want to log something from front-end without methods, then you could use

anotherPunyMethod(text){
    if(isClient){
        Meteor.call('aPipeMethodYouSetOnServerFolder', somthn, (err, res) => {...})
    }else if(isServer){
        Meteor.call('aPipeMethodYouSetOnServerFolder', somthn, (err, res) => {...})
        this.writeStream.write('logging to a file system from server only \n');
    }
}

And the method you call should be on ./root_folder/server

Meteor.methods({
    'aPipeMethodYouSetOnServerFolder'(text){
        myAmazingLoggerLibraryFromNPM.log(text); // whichever api you want
    }
})

Hope it helps


#6

Thanks for the detailed response.
It is much appreciated.
Indeed this is the approach I took, a bit different implementation but same idea.

Thanks again and best regards