web-dev-qa-db-fra.com

Comment voir les détails des erreurs Django avec Gunicorn?

Je viens de déployer mon projet Django (1.6) avec gunicorn et Nginx.

Cela semble fonctionner correctement, mais j'ai une page où je reçois une erreur HTTP 500 et je ne trouve aucun détail sur l'erreur n'importe où.

Comment faire pour que gunicorn me montre des erreurs?

Voici tout ce que je vois actuellement dans le fichier journal lorsque je clique sur la page en me donnant l'erreur:

>tail gunicorn.errors 
2014-02-21 14:41:02 [22676] [INFO] Listening at: unix:/opt/djangoprojects/reports/bin/gunicorn.sock (22676)
2014-02-21 14:41:02 [22676] [INFO] Using worker: sync
2014-02-21 14:41:02 [22689] [INFO] Booting worker with pid: 22689
...
2014-02-21 19:41:10 [22691] [DEBUG] GET /reports/2/

Voici mon script bash que j'utilise pour démarrer gunicorn:

>cat gunicorn_start
#!/bin/bash

NAME="reports"                                  # Name of the application
DJANGODIR=/opt/djangoprojects/reports          # Django project directory
SOCKFILE=/opt/djangoprojects/reports/bin/gunicorn.sock  # we will communicte using this unix socket
USER=reportsuser                                        # the user to run as
GROUP=webapps                                     # the group to run as
NUM_WORKERS=4                                     # how many worker processes should Gunicorn spawn
Django_SETTINGS_MODULE=reports.settings             # which settings file should Django use
Django_WSGI_MODULE=reports.wsgi                     # WSGI module name

#echo "Starting $NAME as `whoami`"

# Activate the virtual environment
cd $DJANGODIR
source pythonenv/bin/activate
export Django_SETTINGS_MODULE=$Django_SETTINGS_MODULE
export PYTHONPATH=$DJANGODIR:$PYTHONPATH

# Create the run directory if it doesn't exist
RUNDIR=$(dirname $SOCKFILE)
test -d $RUNDIR || mkdir -p $RUNDIR

# Start your Django Unicorn
# Programs meant to be run under supervisor should not daemonize themselves (do not use --daemon)
exec gunicorn ${Django_WSGI_MODULE}:application \
  --name $NAME \
  --workers $NUM_WORKERS \
  --user=$USER --group=$GROUP \
  --log-level=debug \
  --bind=unix:$SOCKFILE \
  --error-logfile /opt/djangoprojects/reports/bin/gunicorn.errors \
  --log-file /opt/djangoprojects/reports/bin/gunicorn.errors

Plus d'informations:

Je commence/arrête gunicorn avec ce script init.d que j'ai copié et modifié en utilisant Sudo service reports start|stop|restart:

>cat /etc/init.d/reports
#!/bin/sh
### BEGIN INIT INFO
# Provides:          Django_gunicorn
# Required-Start:    $local_fs $network $remote_fs
# Required-Stop:     $local_fs $network $remote_fs
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Starts Django_Unicorn reports at boot time.
# Description:       Starts Django_Unicorn reports at boot time.
### END INIT INFO

name=`basename $0`
dir="/opt/djangoprojects/reports"
cmd="${dir}/bin/gunicorn_start"
pid_file="/var/run/$name.pid"
log_file="${dir}/bin/reports.log"

get_pid() {
    cat "$pid_file"    
}

is_running() {
    [ -f "$pid_file" ] && ps `get_pid` > /dev/null 2>&1
}

case "$1" in
    start)
    if is_running; then
        echo "Already running"
    else
        echo -n "Starting ${name}... "
        cd "$dir"
        #Sudo -u "$user" $cmd &>> "$log_file"
        $cmd &>> "$log_file" &
        echo $! > "$pid_file"
        if ! is_running; then
            echo "Unable to start; see $log_file"
            exit 1
        else
            echo "[STARTED]"
        fi
    fi
    ;;
    stop)
    if is_running; then
        echo -n "Stopping ${name}... "
        kill `get_pid`
        for i in {1..10}
        do
            if ! is_running; then
                break
            fi

            echo -n "."
            sleep 1
        done
        echo

        if is_running; then
            echo "Not stopped; may still be shutting down or shutdown may have failed"
            exit 1
        else
            echo "[STOPPED]"
            if [ -f "$pid_file" ]; then
                rm "$pid_file"
            fi
        fi
    else
        echo "Not running"
    fi
    ;;
    restart)
    $0 stop
    if is_running; then
        echo "Unable to stop, will not attempt to start"
        exit 1
    fi
    $0 start
    ;;
    status)
    if is_running; then
        echo "[RUNNING]"
    else
        echo "[STOPPED]"
        exit 1
    fi
    ;;
    *)
    echo "Usage: $0 {start|stop|restart|status}"
    exit 1
    ;;
