Wednesday, March 14, 2012

Free Web Site Monitoring Utility Using Google Docs



I found a very cool web site monitoring utility today and set it up on my account.  I take no credit for this other than to help explain it a little better.  Here is the author's site:

http://joeriks.com/2011/05/17/coding-my-own-automated-web-page-tests-with-google-apps-script-and-a-spreadsheet/

Updated 7/20/2012: Added another column so a single error doesn't trigger an email now the URL must fail twice in a row before an email is sent out.

The instructions were good, but not perfect so here are some improvements to help you along.

Create a new Google Spreadsheet as follows:


Then, Add columns A, B, and C.  Notice that you can check the response-code, and look if text exists in the  response or if text doesn't exist as shown by these examples:


Choose the script editor (later you can view your scripts with the script manager):

Paste in the code

Rename the script

Give it a name and then hit the save button

Now you can test that it runs.  Click the Run/checkUrls function as shown:

Now you have to authorize that the script is allowed to email and update the spreadsheet.  Hit authorize:

If another confirmation comes up, just close it.   Click the Run/checkUrls function again:


Now you can close the script tab (or window) and go back to the spreadsheet window and you should see the spreadsheet (notice that I hovered over column E to show the contents of what was captured):


Now click Resources/All your triggers... and then add new trigger on the window that follows.

Add a trigger as shown (time-driven).  This will allow the script to run continually and update the spreadsheet when it's not open in a browser (very cool!).  Save it and...
That's it!  You now have a functional spreadsheet which will monitor all of your websites and email you if there is an issue.

Here's the code I used for this sample which comes 99% from  http://joeriks.com/2011/05/17/coding-my-own-automated-web-page-tests-with-google-apps-script-and-a-spreadsheet/ .   I implemented this as is with a change to remove the "ERROR" checking which was causing unnecessary error to be emailed AND a change to email more information about the error (including the response code i.e. 200 or 404 and the actual response text (which is useful to find out what went wrong).

Enjoy.

Code follows:

function isCellEmpty(cellData) {
  return typeof(cellData) == "string" && cellData == "";
}
 
function checkUrls() {
 
  // The code below iterates over rows in a sheet and uses the value in
  // column A as an url, requests it and puts the response code in column B
  // and the request string as the comment
 
  var doc = SpreadsheetApp.getActiveSpreadsheet();
  var cell = doc.getRange('a1');
  var mailMessage = "";
 
   
  // leftmost resultcolumn
   
  var resultColumn = 3;
 
  cell.offset(0,resultColumn).setValue(new Date());
  cell.offset(0,resultColumn+1).setValue("Content type");
  //20120704 mhf
  cell.offset(0,resultColumn+2).setValue("Success Prior Run?");
  cell.offset(0,resultColumn+3).setValue("Success?");
  cell.offset(0,resultColumn+4).setValue("Seconds");
  cell.offset(0,resultColumn+5).setValue("Comment");
   
  for (var i = 1; i < 20; ++i)
  {
    var cellData = cell.offset(i,0).getValue();
    if (!isCellEmpty(cellData))
    {
 
      var command = cell.offset(i,1).getValue();
      var optionData = cell.offset(i,2).getValue();
      if (optionData=="") optionData="{}";
      var options = Utilities.jsonParse(optionData);
       
      var hasError = false;
      var startTime = new Date();     
       
      if (command=="" | command=="GET")
      {
 
        var responseCode = 404;
        var requestContentText = "";
        var results = "";       
        var headers;
        var requestType ="";
        var contentType = "";
         
        var expectedResponseCode = 200;
        if (options["response-code"]!=undefined)
          expectedResponseCode = options["response-code"];
 
        try
        {
          var response = UrlFetchApp.fetch(cellData);
          responseCode = response.getResponseCode();
          requestContentText = response.getContentText();
          headers = response.getHeaders();        
          if (headers!=undefined)
            contentType=headers["Content-Type"].toLowerCase();
        }
        catch (e)
        {
          requestContentText = e.message;
        }
                 
        cell.offset(i,resultColumn).setValue(responseCode);
        if (responseCode!=expectedResponseCode)
        {
          hasError = true;     
          results += "Expected response code: " + expectedResponseCode;
        }
         
        if (contentType.indexOf("html")!=-1)
          cell.offset(i,resultColumn).setComment(requestContentText);
        else
          cell.offset(i,resultColumn).setComment("");
         
        cell.offset(i,resultColumn+1).setValue(contentType);
         
        // print results in column
        var colOffset = resultColumn+5;
         
        //// not contain the word ERROR
        //var containsError = (requestContentText.toLowerCase().indexOf("error") != -1);
        ////cell.offset(i,colOffset).setValue("Error: " + containsError);
        //if (containsError)
        //{
        //  results += "Error found. ";
        //  hasError = true;                     
        //}
         
        if (options["should-contain"]!=undefined)
        {
          // not contain the word ERROR
          var shouldContain = options["should-contain"].toLowerCase();
          var doesContain = (requestContentText.toLowerCase().indexOf(shouldContain) != -1);
          if (!doesContain)
          {
            results += "Not found: " + options["should-contain"] + ". ";
            hasError = true;                     
          }
           
        }
 
        if (options["should-not-contain"]!=undefined)
        {
          var shouldNotContain = options["should-not-contain"].toLowerCase();
          var doesContain = (requestContentText.toLowerCase().indexOf(shouldNotContain) != -1);
          if (doesContain)
          {
            results += "Found: " + options["should-not-contain"] + ". ";
            hasError = true;                     
          }
           
        }
        cell.offset(i,colOffset).setValue(results);
         
      }
       
      // timer       
      var endTime = new Date();     
      var timeDiff = endTime-startTime;

      // set the prior success column 20120704 mhf
      cell.offset(i,resultColumn+2).setValue(cell.offset(i,resultColumn+3).getValue());
      cell.offset(i,resultColumn+2).setBackgroundColor(cell.offset(i,resultColumn+3).getBackgroundColor());
      // success? (no errors)
      cell.offset(i,resultColumn+3).setValue(!hasError);
      // Only show an error if it happens 2 times in a row
      var priorStatus = cell.offset(i,resultColumn+2).getValue();
      var currStatus = cell.offset(i,resultColumn+3).getValue();
             
      if (hasError && currStatus == false && priorStatus == false)
      {
        cell.offset(i,resultColumn+3).setBackgroundColor("red");
        mailMessage += "ERROR on " + cellData + "\n" +
          results +
          " actual response code: " + responseCode + "\n" +
          "request content: " + requestContentText + "\n";
      }
      else
        cell.offset(i,resultColumn+3).setBackgroundColor("green");
       
      // time spent (in seconds)     
      cell.offset(i,resultColumn+4).setValue(timeDiff/1000);
       
 
    }
    else
    {
      break;
    }
  }
  if (mailMessage!="")
  {
    MailApp.sendEmail("spam@mitchellfeinstein.com", "ERROR on your web ", mailMessage);
  }
   
}
 
function getTime()
{
  var startTime = new Date();
  Browser.msgBox(startTime);
  var endTime = new Date();
  var timeDiff = endTime-startTime;
  Browser.msgBox(timeDiff);
}