본문 바로가기
DoItDJango

aws 호스팅3

by 자동매매 2023. 4. 28.

https://wikidocs.net/76905

 

4-12 서버 관리자

`[완성 소스]` : [github.com/pahkey/jump2django/tree/4-12](https://github.com/pahkey/jump2django/tree/4-…

wikidocs.net

 

서버 관리자

createsuperuser

(mysite) ubuntu@jumpto:~/projects/mysite$ python manage.py createsuperuser
http://3.37.58.70/admin

 

그러면 아마도 다음처럼 관리자 화면이 깨진것처럼 표시될 것이다.

이렇게 되는 이유는 Nginx가 장고 Admin에서 사용하는 정적 파일을 제대로 읽지 못했기 때문이다. Nginx가 바라보는 정적 파일은 /home/ubuntu/projects/mysite/static 디렉터리에 위치해야 한다. 하지만 장고 Admin이 사용하는 정적 파일들은 다음 디렉터리에 위치한다.

/home/ubuntu/venvs/mysite/lib/python3.8/site-packages/django/contrib/admin/static

장고에 내장된 개발 서버는 장고 Admin이 사용될 때 자동으로 위 디렉터리의 정적 파일을 읽도록 설계되었다. 하지만, Nginx는 그렇게 설계되지 않았다. Nginx는 장고에 특화된 웹 서버가 아니라 범용적인 웹 서버이기 때문이다.

 

STATIC_ROOT

이 문제를 해결하려면 장고 환경설정 파일에 STATIC_ROOT 디렉터리를 설정하고 python manage.py collectstatic 명령을 수행하여 관리자 앱의 정적 파일을 STATIC_ROOT 디렉터리로 복사해야 한다.

먼저 다음처럼 prod.py 파일에 STATIC_ROOT 항목을 다음처럼 추가하자.

 

[파일명: projects\mysite\config\settings\prod.py]

from .base import *
ALLOWED_HOSTS = ['3.37.58.70']
STATIC_ROOT = BASE_DIR / 'static/'
STATICFILES_DIRS = []

 

# Nginx에 정적 파일 위치를 /home/ubuntu/projects/mysite/static 디렉터리로 등록하였으므로 STATIC_ROOT도 위와 같이 설정해야 한다.

# 그런데 base.py 파일 / prod.py 파일에 STATICFILES_DIRS 항목이 이중 지정시 에러 발생하므로 prod.py 파일에는 지정 안함

(mysite) ubuntu@jumpto:~/projects/mysite$ sudo systemctl restart mysite.service

 

변경된 프로그램 서버에 적용하기

[로컬 환경에서 프로그램이 변경된 경우]

  1. git add *
  2. git commit -m "수정내용 코멘트"
  3. git push

[서버 환경에서 변경된 내용 적용]

  1. git pull
  2. sudo systemctl restart mysite.service

 

collectstatic

이제 python manage.py collectstatic 명령을 수행하여 관리자 앱의 정적 파일을 복사하자.

collectstatic 명령은 비단 관리자 앱 뿐만 아니라 필요에 의해 설치한 다른 앱들의 정적 파일들도 복사해 준다.

(mysite) ubuntu@jumpto:~/projects/mysite$ python manage.py collectstatic

yes 클릭

정적 파일이 /home/ubuntu/projects/mysite/static 디렉터리로 복사되었다는 메시지를 확인할 수 있다.

(mysite) ubuntu@jumpto:~/projects/mysite$ cd static
(mysite) ubuntu@jumpto:~/projects/mysite/static$ ls -l

 

DEBUG

장고의 환경설정 파일에는 DEBUG라는 항목이 있다. DEBUG 항목은 기본값이 True. 하지만 장고 공식 문서에는 "운영 환경에서 DEBUG는 반드시 False로 해야 한다"고 되어 있다.

 

DEBUG 설정

서버의 환경설정 파일인 prod.py 파일을 다음과 같이 수정하자.

 

[파일명: projects\mysite\config\settings\prod.py]

from .base import *
ALLOWED_HOSTS = ['3.37.58.70']
STATIC_ROOT = BASE_DIR / 'pybo/static/'
STATICFILES_DIRS = []
DEBUG = False

 

config/urls.py

먼저 config/urls.py 파일에 handler404 라는 변수를 다음처럼 추가하자.

[파일명: projects\mysite\config\urls.py]

from django.contrib import admin
from django.urls import include, path
from pybo.views import base_views 

urlpatterns = [
    path('pybo/', include('pybo.urls')),
    path('common/', include('common.urls')),
    path('admin/', admin.site.urls),
    path('', base_views.index, name='index'), 
]

handler404 = 'common.views.page_not_found'

 

config/urls.py에 설정한 handler404 변수는 좀 특별한 변수이다. handler404 변수를 설정하면 404 오류 발생 시 사용자가 정의한 뷰 함수가 호출된다. 따라서 404 오류가 발생하면 위에 정의한 대로 common/views.py 파일의 page_not_found 함수가 호출될 것이다.

handler404 변수는 반드시 config/urls.py에 등록해야 한다.

 

views.page_not_found

[파일명: projects\mysite\common\views.py]
(... 생략 ...)
def page_not_found(request, exception):
    return render(request, 'common/404.html', {})

 

page_not_found 함수는 request 외에 exception이라는 매개변수를 하나 더 받음에 주의하자.

exception 매개변수는 오류의 내용을 담고 있는 변수이다. 만약 오류의 내용을 화면에 보여 주고 싶다면 exception의 값을 읽어서 화면에 보여줄 수도 있다.

 

404.html

[파일명: projects\mysite\templates\common\404.html]

{% extends 'base.html' %}
{% block content %}
<!-- 404 Page -->
<div class="container">
    <div class="row justify-content-center">
        <div class="col-12 text-center">
            <span class="display-1 d-block">404</span>
            <div class="mb-4 lead">페이지를 찾을 수 없습니다.</div>
            <a href="/" class="btn btn-link">홈으로 돌아가기</a>
        </div>
    </div>
</div>
{% endblock %}

 

로깅

서버 오류

보통 운영 환경에서는 오류 식별을 위해 로그 파일을 사용한다.

DEFAULT_LOGGING

로그를 파일로 저장하기 위해서는 장고의 DEFAULT_LOGGING 설정을 먼저 알아야 한다. 장고의 DEFAULT_LOGGING 설정은 다음과 같다.

 

[장고 DEFAULT_LOGGING 설정]

DEFAULT_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}] {message}',
            'style': '{',
        },
    },
    'handlers': {
        'console': {
            'level': 'INFO',
            'filters': ['require_debug_true'],
            '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'],
            'level': 'INFO',
        },
        'django.server': {
            'handlers': ['django.server'],
            'level': 'INFO',
            'propagate': False,
        },
    }
}

 

