pythonazure

Print function not working without flush=True when used with the While loop


Is there some known issue where the Python environment for Azure Runbooks does not like the while loop function in conjunction with the print function without appending flush=True?

I have not seen one print statement returned when the word while has been included in the main function.

This is designed to send an alert whenever the status of my Azure VM changes. (This will be via email, but for ease, I have just included print statements...which should work)

Remove the while functionality, and all the print statements appear.

It's such a silly situation. Even the print("Can you see this message....") nor the print("Entering loop...") does not print. These print statements are not even printing in the while loop so it should print even if there is an issue with the execution of the while loop (which there is not). I would show you via debug printing but if the thing actually printed like I told it to, then you would be able to see.

My blood pressure seriously is rising because of this. The compute_client is authenticated, and it returns the correct vm_name, so why - whenever I include the while loop - does it have a hissy fit?

I am so angry with this.

import time, random, smtplib, inspect, string
from datetime import datetime
from email.mime.text import MIMEText
from azure.common.credentials import ServicePrincipalCredentials
from azure.mgmt.compute import ComputeManagementClient

# Azure setup
subscription_id = 'xxx'  
resource_group_name = 'xxx'  
vm_name = 'xxx'  

from azure.core.exceptions import ClientAuthenticationError

# Authentication of Azure's Service Principal
def service_principal_authentication():
    """Checks the Authentication of Azure's Service Principal """
    print("Authenticating Service Principal....")
    try:
        credentials = ServicePrincipalCredentials(
            client_id='xxx',
            secret='xxx',
            tenant='xxx'
        )
        print("Service Principal Authenticity Verified")
        return ComputeManagementClient(credentials, subscription_id)
    except Exception as e:
        print(f"There was an error with the authenticating the Service Principal: {e}")
    except ClientAuthenticationError as e:
        print(f"Client Authentication Error {e}")

# Email setup
sender_email = 'xxx'  
receiver_email = 'xxxx' 
smtp_server = 'xxx'   
smtp_port = 587
smtp_password = 'xxx'  

def send_notification(subject, body):
    """Send an email notification."""
    msg = MIMEText(body)
    msg['Subject'] = subject
    msg['From'] = sender_email
    msg['To'] = receiver_email

    try:
        with smtplib.SMTP(smtp_server, smtp_port) as server:
            server.starttls()
            server.login(sender_email, smtp_password)
            server.sendmail(sender_email, receiver_email, msg.as_string())
            print("Email sent successfully!")
    except Exception as e:
        print(f"{e} in ", inspect.stack()[0].function)

def get_vm_status(compute_client):
    """Retrieve the current status of the VM."""
    print("Retrieving VM status......")
    try:
        # Explicitly request the instance view
        vm = compute_client.virtual_machines.get(resource_group_name, vm_name, expand='instanceView')
        
        # Check if instance view is available
        if vm.instance_view:
            print(f"Can you see this message? {inspect.stack()[0].function}")
            return vm.instance_view.statuses[1].code  # Assuming the status is at index 1
        else:
            print("Instance view is not available.")
            return None
        
    except ClientAuthenticationError as e:
        print(f"There was a client authentication error {e}")
    except Exception as e:
        print(f"Error retrieving VM status: {e}, in function {inspect.stack()[0].function}")

def generate_incident_reference_number():
    incident_timestamp = datetime.utcnow().strftime("%Y%m%d%H%M%S")
    incident_number = ''.join(random.choices(string.ascii_uppercase + string.digits, k=10))
    return f"INC{incident_timestamp}{incident_number}"

def log_to_azure_monitor():
    pass

def main():
    print("Entering loop.....",flush=True)
    while True:
        compute_client = service_principal_authentication()
        previous_vm_status = None
        current_vm_status = get_vm_status(compute_client)

        if current_vm_status != previous_vm_status:
            incident_number = generate_incident_reference_number()
            print(f"This has changed {current_vm_status} - {previous_vm_status} - {incident_number}", flush=True)
        else:
            print(f"This has remained the same {current_vm_status}", flush=True)
        previous_vm_status = current_vm_status
        time.sleep(10)

