You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Cloud Run Jobs容器提前退出致日志丢失问题求助

Cloud Run Job 日志丢失问题修复方案

问题背景

查看Cloud Run Job特定任务日志时发现:多个任务显示Container called exit(0)(任务执行成功),但大量前置日志缺失,Log Explorer中也无法查看。推测容器未完成日志刷新就已退出。

用户尝试在脚本中添加日志刷新和延迟退出逻辑,但仅短运行任务能看到logger.info("Waiting for 5 seconds before exiting to allow logs to be flushed.")的日志,长任务仍存在日志丢失问题。相关代码片段如下:

任务执行脚本片段

try:
    logger.info("TESTS")
    task_params = get_task_params()
    run_task(TASK_INDEX, TASK_ATTEMPT, task_params)
    logger.info("Waiting for 5 seconds before exiting to allow logs to be flushed.")
    logging_config.flush_logs()
    time.sleep(5)  # Wait 5 seconds before exiting to allow logs to be flushed
except Exception as err:
    message = f"Task #{TASK_INDEX}, Attempt #{TASK_ATTEMPT} failed: {str(err)}"
    logger.info(message)
    sys.exit(1)  # Non-zero exit code for failure

日志配置文件config.py片段

def setup_logging(config):
    # Instantiates a client
    client = google.cloud.logging.Client()
    # Retrieves a Cloud Logging handler based on the environment
    # you're running in and integrates the handler with the
    # Python logging module. By default this captures all logs
    # at INFO level and higher
    client.setup_logging()

    if not os.path.exists(os.path.dirname(config.LOG_FILE)):
        os.makedirs(os.path.dirname(config.LOG_FILE))

    log_formatter = logging.Formatter(
        "%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]"
    )

    # Configure the file handler
    file_handler = RotatingFileHandler(
        config.LOG_FILE, maxBytes=1024 * 1024 * 10, backupCount=10
    )
    file_handler.setFormatter(log_formatter)
    file_handler.setLevel(getattr(logging, config.LOG_LEVEL.upper()))

    # Configure the stream handler
    stream_handler = logging.StreamHandler()
    stream_handler.setFormatter(log_formatter)
    stream_handler.setLevel(getattr(logging, config.LOG_LEVEL.upper()))

    # Set the log level and handlers for the logger
    logger = logging.getLogger()  # Define logger at the module level
    logger.setLevel(getattr(logging, config.LOG_LEVEL.upper()))
    logger.addHandler(file_handler)
    logger.addHandler(stream_handler)

    # Log the initialization
    logger.info("Logging is set up")


def flush_logs(config=configurations["default"]):
    logger = logging.getLogger()
    handlers = logger.handlers[:]
    for handler in handlers:
        handler.flush()
        handler.close()
        logger.removeHandler(handler)

    logger.info("All logging handlers have been flushed and closed.")

    logging.shutdown()


# Initialize logging with the desired configuration
setup_logging(configurations["default"])

修复方案

1. 强制Cloud Logging Handler使用同步模式

client.setup_logging()默认添加异步Handler,长任务中异步日志可能未完成上传容器就已退出。修改日志初始化逻辑,改用同步Handler:

def setup_logging(config):
    client = google.cloud.logging.Client()
    # 替换默认异步Handler为同步Handler
    handler = google.cloud.logging.handlers.CloudLoggingHandler(client)
    logging.getLogger().addHandler(handler)
    
    # 保留原有文件和流Handler配置(省略重复代码)

2. 优化日志刷新逻辑,避免提前移除Handler

当前flush_logs方法在刷新后直接关闭所有Handler,导致最后一条日志无法被处理。调整逻辑:

def flush_logs(config=configurations["default"]):
    logger = logging.getLogger()
    # 先记录刷新开始日志,再处理Handler
    logger.info("Starting to flush all logging handlers.")
    
    handlers = logger.handlers[:]
    for handler in handlers:
        handler.flush()
        # 仅关闭文件Handler,流和Cloud Logging Handler无需主动关闭
        if isinstance(handler, RotatingFileHandler):
            handler.close()
            logger.removeHandler(handler)
    
    logging.shutdown()
    # 注意:此时Logger已无有效Handler,不要再添加日志

3. 等待Cloud Logging队列排空,替代固定延迟

固定5秒延迟对长任务不可靠,直接等待Cloud Logging后台队列完成上传:

from google.cloud.logging.handlers.transports import BackgroundThreadTransport

# 在run_task执行完成后添加:
logger.info("Waiting for Cloud Logging to finish uploading logs.")
# 遍历Handler,找到Cloud Logging Handler并等待队列排空
for handler in logger.handlers:
    if isinstance(handler, google.cloud.logging.handlers.CloudLoggingHandler):
        if isinstance(handler.transport, BackgroundThreadTransport):
            handler.transport.worker.join()

# 再执行本地日志刷新
logging_config.flush_logs()

4. 禁用Stream Handler的缓冲

默认Stream Handler存在缓冲,修改为无缓冲模式:

import sys
import os

# 在创建Stream Handler时添加:
stream_handler = logging.StreamHandler()
stream_handler.setFormatter(log_formatter)
stream_handler.setLevel(getattr(logging, config.LOG_LEVEL.upper()))
# 禁用缓冲
stream_handler.stream = os.fdopen(sys.stdout.fileno(), 'w', 0)

5. 延长Cloud Run Job的终止等待时间

在Job配置中增加terminationGracePeriodSeconds参数,给容器足够时间完成日志上传(最大可设为3600秒):

# Job配置示例片段
apiVersion: run.googleapis.com/v1
kind: Job
metadata:
  name: your-job-name
spec:
  template:
    spec:
      containers:
      - name: your-container
        image: your-image
      terminationGracePeriodSeconds: 60  # 根据任务时长调整

内容的提问来源于stack exchange,提问作者trailblazer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 15:24:52