이 설정에 사용된 항목을 가벼운 마음으로 하나씩 살펴보자.

 

version

version고정값 1을 사용해야 한다. 만약 다른 값을 입력하면 ValueError가 발생한다. 이 값은 의미 없어 보일 수도 있지만, logging 모듈이 업그레이드되어도 현재 설정을 보장해 주는 안전장치이다.

 

disable_existing_loggers

disable_existing_loggers 항목은 False로 설정했다.

만약 True로 설정하면 기존에 설정된 로거들을 사용하지 않게 된다.

 

filters

필터는 특정 조건에서 로그를 출력하거나 출력하지 않기 위해서 사용된다.

require_debug_false 필터는 DEBUG=False인지를 판단하는 필터이고,

require_debug_true DEBUG=True 인지를 판단하는 필터이다.

조건 판단을 위해 각각 django.utils.log.RequireDebugFalse django.utils.log.RequireDebugTrue 클래스를 호출하여 DEBUG 항목의 True, False를 판단한다.

 

formatters

포맷터에는 로그를 출력할 형식을 정의한다. 포맷터에 사용된 항목은 다음과 같다.

  • server_time - 서버의 시간
  • message - 출력내용

 

handlers

핸들러는 로그의 출력 방법을 정의한다. 다음은 DEFAULT_LOGGING 설정에 등록된 핸들러이다.

  • console - 콘솔에 로그를 출력한다. 로그 레벨이 INFO 이상이고 DEBUG=True일 때만 로그를 출력한다.
  • django.server - python manage.py runserver로 작동하는 개발 서버에서만 사용하는 핸들러로 콘솔에 로그를 출력한다.
  • mail_admins - 로그 내용을 이메일로 전송하는 핸들러로, 로그 레벨이 ERROR 이상이고 DEBUG=False 일때만 로그를 전송한다. 이 핸들러를 사용하려면 환경설정 파일에 ADMINS라는 항목을 추가하고 관리자 이메일을 등록해야 한다 (: ADMINS = ['pahkey@gmail.com']). 그리고 이메일 발송을 위한 SMTP 설정도 필요하다.

 

