การตั้งค่าการบันทึก Python อย่างหรูหราใน Django


101

ฉันยังไม่พบวิธีตั้งค่าการบันทึก Python ด้วย Django ที่ฉันพอใจ ข้อกำหนดของฉันค่อนข้างง่าย:

  • ตัวจัดการบันทึกที่แตกต่างกันสำหรับเหตุการณ์ที่แตกต่างกันนั่นคือฉันต้องการที่จะสามารถบันทึกไปยังไฟล์ต่างๆ
  • เข้าถึงคนตัดไม้ในโมดูลของฉันได้อย่างง่ายดาย โมดูลควรจะสามารถค้นหาคนตัดไม้ได้โดยใช้ความพยายามเพียงเล็กน้อย
  • ควรใช้กับโมดูลบรรทัดคำสั่งได้อย่างง่ายดาย ส่วนต่างๆของระบบคือบรรทัดรับคำสั่งแบบสแตนด์อะโลนหรือกระบวนการดีมอน การบันทึกควรใช้กับโมดูลเหล่านี้ได้อย่างง่ายดาย

การตั้งค่าปัจจุบันของฉันคือการใช้logging.confไฟล์และการบันทึกการตั้งค่าในแต่ละโมดูลที่ฉันเข้าสู่ระบบ มันไม่ถูกปาก

คุณมีการตั้งค่าการบันทึกที่คุณชอบหรือไม่? โปรดดูรายละเอียด: คุณตั้งค่าการกำหนดค่าอย่างไร (คุณใช้logging.confหรือตั้งค่าในรหัส) คุณเริ่มต้นคนตัดไม้ที่ไหน / เมื่อใดและคุณจะเข้าถึงได้อย่างไรในโมดูลของคุณ ฯลฯ


1
คุณอาจพบต่อไปนี้ screencast ประโยชน์ - ericholscher.com/blog/2008/aug/29/... นอกจากนี้การสนับสนุนที่ดีกว่าสำหรับการเข้าสู่ระบบ Django ได้รับการเสนอโดย Simon Willison (ดูsimonwillison.net/2009/Sep/28/ponies )
Dominic Rodger

@Dominic Rodger - คุณสามารถบันทึกแอพที่ยืดหยุ่นได้แล้วใน Django ซึ่งเป็นข้อเสนอของ Simon เพื่ออำนวยความสะดวกในการเข้าสู่ระบบภายใน Django เป็นหลัก มีการทำงานใน Python เพื่อเพิ่มการกำหนดค่าตามพจนานุกรมในการบันทึก Python ซึ่ง Django อาจได้รับประโยชน์
Vinay Sajip

คำตอบ:


58

วิธีที่ดีที่สุดที่ฉันพบคือการเริ่มต้นการตั้งค่าการบันทึกใน settings.py - ไม่มีที่ไหนเลย คุณสามารถใช้ไฟล์คอนฟิกูเรชันหรือทำแบบโปรแกรมทีละขั้นตอนก็ได้ขึ้นอยู่กับความต้องการของคุณ สิ่งสำคัญคือฉันมักจะเพิ่มตัวจัดการที่ฉันต้องการลงใน root logger โดยใช้ระดับและบางครั้งการบันทึกตัวกรองเพื่อรับเหตุการณ์ที่ฉันต้องการไปยังไฟล์คอนโซล syslogs และอื่น ๆ ที่เหมาะสมแน่นอนคุณสามารถเพิ่มตัวจัดการให้กับตัวบันทึกอื่น ๆ ได้ เช่นกัน แต่โดยทั่วไปแล้วฉันไม่จำเป็นต้องมีประสบการณ์นี้

ในแต่ละโมดูลฉันกำหนดคนตัดไม้โดยใช้

logger = logging.getLogger(__name__)