if __name__ == "__main__":
    main()

Solution

  • As mentioned, Azure Runbooks have output buffering, and this can sometimes prevent the immediate display of print statements within loops.

    Neither is printing. Code updated to your suggestion so you can see where I have placed the additional print statements.

    The script may be blocking before reaching the get_vm_status function or while executing the function itself.

    Start by adding a print statement right before calling get_vm_status to check if it's being called at all. Then, in the get_vm_status function, place prints just before each major operation to track its execution.

    Updated code:

    import time
    import random
    import smtplib
    import inspect
    import string
    from datetime import datetime
    from email.mime.text import MIMEText
    from azure.common.credentials import ServicePrincipalCredentials
    from azure.mgmt.compute import ComputeManagementClient
    from azure.core.exceptions import ClientAuthenticationError
    
    # Azure setup
    subscription_id = 'xxx'  
    resource_group_name = 'xxx'  
    vm_name = 'xxx'  
    
    # Authentication of Azure's Service Principal
    def service_principal_authentication():
        """Checks the Authentication of Azure's Service Principal """
        print("Authenticating Service Principal....")
        try:
            credentials = ServicePrincipalCredentials(
                client_id='xxx',
                secret='xxx',
                tenant='xxx'
            )
            print("Service Principal Authenticity Verified")
            return ComputeManagementClient(credentials, subscription_id)
        except Exception as e:
            print(f"There was an error with the authenticating the Service Principal: {e}")
        except ClientAuthenticationError as e:
            print(f"Client Authentication Error {e}")
    
    # Email setup
    sender_email = 'xxx'  
    receiver_email = 'xxxx' 
    smtp_server = 'xxx'   
    smtp_port = 587
    smtp_password = 'xxx'  
    
    def send_notification(subject, body):
        """Send an email notification."""
        msg = MIMEText(body)
        msg['Subject'] = subject
        msg['From'] = sender_email
        msg['To'] = receiver_email
    
        try:
            with smtplib.SMTP(smtp_server, smtp_port) as server:
                server.starttls()
                server.login(sender_email, smtp_password)
                server.sendmail(sender_email, receiver_email, msg.as_string())
                print("Email sent successfully!")
        except Exception as e:
            print(f"{e} in ", inspect.stack()[0].function)
    
    def get_vm_status(compute_client):
        """Retrieve the current status of the VM."""
        print("Entering get_vm_status function.....", flush=True)
        try:
            print("Retrieving VM status......", flush=True)
            # Explicitly request the instance view
            vm = compute_client.virtual_machines.get(resource_group_name, vm_name, expand='instanceView')
            
            # Check if instance view is available
            if vm.instance_view:
                print(f"Can you see this message? {inspect.stack()[0].function}", flush=True)
                return vm.instance_view.statuses[1].code  # Assuming the status is at index 1
            else:
                print("Instance view is not available.", flush=True)
                return None
            
        except ClientAuthenticationError as e:
            print(f"There was a client authentication error {e}", flush=True)
        except Exception as e:
            print(f"Error retrieving VM status: {e}, in function {inspect.stack()[0].function}", flush=True)
    
    def generate_incident_reference_number():
        incident_timestamp = datetime.utcnow().strftime("%Y%m%d%H%M%S")
        incident_number = ''.join(random.choices(string.ascii_uppercase + string.digits, k=10))
        return f"INC{incident_timestamp}{incident_number}"
    
    def log_to_azure_monitor():
        pass
    
    def main():
        print("Entering loop.....", flush=True)
        while True:
            print("Before calling get_vm_status....", flush=True)  # Debug print before calling the function
            compute_client = service_principal_authentication()
            previous_vm_status = None
            current_vm_status = get_vm_status(compute_client)
    
            if current_vm_status != previous_vm_status:
                incident_number = generate_incident_reference_number()
                print(f"This has changed {current_vm_status} - {previous_vm_status} - {incident_number}", flush=True)
            else:
                print(f"This has remained the same {current_vm_status}", flush=True)
            previous_vm_status = current_vm_status
            time.sleep(10)
    
    if __name__ == "__main__":
        main()