···9191- Authenticate using the AT Protocol.
9292- Update the Bluesky avatar accordingly.
93939494-Execution logs will be displayed directly in the console.
9494+Execution logs will be displayed directly in the console, and a log file will be created in the `~/logs/bluesky-avatar-updater` directory. The log file will rotate when it reaches 10 MB, keeping up to 5 backup log files, each named with a timestamp for each execution.
95959696## Automating with Cron (Linux)
9797···1201202. Add the following line to run the script every hour at the top of the hour:
121121122122 ```bash
123123- 0 * * * * /path/to/your/venv/bin/python3 /path/to/bluesky-avatar-updater/src/main.py
123123+ 0 * * * * /path/to/your/.venv/bin/python3 /path/to/bluesky-avatar-updater/src/main.py
124124 ```
125125126126-Replace `/path/to/your/venv/bin/python3` with the path to your virtual environment's Python interpreter and `/path/to/bluesky-avatar-updater/src/main.py` with the full path to the `main.py` script.
126126+Replace `/path/to/your/.venv/bin/python3` with the path to your virtual environment's Python interpreter and `/path/to/bluesky-avatar-updater/src/main.py` with the full path to the `main.py` script.
127127128128## Troubleshooting
129129
+20-2
src/main.py
···99from atproto.exceptions import BadRequestError
1010import sys
1111from crontab import CronTab
1212+from logging.handlers import RotatingFileHandler
12131314# Ensure the script is run inside a virtual environment
1415if not hasattr(sys, 'real_prefix') and not (hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix):
···2425JSON_PATH = os.path.join(ASSETS_DIR, "cids.json")
2526SCRIPT_PATH = os.path.abspath(__file__)
26272727-# Configure basic console logging
2828+# Define the log file directory and log file path
2929+log_dir = os.path.expanduser("~/logs/bluesky-avatar-updater")
3030+if not os.path.exists(log_dir):
3131+ os.makedirs(log_dir)
3232+3333+# Log file with datetime stamp, but will be rotated
3434+log_file_path = os.path.join(log_dir, f"avatar_update_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log")
3535+3636+# Configure logging to both console and file with rotation
2837console_handler = logging.StreamHandler()
2938console_handler.setLevel(logging.INFO) # Show only INFO and higher levels on console
3939+4040+# Create a rotating file handler (max file size 10 MB, backup 5 old log files)
4141+file_handler = RotatingFileHandler(
4242+ log_file_path, maxBytes=10 * 1024 * 1024, backupCount=5
4343+)
4444+file_handler.setLevel(logging.INFO) # Save all logs to file
4545+3046formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
3147console_handler.setFormatter(formatter)
4848+file_handler.setFormatter(formatter)
32493350# Root logger setup
3451logger = logging.getLogger()
···3855for handler in logger.handlers[:]:
3956 logger.removeHandler(handler)
40574141-# Add the custom console handler
5858+# Add the custom handlers
4259logger.addHandler(console_handler)
6060+logger.addHandler(file_handler)
43614462# Suppress httpx logging (this stops httpx internal logs)
4563logging.getLogger("httpx").setLevel(logging.WARNING) # Suppress INFO and DEBUG logs from httpx