loggers

로그를 출력하는 프로그램에서 사용하는 로거(logger)의 이름을 의미한다. DEFAULT_LOGGING 설정에는 다음과 같은 로거들이 등록되어 있다.

  • django - 장고 프레임워크가 사용하는 로거, 로그 레벨이 INFO 이상일 경우에만 로그를 출력한다.
  • django.server - 개발 서버가 사용하는 로거, 로그 레벨이 INFO 이상일 경우에만 로그를 출력한다. 'propagate': False의 의미는 django.server가 출력하는 로그를 django 로거로 전달하지 않는다는 의미이다. 만약 'propagate': True로 설정하면 최상위 패키지명이 django로 동일하기 때문에 django.server 하위 패키지에서 출력하는 로그가 django.server 로거에도 출력되고 django 로거에도 출력되어 이중으로 출력된다.

 

로그 레벨

로그 레벨은 다음과 같이 5단계로 구성된다.

각 단계는 logging.debug, logging.info, logging.warning, logging.error, logging.critical 함수로 출력할 수 있다.

  • 1단계 DEBUG: 디버깅 목적으로 사용
  • 2단계 INFO: 일반 정보를 출력할 목적으로 사용
  • 3단계 WARNING: 경고 정보를 출력할 목적으로(작은 문제) 사용
  • 4단계 ERROR: 오류 정보를 출력할 목적으로(큰 문제) 사용
  • 5단계 CRITICAL: 아주 심각한 문제를 출력할 목적으로 사용

설명에서 짐작할 수 있듯이 로그 레벨의 순서는 다음과 같다.

DEBUG < INFO < WARNING < ERROR < CRITICAL

로그는 설정한 레벨 이상의 로그만 출력된다. 예를 들어 핸들러나 로거의 로그 레벨을 INFO로 설정하면 DEBUG 로그는 출력되지 않고 INFO 이상의 로그만 출력된다. , logging.debug로 출력하는 로그는 출력되지 않고 logging.info, logging.warning, logging.error, logging.critical로 출력한 로그만 출력된다는 말이다. 만약 로그 레벨을 ERROR로 설정한다면 logging.error, logging.critical로 출력한 로그만 출력될 것이다.

장고의 DEFAULT_LOGGING 설정을 알아보았다. 장고 로깅에 대한 보다 자세한 내용은 아래의 URL을 참고하자.

장고 로깅docs.djangoproject.com/en/4.0/topics/logging

 

로그 파일

현재는 서버 환경에서 발생한 오류를 확인할 방법이 없으므로 다음과 같은 방법을 사용해야 한다.

  1. 오류 발생 시 관리자 이메일로 오류 내용을 발송(로깅 설정을 변경할 필요 없음, 다만 이메일 발송을 위해 관리자 이메일과 이메일 발송을 위한 설정이 필요하다.)
  2. 서버 환경에서 오류 발생 시 특정 파일에 로그 출력

위의 2가지 방법을 모두 사용해도 되지만 파이보는 2번째 방법을 사용하기로 하자.

 

DEFAULT_LOGGING 설정 복사

먼저 위에서 검토했던 DEFAULT_LOGGING settings/base.py LOGGING이라는 변수명으로 바꾸고 내용은 그대로 복사하자. 환경 파일에 LOGGING이라는 항목을 설정하면 장고는 로깅 설정으로 인식한다.

 