และใช้เพื่อบันทึกเหตุการณ์ในโมดูล (และถ้าฉันต้องการแยกความแตกต่างเพิ่มเติม) ให้ใช้คนตัดไม้ซึ่งเป็นลูกของคนตัดไม้ที่สร้างขึ้นด้านบน

หากแอปของฉันจะถูกใช้ในไซต์ที่ไม่ได้กำหนดค่าการเข้าสู่ระบบ settings.py ฉันจะกำหนด NullHandler ไว้ที่ใดที่หนึ่งดังนี้:

#someutils.py

class NullHandler(logging.Handler):
    def emit(self, record):
        pass

null_handler = NullHandler()

และตรวจสอบให้แน่ใจว่ามีการเพิ่มอินสแตนซ์ให้กับตัวบันทึกทั้งหมดที่สร้างขึ้นในโมดูลในแอปของฉันซึ่งใช้การบันทึก (หมายเหตุ: NullHandler อยู่ในแพ็คเกจการบันทึกสำหรับ Python 3.1 แล้วและจะอยู่ใน Python 2.7) ดังนั้น:

logger = logging.getLogger(__name__)
logger.addHandler(someutils.null_handler)

สิ่งนี้ทำเพื่อให้แน่ใจว่าโมดูลของคุณเล่นได้ดีในไซต์ที่ไม่ได้กำหนดค่าการเข้าสู่ระบบ settings.py และคุณจะไม่ได้รับข้อความ "ไม่พบตัวจัดการสำหรับคนตัดไม้ XYZ" ที่น่ารำคาญ (ซึ่งเป็นคำเตือนเกี่ยวกับ การบันทึกที่กำหนดค่าไม่ถูกต้อง)

การทำเช่นนี้ตรงตามข้อกำหนดที่คุณระบุไว้:

  • คุณสามารถตั้งค่าตัวจัดการบันทึกที่แตกต่างกันสำหรับเหตุการณ์ต่างๆได้เช่นเดียวกับที่คุณทำในปัจจุบัน
  • ง่ายต่อการเข้าถึงการตัดไม้ในโมดูลของคุณ - getLogger(__name__)การใช้งาน
  • ได้อย่างง่ายดายที่ใช้บังคับกับโมดูลบรรทัดคำสั่ง - settings.pyพวกเขายังนำเข้า

ปรับปรุง:หมายเหตุว่าเป็นของรุ่น 1.3 Django ในขณะนี้ประกอบด้วยการสนับสนุนสำหรับการเข้าสู่ระบบ


สิ่งนี้ไม่ต้องการให้ทุกโมดูลมีตัวจัดการที่กำหนดไว้ใน config (คุณไม่สามารถใช้ตัวจัดการสำหรับ foo เพื่อจัดการ foo.bar) ได้หรือไม่? ดูบทสนทนาเมื่อหลายปีก่อนได้ที่groups.google.com/group/comp.lang.python/browse_thread/thread/…
andrew cooke

1
@andrew Cooke: คุณสามารถใช้จัดการสำหรับกับเหตุการณ์จับบันทึกลงในfoo foo.barเรื่อง เธรดนั้น - ตอนนี้ทั้ง fileConfig และ dictConfig มีตัวเลือกในการป้องกันการปิดใช้งานตัวบันทึกแบบเก่า ดูปัญหานี้: bugs.python.org/issue3136ซึ่งเกิดขึ้นในสองสามเดือนหลังจากปัญหาของคุณbugs.python.org/issue2697 - อย่างไรก็ตามปัญหานี้ได้รับการจัดการตั้งแต่เดือนมิถุนายน 2551
Vinay Sajip

มันจะไม่ดีกว่าการทำlogger = someutils.getLogger(__name__)ที่someutils.getLoggerผลตอบแทนตัดไม้จากlogging.getLoggerกับ null_handler เพิ่มอยู่แล้ว?
7yl4r

