Index: /branches/rel_apv_10_7_0_21_pre_sight/usr/click/tools/azure/AZFailoverManager.py
===================================================================
--- /branches/rel_apv_10_7_0_21_pre_sight/usr/click/tools/azure/AZFailoverManager.py	(revision 38784)
+++ /branches/rel_apv_10_7_0_21_pre_sight/usr/click/tools/azure/AZFailoverManager.py	(working copy)
@@ -3,6 +3,8 @@
 import os
 from az_nic.NICController import NICController
 from az_route_table.RouteTableFailoverManager import RouteTableFailoverManager
+from ConfigManager import IPConfigManager
+from logger.ProxyAZLogger import ProxyAZLogger
 class AZFailoverManager():
     '''This class is used while failover happen and failover recover.\n
     It does two things:
@@ -17,18 +19,18 @@
         self.to_nic_name = ip_config_setting['to_nic_name']
         # self.to_ip_config_name = ip_config_setting['to_ip_config_name']
     def failover_happen(self):
-        '''two things to do: 1. transfer ip config. 2. transfer UDR'''
+        '''two things to do: 1. transfer ip config.'''
+        ProxyAZLogger.info("start to transfer all IPs")
         nicC = NICController(self.subscription_id, self.resource_group_name)
-        # nicC.transfer_ip_config(self.from_nic_name, self.from_ip_config_name, self.to_nic_name, self.to_ip_config_name)
         nicC.transfer_all_ip_config(from_nic_name=self.from_nic_name, to_nic_name=self.to_nic_name)
-        # Assumption is that two vAPV is within the same subnet. No need to transfer UDR
-        # fm = RouteTableFailoverManager()
-        # fm.handle_failover_happen()
+        ProxyAZLogger.info("finish transfering all IPs")
 
     def failover_recover(self):
+        ProxyAZLogger.info("start to transfer all IPs")
         nicC = NICController(self.subscription_id, self.resource_group_name)
-        # nicC.transfer_ip_config(self.to_nic_name, self.to_ip_config_name, self.from_nic_name, self.to_ip_config_name)
         nicC.transfer_all_ip_config(from_nic_name=self.to_nic_name, to_nic_name= self.from_nic_name)
+        ProxyAZLogger.info("finish to transfer all IPs")
+
 if __name__=="__main__":
     parser = argparse.ArgumentParser(description='Process optional name argument.')
     parser.add_argument('-mode', type=str, help='Enter 1 or 2: mode 1 is for failover happen; mode 2 is failover recover')
@@ -44,12 +46,11 @@
         for i in ip_config_setting_json:
             data = ip_config_setting_json[i]
             az_failover_manager = AZFailoverManager(data)
-
             if args.mode=='1':
-                # print("mode 1")
+                ProxyAZLogger.info("mode 1 is for failover happen")
                 az_failover_manager.failover_happen()
             elif  args.mode=='2':
-                # print("mode 2")
+                ProxyAZLogger.info("mode 2 is failover recover")
                 az_failover_manager.failover_recover()
     else:
-        print(f"Error: mode must arugment must be 1 or 2. mode:[{args.mode}]")
\ No newline at end of file
+        ProxyAZLogger.error(f"Error: mode must arugment must be 1 or 2. mode:[{args.mode}]")
\ No newline at end of file
Index: /branches/rel_apv_10_7_0_21_pre_sight/usr/click/tools/azure/AZFailoverPoller.py
===================================================================
--- /branches/rel_apv_10_7_0_21_pre_sight/usr/click/tools/azure/AZFailoverPoller.py	(revision 38784)
+++ /branches/rel_apv_10_7_0_21_pre_sight/usr/click/tools/azure/AZFailoverPoller.py	(working copy)
@@ -9,6 +9,7 @@
 from apv_controller.APVController import APVController
 from AZFailoverManager import AZFailoverManager
 from ConfigManager import IPConfigManager
+from logger.ProxyAZLogger import ProxyAZLogger
 
 # todo put it in APV enviroment, parse heartbeat
 class AZFailoverPoller():
@@ -17,17 +18,17 @@
     def start(self, ip_config_setting_json):
         
         in_failover = False
-        polling_period = 2 #seconds
-        # self.print_current_time(1)
+        polling_period = 1 #seconds
         apv_contronller = APVController()
         if apv_contronller.is_primary():
-            print(f"This APV [{apv_contronller.get_apv_name()}] is primary APV in HA.")
+            ProxyAZLogger.debug(f"This APV [{apv_contronller.get_apv_name()}] is primary APV in HA. Stop pooling.")
             return
-        print(f"This APV [{apv_contronller.get_apv_name()}] is secondary APV in HA.")
-        print(f'APV [{apv_contronller.get_apv_name()}] start polling')
+        ProxyAZLogger.debug(f"This APV [{apv_contronller.get_apv_name()}] is secondary APV in HA.")
+        ProxyAZLogger.info(f'APV [{apv_contronller.get_apv_name()}] start polling')
         # pooling
         while True:
             time.sleep(polling_period)
+            ProxyAZLogger.debug("try pooling")
             # primary machine doesn't need to do pooling
             if apv_contronller.is_primary():
                 break
@@ -35,14 +36,14 @@
                 if not in_failover:
                     # failover happen
                     current_time = datetime.now().strftime("%Y-%m-%d %H %M %S")
-                    print(f"Failover happen on {current_time}")
+                    ProxyAZLogger.info(f"Failover happen on {current_time}")
                     for idx in ip_config_setting_json:
                         data = ip_config_setting_json[idx]
                         az_failover_manager = AZFailoverManager(data)
                         az_failover_manager.failover_happen()
                     
                     #todo need to verify that all setting are set, and then set is_failover=True
-                    print("Succeessfully handle failover transition")
+                    ProxyAZLogger.info("Succeessfully handle failover transition")
 
                     # set in_failover=True for condition that the primary APV is ON again.
                     in_failover = True
@@ -50,7 +51,7 @@
             elif in_failover:
                 # failover happended before and now the primary APV is ON
                 current_time = datetime.now().strftime("%Y-%m-%d %H %M %S")
-                print(f"Failover recover on {current_time}")
+                ProxyAZLogger.info(f"Failover recover on {current_time}")
                 for idx in ip_config_setting_json:
                     data = ip_config_setting_json[idx]
                     az_failover_manager = AZFailoverManager(data)
@@ -64,9 +65,9 @@
         for proc in psutil.process_iter(['pid', 'cmdline']):
             if process_name in proc.info['cmdline']:
                 proc.kill()
-                print('kill ', process_name)
+                ProxyAZLogger.info(f'kill {process_name}')
                 return
-        print(f"Didn't stop poller. Cannnot found module name:{process_name}. Please check current module name.", )
+        ProxyAZLogger.info(f"Didn't stop poller. Cannnot found module name:{process_name}. Please check current module name.", )
     
     def stop(self):
         '''kill poller that is another running process by its filename, a.k.a. this file name.'''
@@ -82,7 +83,7 @@
                 print("pid: ", pid, " Current Time:", current_time)
                 time.sleep(interval)
         except KeyboardInterrupt:
-            print("\nProcess interrupted. Exiting...")
+            ProxyAZLogger.error("\nProcess interrupted. Exiting...")
 if __name__=='__main__':
     parser = argparse.ArgumentParser(description='Process optional name argument.')
     parser.add_argument('-mode', type=str, help='Enter 1 or 2: mode 1: start polling; mode 2: stop polling')
@@ -95,10 +96,10 @@
     ip_config_setting_json = ip_config_manager.get_ip_config()
 
     if args.mode=='1':
-        print("mode 1")    
+        ProxyAZLogger.info("start polling mode")    
         az_polloer.start(ip_config_setting_json)
     elif  args.mode=='2':
-        print("mode 2")
+        ProxyAZLogger.info("stop polling mode")
         az_polloer.stop()
     else:
-        print(f"Error: mode must arugment must be 1 or 2. mode:[{args.mode}]")
\ No newline at end of file
+        ProxyAZLogger.error(f"Error: mode must arugment must be 1 or 2. mode:[{args.mode}]")
\ No newline at end of file
Index: /branches/rel_apv_10_7_0_21_pre_sight/usr/click/tools/azure/ConfigManager.py
===================================================================
--- /branches/rel_apv_10_7_0_21_pre_sight/usr/click/tools/azure/ConfigManager.py	(revision 38784)
+++ /branches/rel_apv_10_7_0_21_pre_sight/usr/click/tools/azure/ConfigManager.py	(working copy)
@@ -1,7 +1,7 @@
 import json
 import os
 import argparse
-
+from logger.ProxyAZLogger import ProxyAZLogger
 class IPConfigManager():
     '''set needed configuration for transferring routing table and network interface'''
     def __init__(self, ip_config_file_path) -> None:
@@ -27,6 +27,7 @@
         return ip_config_setting_json
 
     def print_ip_config(self, idx, single_data):
+        ProxyAZLogger.debug("print_ip_config")
         print(f"{idx}:")
         print(f"    Subscription ID:     {single_data['subscription_id']}")
         print(f"    Resource group name: {single_data['resource_group_name']}")
@@ -46,10 +47,12 @@
                     data = ip_config_setting_json[f"{idx}"]
                     self.print_ip_config(idx, data)
         except Exception:
+            ProxyAZLogger.error("Error: No ip configuration found. Set it before examine it.")
             print("Error: No ip configuration found. Set it before examine it.")
 
     def clear_ip_config(self, idx=0):
         if os.path.exists(self.ip_config_file_path):
+            ProxyAZLogger.info(f"clear ip config with idx: {idx}")
             if idx == 0:
                 os.remove(self.ip_config_file_path)
                 print('clear all ip configuration')
@@ -66,12 +69,8 @@
                     self.save_ip_config(ip_config_setting_json)
 
 
+
     def set_ip_config(self, subscription_id, resource_group_name, from_nic_name, to_nic_name):
-        # subscription_id = input("Enter subscription ID: ")
-        # resource_group_name = input("Enter resource group name: ")
-        # from_nic_name = input("Enter NIC name of the primary machine: ")
-        # from_ip_config_name = input("Enter name of ip configuration need to be switch while failover happen: ")
-        # to_nic_name = input("Enter NIC name of the secondary machine: ")
         data = {
             "subscription_id": subscription_id,
             "resource_group_name": resource_group_name,
@@ -88,6 +87,7 @@
                 break
 
         # Write the dictionary of setting to a JSON file
+        ProxyAZLogger.info(f"save ip config: {ip_config_setting_json}")
         self.save_ip_config(ip_config_setting_json)
 
 if __name__=="__main__":
@@ -112,11 +112,14 @@
     
     ip_config_manager = IPConfigManager(setting_file_location)
     if args.mode=='set':
+        ProxyAZLogger.info("config manager set mode")
         ip_config_manager.set_ip_config(args.sub_id, args.res_grp_name, args.src_nic_name, args.dest_nic_name)
     elif  args.mode=='show':
+        ProxyAZLogger.info("config manager show mode")
         ip_config_manager.show_ip_config(args.index)
     elif  args.mode=='clear':
+        ProxyAZLogger.info("config manager clear mode")
         ip_config_manager.clear_ip_config(args.index)
 
     else:
-        print(f"Error: mode must arugment must be set, show, and clear. mode:[{args.mode}]")
+        ProxyAZLogger.error(f"Error: mode must arugment must be set, show, and clear. mode:[{args.mode}]")
Index: /branches/rel_apv_10_7_0_21_pre_sight/usr/click/tools/azure/RouteConfigManager.py
===================================================================
--- /branches/rel_apv_10_7_0_21_pre_sight/usr/click/tools/azure/RouteConfigManager.py	(revision 38784)
+++ /branches/rel_apv_10_7_0_21_pre_sight/usr/click/tools/azure/RouteConfigManager.py	(working copy)
@@ -2,6 +2,7 @@
 import os
 import argparse
 import ipaddress
+from logger.ProxyAZLogger import ProxyAZLogger
 
 class RouteConfigManager():
     '''set needed configuration for transferring routing table and network interface'''
@@ -48,9 +49,12 @@
                     self.print_UDR_config(idx, data)
         except Exception:
             print("Error: No ip configuration found. Set it before examine it.")
+            ProxyAZLogger.error("Error: No ip configuration found. Set it before examine it.")
 
     def clear_UDR_config(self, idx=0):
         if os.path.exists(self.ip_config_file_path):
+            ProxyAZLogger.info(f"clear UDR config with idx: {idx}")
+            
             if idx == 0:
                 os.remove(self.ip_config_file_path)
                 print('clear all ip configuration')
@@ -88,6 +92,7 @@
 
         # Write the dictionary of setting to a JSON file
         self.save_UDR_config(ip_config_setting_json)
+        ProxyAZLogger.info(f"save UDR config: {ip_config_setting_json}")
 
     def is_valid_ipv4_address(self, address):
         try:
@@ -113,11 +118,12 @@
     ip_config_setting_file_name = 'route_table_config_setting.json'
     if not os.path.exists(config_dir):
         os.makedirs(config_dir)
-        print(f"Directory '{config_dir}' created successfully.")
+        ProxyAZLogger.info(f"Directory '{config_dir}' created successfully.")
     setting_file_location = os.path.join(config_dir, ip_config_setting_file_name)
     # print("setting_file_location", setting_file_location)
     route_config_manager = RouteConfigManager(setting_file_location)
     if args.mode=='set':
+        ProxyAZLogger.info("set route config")
         is_ip_valid = True
         is_ip_valid &= route_config_manager.is_valid_ipv4_address(args.ip_running)
         is_ip_valid &= route_config_manager.is_valid_ipv4_address(args.ip_deallocating)
@@ -130,9 +136,12 @@
                                                 )
         else:
             print("IP4 format is not correct.")
+            ProxyAZLogger.error("IP4 format is not correct.")
     elif  args.mode=='show':
+        ProxyAZLogger.info("show route config")
         route_config_manager.show_UDR_config(args.index)
     elif  args.mode=='clear':
+        ProxyAZLogger.info("clear route config")
         route_config_manager.clear_UDR_config(args.index)
 
     else:
Index: /branches/rel_apv_10_7_0_21_pre_sight/usr/click/tools/azure/UDRPoller.py
===================================================================
--- /branches/rel_apv_10_7_0_21_pre_sight/usr/click/tools/azure/UDRPoller.py	(revision 38784)
+++ /branches/rel_apv_10_7_0_21_pre_sight/usr/click/tools/azure/UDRPoller.py	(working copy)
@@ -7,7 +7,7 @@
 from az_route_table.RouteTableController import RouteTableController
 
 from RouteConfigManager import RouteConfigManager
-
+from logger.ProxyAZLogger import ProxyAZLogger
 class UDRPoller():
     def __init__(self, rout_table_config_setting_json) -> None:
         self.rout_table_config_setting_json = rout_table_config_setting_json
@@ -38,15 +38,15 @@
 
         while True:
             time.sleep(polling_period)
-
+            
             i = 0
             for idx in self.rout_table_config_setting_json:
                 data = self.rout_table_config_setting_json[idx]
 
-                print("polling...")
+                ProxyAZLogger.info("UDR pooling")
                 if not vm_controller_list[i].is_VM_alive_by_name(data["monitored_vm_name"]):
                     # target vm is down
-                    print("detect target vm is down")
+                    ProxyAZLogger.info("detect target vm is down")
                     route_table_controller_list[i].change_all_UDR_routes_by_ip(
                         ori_next_hop_ip=data["target_ip_when_running"],
                         modify_next_hop_ip=data["target_ip_when_deallocated"],
@@ -55,7 +55,7 @@
                     is_target_vm_down_list[i]=True
                 else:
                     # target vm is up
-                    print("detect target vm is up")
+                    ProxyAZLogger.info("detect target vm is up")
                     route_table_controller_list[i].change_all_UDR_routes_by_ip(
                         ori_next_hop_ip=data["target_ip_when_deallocated"],
                         modify_next_hop_ip=data["target_ip_when_running"],
@@ -104,10 +104,10 @@
 
     udr_pooler = UDRPoller(rout_table_config_setting_json)
     if args.mode=='1':
-        # print("mode 1")
+        ProxyAZLogger.info("starting pooling mode")
         udr_pooler.start()
         pass
     elif  args.mode=='2':
-        # print("mode 2")
+        ProxyAZLogger.info("stopping pooling mode")
         udr_pooler.stop()
         pass
\ No newline at end of file
Index: /branches/rel_apv_10_7_0_21_pre_sight/usr/click/tools/azure/apv_controller/APVController.py
===================================================================
--- /branches/rel_apv_10_7_0_21_pre_sight/usr/click/tools/azure/apv_controller/APVController.py	(revision 38784)
+++ /branches/rel_apv_10_7_0_21_pre_sight/usr/click/tools/azure/apv_controller/APVController.py	(working copy)
@@ -2,6 +2,7 @@
 import re
 import subprocess
 import argparse
+from collections import deque
 
 class APVCommander():
     def __init__(self) -> None:
@@ -56,6 +57,8 @@
 class APVController():
     def __init__(self) -> None:
         self.name = self.get_apv_name()
+        peer_state_q_max_len = 3 #workaround
+        self.peer_state_q:deque = deque(maxlen=peer_state_q_max_len)
     def get_apv_version():
         commander = APVCommander()
         res = commander.launch_apv_command("show version")
@@ -107,7 +110,7 @@
         return self.name==sorted_group_priority_list[-1][0]
     def is_primary_alive(self)->bool:
         '''This function is to check if the primary APV is alive or not.
