python-How to check website or url health periodically and automatically by using python script?

1. Purpose

In this post, I would demo how to create a python script to check a url(website/webservice)’s health periodically and automatically .

2. Environment

  • Python 2.x and 3.x
  • Linux system

3. The solution

3.1 The environment

Suppose we hava a web service which provides some sort of services to others, now we want it to run always and if it’s not healthy, we should restart the service automatically.

The service URL prefix is:

http://localhost:8080/myservice

And we also have a restart script for this service located at:

/opt/myservice/restart.sh

3.2 Add a health check endpoint for our service

For health check purpose, we should add a dedicated endpoint to be accessed by the health check script, it should be fast , here is our implementation in java language:

	@RequestMapping(method = RequestMethod.GET,value = "hello",headers = "Accept=application/json")
	public @ResponseBody String hello() {
		return "world";
	}

It means that if we caccess the below url:

http://localhost:8080/myservice/hello

We should get this result(http response status code equals 200):

world

Otherwise , the status code should not be 200 , just as the picture shows:

image-20210616152439563

It health check fails, the restart script should be run immediately.

3.3 Setup the logger and imports for python script

Before coding the business, we should setup the environment for our script, just as follows:

import time
import os
import logging
from logging.handlers import TimedRotatingFileHandler

#setup the loggers for daily logging file rolling
logger = logging.getLogger()
logger.setLevel(logging.INFO)
file_handler = TimedRotatingFileHandler("healthcheck_myservice.log", when="midnight", interval=1)
file_handler.suffix = "%Y%m%d"
ch = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
ch.setFormatter(formatter)
logger.addHandler(file_handler)

# global variables.
THE_URL = "http://localhost:8080/myservice/hello"
RESTART_SH = '/opt/myservice/restart.sh'
SLEEP_INTERVAL = 60

In the above code, we use the logging module in python to setup a daily rolling file handler for our program, it would create a log file everyday with the suffix year-month-day. You can view more information about the TimedRotatingFileHandler from this website.

The parameter when="midnight" in TimedRotatingFileHandler means that it would Roll over at midnight.

3.4 Implement the health check function with python

First, we need a function to check the status of the service, just as follows:

def health_check(url):
    try:
        logger.info("start access url %s" % (url))
        
        import urllib
        a=urllib.urlopen(url) #this is python2 version, the python3 compatible version is listed below
        
        if a:
            if a.getcode()==200:
                lines = a.readlines()
                if lines is not None and len(lines)==1:
                    if lines[0]=='world':
                        logger.info("health ok")
                        return True
            else:
                logger.info("warning code:"+str(a.getcode()))
    except Exception as e:
        logger.error('health check failed %s ' % (str(e)))
    logger.info("health not ok")
    return False

You can see that , the above function would return True only when the service response code is 200 and the responsecontent is world, or else, it would return False to the caller.

For python 2.x , we can use:

import urllib
urllib.urlopen(url)

For python 3.x , you can replace the urllib.urlopen with follows:

import urllib.request
with urllib.request.urlopen(url) as response:
   html = response.read()

3.5 Implement the periodically checking logic

Now we should create a function to run the health check periodically and restart the service automatically if the check fails.

def check_periodically(url,failed_action_func):
    while True:
        try:
            time.sleep(SLEEP_INTERVAL)
            logger.info("check awake, start check")
            if not health_check(url):
                failed_action_func()
        except Exception as e:
            logger.error('check periodically failed %s ' % (str(e)))
        pass
        
def restart_server():
    do_script(RESTART_SH)
    pass

def do_script(the_scripts):
    logger.info("=========start script %s======"%the_scripts)
    result = os.popen(the_scripts).read()
    logger.info(result)
    logger.info("=========end   script %s======" % the_scripts)
    pass

3.6 The main function

At last, we should provide the main function to execute the whole script:

if __name__ == '__main__':
    check_periodically(THE_URL,restart_server)
    pass

3.7 Save the script and execute it in background

Then we save the above script as healthcheck_myservice.py, and execute in background as follows:

$ nohup python healthcheck_myservice.py > healthcheck_myservice.out &

Then we can check the log as follows:

$ tail -f healthcheck_myservice.log

We can get these logs:

2021-06-16 15:54:23,775 - root - INFO - health ok
2021-06-16 15:55:23,834 - root - INFO - check awake, start check
2021-06-16 15:55:23,834 - root - INFO - start access url http://localhost:8080/myservice/hello
2021-06-16 15:55:23,839 - root - INFO - health ok
2021-06-16 15:56:23,899 - root - INFO - check awake, start check
2021-06-16 15:56:23,899 - root - INFO - start access url http://localhost:8080/myservice/hello
2021-06-16 15:56:23,906 - root - INFO - health ok

It works!

4. The whole script

#check if my service is running fine, or else, it would restart it

import urllib
import time
import os
import logging
from logging.handlers import TimedRotatingFileHandler

logger = logging.getLogger()
logger.setLevel(logging.INFO)
file_handler = TimedRotatingFileHandler("healthcheck_myservice.log", when="midnight", interval=1)
file_handler.suffix = "%Y%m%d"
ch = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
ch.setFormatter(formatter)
logger.addHandler(file_handler)
#logger.addHandler(ch)

THE_URL = "http://localhost:8080/myservice/hello"
RESTART_SH = '/opt/myservice/restart.sh'
SLEEP_INTERVAL = 60


#perform the health check, return True if the url is accessible.
def health_check(url):
    try:
        logger.info("start access url %s" % (url))
        a=urllib.urlopen(url)
        if a:
            if a.getcode()==200:
                lines = a.readlines()
                if lines is not None and len(lines)==1:
                    if lines[0]=='world':
                        logger.info("health ok")
                        return True
            else:
                logger.info("warning code:"+str(a.getcode()))
    except Exception as e:
        logger.error('health check failed %s ' % (str(e)))
    logger.info("health not ok")
    return False

#check the health for specified period
def check_periodically(url,failed_action_func):
    while True:
        try:
            time.sleep(SLEEP_INTERVAL)
            logger.info("check awake, start check")
            if not health_check(url):
                failed_action_func()
        except Exception as e:
            logger.error('check periodically failed %s ' % (str(e)))
        pass

def restart_server():
    do_script(RESTART_SH)
    pass

def do_script(the_scripts):
    logger.info("=========start script %s======"%the_scripts)
    result = os.popen(the_scripts).read()
    logger.info(result)
    logger.info("=========end   script %s======" % the_scripts)
    pass

if __name__ == '__main__':
    check_periodically(THE_URL,restart_server)
    pass

5. Summary

In this post, I demonstrated how to use python to do health check job in linux.