从cron作业和使用cron主管使用django manage.py从命令行运行时,脚本的行为有所不同

时间:2018-09-05 14:09:35

标签: python django cron

我知道crons在与命令行不同的环境中运行,但是我到处都使用绝对路径,而且我不明白为什么我的脚本的行为会有所不同。我相信这与我的cron_supervisor有某种关系,该cron_supervisor在子进程中运行django“ manage.py”。

Cron:

0 * * * * /home/p1/.virtualenvs/prod/bin/python /home/p1/p1/manage.py cron_supervisor --command="/home/p1/.virtualenvs/prod/bin/python /home/p1/p1/manage.py envoyer_argent"

这将调用cron_supervisor,并且将其称为脚本,但是该脚本不会像我将要运行的那样执行:

/home/p1/.virtualenvs/prod/bin/python /home/p1/p1/manage.py envoyer_argent

通过另一个脚本运行脚本时,要正确调用该脚本是否需要做一些特别的事情?

这里是主管,主要是用于错误处理,并确保在cron脚本本身中出现问题时向我们发出警告。

import logging
import os
from subprocess import PIPE, Popen

from django.core.management.base import BaseCommand

from command_utils import email_admin_error, isomorphic_logging
from utils.send_slack_message import send_slack_message

CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
PROJECT_DIR = CURRENT_DIR + '/../../../'

logging.basicConfig(
  level=logging.INFO,
  filename=PROJECT_DIR + 'cron-supervisor.log',
  format='%(asctime)s %(levelname)s: %(message)s',
  datefmt='%Y-%m-%d %H:%M:%S'
)


class Command(BaseCommand):
  help = "Control a subprocess"

  def add_arguments(self, parser):
    parser.add_argument(
      '--command',
      dest='command',
      help="Command to execute",
    )
    parser.add_argument(
      '--mute_on_success',
      dest='mute_on_success',
      action='store_true',
      help="Don't post any massage on success",
    )

  def handle(self, *args, **options):
    try:
      isomorphic_logging(logging, "Starting cron supervisor with command \"" + options['command'] + "\"")
      if options['command']:
        self.command = options['command']
      else:
        error_message = "Empty required parameter --command"
        # log error
        isomorphic_logging(logging, error_message, "error")
        # send slack message
        send_slack_message("Cron Supervisor Error: " + error_message)
        # send email to admin
        email_admin_error("Cron Supervisor Error", error_message)
        raise ValueError(error_message)

      if options['mute_on_success']:
        self.mute_on_success = True
      else:
        self.mute_on_success = False

      # running process
      process = Popen([self.command], stdout=PIPE, stderr=PIPE, shell=True)
      output, error = process.communicate()

      if output:
        isomorphic_logging(logging, "Output from cron:" + output)

      # check for any subprocess error
      if process.returncode != 0:
        error_message = 'Command \"{command}\" - Error \nReturn code: {code}\n```{error}```'.format(
          code=process.returncode,
          error=error,
          command=self.command,
        )
        self.handle_error(error_message)

      else:
        message = "Command \"{command}\" ended without error".format(command=self.command)
        isomorphic_logging(logging, message)
        # post message on slack if process isn't muted_on_success
        if not self.mute_on_success:
          send_slack_message(message)
    except Exception as e:
      error_message = 'Command \"{command}\" - Error \n```{error}```'.format(
        error=e,
        command=self.command,
      )
      self.handle_error(error_message)

  def handle_error(self, error_message):
    # log the error in local file
    isomorphic_logging(logging, error_message)
    # post message in slack
    send_slack_message(error_message)
    # email admin
    email_admin_error("Cron Supervisor Error", error_message)

由cron通过cron_supervisor调用时脚本未正确执行的示例:

# -*- coding: utf-8 -*-

import json
import logging
import os

from django.conf import settings
from django.core.management.base import BaseCommand

from utils.lock import handle_lock

logging.basicConfig(
  level=logging.INFO,
  filename=os.path.join(settings.BASE_DIR, 'crons.log'),
  format='%(asctime)s %(levelname)s: %(message)s',
  datefmt='%Y-%m-%d %H:%M:%S'
)
class Command(BaseCommand):
  help = "Envoi de l'argent en attente"

  @handle_lock
  def handle(self, *args, **options):

    logging.info("some logs that won't be log (not called)")

logging.info("Those logs will be correcly logged")

此外,我还有一个我也不十分了解的日志记录问题,我指定将日志存储在cron-supervisor.log中,但它们没有存储在那里,我不知道为什么。 (但这与我的主要问题无关,只是对调试没有帮助)

1 个答案:

答案 0 :(得分:1)

您的cron工作不能只在virtualenv中运行Python解释器;这是完全不够的。您需要像在交互式环境中一样activate进行环境操作。

0 * * * * . /home/p1/.virtualenvs/prod/bin/activate; python /home/p1/p1/manage.py cron_supervisor --command="python /home/p1/p1/manage.py envoyer_argent"

这已经足够复杂,您可能需要创建一个包含这些命令的单独包装脚本。

如果没有对当前脚本如何工作的适当诊断,则很可能仅此修复程序是不够的。 Cron作业不仅(或特别是)需要绝对路径。与交互式shell相比,主要区别在于cron作业在不同的备用环境下运行,例如外壳程序的PATH,各种库路径,环境变量等可能不同或完全丢失;当然,没有交互式功能可用。

希望您的virtualenv可以处理系统变量;如果正确完成,则激活它会设置脚本所需的所有变量(PATHPYTHONPATH等)。仍然可能有诸如语言环境设置之类的东西,只有当您以交互方式登录时,shell才会设置它们。但同样,没有细节,我们只是希望这对您来说不是问题。

有人建议使用绝对路径的原因是,不管您的工作目录是什么,它都将起作用。但是正确编写的脚本应该可以在任何目录下正常工作。如果重要,则cron作业将在所有者的主目录中启动。如果您要指向从那里开始的相对路径,那么在cron作业内部和外部都可以正常工作。

顺便说一句,如果subprocess.Popen()模块中的更高层包装器之一满足您的要求,则可能不应该使用subprocess。除非与旧版Python的兼容性很重要,否则您应该使用subprocess.run() ...,尽管将Python作为Python的子进程运行也常常是无用的omplication。另请参阅我对this related question.

的回答