[英]Script behaves differently when run from cron job and from command line using django manage.py when using a cron supervisor
I know crons run in a different environment than command lines, but I'm using absolute paths everywhere and I don't understand why my script behaves differently.我知道 cron 在与命令行不同的环境中运行,但我在任何地方都使用绝对路径,我不明白为什么我的脚本表现不同。 I believe it is somehow related to my cron_supervisor which runs the django "manage.py" within a sub process.
我相信它与我的 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"
This will call the cron_supervisor, and it's call the script, but the script won't be executed as it would if I would run:这将调用 cron_supervisor,它调用脚本,但脚本不会像我运行时那样执行:
/home/p1/.virtualenvs/prod/bin/python /home/p1/p1/manage.py envoyer_argent
Is there something particular to be done for the script to be called properly when running it through another script?在通过另一个脚本运行脚本时,是否需要做一些特别的事情才能正确调用脚本?
Here is the supervisor, which basically is for error handling and making sure we get warned if something goes wrong within the cron scripts themselves.这是主管,它基本上用于错误处理,并确保我们在 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)
Example of script not executed properly when being called by the cron, through the cron_supervisor:通过 cron_supervisor 被 cron 调用时未正确执行的脚本示例:
# -*- 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")
Additionally, I have another issue with the logging which I don't quite understand either, I specify to store logs within cron-supervisor.log
but they don't get stored there, I couldn't figure out why.此外,我还有另一个我不太明白的日志问题,我指定将日志存储在
cron-supervisor.log
但它们没有存储在那里,我不知道为什么。 (but that's not related to my main issue, just doesn't help with debug) (但这与我的主要问题无关,只是对调试没有帮助)
Your cron
job can't just run the Python interpreter in the virtualenv;您的
cron
作业不能只在 virtualenv 中运行 Python 解释器; this is completely insufficient.这是完全不够的。 You need to
activate
the env just like in an interactive environment.您需要像在交互式环境中一样
activate
env。
0 * * * * . /home/p1/.virtualenvs/prod/bin/activate; python /home/p1/p1/manage.py cron_supervisor --command="python /home/p1/p1/manage.py envoyer_argent"
This is already complex enough that you might want to create a separate wrapper script containing these commands.这已经足够复杂了,您可能想要创建一个包含这些命令的单独包装脚本。
Without proper diagnostics of how your current script doesn't work, it's entirely possible that this fix alone is insufficient.如果没有正确诊断当前脚本如何无法工作,仅此修复程序完全有可能是不够的。 Cron jobs do not only (or particularly) need absoute paths;
Cron 作业不仅(或特别)需要绝对路径; the main differences compared to interactive shells is that cron jobs run with a different and more spare environment, where eg the shell's
PATH
, various library paths, environment variables etc can be different or missing altogether;与交互式 shell 相比,主要区别在于 cron 作业在不同且更空闲的环境中运行,例如 shell 的
PATH
、各种库路径、环境变量等可能不同或完全缺失; and of course, no interactive facilities are available.当然,没有互动设施可用。
The system variables will hopefully be taken care of by your virtualenv;系统变量有望由您的 virtualenv 处理; if it's correctly done, activating it will set up all the variables (
PATH
, PYTHONPATH
, etc) your script needs.如果正确完成,激活它将设置脚本所需的所有变量(
PATH
、 PYTHONPATH
等)。 There could still be things like locale settings which are set up by your shell only when you log in interactively;只有当您以交互方式登录时,您的 shell 才会设置诸如区域设置之类的东西; but again, without details, let's just hope this isn't an issue for you.
但同样,没有详细信息,我们只是希望这对您来说不是问题。
The reason some people recommend absolute paths is that this will work regardless of your working directory.有些人推荐绝对路径的原因是无论您的工作目录如何,这都可以使用。 But a correctly written script should work fine in any directory;
但是正确编写的脚本应该可以在任何目录中正常工作; if it matters, the cron job will start in the owner's home directory.
如果重要,cron 作业将在所有者的主目录中启动。 If you wanted to point to a relative path from there, this will work fine inside a cron job just as it does outside.
如果您想从那里指向相对路径,这将在 cron 作业中正常工作,就像在外部一样。
As an aside, you probably should not use subprocess.Popen()
if one of the higher-level wrappers from the subprocess
module do what you want. subprocess.Popen()
如果来自subprocess
模块的更高级别包装器之一执行您想要的操作,则您可能不应该使用subprocess.Popen()
。 Unless compatibility with legacy Python versions is important, you should probably use subprocess.run()
... though running Python as a subprocess of Python is also often a useless oomplication.除非与旧版 Python 版本的兼容性很重要,否则您可能应该使用
subprocess.run()
……尽管将 Python 作为 Python 的子进程运行通常也是无用的 oomplication。 See also my answer to this related question.另请参阅我对这个相关问题的回答。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.