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_profilesayfası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=1yaparak (windows için export yerine set) uygulamamızıDEBUGmodda ç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.

DEBUGmodda 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.pymodülünü oluşturalım
xxxxxxxxxx# externalfrom flask import render_template# internalfrom 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_templatemethodundan sonra404, 500sayılarını görüyorsunuz. Varsayılan olarak200olduğ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.htmlsayfası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.htmlsayfası
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.pymodülümüzü tanıması içinmyapp/__init__.py'a import edelim
xxxxxxxxxx# ...from app import routes, models, errors- Terminalden
export FLASK_DEBUG=0yaptı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.pymodülümüze email için birkaç düzenleme yapalım.
xxxxxxxxxxclass 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
debugmodda 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@localhostTo: email_adresiniz@gmail.comSubject: Blog HataDate: Sun, 01 Apr 2018 11:52:04 +0300Content-Type: text/plain; charset="utf-8"Content-Transfer-Encoding: 7bitMIME-Version: 1.0X-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 runyaptığınız terminalde aşağıdaki işlemleri de yapınız.
xxxxxxxxxxexport MAIL_SERVER=smtp.googlemail.comexport MAIL_PORT=587export MAIL_USE_TLS=1export MAIL_USERNAME=<gmail_adresiniz>export MAIL_PASSWORD=<gmail_parolaniz>flask rundedikten 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__.pymodülümüze aşağıdaki işlemleri uyguluyoruz.
xxxxxxxxxx# ...from logging.handlers import RotatingFileHandlerimport 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
logsadı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.Formatterloglamayı formatlı biçimde yapmamızı sağlıyor.- Burada loglama seviyesini
INFOolarak tuttuk. Önem derecesine göreDEBUG, INFO, WARNING, ERROR ve CRITICALseviyeleri 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.pymodülünde bir kaç değişiklik yapıyoruz.
xxxxxxxxxxclass 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_usernamemethodu ile kullanıcı adını database'den kontrol edecek. myapp/routes.pymodü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