-        return True: primary APV is alive
+        return True: primary APV is alive, do nothing
         return False: Primary APV is not alive.
         This must be called in secondary APV.
         Algo:
@@ -116,13 +119,43 @@
         '''
         commander = APVCommander()
         context = commander.launch_apv_command("show ha status domain")
+        print(context)
         pattern ='Peer[\s]+unit:[\s]+(.+)\s(UP|DOWN)\s*'
         peer_up_or_down_list = re.findall(pattern, context)
+        print(peer_up_or_down_list)
+        
         # print("peer_up_or_down_list", peer_up_or_down_list)
+        
         if not peer_up_or_down_list:
             error_msg = "Error: cannot parse peer status in "+ context
             raise Exception(error_msg)
-        return peer_up_or_down_list[0][1] not in ["DOWN", '-']
+        
+        
+        import sys
+        from datetime import datetime
+        file = open('/tmp/heartbeat_workaround.txt', 'a')
+
+        # Redirect sys.stdout to the file
+        sys.stdout = file
+
+
+        timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')  # Current time formatted as YYYY-MM-DD HH:MM:SS
+        print(f"[{timestamp}] {peer_up_or_down_list[0]}")
+        sys.stdout = sys.__stdout__
+        # to solve ha flapping issue, we use container to store the recent five heartbeat results
+        # true failure happen <=> in the last five heartbeat check, all state is DOWN
+        self.peer_state_q.append(peer_up_or_down_list[0][1])
+        if self.peer_state_q.count("DOWN")==self.peer_state_q.maxlen:
+            # after checking recent five heartbeat results, the peer is truely DOWN
+            return False
+        elif self.peer_state_q.count("-")==self.peer_state_q.maxlen:
+            # user doesn't turn on ha functionality
+            return True
+        elif self.peer_state_q.count("UP")==self.peer_state_q.maxlen:
+            # after checking recent five heartbeat results, the peer is truely up
+            return True
+        return True # the machine just booted, do nothing
+
 
 
 if __name__=='__main__':
@@ -131,5 +164,5 @@
     args = parser.parse_args()
 
     apv_c = APVController()
-    print("+++++mary", apv_c.is_primary())
+    print(apv_c.is_primary_alive())
     # print(f"run command {args.c}")
\ No newline at end of file
Index: /branches/rel_apv_10_7_0_21_pre_sight/usr/click/tools/azure/az_auth/AZAuthController.py
===================================================================
--- /branches/rel_apv_10_7_0_21_pre_sight/usr/click/tools/azure/az_auth/AZAuthController.py	(revision 38784)
+++ /branches/rel_apv_10_7_0_21_pre_sight/usr/click/tools/azure/az_auth/AZAuthController.py	(working copy)
@@ -4,6 +4,7 @@
 import os
 import time
 import re
+from logger.AZLogger import AZLogger
 
 class AZAuthController():
     def __init__(self) -> None:
@@ -25,7 +26,7 @@
         login_msg_file_name = "login_msg.txt"
         login_response_file_name = "login_response.txt"
 
-        command = ["az", "login"]
+        command = ["az", "login", "--use-device-code"]
         # print("Execute command ", " ".join(command))
         login_msg_file = self.get_login_message_file(file_name=login_msg_file_name)
         result = subprocess.Popen(command, stdout=login_msg_file, stderr=login_msg_file)
@@ -72,6 +73,7 @@
         return True for user has logged in
         return False for user has NOT logged in
         '''