[파일명: projects\mysite\config\settings\base.py]

(... 생략 ...)

 

# 로깅설정

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}] {message}',

            'style': '{',

        },

    },

    'handlers': {

        'console': {

            'level': 'INFO',

            'filters': ['require_debug_true'],

            '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'],

            'level': 'INFO',

        },

        'django.server': {

            'handlers': ['django.server'],

            'level': 'INFO',

            'propagate': False,

        },

    }

}

 

포맷터 추가

[파일명: projects\mysite\config\settings\base.py]

(... 생략 ...)

LOGGING = {

    (... 생략 ...)

    'formatters': {

        'django.server': {

            '()': 'django.utils.log.ServerFormatter',

            'format': '[{server_time}] {message}',

            'style': '{',

        },

        'standard': {

            'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s'

        },

    },

    (... 생략 ...)

}

 

standard 포맷터에 사용한 항목은 다음과 같다.

  • asctime - 현재 시간
  • levelname - 로그의 레벨(debug, info, warning, error, critical)
  • name - 로거명
  • message - 출력 내용

핸들러 추가

[파일명: projects\mysite\config\settings\base.py]

(... 생략 ...)

LOGGING = {

    (... 생략 ...)

    'handlers': {

        'console': {

            'level': 'INFO',

            'filters': ['require_debug_true'],

            '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'

        },

        'file': {

            'level': 'INFO',

            'filters': ['require_debug_false'],

            'class': 'logging.handlers.RotatingFileHandler',

            'filename': BASE_DIR / 'logs/mysite.log',

            'maxBytes': 1024*1024*5,  # 5 MB

            'backupCount': 5,

            'formatter': 'standard',

        },

    },

    (... 생략 ...)

}

file 핸들러에 사용한 항목은 다음과 같다.

  • level - 출력 레벨로 INFO를 사용
  • filters - DEBUG=False인 운영 환경에서 사용
  • class - 파일 핸들러로 RotatingFileHandler 사용, RotatingFileHandler는 파일 크기가 설정한 크기보다 커지면 파일 뒤에 인덱스를 붙여서 백업한다. 이 핸들러의 장점은 로그가 무한히 증가되더라도 일정 개수의 파일로 롤링(Rolling)되기 때문에 로그 파일이 너무 커져서 디스크가 꽉 차는 위험을 방지할 수 있다.
  • filename - 로그 파일명은 logs 디렉터리에 mysite.log로 설정
  • maxBytes - 로그 파일의 최대 크기는 5MB로 설정
  • backupCount - 롤링되는 파일의 개수를 의미한다. 5개의 로그 파일로 유지되도록 설정했다.
  • formatter - 포맷터는 standard를 사용

핸들러 등록

[파일명: projects\mysite\config\settings\base.py]

LOGGING = {

    (... 생략 ...)

    'loggers': {

        'django': {

            'handlers': ['console', 'mail_admins', 'file'],

            'level': 'INFO',

        },

        'django.server': {

            'handlers': ['django.server'],

            'level': 'INFO',

            'propagate': False,

        },

    }

}

이렇게 변경하고 git을 이용하여 base.py 파일의 변경 내역을 서버에 적용하자.

logs 디렉터리 생성

그리고 다음처럼 서버에 logs 디렉터리를 반드시 생성해야 한다.

(mysite) ubuntu@jumpto:~/projects/mysite$ mkdir logs

만약 logs 디렉터리를 생성하지 않고 Gunicorn을 재시작하면 다음과 같은 502 오류가 발생한다.

 

마찬가지로 개발 환경에도 다음처럼 logs 디렉터리를 생성해 주어야 한다.

로컬 환경인 경우 로그 파일에 로그가 쌓이도록 설정하지는 않았지만 디렉터리가 없으면 오류가 발생한다.

(mysite) c:\projects\mysite>mkdir logs

그리고 logs 디렉터리는 버전 관리 대상이 아니므로 .gitignore 파일에도 logs 디렉터리를 추가하자.

 

[파일명: projects\mysite\.gitignore]

