Setup elegan dari logging Python di Django

101

Saya belum menemukan cara menyiapkan logging Python dengan Django yang saya sukai. Persyaratan saya cukup sederhana:

  • Penangan log yang berbeda untuk acara yang berbeda - yaitu, saya ingin dapat masuk ke file yang berbeda
  • Akses mudah ke logger di modul saya. Modul harus dapat menemukan pencatatnya dengan sedikit usaha.
  • Harus mudah diterapkan ke modul baris perintah. Bagian dari sistem adalah baris perintah atau proses daemon yang berdiri sendiri. Logging harus dapat digunakan dengan mudah dengan modul ini.

Setup saya saat ini adalah dengan menggunakan logging.conffile dan setup logging di setiap modul tempat saya login. Rasanya tidak benar.

Apakah Anda memiliki pengaturan logging yang Anda sukai? Harap jelaskan: bagaimana Anda mengatur konfigurasi (apakah Anda menggunakan logging.confatau mengaturnya dalam kode), di mana / kapan Anda memulai logger, dan bagaimana Anda mendapatkan akses ke mereka dalam modul Anda, dll.

Parand
sumber
1
Anda mungkin menemukan screencast berikut berguna - ericholscher.com/blog/2008/aug/29/… . Juga, dukungan yang lebih baik untuk pencatatan di Django telah diusulkan oleh Simon Willison (lihat simonwillison.net/2009/Sep/28/ponies ).
Dominic Rodger
@Dominic Rodger - Anda sudah dapat melakukan pencatatan fleksibel aplikasi di Django, proposal Simon terutama untuk memfasilitasi pencatatan di internal Django. Ada pekerjaan yang sedang dilakukan dengan Python untuk menambahkan konfigurasi berbasis kamus ke logging Python, yang darinya Django dapat mengambil manfaat.
Vinay Sajip

Jawaban:

57

Cara terbaik yang saya temukan sejauh ini adalah dengan menginisialisasi pengaturan logging di settings.py - tidak di tempat lain. Anda dapat menggunakan file konfigurasi atau melakukannya secara programatis selangkah demi selangkah - itu tergantung pada kebutuhan Anda. Kuncinya adalah saya biasanya menambahkan handler yang saya inginkan ke root logger, menggunakan level dan terkadang logging. Filter untuk mendapatkan event yang saya inginkan ke file, konsol, syslogs yang sesuai, dll. Anda tentu saja dapat menambahkan handler ke logger lain. juga, tapi biasanya tidak ada kebutuhan untuk ini dalam pengalaman saya.

Di setiap modul, saya mendefinisikan logger menggunakan

logger = logging.getLogger(__name__)

dan gunakan itu untuk mencatat peristiwa dalam modul (dan, jika saya ingin membedakan lebih jauh) gunakan logger yang merupakan anak dari logger yang dibuat di atas.

Jika aplikasi saya berpotensi digunakan di situs yang tidak mengonfigurasi logging in settings.py, saya mendefinisikan NullHandler di suatu tempat sebagai berikut:

#someutils.py

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

null_handler = NullHandler()

dan memastikan bahwa sebuah instance ditambahkan ke semua logger yang dibuat dalam modul di aplikasi saya yang menggunakan logging. (Catatan: NullHandler sudah ada dalam paket logging untuk Python 3.1, dan akan menggunakan Python 2.7.) Jadi:

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

Hal ini dilakukan untuk memastikan bahwa modul Anda berfungsi dengan baik di situs yang tidak mengkonfigurasi logging in settings.py, dan bahwa Anda tidak mendapatkan pesan "Tidak ada penangan yang dapat ditemukan untuk logger XYZ" yang mengganggu (yang merupakan peringatan tentang kemungkinan salah konfigurasi logging).

Melakukannya dengan cara ini memenuhi persyaratan yang Anda nyatakan:

  • Anda dapat mengatur penangan log yang berbeda untuk berbagai acara, seperti yang Anda lakukan saat ini.
  • Akses mudah ke logger di modul Anda - gunakan getLogger(__name__).
  • Dapat diterapkan dengan mudah ke modul baris perintah - modul ini juga diimpor settings.py.