1
@ 7yl4r คุณไม่จำเป็นต้องมีคนตัดไม้ทุกคนที่มีการNullHandlerเพิ่ม - โดยปกติจะเป็นเพียงคนตัดไม้ระดับบนสุดสำหรับลำดับชั้นของแพ็คเกจของคุณ นั่นจะเป็นการใช้งานมากเกินไป IMO
Vinay Sajip

123

ฉันรู้ว่านี่เป็นคำตอบที่ได้รับการแก้ไขแล้ว แต่ตาม django> = 1.3 มีการตั้งค่าการบันทึกใหม่

การย้ายจากเก่าไปใหม่ไม่ใช่เรื่องอัตโนมัติดังนั้นฉันคิดว่าฉันจะเขียนมันไว้ที่นี่

และแน่นอนตรวจสอบ django docสำหรับข้อมูลเพิ่มเติม

นี่คือคอนฟิกพื้นฐานที่สร้างขึ้นโดยค่าเริ่มต้นด้วย django-admin createproject v1.3 - ระยะทางอาจเปลี่ยนแปลงด้วย django เวอร์ชันล่าสุด:

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'mail_admins': {
            'level': 'ERROR',
            'class': 'django.utils.log.AdminEmailHandler',
        }
    },
    'loggers': {
        'django.request': {
            'handlers': ['mail_admins'],
            'level': 'ERROR',
            'propagate': True,
        }
    }
}

โครงสร้างนี้ยึดตามมาตรฐานPython logging dictConfigซึ่งกำหนดบล็อกต่อไปนี้:

  • formatters - ค่าที่สอดคล้องกันจะเป็นคำสั่งซึ่งแต่ละคีย์เป็นรหัสตัวจัดรูปแบบและแต่ละค่าเป็นคำสั่งที่อธิบายวิธีกำหนดค่าอินสแตนซ์ฟอร์แมตเตอร์ที่เกี่ยวข้อง
  • filters - ค่าที่เกี่ยวข้องจะเป็นคำสั่งซึ่งแต่ละคีย์เป็นรหัสตัวกรองและแต่ละค่าเป็นคำสั่งที่อธิบายวิธีกำหนดค่าอินสแตนซ์ตัวกรองที่เกี่ยวข้อง
  • handlers- ค่าที่เกี่ยวข้องจะเป็นคำสั่งซึ่งแต่ละคีย์เป็นรหัสตัวจัดการและแต่ละค่าเป็นคำสั่งที่อธิบายวิธีการกำหนดค่าอินสแตนซ์ตัวจัดการที่เกี่ยวข้อง ตัวจัดการแต่ละตัวมีคีย์ต่อไปนี้:

    • class(บังคับ) นี่คือชื่อแบบเต็มของคลาสตัวจัดการ
    • level(ไม่จำเป็น). ระดับของตัวจัดการ
    • formatter(ไม่จำเป็น). รหัสของฟอร์แมตเตอร์สำหรับตัวจัดการนี้
    • filters(ไม่จำเป็น). รายการรหัสของตัวกรองสำหรับตัวจัดการนี้

ฉันมักจะทำสิ่งนี้อย่างน้อยที่สุด:

  • เพิ่มไฟล์. log
  • กำหนดค่าแอปของฉันให้เขียนลงในบันทึกนี้

