Error Handling
- Projemizde hata oluştuğunda hatanın tespiti için neler yapacağımızı bu bölümde göreceğiz.
- Siz de web uygulamanızda ikinci bir kullanıcının kayıtlı olduğuna emin olarak login olduktan sonra kullanıcı adınızı(username) diğer kullanıcı adıyla aynı yapacak şekilde
/edit_profile
sayfasından değiştirin.
Sonuç :
- Projemiz yayında iken kullanıcının hata mesajı detaylarını görmemesi güvenlik açısından önemlidir. Ancak geliştirme aşamasında biz hata mesajlarını görmeliyiz.
- Terminalden
export FLASK_DEBUG=1
yaparak (windows için export yerine set) uygulamamızıDEBUG
modda çalıştırıp tekrar username'i başka bir kullanıcı adıyla aynı yapmaya çalışın. - Ekran görüntüsü aşağıda göreceğiniz gibi Debugger is active!, Debugger PIN ... bilgileri görünüyor.
DEBUG
modda iken python modüllerinizde herhangi bir değişiklik yaptığınızda sunucu otomatik yeniden başlayacak yani sürekli programı yeniden başlatmak zorunda kalmayacaksınız.Proje production(yayında, serverda)'da iken DEBUG modda ** OLMAMALI **
. Kullanıcı debug modda iken uygulamamızda(projemizde) uzaktan kod çalıştırabilir. Saldırganlara bu hediyeyi vermek istemeyiz.- DEBUG modda iken aynı username ataması yapmaya çalıştığımızda:
- Daha detaylı hata mesajları aldık. Bu hata mesajı
SQLAlchemy
'den geliyor. - UNIQUE constraint failed... mesajından anlaşıldığı gibi uniqe belirlenmiş alana biz var olan kaydın aynısını atama yapmaya çalışınca hata verdi.
Custom Error Pages (Özel Hata Sayfaları)
- Özel hata mesajları göstermek için Flask'in sunduğu
@errorhandler(_hata_tipi_)
decorator'ını kullanacağız. myapp/errors.py
modülünü oluşturalım
xxxxxxxxxx
# external
from flask import render_template
# internal
from myapp import app, db
errorhandler(404) .
def not_found_error(error):
return render_template('404.html'), 404
errorhandler(500) .
def internal_error(error):
db.session.rollback()
return render_template('500.html'), 500
- Gösterim methodlarına (view functions) benzer şekilde methodlarımızı yazdık.
- Burada
render_template
methodundan sonra404, 500
sayılarını görüyorsunuz. Varsayılan olarak200
olduğu için view methodlarında yazmadık daha önce. Bu sayılar HTTP mesaj türlerini ifade etmektedir. Daha fazla bilgi için -> HTTP Messages db.session.rollback()
database session'ı resetler.- Şimdi
templates/404.html
sayfasını oluşturalım.
xxxxxxxxxx
{%extends 'base.html'%}
{%block content%}
<h1>File not found!</h1>
<a href="{{url_for('index')}}">Anasayfa</a>
{%endblock%}
- Ve
templates/500.html
sayfası
xxxxxxxxxx
{%extends 'base.html'%}
{%block content%}
<h1>Beklenmeyen bir hata oluştu!</h1>
<span>Yönetici bilgilendirildi, en kısa sürede ilgileneceğiz.</span>
<a href="{{url_for('index')}}">Anasayfa</a>
{%endblock%}
Son olarak Flask'in
errors.py
modülümüzü tanıması içinmyapp/__init__.py
'a import edelim
xxxxxxxxxx
# ...
from app import routes, models, errors
- Terminalden
export FLASK_DEBUG=0
yaptıktan sonra tekrarusername
'i başka bir kaydın username'i ile aynı yapmaya çalışalım. Sonuç:
Hata Mesajlarını Email Olarak Göndermek (Sending Errors by Email)
- Hata mesajlarını uygulamamız terminale yazıyor. Ancak production'da kimse hata mesajlarına bakmayacak. Dolayısıyla herhangi bir hata olduğunda hemen görebilmek için hata mesajlarını email ile bildirmemiz yerinde olacaktır.
config.py
modülümüze email için birkaç düzenleme yapalım.
xxxxxxxxxx
class Config(object):
#..
# Email configurations
MAIL_SERVER = os.environ.get('MAIL_SERVER')
MAIL_PORT = int(os.environ.get('MAIL_PORT') or 25)
MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS') is not None
MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
ADMINS = ['email_adresiniz@gmail.com']
- Yorum satırı olarak bıraktıklarımız environment'da ayarlanmış
SERVER, PORT ..
vs. varsa onları alacaktır. Ancak burada biz string olarak manuel atama yaptık. - Burada google'ın sunduğu mail servislerini kullanıyoruz.
- Flask log'lama yapmak için Python'ın logging paketini kullanır. Simple Mail Transfer Protocol Handler (SMTPHandler) ile hata bildirim emailini göndereceğiz.
xxxxxxxxxx
#...
if not app.debug:
if app.config['MAIL_SERVER']:
auth = None
if app.config['MAIL_USERNAME'] or app.config['MAIL_PASSWORD']:
auth = (app.config['MAIL_USERNAME'], app.config['MAIL_PASSWORD'])
secure = None
if app.config['MAIL_USE_TLS']:
secure = ()
mail_handler = SMTPHandler(
mailhost=(app.config['MAIL_SERVER'], app.config['MAIL_PORT']),
fromaddr='sender-adnan@' + app.config['MAIL_SERVER'],
toaddrs=app.config['ADMINS'], subject='Blog Hata',
credentials=auth, secure=secure)
mail_handler.setLevel(logging.ERROR)
app.logger.addHandler(mail_handler)
- Email gönderimi
debug
modda değilse ve konfigürasyonda mail server mevcutsa diye kontrol yaptık. mail_handler.setLevel
'a dikkat ederseniz sadece hata(ERROR) mesajlarını bildirecek şekilde düzenledik. Uyarı, bilgi, debug mesajları bildirilmeyecek şu anda.- Email gönderimini test etmek için 2 yöntem var. Birisi python'ın sunduğu SMTP debugging server. Gerçek bir email server kadar olmasa da emailleri kabul eden ancak email göndermek yerine maili terminale yazan testimizi kolayca yapabileceğimiz bir serverdır.
- Şimdi ikinci bir terminal açıp aşağıdaki gibi email server'ı çalıştıralım.
xxxxxxxxxx
(env) $ python -m smtpd -n -c DebuggingServer localhost:8025
- Şimdi birinci terminalimize dönüp, server'ı localhost, port'u 8025, debug modu da False yani 0 yapalım.
xxxxxxxxxx
(env) $ export MAIL_SERVER=localhost
(env) $ export MAIL_PORT=8025
(env) $ export FLASK_DEBUG=0
(env) $ flask run
- Şimdi tekrar username'i başka kullanıcının username'i ile aynı yapalım tekrar hata mesajını yakalayalım. İkinci terminale bakacak olursak;
xxxxxxxxxx
(env) $ python -m smtpd -n -c DebuggingServer localhost:8025
---------- MESSAGE FOLLOWS ----------
From: sender-adnan@localhost
To: email_adresiniz@gmail.com
Subject: Blog Hata
Date: Sun, 01 Apr 2018 11:52:04 +0300
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: 7bit
MIME-Version: 1.0
X-Peer: 127.0.0.1
....
......
sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) UNIQUE constraint failed:
user.username [SQL: 'UPDATE user SET username=?, about_me=? WHERE user.id = ?']
[parameters: ('adnan', '', 2)] (Background on this error at: http://sqlalche.me/e/gkpj)
------------ END MESSAGE ------------
- Terminalde hata mesajını ve hatanın hangi modüllerde hangi tür exception ve hatalara sebep olduğunu görebilirsiniz.
- Email gönderimini test etmenin ikinci yöntemi GOOGLE'ın email serverını kullanmak.
flask run
yaptığınız terminalde aşağıdaki işlemleri de yapınız.
xxxxxxxxxx
export MAIL_SERVER=smtp.googlemail.com
export MAIL_PORT=587
export MAIL_USE_TLS=1
export MAIL_USERNAME=<gmail_adresiniz>
export MAIL_PASSWORD=<gmail_parolaniz>
flask run
dedikten sonra tekrar username başka kullanıcınınki ile aynı olacak şekilde test edin. Gmail adresinize güvenlik uyarısı maili gelecek.
- Google oturum açma girişimini güvenlik nedenleri ile engelledi. Eğer Gmail ayarlarınızda düzenlemeleri yaparsanız gmail hesabınıza hata bildirim mesajlarını alabileceksiniz.
Logging to Files
- Hata mesajlarını email üzerinden almak iyi bir yöntemdir ancak her zaman ve zeminde değil. Burada hata, bilgi, debug mesajlarını dosyalarda tutmayı göreceğiz.
myapp/__init__.py
modülümüze aşağıdaki işlemleri uyguluyoruz.
xxxxxxxxxx
# ...
from logging.handlers import RotatingFileHandler
import os
# ...
if not app.debug:
# ...
if not os.path.exists('logs'):
os.mkdir('logs')
file_handler = RotatingFileHandler('logs/microblog.log', maxBytes=10240,
backupCount=10)
file_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'))
file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler)
app.logger.setLevel(logging.INFO)
app.logger.info('Microblog startup')
- Loglama işini de debug moda değilken yani production'da yapıyoruz.
- Eğer
logs
adında bir klasör yoksa os.mkdir ile oluşturuyoruz. - RotatingFileHandler loglama için kullanışlı bir sınıftır. Uzun süre çalıştığında log dosyalarının çok büyümemesini garantiye alır. Burada 10 KB olarak belirledik. Ayrıca son 10 log dosyasını da back-up için tutmasını söyledik.
logging.Formatter
loglamayı formatlı biçimde yapmamızı sağlıyor.- Burada loglama seviyesini
INFO
olarak tuttuk. Önem derecesine göreDEBUG, INFO, WARNING, ERROR ve CRITICAL
seviyeleri artıyor.
Buraya kadar hata yakalama, özel hata mesajları, email bildirimi, loglama vs. gördük. Şimdi de kullanıcı adının (username) duplication problemini giderelim.
myapp/forms.py
modülünde bir kaç değişiklik yapıyoruz.
xxxxxxxxxx
class EditProfileForm(FlaskForm):
'''Kullanıcı profil düzenleme formu'''
username = StringField('Kullanıcı Adı', validators=[DataRequired()])
about_me = TextAreaField('Hakkımda', validators=[Length(min=0, max=280)])
submit = SubmitField('Submit')
def __init__(self, original_username, *args, **kwargs):
super(EditProfileForm, self).__init__(*args, **kwargs)
self.original_username = original_username
def validate_username(self, username):
if username.data != self.original_username:
user = User.query.filter_by(username=self.username.data).first()
if user is not None:
raise ValidationError(
"Lütfen başka bir kullanıcı adı deneyin!")
- EditProfileForm sınıfı çağırıldığında parametre olarak orjinal username alacak ve
def validate_username
methodu ile kullanıcı adını database'den kontrol edecek. myapp/routes.py
modülümüzde de EditProfileForm sınıfına başlangıç değeri olarak orjinal username'i verelim.
xxxxxxxxxx
route('/edit_profile', methods=['GET', 'POST']) .
def edit_profile():
'''Kullanıcı bilgileri düzenleme sayfasını render eder'''
form = EditProfileForm(current_user.username)
- Son username testini yapalım. kayace kullanıcısı ile giriş yapıp adnan kullanıcısı ismiyle aynı kullanıcı adı vermeye çalışınca hata mesajını verdi.
Yorumlar