esac

exit 0
22
Greg

De votre commentaire, je pense que ceci est un problème de configuration dans votre site Django, pas une question de journal gunicorn, les journaux ne montreront pas plus que Django lui envoyer.

Voici un exemple de la façon dont vous pouvez configurer le paramètre Django pour envoyer le journal dans votre fichier (au lieu de l'envoyer aux administrateurs par courrier électronique par défaut):

LOGGING = {
    'version': 1,
    'disable_existing_loggers': True,
    'formatters': {
        'verbose': {
            'format': '%(asctime)s %(levelname)s [%(name)s:%(lineno)s] %(module)s %(process)d %(thread)d %(message)s'
        }
    },
    'handlers': {
        'gunicorn': {
            'level': 'DEBUG',
            'class': 'logging.handlers.RotatingFileHandler',
            'formatter': 'verbose',
            'filename': '/opt/djangoprojects/reports/bin/gunicorn.errors',
            'maxBytes': 1024 * 1024 * 100,  # 100 mb
        }
    },
    'loggers': {
        'gunicorn.errors': {
            'level': 'DEBUG',
            'handlers': ['gunicorn'],
            'propagate': True,
        },
    }
}

Lisez configurez la journalisation (il fournit une très bonne explication des options des paramètres de journalisation) et étudiez le fichier Django/utils/log.py pour configurer la journalisation Django afin qu’elle apparaisse plus en détail dans les journaux de Gunicorn.

Vérifiez également cette réponse et ceci qui fournissent des exemples de configuration pour envoyer les erreurs de journal directement dans un fichier. Et envisagez d'utiliser Sentry pour gérer les erreurs de journalisation, tel que recommandé par Django.

J'espère que cela t'aides.

14
juliocesar

1. envoyer des erreurs à la console

Ce sont les loggers qui utilisent mail_admins par défaut (voir Django/utils/log.py):

    'Django.request': {
        'handlers': ['mail_admins'],
        'level': 'ERROR',
        'propagate': False,
    },
    'Django.security': {
        'handlers': ['mail_admins'],
        'level': 'ERROR',
        'propagate': False,
    },

vous auriez besoin de changer les gestionnaires pour accéder à la variable console afin qu'elle apparaisse dans votre journal des armes à feu plutôt que d'envoyer des courriels avec mail_admins. S'il vous plaît noter que ce n'est pas aussi bavard que lorsque DEBUG=True.

 'loggers': {
    'Django': {
        'level': 'ERROR',
        'handlers': ['console'],
    },
 }

2. erreurs d’envoi via mail_admins

Également en fonction de la configuration de la journalisation, créez explicitement un gestionnaire qui appelle mail_admins; par exemple. basé sur Django/utils/log.py:

 'handlers': {
    'mail_admins': {
        'level': 'ERROR',
        'class': 'Django.utils.log.AdminEmailHandler'
    },
 },
 'loggers': {
    'Django': {
        'handlers': ['mail_admins'],
    },
 }

Pour cela, vous devez définir une variable settings liée au courrier électronique.

3. autres solutions

Si vous ne cherchiez pas la solution 1, votre question est une copie de: Comment consignez-vous les erreurs de serveur sur les sites Django

7
dnozay

Réponse courte:

Avec la configuration de la journalisation suivante, vos erreurs commenceront à apparaître dans la sortie Gunicorn (undaemonized) ou le serveur d'exécution, même lorsque DEBUG est défini sur False. Ils devraient quand même apparaître lorsque DEBUG est True.

LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'filters': {
    'require_debug_false': {
        '()': 'Django.utils.log.RequireDebugFalse',
    },
    'require_debug_true': {
        '()': 'Django.utils.log.RequireDebugTrue',
    },
},
'formatters': {
    'Django.server': {
        '()': 'Django.utils.log.ServerFormatter',
        'format': '[%(server_time)s] %(message)s',
    }
},
'handlers': {
    'console': {
        'level': 'INFO',
        'filters': ['require_debug_true'],
        'class': 'logging.StreamHandler',
    },
    # Custom handler which we will use with logger 'Django'.
    # We want errors/warnings to be logged when DEBUG=False
    'console_on_not_debug': {
        'level': 'WARNING',
        'filters': ['require_debug_false'],
        'class': 'logging.StreamHandler',
    },
    'Django.server': {
        'level': 'INFO',
        'class': 'logging.StreamHandler',
        'formatter': 'Django.server',
    },
    'mail_admins': {
        'level': 'ERROR',
        'filters': ['require_debug_false'],
        'class': 'Django.utils.log.AdminEmailHandler'
    }
},
'loggers': {
    'Django': {
        'handlers': ['console', 'mail_admins', 'console_on_not_debug'],
        'level': 'INFO',
    },
    'Django.server': {
        'handlers': ['Django.server'],
        'level': 'INFO',
        'propagate': False,
    },
}
}