ซึ่งแปลเป็น:

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
        },
        'simple': {
            'format': '%(levelname)s %(message)s'
        },
    },
    'filters': {
        'require_debug_false': {
            '()': 'django.utils.log.RequireDebugFalse'
        }
    },
    'handlers': {
        'null': {
            'level':'DEBUG',
            'class':'django.utils.log.NullHandler',
        },
        'console':{
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        # I always add this handler to facilitate separating loggings
        'log_file':{
            'level': 'DEBUG',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': os.path.join(VAR_ROOT, 'logs/django.log'),
            'maxBytes': '16777216', # 16megabytes
            'formatter': 'verbose'
        },
        'mail_admins': {
            'level': 'ERROR',
            'filters': ['require_debug_false'],
            'class': 'django.utils.log.AdminEmailHandler',
            'include_html': True,
        }
    },
    'loggers': {
        'django.request': {
            'handlers': ['mail_admins'],
            'level': 'ERROR',
            'propagate': True,
        },
        'apps': { # I keep all my of apps under 'apps' folder, but you can also add them one by one, and this depends on how your virtualenv/paths are set
            'handlers': ['log_file'],
            'level': 'INFO',
            'propagate': True,
        },
    },
    # you can also shortcut 'loggers' and just configure logging for EVERYTHING at once
    'root': {
        'handlers': ['console', 'mail_admins'],
        'level': 'INFO'
    },
}

แก้ไข

ดูข้อยกเว้นของคำขอจะถูกบันทึกไว้เสมอและตั๋ว # 16288 :

ฉันอัปเดตคอนฟิกตัวอย่างข้างต้นเพื่อรวมตัวกรองที่ถูกต้องสำหรับ mail_admins ไว้อย่างชัดเจนดังนั้นโดยค่าเริ่มต้นอีเมลจะไม่ถูกส่งเมื่อการดีบักเป็น True

คุณควรเพิ่มตัวกรอง:

'filters': {
    'require_debug_false': {
        '()': 'django.utils.log.RequireDebugFalse'
    }
},

และนำไปใช้กับตัวจัดการ mail_admins:

    'mail_admins': {
        'level': 'ERROR',
        'filters': ['require_debug_false'],
        'class': 'django.utils.log.AdminEmailHandler',
        'include_html': True,
    }

มิฉะนั้นdjango.core.handers.base.handle_uncaught_exceptionจะไม่ส่งข้อผิดพลาดไปยังตัวบันทึก 'django.request' หากการตั้งค่า DEBUG เป็น True

หากคุณไม่ทำสิ่งนี้ใน Django 1.5 คุณจะได้รับไฟล์

DeprecationWarning: คุณไม่มีตัวกรองที่กำหนดไว้ในตัวจัดการการบันทึก 'mail_admins': การเพิ่มตัวกรองการดีบักโดยนัย - เท็จเท่านั้น

แต่สิ่งต่างๆจะยังคงทำงานได้อย่างถูกต้องทั้งใน django 1.4 และ django 1.5

** สิ้นสุดการแก้ไข **

conf นั้นได้รับแรงบันดาลใจอย่างมากจากตัวอย่าง conf ใน django doc แต่เพิ่มส่วนของไฟล์บันทึก

ฉันมักจะทำสิ่งต่อไปนี้:

LOG_LEVEL = 'DEBUG' if DEBUG else 'INFO'

...
    'level': LOG_LEVEL
...

จากนั้นในรหัส python ของฉันฉันจะเพิ่ม NullHandler เสมอในกรณีที่ไม่มีการกำหนด Conf การบันทึกใด ๆ สิ่งนี้หลีกเลี่ยงคำเตือนสำหรับไม่ได้ระบุ Handler มีประโยชน์อย่างยิ่งสำหรับ libs ที่ไม่จำเป็นต้องเรียกเฉพาะใน Django ( ref )

import logging
# Get an instance of a logger
logger = logging.getLogger(__name__)
class NullHandler(logging.Handler): #exists in python 3.1
    def emit(self, record):
        pass
nullhandler = logger.addHandler(NullHandler())

# here you can also add some local logger should you want: to stdout with streamhandler, or to a local file...

[... ]

logger.warning('etc.etc.')

หวังว่านี่จะช่วยได้!


Stefano ขอบคุณมากสำหรับคำตอบโดยละเอียดมีประโยชน์มาก สิ่งนี้อาจทำให้คุ้มค่ากับการอัปเกรดเป็น 1.3
Parand

