<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>نوشته های علیرضا آیین‌مهر</title>
        <link>https://virgool.io/feed/@DarkSun</link>
        <description>BackEnd Developer who prefers Vue but uses React</description>
        <language>fa</language>
        <pubDate>2026-06-18 02:39:18</pubDate>
        <image>
            <url>https://files.virgool.io/upload/users/21401/avatar/pfuJjo.png?height=120&amp;width=120</url>
            <title>علیرضا آیین‌مهر</title>
            <link>https://virgool.io/@DarkSun</link>
        </image>

                    <item>
                <title>مروری بر تغییرات پایتون ۳.۹</title>
                <link>https://virgool.io/@DarkSun/python-39-changes-in-a-glance-o3qluqkhi6th</link>
                <description>نسخه‌ی پایدار پایتون ۳.۹، تو تاریخ ۵ اکتبر منتشر شد، تو این پست یه مرور کوتاه و کلی روی تغییرات جدیدی که روی پایتون ۳.۹ اعمال شده با هم انجام می‌دیم.Photo by Christina Morillo from Pexels ? امکانات سینتکسی جدید— PEP 584: Union operators added to &amp;amp;amp;#x60;dict&amp;amp;amp;#x60;— PEP 585: Type hinting generics in standard collections— PEP 614: Relaxed grammar restrictions on decoratorsاین ۳تا PEP، امکانات جدید سینتکسی رو به صورت کامل توضیح می‌دن، هر کدومش رو اینجا به صورت خلاصه مرور می‌کنیم? PEP 584, union operators added to dictیه Operator یا عمل‌گر جدید برای &#x60;dict&#x60; اضافه شده، کار این عمل‌گر Merge کردن ۲ یا چند &#x60;dict&#x60; هستبرای مثال:personal_info = {&#039;name&#039;: &#039;John&#039;, &#039;lastName&#039;: &#039;Doe&#039;}address_info = {&#039;country&#039;: &#039;Doeland&#039;}اگر بخوایم این ۲ dict رو با هم یکی کنیم، تا قبل پایتون ۳.۹ با این روش انجام می‌دادیمuser_info = {**personal_info, **address_info}و مقدار &#x60;user_info&#x60; می‌شد{&#039;name&#039;: &#039;John&#039;, &#039;lastName&#039;: &#039;Doe&#039;, &#039;country&#039;: &#039;Doeland&#039;}تو نسخه‌ی ۳.۹ با استفاده از Pipe می‌تونیم همین کار رو بکنیمuser_info = personal_info | address infoبرای Assignment هم، همین Operator اضافه شده، مثال قبلی رو در نظر بگیرید، اما با این فرق که به جای &#x60;user_info&#x60; می‌خوایم، مقادیر &#x60;address_info&#x60; رو داخل &#x60;personal_info&#x60; بریزیمpersonal_info |= address_infoتوی این حالت، تمام keyهای address_info داخل personal_info هم Merge میشن، قبلا برای انجام این‌کار می‌تونستیم از متود &#x60;update&#x60; استفاده کنیمpersonal_info.update(address_info)? PEP 585 type hinting generics in standard collectionsاز پایتون ۳.۵، به پایتون Type Hinting اضافه شد.برای مثال شما یه تابع تعریف کردید به این شکلdef round_and_sum(x, y):
    return round(x) + round(y)داخل بدنه‌ی تابع، ویرایشگر یا IDE شما هیچ ایده‌ای نداره که پارامتر‌های &#x60;x&#x60; و &#x60;y&#x60; از چه نوعی هستن تا طبق Data Typeشون بهتون Auto Completion بدهبرای حل این مشکل، می‌تونیم روی تعریف پارامتر‌ها بهشون Type بدیمdef round_and_sum(x: float, y: float):
    return round(x) + rount(y)تو مشخص کردن نوع &#x60;x&#x60; و &#x60;y&#x60;، اگر داخل بدنه‌ی تابع، جلوشون نقطه بزاریم، بهمون متود‌های مختلف float رو پیشنهاد می‌ده، این موارد صرفا زمان Type Checking وجود دارن و تاثیری توی Runtime ندارنتوی این مثال، از float استفاده کردیم، که یک تایپ سادست، خودش چیزی رو داخل خودش نگه نمی‌داره، همین مثال رو با استفاده از Iteratorها می‌زنیم و با Generic Typeها آشنا می‌شیمfrom typing import Tuple
