Watchtower (Python CloudWatch Logging)
raw JSON → 3.4.0 verified Tue May 12 auth: no python install: verified
Watchtower is a log handler for Amazon Web Services (AWS) CloudWatch Logs. It acts as a lightweight adapter between the Python `logging` system and CloudWatch Logs, using the `boto3` AWS SDK to aggregate logs into batches and send them to AWS. It is currently at version 3.4.0 and sees regular, although not strictly scheduled, releases with bug fixes and new features.
pip install watchtower Common errors
error ModuleNotFoundError: No module named 'watchtower' ↓
cause The 'watchtower' package is not installed in the current Python environment.
fix
pip install watchtower
error ImportError: cannot import name 'CloudWatchLogHandler' from 'watchtower' ↓
cause The class name for the CloudWatch log handler was changed from `CloudWatchLogHandler` to `WatchtowerLogHandler` in library version 0.7.0; this error occurs when using an outdated import statement.
fix
from watchtower import WatchtowerLogHandler
error botocore.exceptions.NoCredentialsError: Unable to locate credentials ↓
cause Boto3, used by Watchtower, cannot find AWS access keys or assume an IAM role, which are necessary to interact with AWS services.
fix
Configure AWS credentials using environment variables (e.g., AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY), the shared credentials file (~/.aws/credentials), or an attached IAM role for EC2/ECS/EKS/Lambda.
error botocore.exceptions.ClientError: An error occurred (AccessDeniedException) when calling the PutLogEvents operation: User is not authorized to perform: logs:PutLogEvents on resource ↓
cause The AWS IAM user or role assumed by Watchtower lacks the necessary permissions to write log events to the specified CloudWatch Logs group and stream.
fix
Grant the IAM entity (user/role) permissions for
logs:CreateLogGroup, logs:CreateLogStream, and logs:PutLogEvents on the relevant CloudWatch Logs resources. Warnings
breaking Starting with v3.0.0, non-JSON-serializable objects (e.g., `datetime` objects, custom classes) in log messages are now represented by their `repr()` string instead of being converted to `isoformat()` or `null`. This change might increase log data volume or alter parsing expectations. ↓
fix Review existing code that logs complex objects and adjust downstream log parsing or processing logic to account for `repr()` string representations. If the old behavior is desired, custom JSON serialization should be implemented before passing data to the logger.
deprecated The `datetime.utcnow()` method, previously used internally by Watchtower, has been replaced due to deprecation. While this primarily affects internal implementation, users with custom logging formats or direct manipulation of `datetime` objects might encounter related issues or are encouraged to update their own code to use timezone-aware `datetime.now(timezone.utc)` for consistency. ↓
fix Ensure your Python environment uses a version of `watchtower` that includes the fix. For your own code, replace `datetime.utcnow()` with `datetime.now(timezone.utc)`.
gotcha As of v3.0.1, Watchtower truncates log messages based on byte length (256 KB CloudWatch Logs limit) rather than Unicode character count. This can lead to unexpected truncation of messages containing multi-byte Unicode characters, potentially cutting off messages mid-character. ↓
fix Be aware of the byte-length limit for log messages. If logs frequently contain long Unicode strings, consider pre-truncating or encoding them to ensure meaningful parts are preserved within the byte limit before they reach Watchtower.
gotcha CloudWatch log stream naming conventions changed in v3.4.0, specifically removing ':' from `program_name` to prevent issues. Also, `strftime` format strings are explicitly noted as being required for certain configurations. Incorrect stream naming can lead to logs not appearing where expected or creating many unintended log streams. ↓
fix If customizing log stream names, ensure they comply with CloudWatch naming rules and the expected `strftime` format. For high-volume applications or those using process pools, ensure the log stream name is unique per source using template variables like `{machine_name}/{program_name}/{logger_name}/{process_id}`.
gotcha Watchtower uses `boto3`, which in turn relies on `botocore` and `urllib3`. These dependencies can produce a significant amount of `DEBUG` level log messages, which can overwhelm application logs if not properly filtered. ↓
fix To reduce noise, set the logging level for `boto3`, `botocore`, and `urllib3` to `WARNING` or higher. For example: `logging.getLogger('boto3').setLevel(logging.WARNING)`, `logging.getLogger('botocore').setLevel(logging.WARNING)`, `logging.getLogger('urllib3').setLevel(logging.WARNING)`.
gotcha The process running Watchtower requires appropriate AWS Identity and Access Management (IAM) permissions to call the CloudWatch Logs API (e.g., `logs:CreateLogGroup`, `logs:CreateLogStream`, `logs:PutLogEvents`). Lack of these permissions is a common reason for logs not appearing in CloudWatch. ↓
fix Attach an AWS managed IAM policy (e.g., `CloudWatchLogsFullAccess` for testing, or a more restrictive custom policy with `logs:CreateLogGroup`, `logs:CreateLogStream`, `logs:PutLogEvents`) to the IAM role or user credentials used by your application. Refer to `boto3` credentials documentation for how credentials are loaded.
breaking The `CloudWatchLogHandler` constructor received an unexpected keyword argument 'region_name'. This argument was introduced in `watchtower` v3.0.0. Using it with older versions (prior to v3.0.0) will result in a `TypeError`. ↓
fix Upgrade `watchtower` to version 3.0.0 or higher to use the `region_name` argument directly. If upgrading is not an option, configure the AWS region via a `boto3.session.Session` object and pass it using the `session` argument to `CloudWatchLogHandler` (e.g., `CloudWatchLogHandler(session=boto3.Session(region_name='your-region'))`), or ensure the AWS region is configured through environment variables or AWS config files which `boto3` will pick up automatically.
breaking Starting with Watchtower v3.0.0, the `CloudWatchLogHandler` no longer accepts `region_name` as a direct keyword argument in its constructor. The AWS region must now be configured through environment variables (e.g., `AWS_REGION`), `boto3` configuration, or by providing an initialized `boto3.Session` object via the `boto3_session` argument. ↓
fix Remove `region_name` from the `CloudWatchLogHandler` constructor. Ensure the AWS region is set via `AWS_REGION` environment variable or provide a `boto3.Session` configured with the desired region using the `boto3_session` argument. For example, `handler = CloudWatchLogHandler(log_group_name='my-group', boto3_session=boto3.Session(region_name='us-east-1'))` or simply rely on `AWS_REGION` environment variable if it's set.
Install compatibility verified last tested: 2026-05-12 v3.4.0 (up to date)
python os / libc status wheel install import disk mem side effects
3.10 alpine (musl) wheel - 0.74s 50.7M 14.3M clean
3.10 alpine (musl) - - 0.69s 50.5M 14.3M -
3.10 slim (glibc) wheel 3.9s 0.50s 51M 14.3M clean
3.10 slim (glibc) - - 0.54s 51M 14.3M -
3.11 alpine (musl) wheel - 0.85s 53.6M 17.0M clean
3.11 alpine (musl) - - 0.95s 53.4M 17.1M -
3.11 slim (glibc) wheel 3.6s 0.75s 54M 17.0M clean
3.11 slim (glibc) - - 0.73s 54M 17.0M -
3.12 alpine (musl) wheel - 0.80s 45.2M 16.0M clean
3.12 alpine (musl) - - 0.81s 45.1M 16.0M -
3.12 slim (glibc) wheel 3.3s 0.78s 46M 16.0M clean
3.12 slim (glibc) - - 0.78s 46M 16.0M -
3.13 alpine (musl) wheel - 0.77s 45.0M 17.0M clean
3.13 alpine (musl) - - 0.79s 44.7M 17.0M -
3.13 slim (glibc) wheel 2.9s 0.80s 45M 17.0M clean
3.13 slim (glibc) - - 0.84s 45M 17.0M -
3.9 alpine (musl) wheel - 0.53s 50.1M 13.6M clean
3.9 alpine (musl) - - 0.57s 50.0M 13.6M -
3.9 slim (glibc) wheel 4.6s 0.52s 51M 13.6M clean
3.9 slim (glibc) - - 0.48s 50M 13.6M -
Imports
- CloudWatchLogHandler
from watchtower import CloudWatchLogHandler - logging
import logging
Quickstart last tested: 2026-04-24
import logging
import os
from watchtower import CloudWatchLogHandler
# Configure basic logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Set AWS credentials via environment variables for boto3 (e.g., AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION)
# Or configure AWS CLI (aws configure) for boto3 to pick up credentials automatically
# Create a CloudWatchLogHandler instance
# Recommended: specify log_group_name and region_name explicitly
# boto3 will automatically pick up credentials from env vars or IAM roles.
handler = CloudWatchLogHandler(
log_group_name=os.environ.get('AWS_LOG_GROUP', 'my-python-app'),
region_name=os.environ.get('AWS_REGION', 'us-east-1')
)
# Add the handler to the logger
logger.addHandler(handler)
# Log some messages
logger.info('Hello from Watchtower!')
logger.warning('This is a warning message.')
logger.error(dict(error_code=500, message='Something went wrong'))
# For applications that might exit quickly, ensure logs are flushed
# In typical web applications or long-running services, this is handled on shutdown.
handler.flush()