There are more AWS SDK examples available in the AWS Doc SDK Examples
CloudWatch Logs examples using SDK for Python (Boto3)
The following code examples show you how to perform actions and implement common scenarios by using the AWS SDK for Python (Boto3) with CloudWatch Logs.
Actions are code excerpts from larger programs and must be run in context. While actions show you how to call individual service functions, you can see actions in context in their related scenarios.
Scenarios are code examples that show you how to accomplish specific tasks by calling multiple functions within a service or combined with other AWS services.
Each example includes a link to the complete source code, where you can find instructions on how to set up and run the code in context.
Get started
The following code example shows how to get started using CloudWatch Logs.
- SDK for Python (Boto3)
-
Note
There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository
. def hello_cloudwatch_logs() -> None: """ Verifies connectivity to CloudWatch Logs by calling DescribeLogGroups and displaying the first page of log groups with name, ARN, and creation time. """ logs_client = boto3.client("logs") try: response = logs_client.describe_log_groups() log_groups = response.get("logGroups", list()) if not log_groups: print("No log groups found in this Region for your account.") else: print(f"Found {len(log_groups)} log group(s):\n") for lg in log_groups: name = lg.get("logGroupName", "N/A") arn = lg.get("arn", "N/A") creation_ms = lg.get("creationTime", 0) creation_dt = datetime.fromtimestamp( creation_ms / 1000, tz=timezone.utc ) print(f" Name: {name}") print(f" ARN: {arn}") print(f" Created: {creation_dt.strftime('%Y-%m-%d %H:%M:%S UTC')}") print() except ClientError as error: logger.error( "Failed to describe log groups: %s", error.response["Error"]["Message"], ) raise-
For API details, see DescribeLogGroups in AWS SDK for Python (Boto3) API Reference.
-
Actions
The following code example shows how to use DeleteSyslogConfiguration.
- SDK for Python (Boto3)
-
Note
There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository
. def delete_syslog_configuration( self, log_group_identifier: str, vpc_endpoint_id: str ) -> None: """ Deletes a syslog configuration for a log group. After deletion, syslog data is no longer ingested through the specified VPC endpoint. :param log_group_identifier: The name or ARN of the log group. :param vpc_endpoint_id: The ID of the VPC endpoint associated with the syslog configuration. :raises ClientError: If the syslog configuration does not exist (in which case the error is logged but not re-raised during cleanup). """ try: self.logs_client.delete_syslog_configuration( logGroupIdentifier=log_group_identifier, vpcEndpointId=vpc_endpoint_id, ) logger.info( "Deleted syslog configuration for log group '%s' and VPC endpoint '%s'.", log_group_identifier, vpc_endpoint_id, ) except ClientError as error: if error.response["Error"]["Code"] == "ResourceNotFoundException": logger.info( "Syslog configuration does not exist or was already deleted: %s", error.response["Error"]["Message"], ) else: logger.error( "Failed to delete syslog configuration: %s", error.response["Error"]["Message"], ) raise-
For API details, see DeleteSyslogConfiguration in AWS SDK for Python (Boto3) API Reference.
-
The following code example shows how to use GetQueryResults.
- SDK for Python (Boto3)
-
Note
There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository
. def _wait_for_query_results(self, client, query_id): """ Waits for the query to complete and retrieves the results. :param query_id: The ID of the initiated query. :type query_id: str :return: A list containing the results of the query. :rtype: list """ while True: time.sleep(1) results = client.get_query_results(queryId=query_id) if results["status"] in [ "Complete", "Failed", "Cancelled", "Timeout", "Unknown", ]: return results.get("results", [])-
For API details, see GetQueryResults in AWS SDK for Python (Boto3) API Reference.
-
The following code example shows how to use ListSyslogConfigurations.
- SDK for Python (Boto3)
-
Note
There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository
. def list_syslog_configurations( self, log_group_identifier: Optional[str] = None, vpc_endpoint_id: Optional[str] = None, ) -> list[dict[str, Any]]: """ Lists syslog configurations, optionally filtered by log group or VPC endpoint. Handles pagination by following nextToken until all results are returned. :param log_group_identifier: Optional log group name or ARN to filter by. :param vpc_endpoint_id: Optional VPC endpoint ID to filter by. :return: A list of syslog configuration dictionaries. :raises ClientError: If a filter parameter is invalid. """ try: configurations = list() params = dict() if log_group_identifier is not None: params["logGroupIdentifier"] = log_group_identifier if vpc_endpoint_id is not None: params["vpcEndpointId"] = vpc_endpoint_id while True: response = self.logs_client.list_syslog_configurations(**params) configurations.extend(response.get("syslogConfigurations", list())) next_token = response.get("nextToken", None) if next_token is None: break params["nextToken"] = next_token logger.info("Listed %d syslog configuration(s).", len(configurations)) return configurations except ClientError as error: if error.response["Error"]["Code"] == "InvalidParameterException": logger.error( "Invalid filter parameter: %s. Ensure the log group identifier " "or VPC endpoint ID is correctly formatted.", error.response["Error"]["Message"], ) else: logger.error( "Failed to list syslog configurations: %s", error.response["Error"]["Message"], ) raise-
For API details, see ListSyslogConfigurations in AWS SDK for Python (Boto3) API Reference.
-
The following code example shows how to use PutResourcePolicy.
- SDK for Python (Boto3)
-
Note
There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository
. def put_resource_policy( self, log_group_arn: str, vpc_endpoint_id: str, ) -> dict[str, Any]: """ Creates or updates a resource-scoped policy on a log group that grants the syslog.logs.amazonaws.com service principal permission to call logs:PutLogEvents and logs:CreateLogStream. A resource-scoped policy (one attached to a specific log group) is specified with resourceArn and policyDocument. The PutResourcePolicy API does not allow policyName to be combined with resourceArn. :param log_group_arn: The ARN of the log group (with trailing :*). :param vpc_endpoint_id: The VPC endpoint ID to scope the condition. :return: The resource policy response. """ policy_document = json.dumps( { "Version":"2012-10-17", "Statement": [ { "Sid": "SyslogIngestPermissions", "Effect": "Allow", "Principal": {"Service": "syslog.logs.amazonaws.com"}, "Action": [ "logs:PutLogEvents", "logs:CreateLogStream", ], "Resource": log_group_arn, "Condition": { "StringEquals": {"aws:sourceVpce": vpc_endpoint_id} }, } ], } ) try: response = self.logs_client.put_resource_policy( policyDocument=policy_document, resourceArn=log_group_arn, ) logger.info("Put resource policy on log group '%s'.", log_group_arn) return response except ClientError as error: logger.error( "Failed to put resource policy: %s", error.response["Error"]["Message"], ) raise-
For API details, see PutResourcePolicy in AWS SDK for Python (Boto3) API Reference.
-
The following code example shows how to use PutSyslogConfiguration.
- SDK for Python (Boto3)
-
Note
There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository
. def put_syslog_configuration( self, log_group_identifier: str, vpc_endpoint_id: str ) -> None: """ Creates or updates a syslog configuration for a log group. This enables ingestion of syslog data through the specified VPC endpoint. :param log_group_identifier: The name or ARN of the log group to associate with the syslog configuration. :param vpc_endpoint_id: The ID of the VPC endpoint to use for syslog ingestion. :raises ClientError: If the log group or VPC endpoint does not exist. """ try: self.logs_client.put_syslog_configuration( logGroupIdentifier=log_group_identifier, vpcEndpointId=vpc_endpoint_id, ) logger.info( "Created syslog configuration for log group '%s' with VPC endpoint '%s'.", log_group_identifier, vpc_endpoint_id, ) except ClientError as error: if error.response["Error"]["Code"] == "ResourceNotFoundException": logger.error( "The specified log group or VPC endpoint does not exist: %s", error.response["Error"]["Message"], ) else: logger.error( "Failed to put syslog configuration: %s", error.response["Error"]["Message"], ) raise-
For API details, see PutSyslogConfiguration in AWS SDK for Python (Boto3) API Reference.
-
The following code example shows how to use StartLiveTail.
- SDK for Python (Boto3)
-
Include the required files.
import boto3 import time from datetime import datetimeStart the Live Tail session.
# Initialize the client client = boto3.client('logs') start_time = time.time() try: response = client.start_live_tail( logGroupIdentifiers=log_group_identifiers, logStreamNames=log_streams, logEventFilterPattern=filter_pattern ) event_stream = response['responseStream'] # Handle the events streamed back in the response for event in event_stream: # Set a timeout to close the stream. # This will end the Live Tail session. if (time.time() - start_time >= 10): event_stream.close() break # Handle when session is started if 'sessionStart' in event: session_start_event = event['sessionStart'] print(session_start_event) # Handle when log event is given in a session update elif 'sessionUpdate' in event: log_events = event['sessionUpdate']['sessionResults'] for log_event in log_events: print('[{date}] {log}'.format(date=datetime.fromtimestamp(log_event['timestamp']/1000),log=log_event['message'])) else: # On-stream exceptions are captured here raise RuntimeError(str(event)) except Exception as e: print(e)-
For API details, see StartLiveTail in AWS SDK for Python (Boto3) API Reference.
-
The following code example shows how to use StartQuery.
- SDK for Python (Boto3)
-
Note
There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository
. def perform_query(self, date_range): """ Performs the actual CloudWatch log query. :param date_range: A tuple representing the start and end datetime for the query. :type date_range: tuple :return: A list containing the query results. :rtype: list """ client = boto3.client("logs") try: try: start_time = round( self.date_utilities.convert_iso8601_to_unix_timestamp(date_range[0]) ) end_time = round( self.date_utilities.convert_iso8601_to_unix_timestamp(date_range[1]) ) response = client.start_query( logGroupName=self.log_group, startTime=start_time, endTime=end_time, queryString=self.query_string, limit=self.limit, ) query_id = response["queryId"] except client.exceptions.ResourceNotFoundException as e: raise DateOutOfBoundsError(f"Resource not found: {e}") while True: time.sleep(1) results = client.get_query_results(queryId=query_id) if results["status"] in [ "Complete", "Failed", "Cancelled", "Timeout", "Unknown", ]: return results.get("results", []) except DateOutOfBoundsError: return [] def _initiate_query(self, client, date_range, max_logs): """ Initiates the CloudWatch logs query. :param date_range: A tuple representing the start and end datetime for the query. :type date_range: tuple :param max_logs: The maximum number of logs to retrieve. :type max_logs: int :return: The query ID as a string. :rtype: str """ try: start_time = round( self.date_utilities.convert_iso8601_to_unix_timestamp(date_range[0]) ) end_time = round( self.date_utilities.convert_iso8601_to_unix_timestamp(date_range[1]) ) response = client.start_query( logGroupName=self.log_group, startTime=start_time, endTime=end_time, queryString=self.query_string, limit=max_logs, ) return response["queryId"] except client.exceptions.ResourceNotFoundException as e: raise DateOutOfBoundsError(f"Resource not found: {e}")-
For API details, see StartQuery in AWS SDK for Python (Boto3) API Reference.
-
Scenarios
The following code example shows how to:
Deploy a CloudFormation stack that provisions a syslog VPC endpoint.
Create a log group and add a resource policy for the syslog service.
Create a syslog configuration with PutSyslogConfiguration.
List syslog configurations with and without filters.
Clean up by deleting the syslog configuration, log group, and stack.
- SDK for Python (Boto3)
-
Note
There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository
. Run an interactive scenario at a command prompt.
class SyslogIngestionScenario: """Interactive scenario demonstrating CloudWatch Logs syslog ingestion.""" STACK_NAME = "syslog-demo-stack" def __init__( self, cloudwatch_logs_wrapper: CloudWatchLogsWrapper, cf_client: boto3.client, ) -> None: """ :param cloudwatch_logs_wrapper: A CloudWatchLogsWrapper instance. :param cf_client: A Boto3 CloudFormation client. """ self.wrapper = cloudwatch_logs_wrapper self.cf_client = cf_client self.vpc_endpoint_id: Optional[str] = None self.log_group_name: Optional[str] = None self.log_group_arn: Optional[str] = None self.stack_deployed = False # ------------------------------------------------------------------ # Setup phase # ------------------------------------------------------------------ def deploy_cfn_stack(self) -> str: """ Deploys the CloudFormation stack that provisions the VPC endpoint prerequisite. Waits for CREATE_COMPLETE and returns the VpcEndpointId output. :return: The VPC endpoint ID. """ print("\n" + "=" * 68) print("Step 1: Deploy the CloudFormation prerequisite stack") print("=" * 68) print( f"\nDeploying stack '{self.STACK_NAME}' to create a VPC, subnet, " "security group, and syslog VPC endpoint.\n" "This can take several minutes..." ) try: self.cf_client.create_stack( StackName=self.STACK_NAME, TemplateBody=_load_cfn_template(), Capabilities=["CAPABILITY_IAM"], ) # Only this run's own stack should be torn down in cleanup. Mark it # as soon as creation is initiated so a stack that fails partway # (e.g. ROLLBACK) is still cleaned up. self.stack_deployed = True except ClientError as error: if error.response["Error"]["Code"] == "AlreadyExistsException": # The stack pre-existed this run. Reuse it but do NOT mark it # for cleanup — deleting a stack we did not create could destroy # infrastructure the user set up themselves. print( f"Stack '{self.STACK_NAME}' already exists. Reusing it; it " "will not be deleted during cleanup." ) else: raise waiter = self.cf_client.get_waiter("stack_create_complete") try: waiter.wait( StackName=self.STACK_NAME, WaiterConfig={"Delay": 30, "MaxAttempts": 40}, ) except WaiterError: # The waiter fails both when the stack genuinely failed # (CREATE_FAILED, ROLLBACK_COMPLETE, ...) and, harmlessly, when the # stack already existed in a completed state before this run. # Inspect the actual status so a real failure surfaces clearly # instead of as a confusing "missing outputs" error later. status = self.cf_client.describe_stacks(StackName=self.STACK_NAME)[ "Stacks" ][0]["StackStatus"] if status not in ("CREATE_COMPLETE", "UPDATE_COMPLETE"): raise RuntimeError( f"CloudFormation stack '{self.STACK_NAME}' did not reach a " f"successful state (status: {status}). Check the stack events " "in the CloudFormation console for the failure reason." ) response = self.cf_client.describe_stacks(StackName=self.STACK_NAME) outputs = response["Stacks"][0].get("Outputs", list()) vpc_endpoint_id = None for output in outputs: if output["OutputKey"] == "VpcEndpointId": vpc_endpoint_id = output["OutputValue"] break if vpc_endpoint_id is None: raise RuntimeError( "VpcEndpointId not found in stack outputs. " "Check the CloudFormation stack for errors." ) self.vpc_endpoint_id = vpc_endpoint_id print(f"\nStack deployed. VPC Endpoint ID: {vpc_endpoint_id}") return vpc_endpoint_id def create_and_verify_log_group(self) -> None: """ Prompts the user for a log group name, creates it, and verifies it with DescribeLogGroups. """ print("\n" + "=" * 68) print("Step 2: Create and verify a log group") print("=" * 68) name_pattern = re.compile(r"^[a-zA-Z0-9_\-/.#]{1,512}$") while True: log_group_name = q.ask( "\nEnter a name for the syslog log group (e.g. /syslog/demo): " ) if name_pattern.match(log_group_name): break print("Invalid name. Use 1-512 characters: a-z, A-Z, 0-9, " "_, -, /, ., #") self.log_group_name = log_group_name self.wrapper.create_log_group(log_group_name) log_groups = self.wrapper.describe_log_groups( log_group_name_prefix=log_group_name ) for lg in log_groups: if lg.get("logGroupName") == log_group_name: creation_ms = lg.get("creationTime", 0) creation_dt = datetime.fromtimestamp( creation_ms / 1000, tz=timezone.utc ) self.log_group_arn = lg.get("logGroupArn", "") print(f"\nLog group verified:") print(f" Name: {lg['logGroupName']}") print(f" ARN: {lg.get('arn', 'N/A')}") print(f" Created: {creation_dt.strftime('%Y-%m-%d %H:%M:%S UTC')}") return print(f"\nWARNING: Log group '{log_group_name}' not found in describe results.") def add_resource_policy(self) -> None: """ Adds a resource policy to the log group so the syslog service can write to it. """ print("\n" + "=" * 68) print("Step 3: Add a resource policy for syslog ingestion") print("=" * 68) if self.log_group_arn is None or self.vpc_endpoint_id is None: print( "WARNING: Cannot add resource policy - missing log group ARN or VPC endpoint." ) return self.wrapper.put_resource_policy( log_group_arn=self.log_group_arn, vpc_endpoint_id=self.vpc_endpoint_id, ) print( "\nResource policy created. The " "syslog.logs.amazonaws.com service principal can now call " "logs:PutLogEvents and logs:CreateLogStream on the log group." ) # ------------------------------------------------------------------ # Create syslog configuration phase # ------------------------------------------------------------------ def create_syslog_configuration(self) -> None: """ Creates a syslog configuration that associates the VPC endpoint with the log group. """ print("\n" + "=" * 68) print("Step 4: Create the syslog configuration") print("=" * 68) print( "\nAssociating the VPC endpoint with the log group so that syslog " "messages arriving at the endpoint are stored in the log group." ) print( "CloudWatch Logs parses common syslog formats (RFC 5424, " "RFC 3164, Cisco FTD/ASA) and extracts fields such as " "facility, severity, hostname, and appName.\n" ) self.wrapper.put_syslog_configuration( log_group_identifier=self.log_group_name, vpc_endpoint_id=self.vpc_endpoint_id, ) print( f"\nSyslog ingestion is now enabled for log group " f"'{self.log_group_name}' through VPC endpoint " f"'{self.vpc_endpoint_id}'." ) print("\nSupported transport options:") print(" - TCP with TLS on port 6514 (recommended)") print(" - Plaintext TCP on port 1514") print(" - UDP on port 514") # ------------------------------------------------------------------ # List syslog configurations phase # ------------------------------------------------------------------ def _display_configurations(self, configs: list) -> None: """Helper to display a list of syslog configurations.""" if not configs: print(" (no configurations found)") return for cfg in configs: created_ms = cfg.get("createdAt", 0) created_dt = datetime.fromtimestamp(created_ms / 1000, tz=timezone.utc) print(f" Log Group ARN: {cfg.get('logGroupArn', 'N/A')}") print(f" VPC Endpoint ID: {cfg.get('vpcEndpointId', 'N/A')}") print(f" Source Type: {cfg.get('sourceType', 'N/A')}") print(f" Created At: {created_dt.strftime('%Y-%m-%d %H:%M:%S UTC')}") print() def list_all_configurations(self) -> None: """Lists all syslog configurations in the account (no filter).""" print("\n" + "=" * 68) print("Step 5a: List all syslog configurations (no filter)") print("=" * 68) configs = self.wrapper.list_syslog_configurations() print(f"\nAll syslog configurations ({len(configs)} found):\n") self._display_configurations(configs) def list_configurations_by_log_group(self) -> None: """Lists syslog configurations filtered by the scenario's log group.""" print("\n" + "=" * 68) print("Step 5b: List configurations filtered by log group") print("=" * 68) configs = self.wrapper.list_syslog_configurations( log_group_identifier=self.log_group_name, ) print( f"\nConfigurations for log group '{self.log_group_name}' " f"({len(configs)} found):\n" ) self._display_configurations(configs) def list_configurations_by_vpc_endpoint(self) -> None: """Lists syslog configurations filtered by VPC endpoint.""" print("\n" + "=" * 68) print("Step 5c: List configurations filtered by VPC endpoint") print("=" * 68) configs = self.wrapper.list_syslog_configurations( vpc_endpoint_id=self.vpc_endpoint_id, ) print( f"\nConfigurations for VPC endpoint '{self.vpc_endpoint_id}' " f"({len(configs)} found):\n" ) self._display_configurations(configs) # ------------------------------------------------------------------ # Cleanup phase # ------------------------------------------------------------------ def cleanup(self, prompt: bool = True) -> None: """ Cleans up the resources created during the scenario. Tolerates resources that were never created or are already gone, so it is safe to call after a partial failure. :param prompt: When True (the normal end-of-scenario path), ask the user to confirm before deleting. When False (best-effort cleanup after an error), skip the prompt and delete whatever was created. """ print("\n" + "=" * 68) print("Cleanup") print("=" * 68) if prompt: do_cleanup = q.ask( "\nDo you want to delete all resources created during this " "scenario? (y/n) ", q.is_yesno, ) # q.is_yesno converts the answer to a bool (True when the user # answered 'y'); check it directly rather than calling string methods. if not do_cleanup: print("Skipping cleanup. Resources remain in your account.") return else: print( "\nAn error occurred; attempting best-effort cleanup of any " "resources that were created..." ) # Delete syslog configuration if self.log_group_name and self.vpc_endpoint_id: print( f"\nDeleting syslog configuration for log group " f"'{self.log_group_name}'..." ) self.wrapper.delete_syslog_configuration( log_group_identifier=self.log_group_name, vpc_endpoint_id=self.vpc_endpoint_id, ) print( " After deletion, syslog data is no longer ingested through " "the VPC endpoint into this log group." ) # Verify deletion configs = self.wrapper.list_syslog_configurations( log_group_identifier=self.log_group_name, ) if not configs: print(" Verified: configuration has been removed.") else: print( f" WARNING: Still found {len(configs)} configuration(s). " "They may take a moment to be fully removed." ) # Delete log group if self.log_group_name: print(f"\nDeleting log group '{self.log_group_name}'...") self.wrapper.delete_log_group(self.log_group_name) print( " The log group and all archived log events have been " "permanently deleted." ) # Delete CloudFormation stack if self.stack_deployed: print(f"\nDeleting CloudFormation stack '{self.STACK_NAME}'...") try: self.cf_client.delete_stack(StackName=self.STACK_NAME) waiter = self.cf_client.get_waiter("stack_delete_complete") waiter.wait( StackName=self.STACK_NAME, WaiterConfig={"Delay": 30, "MaxAttempts": 40}, ) print(" Stack deleted successfully.") except (ClientError, WaiterError) as error: logger.warning("Stack deletion issue: %s", error) print( f" WARNING: Stack deletion may still be in progress. " f"Check the CloudFormation console." ) # ------------------------------------------------------------------ # Run the full scenario # ------------------------------------------------------------------ def run_scenario(self) -> None: """Runs all phases of the syslog ingestion scenario.""" print("\n" + "=" * 68) print("CloudWatch Logs Syslog Ingestion Scenario") print("=" * 68) print( "\nThis scenario demonstrates CloudWatch Logs managed syslog " "ingestion, which lets you route syslog data from firewalls, " "routers, switches, and Linux servers directly into CloudWatch " "Logs through a VPC endpoint - without running a separate " "collection tier.\n" ) succeeded = False try: # Setup self.deploy_cfn_stack() self.create_and_verify_log_group() self.add_resource_policy() # Create syslog configuration self.create_syslog_configuration() q.ask("\nPress Enter to continue to listing configurations...") # List syslog configurations self.list_all_configurations() self.list_configurations_by_log_group() self.list_configurations_by_vpc_endpoint() succeeded = True # On success, prompt before cleaning up. self.cleanup(prompt=True) finally: # On failure, run best-effort cleanup without prompting so that # partially-created resources are not left behind. if not succeeded: self.cleanup(prompt=False) print("\n" + "=" * 68) print("Scenario complete!") print("=" * 68) print( "\nYou have successfully demonstrated CloudWatch Logs managed " "syslog ingestion:\n" " - Deployed a CloudFormation stack with a syslog VPC endpoint.\n" " - Created a log group and authorized the syslog service.\n" " - Associated a syslog configuration with PutSyslogConfiguration.\n" " - Listed configurations with and without filters.\n" " - Cleaned up all resources.\n" )Create a class that wraps CloudWatch Logs operations.
class CloudWatchLogsWrapper: """Encapsulates Amazon CloudWatch Logs actions.""" def __init__(self, logs_client: boto3.client) -> None: """ Initializes the CloudWatchLogsWrapper with a CloudWatch Logs client. :param logs_client: A Boto3 CloudWatch Logs client. """ self.logs_client = logs_client @classmethod def from_client(cls) -> "CloudWatchLogsWrapper": """ Creates a CloudWatchLogsWrapper instance with a default CloudWatch Logs client. :return: An instance of CloudWatchLogsWrapper. """ logs_client = boto3.client("logs") return cls(logs_client)-
For API details, see the following topics in AWS SDK for Python (Boto3) API Reference.
-
The following code example shows how to use CloudWatch Logs to query more than 10,000 records.
- SDK for Python (Boto3)
-
Note
There's more on GitHub. Find the complete example and learn how to set up and run in the AWS Code Examples Repository
. This file invokes an example module for managing CloudWatch queries exceeding 10,000 results.
import logging import os import sys import boto3 from botocore.config import Config from cloudwatch_query import CloudWatchQuery from date_utilities import DateUtilities # Configure logging at the module level. logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s", ) DEFAULT_QUERY_LOG_GROUP = "/workflows/cloudwatch-logs/large-query" class CloudWatchLogsQueryRunner: def __init__(self): """ Initializes the CloudWatchLogsQueryRunner class by setting up date utilities and creating a CloudWatch Logs client with retry configuration. """ self.date_utilities = DateUtilities() self.cloudwatch_logs_client = self.create_cloudwatch_logs_client() def create_cloudwatch_logs_client(self): """ Creates and returns a CloudWatch Logs client with a specified retry configuration. :return: A CloudWatch Logs client instance. :rtype: boto3.client """ try: return boto3.client("logs", config=Config(retries={"max_attempts": 10})) except Exception as e: logging.error(f"Failed to create CloudWatch Logs client: {e}") sys.exit(1) def fetch_environment_variables(self): """ Fetches and validates required environment variables for query start and end dates. Fetches the environment variable for log group, returning the default value if it does not exist. :return: Tuple of query start date and end date as integers and the log group. :rtype: tuple :raises SystemExit: If required environment variables are missing or invalid. """ try: query_start_date = int(os.environ["QUERY_START_DATE"]) query_end_date = int(os.environ["QUERY_END_DATE"]) except KeyError: logging.error( "Both QUERY_START_DATE and QUERY_END_DATE environment variables are required." ) sys.exit(1) except ValueError as e: logging.error(f"Error parsing date environment variables: {e}") sys.exit(1) try: log_group = os.environ["QUERY_LOG_GROUP"] except KeyError: logging.warning("No QUERY_LOG_GROUP environment variable, using default value") log_group = DEFAULT_QUERY_LOG_GROUP return query_start_date, query_end_date, log_group def convert_dates_to_iso8601(self, start_date, end_date): """ Converts UNIX timestamp dates to ISO 8601 format using DateUtilities. :param start_date: The start date in UNIX timestamp. :type start_date: int :param end_date: The end date in UNIX timestamp. :type end_date: int :return: Start and end dates in ISO 8601 format. :rtype: tuple """ start_date_iso8601 = self.date_utilities.convert_unix_timestamp_to_iso8601( start_date ) end_date_iso8601 = self.date_utilities.convert_unix_timestamp_to_iso8601( end_date ) return start_date_iso8601, end_date_iso8601 def execute_query( self, start_date_iso8601, end_date_iso8601, log_group="/workflows/cloudwatch-logs/large-query", query="fields @timestamp, @message | sort @timestamp asc" ): """ Creates a CloudWatchQuery instance and executes the query with provided date range. :param start_date_iso8601: The start date in ISO 8601 format. :type start_date_iso8601: str :param end_date_iso8601: The end date in ISO 8601 format. :type end_date_iso8601: str :param log_group: Log group to search: "/workflows/cloudwatch-logs/large-query" :type log_group: str :param query: Query string to pass to the CloudWatchQuery instance :type query: str """ cloudwatch_query = CloudWatchQuery( log_group=log_group, query_string=query ) cloudwatch_query.query_logs((start_date_iso8601, end_date_iso8601)) logging.info("Query executed successfully.") logging.info( f"Queries completed in {cloudwatch_query.query_duration} seconds. Total logs found: {len(cloudwatch_query.query_results)}" ) def main(): """ Main function to start a recursive CloudWatch logs query. Fetches required environment variables, converts dates, and executes the query. """ logging.info("Starting a recursive CloudWatch logs query...") runner = CloudWatchLogsQueryRunner() query_start_date, query_end_date, log_group = runner.fetch_environment_variables() start_date_iso8601 = DateUtilities.convert_unix_timestamp_to_iso8601( query_start_date ) end_date_iso8601 = DateUtilities.convert_unix_timestamp_to_iso8601(query_end_date) runner.execute_query(start_date_iso8601, end_date_iso8601, log_group=log_group) if __name__ == "__main__": main()This module processes CloudWatch queries exceeding 10,000 results.
import logging import time from datetime import datetime import threading import boto3 from date_utilities import DateUtilities DEFAULT_QUERY = "fields @timestamp, @message | sort @timestamp asc" DEFAULT_LOG_GROUP = "/workflows/cloudwatch-logs/large-query" class DateOutOfBoundsError(Exception): """Exception raised when the date range for a query is out of bounds.""" pass class CloudWatchQuery: """ A class to query AWS CloudWatch logs within a specified date range. :vartype date_range: tuple :ivar limit: Maximum number of log entries to return. :vartype limit: int :log_group str: Name of the log group to query :query_string str: query """ def __init__(self, log_group: str = DEFAULT_LOG_GROUP, query_string: str=DEFAULT_QUERY) -> None: self.lock = threading.Lock() self.log_group = log_group self.query_string = query_string self.query_results = [] self.query_duration = None self.datetime_format = "%Y-%m-%d %H:%M:%S.%f" self.date_utilities = DateUtilities() self.limit = 10000 def query_logs(self, date_range): """ Executes a CloudWatch logs query for a specified date range and calculates the execution time of the query. :return: A batch of logs retrieved from the CloudWatch logs query. :rtype: list """ start_time = datetime.now() start_date, end_date = self.date_utilities.normalize_date_range_format( date_range, from_format="unix_timestamp", to_format="datetime" ) logging.info( f"Original query:" f"\n START: {start_date}" f"\n END: {end_date}" f"\n LOG GROUP: {self.log_group}" ) self.recursive_query((start_date, end_date)) end_time = datetime.now() self.query_duration = (end_time - start_time).total_seconds() def recursive_query(self, date_range): """ Processes logs within a given date range, fetching batches of logs recursively if necessary. :param date_range: The date range to fetch logs for, specified as a tuple (start_timestamp, end_timestamp). :type date_range: tuple :return: None if the recursive fetching is continued or stops when the final batch of logs is processed. Although it doesn't explicitly return the query results, this method accumulates all fetched logs in the `self.query_results` attribute. :rtype: None """ batch_of_logs = self.perform_query(date_range) # Add the batch to the accumulated logs with self.lock: self.query_results.extend(batch_of_logs) if len(batch_of_logs) == self.limit: logging.info(f"Fetched {self.limit}, checking for more...") most_recent_log = self.find_most_recent_log(batch_of_logs) most_recent_log_timestamp = next( item["value"] for item in most_recent_log if item["field"] == "@timestamp" ) new_range = (most_recent_log_timestamp, date_range[1]) midpoint = self.date_utilities.find_middle_time(new_range) first_half_thread = threading.Thread( target=self.recursive_query, args=((most_recent_log_timestamp, midpoint),), ) second_half_thread = threading.Thread( target=self.recursive_query, args=((midpoint, date_range[1]),) ) first_half_thread.start() second_half_thread.start() first_half_thread.join() second_half_thread.join() def find_most_recent_log(self, logs): """ Search a list of log items and return most recent log entry. :param logs: A list of logs to analyze. :return: log :type :return List containing log item details """ most_recent_log = None most_recent_date = "1970-01-01 00:00:00.000" for log in logs: for item in log: if item["field"] == "@timestamp": logging.debug(f"Compared: {item['value']} to {most_recent_date}") if ( self.date_utilities.compare_dates( item["value"], most_recent_date ) == item["value"] ): logging.debug(f"New most recent: {item['value']}") most_recent_date = item["value"] most_recent_log = log logging.info(f"Most recent log date of batch: {most_recent_date}") return most_recent_log def perform_query(self, date_range): """ Performs the actual CloudWatch log query. :param date_range: A tuple representing the start and end datetime for the query. :type date_range: tuple :return: A list containing the query results. :rtype: list """ client = boto3.client("logs") try: try: start_time = round( self.date_utilities.convert_iso8601_to_unix_timestamp(date_range[0]) ) end_time = round( self.date_utilities.convert_iso8601_to_unix_timestamp(date_range[1]) ) response = client.start_query( logGroupName=self.log_group, startTime=start_time, endTime=end_time, queryString=self.query_string, limit=self.limit, ) query_id = response["queryId"] except client.exceptions.ResourceNotFoundException as e: raise DateOutOfBoundsError(f"Resource not found: {e}") while True: time.sleep(1) results = client.get_query_results(queryId=query_id) if results["status"] in [ "Complete", "Failed", "Cancelled", "Timeout", "Unknown", ]: return results.get("results", []) except DateOutOfBoundsError: return [] def _initiate_query(self, client, date_range, max_logs): """ Initiates the CloudWatch logs query. :param date_range: A tuple representing the start and end datetime for the query. :type date_range: tuple :param max_logs: The maximum number of logs to retrieve. :type max_logs: int :return: The query ID as a string. :rtype: str """ try: start_time = round( self.date_utilities.convert_iso8601_to_unix_timestamp(date_range[0]) ) end_time = round( self.date_utilities.convert_iso8601_to_unix_timestamp(date_range[1]) ) response = client.start_query( logGroupName=self.log_group, startTime=start_time, endTime=end_time, queryString=self.query_string, limit=max_logs, ) return response["queryId"] except client.exceptions.ResourceNotFoundException as e: raise DateOutOfBoundsError(f"Resource not found: {e}") def _wait_for_query_results(self, client, query_id): """ Waits for the query to complete and retrieves the results. :param query_id: The ID of the initiated query. :type query_id: str :return: A list containing the results of the query. :rtype: list """ while True: time.sleep(1) results = client.get_query_results(queryId=query_id) if results["status"] in [ "Complete", "Failed", "Cancelled", "Timeout", "Unknown", ]: return results.get("results", [])-
For API details, see the following topics in AWS SDK for Python (Boto3) API Reference.
-
The following code example shows how to create an AWS Lambda function invoked by an Amazon EventBridge scheduled event.
- SDK for Python (Boto3)
-
This example shows how to register an AWS Lambda function as the target of a scheduled Amazon EventBridge event. The Lambda handler writes a friendly message and the full event data to Amazon CloudWatch Logs for later retrieval.
Deploys a Lambda function.
Creates an EventBridge scheduled event and makes the Lambda function the target.
Grants permission to let EventBridge invoke the Lambda function.
Prints the latest data from CloudWatch Logs to show the result of the scheduled invocations.
Cleans up all resources created during the demo.
This example is best viewed on GitHub. For complete source code and instructions on how to set up and run, see the full example on GitHub
. Services used in this example
CloudWatch Logs
DynamoDB
EventBridge
Lambda
Amazon SNS