Membuat Python logger mengeluarkan semua pesan ke stdout selain file log

450

Apakah ada cara untuk membuat pendataan Python menggunakan loggingmodul secara otomatis menampilkan sesuatu ke stdout di samping file log di mana mereka seharusnya pergi? Sebagai contoh, saya ingin semua panggilan ke logger.warning, logger.critical, logger.erroruntuk pergi ke tempat-tempat mereka dimaksudkan tetapi di samping selalu disalin ke stdout. Ini untuk menghindari duplikasi pesan seperti:

mylogger.critical("something failed")
print "something failed"
Ben
sumber
1
Silakan periksa jawaban ini stackoverflow.com/questions/9321741/…
SeF

Jawaban:

635

Semua hasil logging ditangani oleh penangan; cukup tambahkan logging.StreamHandler()ke root logger.

Berikut ini contoh mengkonfigurasi stream handler (menggunakan stdoutalih-alih default stderr) dan menambahkannya ke root logger:

import logging
import sys

root = logging.getLogger()
root.setLevel(logging.DEBUG)

handler = logging.StreamHandler(sys.stdout)
handler.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
root.addHandler(handler)
Martijn Pieters
sumber
4
Itu baik-baik saja tetapi jika sudah diarahkan ke file, bagaimana saya bisa mencetaknya stdoutsebagai tambahan?
54
@ user248237: Dengan menambahkan handler baru seperti yang diilustrasikan. Penangan baru tidak menggantikan penangan yang ada, mereka juga dapat memproses entri log.
Martijn Pieters
@ MartijnPieters apakah ada cara untuk menambahkan string ke setiap pernyataan log yang dicetak?
Prakhar Mohan Srivastava
7
@PrakharMohanSrivastava Saya kira Anda bisa menambahkannya ke string yang dilewatkan logging.Formatter.
A.Lan
3
@ himanshu219: use case adalah bahwa segera setelah Anda mulai menambahkan beberapa penangan, Anda biasanya ingin membedakan. DEBUG ke konsol, PERINGATAN dan hingga file, dll.
Martijn Pieters
505

Cara termudah untuk masuk ke stdout:

import logging
import sys
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
Eyal
sumber
57
Hm, tapi ini tidak masuk ke file, kan? Pertanyaannya adalah bagaimana cara login ke file dan konsol.
Weidenrinde
Tautan ref: Python3 Docs: Logging.basicConfig
Czechnology
Setidaknya dalam Python 3, sepertinya menghilangkan stream=sys.stdoutmasih berfungsi untuk masuk ke konsol untuk saya.
Taylor Edmiston
3
@TaylorEdmiston Ya, tapi itu aliran stderr AFAIK. Coba redirect output dari shell.
Sorin
1
BAIK. Ini tidak menjawab keduanya: masuk ke file dan ke konsol, tapi senang menemukan apa yang saya butuhkan dalam 3 baris atau kurang.
Steve3p0
67

Mungkin menggunakan beberapa penangan.

import logging
import auxiliary_module

# create logger with 'spam_application'
log = logging.getLogger('spam_application')
log.setLevel(logging.DEBUG)

# create formatter and add it to the handlers
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# create file handler which logs even debug messages
fh = logging.FileHandler('spam.log')
fh.setLevel(logging.DEBUG)
fh.setFormatter(formatter)
log.addHandler(fh)

# create console handler with a higher log level
ch = logging.StreamHandler()
ch.setLevel(logging.ERROR)
ch.setFormatter(formatter)
log.addHandler(ch)

log.info('creating an instance of auxiliary_module.Auxiliary')
a = auxiliary_module.Auxiliary()
log.info('created an instance of auxiliary_module.Auxiliary')

log.info('calling auxiliary_module.Auxiliary.do_something')
a.do_something()
log.info('finished auxiliary_module.Auxiliary.do_something')

log.info('calling auxiliary_module.some_function()')
auxiliary_module.some_function()
log.info('done with auxiliary_module.some_function()')

# remember to close the handlers
for handler in log.handlers:
    handler.close()
    log.removeFilter(handler)

Silakan lihat: https://docs.python.org/2/howto/logging-cookbook.html

Alok Singh Mahor
sumber
4
Jawaban yang bagus, meskipun agak berantakan. Sukai bagaimana Anda menunjukkan cara menggunakan berbagai level dan format untuk streaming dan file. +1, tapi +2 dalam semangat.
The Unfun Cat
Bagi saya ini tidak berfungsi tanpa sys.stdoutparameter inch = logging.StreamHandler()
veuncent
64