Parand มันเป็น (IMHO!) ที่ควรค่าแก่การก้าวขึ้นสู่ django 1.3 แม้ว่าจะมีบางประเด็นที่ต้องดูแลเพื่อการเปลี่ยนแปลงที่ราบรื่น - เปิดคำถาม SO ใหม่หากคุณมีปัญหา ;-)
Stefano

โดยวิธีการ: ฉันยังคงใช้การตั้งค่าประเภทนี้และบันทึกไฟล์ แต่ฉันย้ายไปที่ยามเพื่อการผลิต!
Stefano

@clime ดีฉันพยายามอธิบายด้วยคำตอบเอง: ในกรณีที่ไม่มีการกำหนดคอนฟิกการบันทึกใด ๆ สิ่งนี้หลีกเลี่ยงคำเตือนสำหรับไม่ได้ระบุ Handler มีประโยชน์อย่างยิ่งสำหรับ libs ที่ไม่จำเป็นต้องเรียกเฉพาะใน Django เท่านั้น (อ้างอิง)
Stefano

ฉันไม่เห็นว่าคุณใช้คำจำกัดความนี้อย่างไร: 'null': {'level': 'DEBUG', 'class': 'django.utils.log.NullHandler',}
clime

9

เราเริ่มต้นการบันทึกในระดับบนสุดurls.pyโดยใช้logging.iniไฟล์

ตำแหน่งของสถานที่logging.iniนั้นมีให้settings.pyแต่นั่นคือทั้งหมด

จากนั้นแต่ละโมดูลจะทำ

logger = logging.getLogger(__name__)

ในการแยกความแตกต่างของอินสแตนซ์การทดสอบการพัฒนาและการใช้งานจริงเรามีไฟล์ logging.ini ที่แตกต่างกัน ส่วนใหญ่เรามี "บันทึกคอนโซล" ที่ไปยัง stderr พร้อมข้อผิดพลาดเท่านั้น เรามี "บันทึกแอปพลิเคชัน" ที่ใช้ล็อกไฟล์แบบปกติที่ไปยังไดเร็กทอรีบันทึก


ฉันใช้สิ่งนี้ได้ยกเว้นการเริ่มต้นใน settings.py แทน urls.py
Parand

คุณใช้การตั้งค่าจาก settings.py ในไฟล์ logging.ini ได้อย่างไร? ตัวอย่างเช่นฉันต้องการการตั้งค่า BASE_DIR ดังนั้นฉันจึงสามารถบอกได้ว่าจะเก็บไฟล์บันทึกไว้ที่ใด
slypete

@slypete: เราไม่ได้ใช้การตั้งค่าใน logging.ini เนื่องจากการบันทึกส่วนใหญ่เป็นอิสระเราจึงไม่ใช้การตั้งค่า Django ใด ๆ ใช่มีความเป็นไปได้ที่จะทำซ้ำบางสิ่ง ไม่มันไม่ได้สร้างความแตกต่างในทางปฏิบัติมากนัก
ล็อตต์

ในกรณีนั้นฉันจะแยกไฟล์ logging.ini ในทุกการติดตั้งแอปของฉัน
slypete

@slypete: คุณมี settings.py สำหรับการติดตั้งแต่ละครั้ง คุณยังมี logging.ini สำหรับการติดตั้งแต่ละครั้ง นอกจากนี้คุณอาจมีไฟล์ Apache conf สำหรับการติดตั้งแต่ละครั้งด้วย พร้อมไฟล์อินเตอร์เฟส wsgi ฉันไม่แน่ใจว่าประเด็นของคุณคืออะไร
ล็อตต์

6

ฉันกำลังใช้ระบบบันทึกซึ่งฉันสร้างขึ้นเอง ใช้รูปแบบ CSV สำหรับการบันทึก

django-csvlog

โครงการนี้ยังไม่มีเอกสารฉบับสมบูรณ์ แต่ฉันกำลังดำเนินการอยู่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.