+        
         command = ["az", "account", "show"]
         result = subprocess.run(command, capture_output=True, text=True).stdout
         # if successfully login id must be in the return json object
@@ -81,6 +83,7 @@
             return False
         
     def show_az(self):
+        AZLogger.info("show az log in information")
         command = ["az", "account", "show"]
         # print("Execute command ", " ".join(command))
         result = subprocess.run(command, capture_output=True, text=True)
@@ -94,4 +97,4 @@
 
 if __name__=="__main__":
     c = AZAuthController()
-    c.is_az_login()
\ No newline at end of file
+    c.show_az()
\ No newline at end of file
Index: /branches/rel_apv_10_7_0_21_pre_sight/usr/click/tools/azure/logger/AZLogger.py
===================================================================
--- /branches/rel_apv_10_7_0_21_pre_sight/usr/click/tools/azure/logger/AZLogger.py	(revision 0)
+++ /branches/rel_apv_10_7_0_21_pre_sight/usr/click/tools/azure/logger/AZLogger.py	(working copy)
@@ -0,0 +1,162 @@
+import logging
+from logging.handlers import RotatingFileHandler
+import json
+from abc import ABC, abstractmethod
+import os
+
+
+class AbstractAZLogger(ABC):
+    @classmethod
+    @abstractmethod
+    def set_log_path1(cls, log_path:str)->None:
+        pass
+        
+    @classmethod
+    @abstractmethod
+    def set_log_path(cls, log_path:str)->None:
+        cls.log_path = log_path
+        
+    @classmethod
+    @abstractmethod
+    def debug(cls, message:str)->None:
+        pass
+    
+    @classmethod
+    @abstractmethod
+    def info(cls, message:str)->None:
+        pass
+    
+    @classmethod
+    @abstractmethod
+    def warning(cls, message:str)->None:
+        pass
+    
+    @classmethod
+    @abstractmethod
+    def error(cls, message:str)->None:
+        pass
+    
+    @classmethod
+    @abstractmethod
+    def _load_config(file_path):
+        pass
+    
+log_levels = {
+    "debug": logging.DEBUG,
+    "info": logging.INFO,
+    "warning": logging.WARNING,
+    "error": logging.ERROR,
+}
+
+class AZLogger(AbstractAZLogger):
+    '''
+    This class is used to log the azure event.
+    We use the python build-in logging under the hood to implement this class.
+    This class have several static method to log the message.
+    There is no interface to configure this class, that is, all the configurations are set inside the class
+    The AZlogger have four levels of severity, debug, info, warning and error which have the same name of class method.
+    '''
+    # is there a way to make the initilization clean?
+    def _load_config(file_path):
+        try:
+            with open(file_path, 'r') as config_file:
+                config = json.load(config_file)
+                return config
+        except FileNotFoundError:
+            print(f"Error: The file {file_path} was not found.")
+        except json.JSONDecodeError:
+            print(f"Error: The file {file_path} is not a valid JSON file.")
+            
+    current_module_directory = os.path.dirname(os.path.abspath(__file__))
+    config_file_path = os.path.join(current_module_directory, "config.json")
+    config = _load_config(config_file_path)
+    log_path = config["log_path"]
+    # log_path = "./azure_ha.log" # for developing purpose
+    log_format = config["log_format"]
+    max_size_log_file = config["max_size_log_file"] #MB
+
+    # logger
+    logger = logging.getLogger("azlogger")
+    log_levels.get(config["level_of_severity"].lower(), logging.DEBUG)
+    logger.setLevel(log_levels.get(config["level_of_severity"].lower(), logging.DEBUG))
+    
+    
+    # log handler
+    file_handler = RotatingFileHandler(log_path, maxBytes=max_size_log_file*1024*1024, backupCount=1)
+    logger.addHandler(file_handler)
+    
+    # log formatter
+    file_formatter = logging.Formatter(log_format, datefmt=config["datefmt"])
+    file_handler.setFormatter(file_formatter)
+    
+    def __init__():
+        pass
+    @classmethod
+    def set_log_path(cls, log_path:str)->None:
+        cls.log_path = log_path
+        
+    @classmethod
+    def debug(cls, message:str)->None:
+        cls.logger.debug(message)
+    @classmethod
+    def info(cls, message:str)->None:
+        cls.logger.info(message)
+    @classmethod
+    def warning(cls, message:str)->None:
+        cls.logger.warning(message)
+    @classmethod
+    def error(cls, message:str)->None:
+        cls.logger.error(message)
+        
+    @classmethod
+    def reload_config(cls)->None:
+
+        try:
+            with open(cls.config_file_path, 'r') as config_file:
+                config = json.load(config_file)
+        except FileNotFoundError:
+            print(f"Error: The file {config_file} was not found.")
+        except json.JSONDecodeError:
+            print(f"Error: The file {config_file} is not a valid JSON file.")
+        
+        log_path = config["log_path"]
+        log_path = "./azure_ha.log" # for developing purpose
+        log_format = config["log_format"]
+        max_size_log_file = config["max_size_log_file"] #MB
+
+        # logger
+        logger = logging.getLogger("azlogger")
+        log_levels.get(config["level_of_severity"].lower(), logging.DEBUG)
+        logger.setLevel(log_levels.get(config["level_of_severity"].lower(), logging.DEBUG))
+        
+
+        
+        # log handler
+        # Remove all existing handlers
+        for handler in logger.handlers[:]:  # Create a copy of the list for safe iteration
+            logger.removeHandler(handler)
+        file_handler = RotatingFileHandler(log_path, maxBytes=max_size_log_file*1024*1024, backupCount=1)
+        
+        # log formatter
+        file_formatter = logging.Formatter(log_format, datefmt=config["datefmt"])
+        file_handler.setFormatter(file_formatter)
+        
+        logger.addHandler(file_handler)
+        
+
+if __name__=="__main__":
+    # test code
+
+    AZLogger.debug("This is a debug message")
+    AZLogger.info("This is an info message")
+    AZLogger.warning("This is a warning message")
+    AZLogger.error("This is a error message")
+    mb = 1
+    for i in range(mb*5000):
+        AZLogger.reload_config()
+        AZLogger.debug(f"This is a debug message {i}")
+        AZLogger.info(f"This is an info message {i}")
+        AZLogger.warning(f"This is a warning message {i}")
+        AZLogger.error(f"This is a error message {i}")
+        import time
+        time.sleep(1)
\ No newline at end of file
Index: /branches/rel_apv_10_7_0_21_pre_sight/usr/click/tools/azure/logger/ProxyAZLogger.py
===================================================================
--- /branches/rel_apv_10_7_0_21_pre_sight/usr/click/tools/azure/logger/ProxyAZLogger.py	(revision 0)
+++ /branches/rel_apv_10_7_0_21_pre_sight/usr/click/tools/azure/logger/ProxyAZLogger.py	(working copy)
@@ -0,0 +1,102 @@
+from logger.AZLogger import AbstractAZLogger
+from logger.AZLogger import AZLogger
+import os
+import json
+
+class ProxyAZLogger(AbstractAZLogger):
+    '''
+    This class is designed using the proxy pattern, a structural design pattern.
+    Becuase there is a requirement of dynamically changing the level of serverity, we use proxy to determine 
+    if configuration file is chaged or not in order to keep the AZLogger class pure.
+    '''
+    config_modified_indicator_file = os.path.join(AZLogger.current_module_directory, 'config_modified.txt')
+    @classmethod
+    def get_config(cls)->dict:
+        '''THe usage of this method refers to the set_config()'''
+        try:
+            with open(AZLogger.config_file_path, 'r') as config_file:
+                config = json.load(config_file)
+                return config
+        except FileNotFoundError:
+            AZLogger.error(f"Error: The file {config_file} was not found.")
+        except json.JSONDecodeError:
+            AZLogger.error(f"Error: The file {config_file} is not a valid JSON file.")
+            
+    @classmethod
+    def set_config(cls, config:dict)->None:
+        '''The suggested method to modify configuration file is to use get_config() method 
+        to get the original configuration dictionary, modify the dictionary objecct, and invoke set_config() with 
+        arugment of modified dictionary object
+        algo: 
+        1. take input to override the original config dictionary.
+        2. write the file.
+        3. set config file modified indictor
+        '''
+        AZLogger.info(f"setting new config: {config}")
+        ori_config = cls.get_config()
+        for key in ori_config.keys():
+            ori_config[key] = config[key]
+        with open(AZLogger.config_file_path, "w") as json_file:
+            json.dump(ori_config, json_file, indent=4)
+        with open(cls.config_modified_indicator_file, 'w') as file:
+            pass
+            # file.write("This is a sample text indicating the config file has been modified.")
+    
+    @classmethod
+    def is_config_modified(cls)->bool:
+        '''This method is used to check if the configuration file is modified or not.
+        The alogrithem to do that is check if there is the modified file existed.
+        If the file existed, the configuration file is modified; otherwise, the configuration is unchaged.
+        '''
+        return os.path.exists(cls.config_modified_indicator_file)
+    @classmethod
+    def _refresh_config(cls)->None:
+        '''If the config file indictor file is existed, then reload the configuration file and delete indictor file.'''
+        if cls.is_config_modified():
+            AZLogger.reload_config()
+            # delete config file changed indicator
+            os.remove(cls.config_modified_indicator_file)
+    @classmethod
+    def debug(cls, message:str)->None:
+        cls._refresh_config()
+        AZLogger.debug(message)
+    @classmethod
+    def info(cls, message:str)->None:
+        cls._refresh_config()
+        AZLogger.info(message)
+    @classmethod
+    def warning(cls, message:str)->None:
+        cls._refresh_config()
+        AZLogger.warning(message)
+    @classmethod
+    def error(cls, message:str)->None:
+        cls._refresh_config()
+        AZLogger.error(message)
+        
+        
+if __name__=="__main__":
+    # test code
+    import os
+    # Get the location of the current module
+    current_module_directory = os.path.dirname(os.path.abspath(__file__))
+
+    ProxyAZLogger.debug("This is a debug message")
+    ProxyAZLogger.info("This is an info message")
+    ProxyAZLogger.warning("This is a warning message")
+    ProxyAZLogger.error("This is a error message")
+    mb = 5
+    for i in range(mb*5000):
+        ProxyAZLogger.debug(f"This is a debug message {i}")
+        ProxyAZLogger.info(f"This is an info message {i}")
+        ProxyAZLogger.warning(f"This is a warning message {i}")
+        ProxyAZLogger.error(f"This is a error message {i}")
+        import time
+        if i%3==0:
+            # test config file changing
+            cur_config = ProxyAZLogger.get_config()
+            cur_config["level_of_severity"] = "info" if cur_config["level_of_severity"]=="debug" else "debug"
+            ProxyAZLogger.set_config(cur_config)
+            print("change")
+        if i ==10:
+            break
+        time.sleep(1)
\ No newline at end of file
Index: /branches/rel_apv_10_7_0_21_pre_sight/usr/click/tools/azure/logger/__init__.py	(added)
===================================================================
--- /branches/rel_apv_10_7_0_21_pre_sight/usr/click/tools/azure/logger/__init__.py	(revision 0)
+++ /branches/rel_apv_10_7_0_21_pre_sight/usr/click/tools/azure/logger/__init__.py	(revision 0)
Index: /branches/rel_apv_10_7_0_21_pre_sight/usr/click/tools/azure/logger/config.json
===================================================================
--- /branches/rel_apv_10_7_0_21_pre_sight/usr/click/tools/azure/logger/config.json	(revision 0)
+++ /branches/rel_apv_10_7_0_21_pre_sight/usr/click/tools/azure/logger/config.json	(working copy)
@@ -0,0 +1,7 @@
+{
+    "log_path": "/var/crash/azure_ha.log",
+    "log_format": "[%(asctime)s] - [%(levelname)s] %(message)s",
+    "max_size_log_file": 0,
+    "level_of_severity": "info",
+    "datefmt": "%Y-%m-%dT%H:%M:%S"
+}
\ No newline at end of file