Si vous voulez voir les erreurs Django dans le journal des erreurs gunicorn, exécutez gunicorn avec --capture-output.

http://docs.gunicorn.org/fr/stable/settings.html#capture-output

Longue réponse

Il existe deux confusions lors de la journalisation:

  1. Si runserver fournit un meilleur journal que gunicorn
  2. settings.DEBUG=True fournit-il un meilleur journal que settings.DEBUG=False

L'enregistrement de journal Any que vous voyez avec le serveur d'exécution peut également être vu avec Gunicorn, à condition que la configuration de la journalisation soit appropriée.

_ {Tout enregistrement de journal que vous voyez avec DEBUG = True peut être vu pendant que DEBUG = False aussi, tant que vous avez la configuration de journalisation appropriée.

Vous pouvez voir la configuration de journalisation Django par défaut sur:

https://github.com/Django/django/blob/1.10.8/Django/utils/log.py#L18

Cela ressemble à: (J'ai enlevé des parties qui ne concernent pas cette réponse)

DEFAULT_LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'filters': {
    'require_debug_false': {
        '()': 'Django.utils.log.RequireDebugFalse',
    },
    'require_debug_true': {
        '()': 'Django.utils.log.RequireDebugTrue',
    },
},
'handlers': {
    'console': {
        'level': 'INFO',
        'filters': ['require_debug_true'],
        'class': 'logging.StreamHandler',
    },
    'mail_admins': {
        'level': 'ERROR',
        'filters': ['require_debug_false'],
        'class': 'Django.utils.log.AdminEmailHandler'
    }
},
'loggers': {
    'Django': {
        'handlers': ['console', 'mail_admins'],
        'level': 'INFO',
    },
}
}

Ce que cela dit c'est:

  1. Envoyez l'enregistrement de journal du journal Django aux gestionnaires console et mail_admins.

  2. Le gestionnaire console a un filtre require_debug_true sur celui-ci. Lorsque settings.DEBUG a la valeur True, le gestionnaire console envoie/imprime le journal sur le flux (en raison de logging.StreamHandler).

Lorsque settings.DEBUG a la valeur False, le gestionnaire console ignore le message de journal qui lui a été envoyé par le consignateur Django.

Si vous souhaitez également que les journaux soient imprimés avec DEBUG = False, ajoutez une handler et faites en sorte que logger Django l'utilise.

Le gestionnaire ressemblerait à:

    'console_on_not_debug': {
        'level': 'WARNING',
        'filters': ['require_debug_false'],
        'class': 'logging.StreamHandler',
    },

Et utilisez ce gestionnaire avec le logger Django:

    'Django': {
        'handlers': ['console', 'mail_admins', 'console_on_not_debug'],
        'level': 'INFO',
    },

Vous pouvez voir l'extrait entier en réponse courte.

Avec cela, les journaux seront imprimés en continu, que vous utilisiez ou non RunServer ou Gunicorn.

Si vous voulez que les journaux soient affichés dans le journal des erreurs gunicorn, vous devez exécuter gunicorn avec --capture-output.

3
Akshar Raaj

La solution la plus simple consiste à configurer la variable ADMINISTRATEURS avec les adresses électroniques des personnes qui devraient recevoir des notifications d'erreur . Lorsque DEBUG = False et qu'une vue lève une exception, Django enverra un courrier électronique à ces personnes avec les informations complètes sur les exceptions.

settings.py

ADMINS = (('John', '[email protected]'), ('Mary', '[email protected]'))
# or only ADMINS = (('John', '[email protected]'),)

Peut-être avez-vous également besoin de EMAIL_Host et de EMAIL_PORT si le bon serveur SMTP n'est pas localhost sur le port 25. Cette solution simple convient assez bien pour les essais de production, sinon elle risque de produire soudainement trop de courriels.

2
hynekcer

Cette configuration a fonctionné pour moi. Ajoutez --capture-output --enable-stdio-inheritance avec la commande gunicorn comme ci-dessous.

/home/ubuntu/inside-env/bin/gunicorn --access-logfile /var/log/access_file_g.log --error-logfile /var/log/error_file_g.log --capture-output --enable-stdio-inheritance --workers 3 --bind unix:/home/ubuntu/path-to-project/webapp.sock project.wsgi:application

Avec cette configuration, activez la journalisation de cette manière

import logging 
logging.basicConfig(level='DEBUG')

logging.info('hello world')

De cette façon, vous pourrez également voir les erreurs dans l'application.

0
SuperNova