Pemutakhiran: Perhatikan bahwa pada versi 1.3, Django sekarang memasukkan dukungan untuk pencatatan .

Vinay Sajip
sumber
Tidakkah hal ini mengharuskan setiap modul memiliki penangan yang ditentukan dalam konfigurasi (Anda tidak dapat menggunakan penangan untuk foo untuk menangani foo.bar)? Lihat percakapan kami bertahun-tahun lalu di groups.google.com/group/comp.lang.python/browse_thread/thread/…
andrew cooke
1
@andrew cooke: Anda dapat menggunakan penangan untuk foountuk menangani peristiwa yang dicatat foo.bar. Kembali. utas itu - fileConfig dan dictConfig sekarang memiliki opsi untuk mencegah penonaktifan penebang lama. Lihat masalah ini: bugs.python.org/issue3136 , yang muncul dalam beberapa bulan setelah masalah Anda bugs.python.org/issue2697 - bagaimanapun, ini telah diselesaikan sejak Juni 2008.
Vinay Sajip
bukankah lebih baik untuk melakukan di logger = someutils.getLogger(__name__)mana someutils.getLoggermengembalikan logger logging.getLoggerdengan null_handler sudah ditambahkan?
7yl4r
1
@ 7yl4r Anda tidak perlu setiap logger memiliki NullHandlertambahan - biasanya hanya logger tingkat atas untuk hierarki paket Anda. Jadi itu akan berlebihan, IMO.
Vinay Sajip
122

Saya tahu ini adalah jawaban yang sudah dipecahkan, tetapi menurut django> = 1.3 ada pengaturan logging baru.

Pindah dari lama ke baru tidak otomatis, jadi saya pikir saya akan menuliskannya di sini.

Dan tentu saja periksa dokumen django untuk lebih banyak lagi.

Ini adalah konfigurasi dasar, dibuat secara default dengan django-admin createproject v1.3 - jarak tempuh mungkin berubah dengan versi django terbaru:

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,
        }
    }
}

Struktur ini didasarkan pada dictConfig logging Python standar , yang menentukan blok-blok berikut:

  • formatters - nilai yang sesuai akan menjadi dict di mana setiap kunci adalah id pemformat dan setiap nilai adalah sebuah dict yang menjelaskan cara mengonfigurasi instance Formatter yang sesuai.
  • filters - nilai terkait akan menjadi dict di mana setiap kunci adalah id filter dan setiap nilai adalah dict yang menjelaskan cara mengonfigurasi instance Filter yang sesuai.
  • handlers- nilai terkait akan menjadi dict di mana setiap kunci adalah id penangan dan setiap nilai adalah dikt yang menjelaskan cara mengkonfigurasi instance Handler yang sesuai. Setiap penangan memiliki kunci berikut:

    • class(wajib). Ini adalah nama kelas penangan yang sepenuhnya memenuhi syarat.
    • level(pilihan). Level pawang.
    • formatter(pilihan). ID pemformat untuk penangan ini.
    • filters(pilihan). Daftar id filter untuk penangan ini.

Saya biasanya melakukan setidaknya ini:

  • tambahkan file .log
  • konfigurasikan aplikasi saya untuk menulis ke log ini

Yang diterjemahkan menjadi:

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

edit

Lihat pengecualian permintaan sekarang selalu dicatat dan Tiket # 16288 :

Saya memperbarui contoh conf di atas untuk secara eksplisit menyertakan filter yang benar untuk mail_admins sehingga, secara default, email tidak dikirim ketika debug adalah True.

Anda harus menambahkan filter:

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

dan terapkan ke penangan mail_admins:

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

Jika django.core.handers.base.handle_uncaught_exceptiontidak, kesalahan tidak akan lolos ke pencatat 'django.request' jika pengaturan.DEBUG adalah True.

Jika Anda tidak melakukan ini di Django 1.5 Anda akan mendapatkan