Anda bisa membuat dua penangan untuk file dan stdout dan kemudian membuat satu logger dengan handlersargumen untuk basicConfig. Ini bisa berguna jika Anda memiliki log_level dan format output yang sama untuk kedua penangan:

import logging
import sys

file_handler = logging.FileHandler(filename='tmp.log')
stdout_handler = logging.StreamHandler(sys.stdout)
handlers = [file_handler, stdout_handler]

logging.basicConfig(
    level=logging.DEBUG, 
    format='[%(asctime)s] {%(filename)s:%(lineno)d} %(levelname)s - %(message)s',
    handlers=handlers
)

logger = logging.getLogger('LOGGER_NAME')
Anton Protopopov
sumber
32

Cara termudah untuk masuk ke file dan ke stderr:

import logging

logging.basicConfig(filename="logfile.txt")
stderrLogger=logging.StreamHandler()
stderrLogger.setFormatter(logging.Formatter(logging.BASIC_FORMAT))
logging.getLogger().addHandler(stderrLogger)
Weidenrinde
sumber
Ini tidak menampilkan label INFO, DEBUG, dan ERROR sebelum pesan logging di konsol. Itu memang menunjukkan label-label dalam file. Adakah ide untuk juga menampilkan label di konsol?
JahMyst
1
Terima kasih, @JahMyst, saya menambahkan Formatter. Sayangnya, ini tidak begitu singkat lagi, tetapi masih cara paling sederhana. :-)
Weidenrinde
12

Berikut ini adalah solusi yang didasarkan pada kuat tetapi kurang didokumentasikan logging.config.dictConfigmetode . Alih-alih mengirim setiap pesan log stdout, ia mengirim pesan dengan level log ERRORdan lebih tinggi ke stderrdan semua yang lainnya stdout. Ini dapat bermanfaat jika bagian lain dari sistem mendengarkan stderratau stdout.

import logging
import logging.config
import sys

class _ExcludeErrorsFilter(logging.Filter):
    def filter(self, record):
        """Filters out log messages with log level ERROR (numeric value: 40) or higher."""
        return record.levelno < 40


config = {
    'version': 1,
    'filters': {
        'exclude_errors': {
            '()': _ExcludeErrorsFilter
        }
    },
    'formatters': {
        # Modify log message format here or replace with your custom formatter class
        'my_formatter': {
            'format': '(%(process)d) %(asctime)s %(name)s (line %(lineno)s) | %(levelname)s %(message)s'
        }
    },
    'handlers': {
        'console_stderr': {
            # Sends log messages with log level ERROR or higher to stderr
            'class': 'logging.StreamHandler',
            'level': 'ERROR',
            'formatter': 'my_formatter',
            'stream': sys.stderr
        },
        'console_stdout': {
            # Sends log messages with log level lower than ERROR to stdout
            'class': 'logging.StreamHandler',
            'level': 'DEBUG',
            'formatter': 'my_formatter',
            'filters': ['exclude_errors'],
            'stream': sys.stdout
        },
        'file': {
            # Sends all log messages to a file
            'class': 'logging.FileHandler',
            'level': 'DEBUG',
            'formatter': 'my_formatter',
            'filename': 'my.log',
            'encoding': 'utf8'
        }
    },
    'root': {
        # In general, this should be kept at 'NOTSET'.
        # Otherwise it would interfere with the log levels set for each handler.
        'level': 'NOTSET',
        'handlers': ['console_stderr', 'console_stdout', 'file']
    },
}

logging.config.dictConfig(config)
Elias Strehle
sumber
harus mengganti nama logger menjadi string kosong untuk benar-benar mendapatkan logger root. Kalau tidak, sangat membantu, terima kasih!
Newtopian
8

Karena tidak ada yang membagikan liner dua yang rapi, saya akan membagikan sendiri:

logging.basicConfig(filename='logs.log', level=logging.DEBUG, format="%(asctime)s:%(levelname)s: %(message)s")
logging.getLogger().addHandler(logging.StreamHandler())
Lexander
sumber
2

Berikut ini contoh yang sangat sederhana:

import logging
l = logging.getLogger("test")

# Add a file logger
f = logging.FileHandler("test.log")
l.addHandler(f)

# Add a stream logger
s = logging.StreamHandler()
l.addHandler(s)

# Send a test message to both -- critical will always log
l.critical("test msg")

Output akan menampilkan "test msg" pada stdout dan juga dalam file.

Kiki Jewell
sumber