.idea
db.sqlite3
*.pyc
__pycache__
logs

 

로그 확인

이제 다시 서버에서 파이보 메인 페이지에 접속해 보자. base_vews.py 파일의 index 함수에서 3 0으로 나누기 때문에 여전히 'Server Error (500)' 오류가 발생하게 될 것이다. 하지만 이제 오류를 확인할 수 있는 로그 파일을 생성했으므로 해당 오류가 정상으로 생성되었는지 확인해 보자.

(mysite) ubuntu@jumpto:~/projects/mysite$ cd logs
(mysite) ubuntu@jumpto:~/projects/mysite/logs$ cat mysite.log

오류 내용이 로그 파일에 정확히 출력되었다.

cat mysite.log 명령에서 사용된 cat 명령은 파일 내용 전체를 출력하는 유닉스 명령어이다. 보통 로그를 확인할 때는 cat 보다는 tail -f mysite.log를 주로 사용한다. tail -f mysite.log를 실행하면 mysite.log 파일에 로그가 쌓일 때마다 로그의 내용이 자동으로 출력된다.

 

파이보 로그

앞에서 설정한 LOGGING은 장고가 사용하는 django django.server라는 로거만 사용했다. 이번에는 새로운 로거를 생성하여 파이보 프로그램에서 로그를 출력하는 방법에 대해서 알아보자.

 

pybo 로거 생성

base_views.py 파일의 index 함수에 임시로 작성한 3/0 코드는 삭제하고 다음과 같이 수정하자.

 

[파일명: projects\mysite\pybo\views\base_views.py]

(... 생략 ...)

import logging

logger = logging.getLogger('pybo')

 

def index(request):

    3/0  # 강제로 오류발생

    logger.info("INFO 레벨로 출력")

    (... 생략 ...)

 

로그 파일에 로그를 출력하기 위해서는 logging 모듈이 필요하다. logger = logging.getLogger("로거명")으로 얻은 logger 객체를 이용하여 logger.debug, logger.error, logger.waning 등의 함수를 이용하여 로그를 출력할 수 있다.

하지만 이렇게 수정하더라도 'INFO 레벨로 출력'이라는 문장은 로그로 출력되지 않을 것이다. 왜냐하면 위 코드에서 사용한 pybo라는 로거는 LOGGING 설정에 등록되지 않은 상태이기 때문이다.

 

pybo 로거 등록

따라서 LOGGING 설정에 pybo 로거를 등록해야 한다. 다음처럼 pybo 로거를 settings/base.py 파일에 추가하자.

 

[파일명: projects\mysite\config\settings\base.py]

(... 생략 ...)

LOGGING = {

   (... 생략 ...)

    'loggers': {

        (... 생략 ...)

        'pybo': {

            'handlers': ['console', 'file'],

            'level': 'INFO',

        },

    },

}

 

console file 핸들러를 사용하는 pybo 로거를 등록해 주었다.

 

출력 로그 확인

이제 서버에 변경 내역을 적용하고 파이보 메인 페이지에 접속하면 다음처럼 mysite.log 파일에 우리가 작성한 로그가 출력된다.

 

(mysite) ubuntu@jumpto:~/projects/mysite$ cd logs

(mysite) ubuntu@jumpto:~/projects/mysite/logs$ tail -f mysite.log

 

 

__name__ 으로 로거 생성하기

logger = logging.getLogger('pybo') 대신 logger = logging.getLogger(__name__)처럼 코딩해도 로그가 잘 출력된다. __name__은 실행되는 파이썬 모듈명을 의미하므로 __name__ pybo.views.base_views으로 해석되어 결국 pybo.views.base_views pybo로 시작하는 패키지이므로 동일한 pybo 로거를 사용하게 된다.

 

'DoItDJango' 카테고리의 다른 글

aws 호스팅5  (0) 2023.04.28
aws 호스팅4  (0) 2023.04.28
aws 호스팅2  (0) 2023.04.28
aws 호스팅1  (0) 2023.04.28
sendgrid를 통한 메일 전송  (0) 2023.04.18

댓글