DeprecationWarning: Anda tidak memiliki filter yang ditentukan pada penangan logging 'mail_admins': menambahkan filter implisit debug-false-only

tetapi semuanya masih akan bekerja dengan benar KEDUA di django 1.4 dan django 1.5.

** akhiri edit **

Conf itu sangat terinspirasi oleh contoh conf di dokumen django, tetapi menambahkan bagian file log.

Saya sering juga melakukan hal berikut:

LOG_LEVEL = 'DEBUG' if DEBUG else 'INFO'

...
    'level': LOG_LEVEL
...

Kemudian dalam kode python saya, saya selalu menambahkan NullHandler jika tidak ada konfigurasi logging yang ditentukan sama sekali. Ini menghindari peringatan karena tidak ada Handler yang ditentukan. Terutama berguna untuk lib yang tidak perlu dipanggil hanya di 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.')

Semoga ini membantu!

Stefano
sumber
Stefano, terima kasih banyak atas jawaban terperinci, sangat membantu. Ini mungkin membuatnya bermanfaat untuk ditingkatkan ke 1.3.
Parand
Parand, sudah pasti (IMHO!) Layak untuk ditingkatkan ke django 1.3, meskipun ada beberapa hal yang harus diperhatikan untuk transisi yang mulus - buka pertanyaan SO baru jika Anda mendapat masalah ;-)
Stefano
ngomong-ngomong: Saya masih menggunakan pengaturan semacam ini dan file log, tetapi saya pindah ke penjaga untuk produksi!
Stefano
@clime well Saya mencoba menjelaskannya dalam jawaban itu sendiri: seandainya tidak ada conf logging yang didefinisikan sama sekali. Ini menghindari peringatan karena tidak ada Handler yang ditentukan. Terutama berguna untuk lib yang tidak perlu dipanggil hanya di Django (ref)
Stefano
Saya tidak melihat bagaimana Anda menggunakan definisi ini: 'null': {'level': 'DEBUG', 'class': 'django.utils.log.NullHandler',}
clime
9

Kami menginisialisasi logging di tingkat atas urls.pydengan menggunakan logging.inifile.

Lokasi logging.inidisediakan settings.py, tapi itu saja.

Setiap modul kemudian melakukannya

logger = logging.getLogger(__name__)

Untuk membedakan contoh pengujian, pengembangan dan produksi, kami memiliki file logging.ini yang berbeda. Untuk sebagian besar, kami memiliki "log konsol" yang menuju ke stderr hanya dengan Kesalahan. Kami memiliki "log aplikasi" yang menggunakan file log bergulir biasa yang masuk ke direktori log.

S. Lott
sumber
Saya akhirnya menggunakan ini, kecuali menginisialisasi di settings.py bukannya urls.py
Parand
Bagaimana Anda menggunakan pengaturan dari settings.py di file logging.ini Anda? Misalnya, saya memerlukan setelan BASE_DIR, jadi saya dapat memberitahukannya di mana untuk menyimpan file log saya.
slypete
@slypete: Kami tidak menggunakan pengaturan di logging.ini. Karena logging sebagian besar independen, kami tidak menggunakan pengaturan Django manapun. Ya, ada kemungkinan mengulang sesuatu. Tidak, itu tidak membuat banyak perbedaan praktis.
S. Lott
Dalam hal ini, saya akan memisahkan file logging.ini di setiap instalasi aplikasi saya.
slypete
@slypete: Anda memiliki settings.py untuk setiap instalasi. Anda juga memiliki logging.ini untuk setiap instalasi. Plus, Anda mungkin juga memiliki file conf Apache untuk setiap instalasi. Ditambah file antarmuka wsgi. Saya tidak yakin apa maksud Anda.
S. Lott
6

Saat ini saya menggunakan sistem logging, yang saya buat sendiri. Ini menggunakan format CSV untuk logging.

django-csvlog.dll

Proyek ini masih belum memiliki dokumentasi lengkap, tetapi saya sedang mengerjakannya.

Oduvan
sumber