AWS Glue Python使用watchtower写入CloudWatch日志丢失问题
问题根因
你写的代码有4个典型的Glue+watchtower适配坑,直接导致日志丢失或者根本没发出去:
- 权限不匹配:Glue作业绑定的默认IAM角色,仅允许向
/aws-glue/python-job日志组下、名称包含当前作业运行ID的日志流写入内容,你硬编码my_stream_name作为流名,会直接被CloudWatch拒绝写入,且watchtower默认不会把自身的SDK报错打印出来,表面看没有任何报错,实际日志根本没发成功。 - 缓冲机制丢日志:watchtower默认开启异步队列+批量攒包的推送逻辑,要攒够指定大小/等待固定间隔才会把日志发到CloudWatch。Glue Python作业执行速度快,代码跑完进程直接被回收,缓冲区里的日志根本没来得及推送就全丢了。
- 日志层级冲突:Glue启动时会提前初始化根日志器,你直接
getLogger(__name__)拿到的实例继承了根日志器的默认配置,你后加的CloudWatch handler会被传播规则拦截,根本不会触发。另外你直接传dict对象作为日志内容,watchtower默认没有开启JSON序列化,遇到非字符串内容会直接抛序列化错误被吞掉。 - 区域配置缺失:watchtower初始化boto3客户端时默认读取系统默认区域配置,如果你没有显式指定Glue作业运行的区域,客户端可能连到其他区域的CloudWatch端点,日志自然写到错误的地方,你在当前区域找不到。
修复方案
直接替换成下面的可运行代码,同时做对应配置即可:
import logging import sys import watchtower def initialize_log() -> logging.Logger: logger = logging.getLogger(__name__) # 清空Glue预初始化的冗余handler,避免冲突 logger.handlers.clear() logger.setLevel(logging.INFO) # 关闭日志向上层传播,防止重复打印/丢日志 logger.propagate = False log_format = "[%(name)s] %(asctime)s %(levelname)-8s %(message)s" date_format = "%a, %d %b %Y %H:%M:%S %Z" formatter = logging.Formatter(log_format, datefmt=date_format) # 标准输出handler,保证Glue作业运行控制台能实时看到日志 stream_handler = logging.StreamHandler(sys.stdout) stream_handler.setFormatter(formatter) logger.addHandler(stream_handler) # 初始化CloudWatch日志handler try: # 从Glue启动参数里拿当前作业运行ID,拼到日志流名里,适配默认权限规则 job_run_id = "" if "--JOB_RUN_ID" in sys.argv: job_run_id = sys.argv[sys.argv.index("--JOB_RUN_ID") + 1] stream_name = f"custom_log_{job_run_id}" if job_run_id else "local_test" cw_handler = watchtower.CloudWatchLogHandler( log_group='/aws-glue/python-job', stream_name=stream_name, use_queues=False, # 关闭异步队列,打一条推一条,实现实时推送同时避免进程退出丢日志 create_log_group=False, # 复用Glue已经创建好的默认日志组,避免建组权限报错 boto3_client_kwargs={"region_name": "ap-east-1"} # 替换成你自己Glue作业部署的区域 ) cw_handler.setFormatter(formatter) logger.addHandler(cw_handler) except Exception as e: logger.warning(f"CloudWatch日志组件初始化失败,仅输出到控制台: {str(e)}") return logger def log(logger, message): logger.info(message) # 字典转字符串再打印,避免序列化报错 logger.info(str(dict(foo="bar", details={})))
必须做的前置配置
- 检查Glue作业绑定的IAM角色权限,确保包含
logs:CreateLogStream、logs:PutLogEvents两个权限,资源范围配置为arn:aws-cn:logs:*:*:log-group:/aws-glue/python-job:*(如果是海外区把aws-cn改成aws即可)。 - 安装适配版本的依赖:在Glue作业的
--additional-python-modules参数里指定watchtower==2.0.1,不要用3.x以上的新版本,Glue内置的boto3版本偏旧,新版watchtower会有兼容问题。 - 查日志的时候选对Glue运行的区域,日志流找名称带对应作业运行ID、前缀是
custom_log_的即可,CloudWatch日志入库有10秒左右的延迟,作业刚跑完等一会再搜。
内容的提问来源于stack exchange,提问作者HouKaide
相关产品推荐
相关产品推荐