def round_and_sum(*args: Tuple[float]):
    result = 0
    for arg in args:
        result += round(arg)
    return resultبا توجه به این که &#x60;*args&#x60; یک Iterable هست از نوع Tuple، نوع خودش مشخص هست، اما نوع مقادیری که داخلش هستن مشخص نیست، باید براش از تایپ Generic که براش ارائه شده استفاده کرد، تایپ‌های Generic، یک تایپی رو به عنوان پارامتر خودشون می‌گیرن، توی مثال بالا، پارامتری که به تایپ Tuple دادم، float هستبه این شکل، اگر من داخل بدنه‌ی تابع بعدش از &#x60;args&#x60; نقطه بذارم، بهم متود‌های مربوط به &#x60;tuple&#x60; رو میده، اگر هم جلوی &#x60;args[0]&#x60; برای مثال، نقطه بذارم، بهم متود‌های &#x60;float&#x60; رو نشون میدهو اما تغییر PEP-585تا نسخه‌ی ۳.۸ برای تایپ‌های Generic باید از کتاب‌خونه‌ی Typing استفاده می‌کردید، تو نسخه‌ی ۳.۹ برای تایپ‌های Generic هم می‌تونید از همون کلاس استفاده کنیدمثال قبلی رو تو پایتون ۳.۹ می‌نویسمdef round_and_sum(*args: tuple[float]):
    result = 0
    for arg in args:
        result += round(arg)
    return resultهمینطور که مشخصه، دیگه نیاز به Import نداره? PEP 614, Relaxed grammar restrictions on decoratorsتا قبل از پایتون ۳.۹، اگر داخل یه Class، یه Decorator تعریف می‌کردید، نمی‌تونستید هم‌زمان هم از Decorator استفاده کنید و هم از Classتون Object بسازیدیه مثال می‌زنم ساده‌تر درک کنیدclass Test:
    def __init__(self, namespace):
        self.namespace = namespace

    def my_decorator(self, function):
        def wrapper(*args, **kwargs):
            print(f&#039;{self.namespace} =&gt; {function.__name__} is running.&#039;)
            return function(*args, **kwargs)
        return wrapperمن اینجا یه کلاس تعریف کردم، که یه پارامتر به نام &#x60;namespace&#x60; می‌گیره، داخل کلاسم یه Decorator تعریف کردم که کارش صرفا این هست که میگه کدوم تابع با کدوم namespace اجرا شدهحالا می‌خوایم از این decorator تو کد‌های قبل نسخه‌ی ۳.۹ استفاده کنیم@Test(namespace=&#039;Testing Environment&#039;).my_decorator
def add(x, y):
    return x + y خط اول، از کلاسم یه Object ساختم و &quot;Testing Environment&quot; رو بهش به عنوان namespace دادم، در ادامه هم متود &#x60;my_decorator&#x60; رو ازش استفاده کردماما در صورتی که این کد رو اجرا کنم با خطای زیر مواجه میشمFile &quot;/tmp/py3.9.py&quot;, line 11    @Test(namespace=&#x27;Testing Environment&#x27;).my_decorator                                                                        ^SyntaxError: invalid syntaxتو نسخه‌های قبل ۳.۹، امکان استفاده از نقطه بعد از پرانتز به عنوان Decorator وجود نداشت، تو نسخه‌ی ۳.۹ این امکان اضافه شدهکدی که بالاتر نوشتم، روی پایتون ۳.۹ کار می‌کنهبرای این که روی نسخه‌های قبلی هم کار کنه، باید به این شکل نوشته بشهtest_env = Test(namespace=&#039;Testing Environment&#039;)

@test_env.my_decorator
def add(x, y):
    return x + yساخته شدن Object به صورت جدا انجام شد و از Decorator هم تونستیم استفاده کنیم? متود‌های جدید— PEP 616: string methods to remove prefixes and suffixes.۲تا متود خیلی خوب و جالب به &#x60;str&#x60; اضافه شده.&#x60;str.removeprefix&#x60;: حذف پیش‌وند از رشته&#x60;str.removesuffix&#x60;: حذف پس‌وند از رشتهبرای مثال یه سری آدرس سایت داریم و می‌خوایم همشون رو یه شکل بکنیم، اگر با www. شروع می‌شن، این www. رو ازشون حذف کنیم و اگر با / تموم میشن، / رو هم ازشون حذف کنیمaddresses = [&amp;quotwww.google.com&amp;quot, &amp;quotbing.com/&amp;quot, &amp;quotduckduckgo.com&amp;quot]
normalized_addresses = []
for address in addresses:
    normalized_address = address.removeprefix(&#039;www.&#039;)  # without www.
    normalized_address = normalized_address.removesuffix(&#039;/&#039;)  # without trailing slash
    normalized_addresses.append(normalized_address)در نهایت اگر normalized_addresses رو &#x60;print&#x60; بگیریم، می‌بینیم که www. ها و / های بعد از آدرس رو حذف کرده!? کتاب‌خانه‌ی zoneinfo به صورت Standardبه نظر من، این یکی از خوشحال‌کننده‌ترین مواردی بود انجام شد، تو نسخه‌های قبلی نیاز بود از کتاب‌خونه‌هایی مثل &#x60;pytz&#x60; استفاده کنید تا بتونید اطلاعات Timezone های دیگه رو داشته باشیدتو نسخه‌ی ۳.۹، یه کتاب‌خونه‌ی پیش‌فرض و استاندارد به نام &#x60;zoneinfo&#x60; اضافه شده که این اطلاعات رو بهتون میده و دیگه نیاز ندارید چندتا کتاب‌خونه‌ی مختلف برای اطلاعات Timezoneهای مختلف نصب کنید.from zoneinfo import ZoneInfo
from datetime import datetime, timedelta
dt = datetime(2020, 10, 31, 12, tzinfo=ZoneInfo(&amp;quotAsia/Tehran&amp;quot))  # Output as string: 2020-10-31 12:00:00+03:30
dt.tzname()  # Output: &amp;quot+0330&amp;quotکلی تغییرات باحال و خفن دیگه وجود داره که اینجا پوشش نمی‌دمشون، اگر علاقه دارید، توصیه می‌کنم حتما و حتما Whats New in Python 3.9 رو داخل مستندات پایتون بررسی کنیدبهینه‌سازی‌های خیلی زیادی انجام شده، سرعت عمل‌کرد و کارایی تا حد زیادی بهبود پیدا کردهحتا یه PEP صرفا برای برنامه‌ی انتشار پایتون نوشته و تایید شدهطبق PEP 602، هر سال، ماه October یه نسخه‌ی پایدار از پایتون منتشر میشهبرای مثال سال آینده،یک‌ماه قبل‌تر از الآن پایتون ۳.۱۰ منتشر میشه و در موردش می‌نویسم :)PEP 602 - Python annual release cycle</description>
                <category>علیرضا آیین‌مهر</category>
                <author>علیرضا آیین‌مهر</author>
                <pubDate>Wed, 25 Nov 2020 08:07:25 +0330</pubDate>
            </item>
                    <item>
                <title>ذخیره‌سازی گزارش درخواست‌ها در Flask</title>
                <link>https://virgool.io/@DarkSun/flask-logging-requests-dir4jqa28oyh</link>
                <description>ذخیره‌سازی گزارش درخواست‌ها در Flaskذخیره‌کردن گزارش انواع درخواست‌ها برای یه برنامه‌ی‌تحت‌وب تقریبا ضروریه، از طریق همین گزارش‌ها می‌تونیم به مشکلات و باگ های احتمالی برناممون پی ببریم و اون هارو حلشون کنیم.تو این پست قصد دارم نحوه ذخیره‌کردن گزارش درخواست‌های یه Web Application که با فریم‌ورک Flask نوشته شده رو توضیح بدم.کسایی که حوصله ندارن توضیحات رو بخونن، می‌تونن به آخر پست مراجعه کنن، یه Snippet از تمام کد هایی که می‌نویسیم قرار میدم. نحوه پیاده‌سازیش بستگی داره به ساختار و سازماندهی کد‌های‌برنامتون، من ساده‌ترین حالت رو توضیح میدم که توش یه فایل &#x60;app.py&#x60; داره پروژمون و تمام کد‌هامون داخل همون قرار می‌گیره.# app.py
from flask import Flask

app = Flask(__name__)

@app.route(&#039;/&#039;)
def index():
    return &#039;Hello world!&#039;
    
if __name__ == &#039;__main__&#039;:
    app.run(debug=True)خوشبختانه Flask خودش از کتابخونه‌ی &#x60;Logging&#x60; برای گزارش دادن استفاده می‌کنه و ما هم می‌تونیم از همون برای ذخیره‌سازی گزارش‌هامون استفاده کنیم.یه نکته‌ی جدا از بحث: وقتی که Django یاد می‌گرفتم، دیدم تو فایل &#x60;settings.py&#x60; یه متغیر داره به نام &#x60;BASE_DIR&#x60; که آدرس مربوط به دایرکتوری پروژه رو نگه می‌داره، این متغیر خیلی کار هارو می‌تونه آسون‌تر کنه و بهتون پیشنهاد می‌کنم شما هم ازش استفاده کنید! من این متغیر رو &#x60;app.config&#x60; اضافه می‌کنم، شما هم دوست داشتید این کار رو بکنید، جلو‌تر ازش استفاده می‌کنیمimport os

# ...

app.config[&#039;BASE_DIR&#039;] = os.path.abspath(os.path.dirname(__file__))برای این که کدمون یکم تمیز‌تر باشه، به &#x60;app.config&#x60; چند‌تا کلید دیگه اضافه می‌کنیمapp.config[&#039;ENABLE_LOGGING&#039;] = True
app.config[&#039;LOGS_DIR&#039;] = os.path.join(app.config[&#039;BASE_DIR&#039;], &#039;logs/&#039;)
app.config[&#039;REQUEST_LOG_FILE&#039;] = os.path.join(app.config[&#039;LOGS_DIR&#039;], &#039;requests.log&#039;)از &#x60;ENABLE_LOGGING&#x60; برای فعال‌سازی و غیر‌فعال‌سازی گزارشات استفاده می‌کنیم، به این شکل دیگه نیاز نیست هر‌بار که می‌خوایم گزارشات ذخیره نشن، قسمتی از کد رو Comment کنیم، با تغییر مقدار &#x60;ENABLE_LOGGING&#x60; به &#x60;False&#x60; این اتفاق میوفتهکلید &#x60;LOGS_DIR&#x60; به پوشه‌ای که گزارشات داخلش ذخیره میشن اشاره می‌کنه و &#x60;REQUEST_LOG_FILE&#x60; هم نام فایل مربوط به گزارش‌هاستfrom logging import Formatter, INFO
from logging.handlers import RotatingFileHandler

# ...

if app.config[&#039;ENABLE_LOGGING&#039;]:

    if not os.path.exists(app.config[&#039;LOGS_DIR&#039;]):
        os.mkdir(app.config[&#039;LOGS_DIR&#039;])

    file_handler = RotatingFileHandler(app.config[&#039;REQUEST_LOG_FILE&#039;], maxBytes=10240, backupCount=10)
    file_handler.setFormatter(Formatter(&#039;%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]&#039;))
    file_handler.setLevel(INFO)تو قطعه کد بالا اول بررسی می‌کنیم در صورتی که &#x60;ENABLE_LOGGING&#x60; مقدارش True هست کد‌های مربوط به ذخیره‌سازی گزارش‌ها اجرا بشن؛ در مرحله بعد بررسی می‌کنیم که پوشه مربوط به گزارش‌ها وجود داره یا نه!؟ اگر وجود نداشت، ساخته می‌شهبعد از ساختن پوشه، متغیر &#x60;file_handler&#x60; رو ایجاد کردیم، یه Object از کلاس RotatingFileHanlder، این کلاس، یه فایل رو به عنوان ورودی می‌گیره، ۲ پارامتر دیگه هم براش تعریف کردیم، &#x60;maxBytes&#x60; به حد‌اکثر حجم هر Log File اشاره داره و &#x60;backupCount&#x60; به تعداد حد‌اکثر فایل های گزارش، یه این معنی که برای مثال یه فایل با نام &#x60;requests.log&#x60; که بالاتر تعریفش کردیم ایجاد میشه، بعد از رسیدن به حجم ۱۰۲۴۰ بایت که تعریف کردیم، به &#x60;requests.log.1&#x60; تغییر نام داده میشه، فایل جدیدی مجددا به اسم &#x60;requests.log&#x60; ایجاد میشه، این عملیات تا موقعی که به فایل &#x60;requests.log.10&#x60; برسیم ادامه داره، این عدد ۱۰ رو با &#x60;backupCount&#x60; تعریف کردیم، بعد از رسیدن به این عدد مجددا این کار رو از شماره ۱ انجام میده و فایل های قبلی پاک میشن..تو خط بعدی فرمت مربوط به ذخیره‌سازی هر گزارش تو فایلمون رو توسط متود &#x60;setFormatter&#x60; مشخص کردیم، در مورد کلاس Formatter، تو مستندات کتاب‌خونه‌ی Logging می‌تونید اطلاعات بیشتری کسب کنید؛ اینجا ما بهش گفتیم که تاریخ، سطح گزارش ( Error, Debugging, Warning, Info و ... )، پیام گزارش و خطی که تو برناممون گزارش رو ایجاد کرده برامون تو فایل گزارش‌ها بنویسه، قسمت آخر که مربوط به خطی میشه که برامون گزارش رو ایجاد کرده تو این آموزش خیلی به‌دردمون نمی‌خوره، اما خوب میزاریم باشه، شاید بعدا استفاده کردید ازش..با استفاده از متور &#x60;setLevel&#x60;، سطح گزارش رو تعریف می‌کنیم، سطوح مختلف رو می‌تونید اینجا ببینید، سطحی که ما تعریف کردیم Info هست و تمام اتفاقات رو گزارش میده، در صورتی که این سطح رو برای مثال Error تعریف کنیم، تنها زمان هایی که برنامه با خطا مواجه میشه برامون ذخیرش می‌کنه، تو لینکی که بالاتر دادم می‌تونید بیشتر در موردش مطالعه کنید.if app.config[&#039;ENABLE_LOGGING&#039;]:

    # ...

    app.logger.addHandler(file_handler)
    app.logger.setLevel(INFO)
    app.logger.info(&#039;LOG START UP&#039;)در ادامه کدهایی که زدیم از &#x60;app.logger&#x60; استفاده می‌کنیم، این متود مربوط میشه به قسمتی که گزارشات Flask رو نشون میده، برای مثال وقتی FLASK_ENVIRONMENT رو روی Development تنظیم می‌کنیم و یا Debug مربوط به برناممون رو فعال می‌کنیم، تمام درخواست‌ها و خطا‌هایی که به وجود میان رو داخل کنسول برامون می‌نویسه، Flask هم از همین کتاب‌خونه Logging استفاده می‌کنهبا استفاده از &#x60;app.logger.addHandler&#x60; اومدیم گفتیم از &#x60;file_handler&#x60; که بالاتر ایجاد کردیم به عنوان Handler برای برناممون استفاده کنه و گزارش‌ها رو براش ارسال کنه، متود &#x60;setLevel&#x60; چیز جدیدی نداره، همونطور که قبلا استفاده کردیم ازش، اینجا هم اومدیم سطح گزارش‌های برناممون رو به Info تغییر دادیمتو قسمت آخر اومدیم به عنوان گزارش با سطح Info، پیامی با محتوای &#x60;LOG START UP&#x60; نوشتیم، الآن اگر برنامه Flaskتون رو اجرا کنید و &#x60;ENABLE_LOGGING&#x60; هم True باشه، باید تو محیط کنسول این پیام رو ببینید، یه نکته هست که اگر Environment روی Development باشه، این پیام رو دوبار می‌بینید، این باگ یا مشکل نیست، Flask وقتی برنامه رو با این Environment اجرا می‌کنید، یک‌بار اجرا می‌کندش و مجددا برای فعال‌سازی Debugger برنامه رو Restart می‌کنهخوب، تا اینجا مقدمات کاری که می‌خوایم انجام بدیم، فراهم کردیم، در حال حاضر گزارش خاصی ذخیره نمیشه، بهتره بگم در حال حاضر، هیچ چیز به درد بخوری ذخیره نمیشه، وقتشه یه مقدار پیشرفته ترش کنیم گزارشگرمون رومن خودم برای پروژم از یه‌سری کد استفاده کردم که گزارش‌هارو رنگی رنگی نشونم میده، خیلیا ممکنه از ویندوز استفاده کنن، به احتمال زیاد CMDشون از اون رنگ‌ها پشتیبانی نمی‌کنه، برای همین ادامه آموزش رو تو حالتی می‌گم که همه‌چیز خیلی سادست، اما آخر آموزش یه لینک قرار می‌دم که راجع به استفاده از رنگ‌ها توی گزارش‌ها توضیح بده...خوشبختانه تو Flask دو تا Decorator داریم به اسم &#x60;before_request&#x60; و &#x60;after_request&#x60;، با استفاده از این Decorator‌ها می‌تونیم به برناممون بگیم وقتی که یه درخواست رو دریافت کرد و زمانی که قصد داره به یه درخواست پاسخ بده چه کار هایی انجام بدهfrom flask import g
from time import time as time_now

# ...

@app.before_request
def before_reqeusts():
    if app.config[&#039;ENABLE_LOGGING&#039;]:
        g.start = time_now()تو کد بالا اومدیم تعریف کردیم، زمانی که یه درخواست ارسال شد، بررسی کنه اگر &#x60;ENABLE_LOGGING&#x60; مقدارش True بود، داخل global های برنامه، که برای هر کاربر جدا هستن، بیاد یه متغیر تعریف کنه به اسم &#x60;start&#x60; و زمان دریافت درخواست به Unix رو داخلش نگه داره، از این متغیر بعدا برای محاسبه زمان ارسال پاسخ استفاده می‌کنیمدقت کنید که تابع before_requests رو خارج از شرطی که قبلا نوشته بودیم نوشتم، دلیلش هم اینه که داخل این تابع می‌تونید هرکار دیگه‌ای به جزء ذخیره کردن گزارش‌ها انجام بدید، اگر داخل اون شرط تعریفش می‌کردیم، اگر &#x60;ENABLE_LOGGING&#x60; رو می‌خواستیم غیرفعال کنیم، دیگه کار های دیگه‌ای که بهش اضافه کردیم انجام نمی‌شد.from ask import request
from rfc3339 import rfc3339
from json import loads as json_loads
import datetime

# ...

@app.after_request
def after_requests(response):
    if app.config[&#039;ENABLE_LOGGING&#039;]:
    
        if request.path == &#039;/favicon.ico&#039; or request.path.startswith(&#039;/static&#039;):
            return response
        
        now = time_now()
        duration = round(now - g.start, 2)
        dt = datetime.datetime.fromtimestamp(now)
        timestamp = rfc3339(dt, utc=True)
        ip = request.headers.get(&#039;X-Forwarded-For&#039;, request.remote_addr)
        host = request.host.split(&#039;:&#039;, 1)[0]
        args = dict(request.args)
        
        log_params = [
        (&#039;method&#039;, request.method),
        (&#039;path&#039;, request.path),
        (&#039;status&#039;, response.status_code),
        (&#039;duration&#039;, duration),
        (&#039;time&#039;, timestamp),
        (&#039;ip&#039;, ip),
        (&#039;host&#039;, host),
        (&#039;params&#039;, args),
        ]
        
        parts = [&quot;{}={}&quot;.format(name, value) for name, value in log_params]

        line = &quot; | &quot;.join(parts)        
        app.logger.info(line)
        
    return responseکد بالا مربوط میشه به عملیاتی که بعد از دریافت درخواست، موقع ارسال پاسخ انجام میدیم، به نظر زیاد میاد، اما خیلی کد ساده‌ایه، قدم به قدم جلو میریممرحله اول طبق معمول بررسی کردیم که ذخیره‌سازی گذارشات فعال هست یا نه، اگر فعال نباشه پاسخ ارسال میشه و هیچ‌کدوم از خط‌های بعدی که برای ذخیره‌سازی نوشته شدن اجرا نمیشنیه سری متغیر تعریف کردیم:متغیر now: زمان ارسال پاسخ به Unixمتغیر duration: زمانی که برنامه‌مون به پردازش درخواست و ارسال پاسخ اختصاص دادمتغیر dt: یه Object از نوع datetime که استفاده از متغیر now ساخته شدهمتغیر timestamp: از متغیر dt زمان رو در فرمت RFC3339 یا ISO-8601 با نوع String می‌سازهمتغیر ip: آدرس IP رو از Header درخواست دریافت‌شده با عنوان &#x60;X-Forwarded-For&#x60; میگیره، اکثر اوقات همون آی‌پی که برناممون روش اجرا شده رو برمی‌گردونهمتغیر host: نام hostی که درخواست بهش ارسال شده رو نگه می‌داره، معمولا اگر از طریق IP، به برنامه دسترسی داشته باشن، همون مقدار متغیر ip رو نشون میدهمتغیر args: پارامتر‌هایی که توسط متود GET یا با URL ارسال میشن رو به عنوان Dictionary نگه می‌داره، دقت کنید که این متغیر، محتوایی که به عنوان Request Body با متود POST یا متود‌های مشابه ارسال میشن رو نشون نمیده، این مورد رد می‌تونید خودتون با توجه به برنامتون و مستندات Flask اضافه کنید.متغیر log_params: تمام متغیر‌هایی که تعریف کردیم، هرچیزی که می‌خوایم تو گزارشات ذخیره بشه، توی این متغیر که یه لیست هست قرار میگیره، هر عضوش یه Tupple هست که ۲ عضو دارن، عضو اول نامی که باهاش چیزی رو تو گزارشات نشون میده، عضو دوم مقدارش، برای مثال &#x60;(&#x27;params&#x27;, args)&#x60; برامون محتوای args رو با عنوان params ذخیره می‌کنهمتغیر parts: این متغیر هم یه لیست هست، تو این لیست تمام محتوای مربوط به log_params به این شکل در میان =&gt; &#x60;name=value&#x60;، تو log_params مثال params رو زدیم، تو این لیست تبدیل میشه به چیزی شبیه به این: &#x60;params={args in dict}&#x60;متغیر line: این متغیر لیست parts رو تبدیل به یه رشته می‌کنه و بین هر عضو &quot; | &quot; رو قرار میده، نتیجه نهایی گزارش هر درخواست همین متغیره که تو خط بعدیش یعنی &#x60;app.logger.info(line)&#x60; همین رشته رو به عنوان گزارش ذخیره می‌کنیمودر آخر هم response رو بر می‌گردونیمبهتون تبریک می‌گم، تقریبا کارمون تمومهحالا اگر برنامه رو اجرا کنید و یه درخواست به صفحه اصلی یعنی http://localhost:5000 بدید، توی کنسولتون یه خط گزارش می‌نویسه، از همین چیزایی که ما نوشتیم به وجود اومده، برای امتحان کردن args هم می‌تونید یه آدرس http://localhost:5000/?hello=world&amp;amp;name=DarkSun بفرستید تا گزارش رو همراه با پارامتر های ارسالی روی URL بهتون نشون بده، گزارشات توی پوشه logs، که تو پوشه پروژتون هست ذخیره میشنکل کدی که نوشتیم رو می‌تونید تو این مخزن Github مشاهده کنیدمنابعی که ازشون کمک گرفتم:The Flask Mega-Tutorial Part VII: Error Handling  =&gt; نحوه به‌دست آوردنش گزارش‌ها مختلف رو توضیح میدهLogging Flask requests with structure and colors =&gt; نحوه ذخیره‌سازی گزارش‌ها به صورت شخصی‌سازی شده رو توضیح میده، در مورد استفاده از رنگ‌ها تو گزارش‌ها هم از همین استفاده کنیدLogging, Flask, and Gunicorn … the Manageable Way =&gt; تو این آموزش ازش استفاده نکردم، اما اگر دوست دارید گزارش‌های کامل‌تری داشته باشید، پیشنهادش می‌کنماین آموزش، اولین پستم توی ویرگول بود، خوشحال میشم دیدگاهتون رو راجع بهش بدونمموفق باشید.</description>
                <category>علیرضا آیین‌مهر</category>
                <author>علیرضا آیین‌مهر</author>
                <pubDate>Sat, 24 Nov 2018 03:53:05 +0330</pubDate>
            </item>
            </channel>
</rss>