konfigurasi logger untuk masuk ke file dan mencetak ke stdout

353

Saya menggunakan modul logging Python untuk mencatat beberapa string debug ke file yang bekerja cukup baik. Sekarang sebagai tambahan, saya ingin menggunakan modul ini untuk juga mencetak string ke stdout. Bagaimana saya melakukan ini? Untuk mencatat string saya ke file saya menggunakan kode berikut:

import logging
import logging.handlers
logger = logging.getLogger("")
logger.setLevel(logging.DEBUG)
handler = logging.handlers.RotatingFileHandler(
    LOGFILE, maxBytes=(1048576*5), backupCount=7
)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)

lalu panggil fungsi logger seperti

logger.debug("I am written to the file")

Terima kasih atas bantuannya di sini!

stdcerr
sumber

Jawaban:

451

Dapatkan pegangan ke root logger dan tambahkan StreamHandler. The StreamHandlermenulis ke stderr. Tidak yakin apakah Anda benar-benar membutuhkan stdout di atas stderr, tetapi ini adalah apa yang saya gunakan ketika saya mengatur logger Python dan saya juga menambahkannya FileHandler. Lalu semua log saya pergi ke kedua tempat (yang sepertinya Anda inginkan).

import logging
logging.getLogger().addHandler(logging.StreamHandler())

Jika Anda ingin menampilkan stdoutbukan stderr, Anda hanya perlu menentukannya ke StreamHandlerkonstruktor.

import sys
# ...
logging.getLogger().addHandler(logging.StreamHandler(sys.stdout))

Anda juga bisa menambahkannya Formattersehingga semua baris log Anda memiliki header yang sama.

yaitu:

import logging
logFormatter = logging.Formatter("%(asctime)s [%(threadName)-12.12s] [%(levelname)-5.5s]  %(message)s")
rootLogger = logging.getLogger()

fileHandler = logging.FileHandler("{0}/{1}.log".format(logPath, fileName))
fileHandler.setFormatter(logFormatter)
rootLogger.addHandler(fileHandler)

consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(logFormatter)
rootLogger.addHandler(consoleHandler)

Mencetak ke format:

2012-12-05 16:58:26,618 [MainThread  ] [INFO ]  my message
Penjilat
sumber
19
Anda juga bisa menginisialisasi StreamHandlerdengan sys.stdout, dan kemudian akan masuk ke itu alih-alih stderr.
Silas Ray
1
@ sr2222 logger.addHandler (sys.stdout) memberi saya NameError: name 'sys' tidak didefinisikan
stdcerr
21
Yah ya ... Anda harus import systerlebih dahulu. Dan benar-benar menginisialisasi pawang, yaituconsoleHandler = logging.StreamHandler(sys.stdout)
Silas Ray
15
Karena seperti yang sudah saya katakan, itu bukan cara Anda melakukannya. Buat HANDLER dengan sys.stdout, lalu pasang handler ke logger.
Silas Ray
6
Jangan lupa rootLogger.setLevel(logging.DEBUG)jika Anda mencoba melihat info atau men-debug pesan
storm_m2138
247

logging.basicConfig()dapat mengambil argumen kata kunci handlerssejak Python 3.3, yang menyederhanakan banyak pengaturan logging, terutama ketika mengatur beberapa penangan dengan formatter yang sama:

handlers- Jika ditentukan, ini harus merupakan iterable dari handler yang sudah dibuat untuk ditambahkan ke root logger. Penangan apa pun yang belum memiliki rangkaian formatter akan ditetapkan sebagai formatter default yang dibuat dalam fungsi ini.

Seluruh pengaturan karenanya dapat dilakukan dengan satu panggilan seperti ini:

import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    handlers=[
        logging.FileHandler("debug.log"),
        logging.StreamHandler()
    ]
)

(Atau dengan import sys+ StreamHandler(sys.stdout)per persyaratan pertanyaan asli - standar untuk StreamHandler adalah menulis ke stderr. Lihat atribut LogRecord jika Anda ingin menyesuaikan format log dan menambahkan hal-hal seperti nama file / baris, info utas dll.)

Pengaturan di atas hanya perlu dilakukan sekali di dekat bagian awal skrip. Anda bisa menggunakan pencatatan dari semua tempat lain di basis kode nanti seperti ini:

logging.info('Useful message')
logging.error('Something bad happened')
...

Catatan: Jika tidak berhasil, orang lain mungkin telah menginisialisasi sistem logging secara berbeda. Komentar menyarankan dilakukan logging.root.handlers = []sebelum panggilan ke basicConfig().

Yirkha
sumber
5
jangan lupa untuk mengatur level = logging.INFO atau level yang diinginkan juga
Andy Matteson
5
Definisi untuk FileHandler: logging.FileHandler(filename, mode='a', encoding=None, delay=False). Ini berarti, bahwa ketika Anda hanya ingin masuk ke folder yang sama, Anda bisa menggunakannya FileHandler("mylog.log"). Jika Anda ingin menimpa log setiap kali, atur "w" sebagai argumen kedua.
user136036
7
Saya mencoba ini, tetapi file output kosong meskipun konsol memberikan output .. Ada saran ..?
Ramesh-X
4
@ Ramesh-X, ini membuatku gila juga. lakukan saja logging.root.handlers = []sebelum panggilan basicConfig, lihat fungsinya - itu menjengkelkan.
ihadanny
70

Menambahkan StreamHandler tanpa argumen masuk ke stderr alih-alih stdout. Jika beberapa proses lain memiliki ketergantungan pada dump stdout (yaitu ketika menulis plugin NRPE), maka pastikan untuk menentukan stdout secara eksplisit atau Anda mungkin mengalami beberapa masalah yang tidak terduga.

Berikut adalah contoh cepat menggunakan kembali nilai yang diasumsikan dan LOGFILE dari pertanyaan:

import logging
from logging.handlers import RotatingFileHandler
from logging import handlers
import sys

log = logging.getLogger('')
log.setLevel(logging.DEBUG)
format = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")

ch = logging.StreamHandler(sys.stdout)
ch.setFormatter(format)
log.addHandler(ch)

fh = handlers.RotatingFileHandler(LOGFILE, maxBytes=(1048576*5), backupCount=7)
fh.setFormatter(format)
log.addHandler(fh)
Hazok
sumber
Saya mencoba ini.
Ajay Kumar
19

Baik dijalankan basicConfigdengan stream=sys.stdoutsebagai argumen sebelum mengatur penangan lain atau masuk pesan, atau secara manual menambahkan StreamHandleryang mendorong pesan ke stdout ke logger root (atau logger lain yang Anda inginkan, dalam hal ini).

Silas Ray
sumber
5

Setelah menggunakan kode Waterboy berulang-ulang dalam beberapa paket Python, saya akhirnya memasukkannya ke dalam paket Python mandiri yang kecil, yang dapat Anda temukan di sini:

https://github.com/acschaefer/duallog

Kode ini didokumentasikan dengan baik dan mudah digunakan. Cukup unduh .pyfile dan sertakan dalam proyek Anda, atau instal seluruh paket melalui pip install duallog.

Lexxer
sumber
Untuk beberapa alasan tidak login ke konsol, tidak ada file (kosong)
JackTheKnife
5

Masuk ke stdoutdan rotating filedengan berbagai tingkatan dan format:

import logging
import logging.handlers
import sys

if __name__ == "__main__":

    # Change root logger level from WARNING (default) to NOTSET in order for all messages to be delegated.
    logging.getLogger().setLevel(logging.NOTSET)

    # Add stdout handler, with level INFO
    console = logging.StreamHandler(sys.stdout)
    console.setLevel(logging.INFO)
    formater = logging.Formatter('%(name)-13s: %(levelname)-8s %(message)s')
    console.setFormatter(formater)
    logging.getLogger().addHandler(console)

    # Add file rotating handler, with level DEBUG
    rotatingHandler = logging.handlers.RotatingFileHandler(filename='rotating.log', maxBytes=1000, backupCount=5)
    rotatingHandler.setLevel(logging.DEBUG)
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    rotatingHandler.setFormatter(formatter)
    logging.getLogger().addHandler(rotatingHandler)

    log = logging.getLogger("app." + __name__)

    log.debug('Debug message, should only appear in the file.')
    log.info('Info message, should appear in file and stdout.')
    log.warning('Warning message, should appear in file and stdout.')
    log.error('Error message, should appear in file and stdout.')
Andrej Debenjak
sumber
2

Berikut ini adalah solusi lengkap yang dibungkus dengan baik berdasarkan jawaban Waterboy dan berbagai sumber lainnya. Ini mendukung logging ke konsol dan file log, memungkinkan untuk pengaturan tingkat log yang berbeda, memberikan output berwarna dan mudah dikonfigurasi (juga tersedia sebagai Gist ):

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# -------------------------------------------------------------------------------
#                                                                               -
#  Python dual-logging setup (console and log file),                            -
#  supporting different log levels and colorized output                         -
#                                                                               -
#  Created by Fonic <https://github.com/fonic>                                  -
#  Date: 04/05/20                                                               -
#                                                                               -
#  Based on:                                                                    -
#  https://stackoverflow.com/a/13733863/1976617                                 -
#  https://uran198.github.io/en/python/2016/07/12/colorful-python-logging.html  -
#  https://en.wikipedia.org/wiki/ANSI_escape_code#Colors                        -
#                                                                               -
# -------------------------------------------------------------------------------

# Imports
import os
import sys
import logging

# Logging formatter supporting colored output
class LogFormatter(logging.Formatter):

    COLOR_CODES = {
        logging.CRITICAL: "\033[1;35m", # bright/bold magenta
        logging.ERROR:    "\033[1;31m", # bright/bold red
        logging.WARNING:  "\033[1;33m", # bright/bold yellow
        logging.INFO:     "\033[0;37m", # white / light gray
        logging.DEBUG:    "\033[1;30m"  # bright/bold black / dark gray
    }

    RESET_CODE = "\033[0m"

    def __init__(self, color, *args, **kwargs):
        super(LogFormatter, self).__init__(*args, **kwargs)
        self.color = color

    def format(self, record, *args, **kwargs):
        if (self.color == True and record.levelno in self.COLOR_CODES):
            record.color_on  = self.COLOR_CODES[record.levelno]
            record.color_off = self.RESET_CODE
        else:
            record.color_on  = ""
            record.color_off = ""
        return super(LogFormatter, self).format(record, *args, **kwargs)

# Setup logging
def setup_logging(console_log_output, console_log_level, console_log_color, logfile_file, logfile_log_level, logfile_log_color, log_line_template):

    # Create logger
    # For simplicity, we use the root logger, i.e. call 'logging.getLogger()'
    # without name argument. This way we can simply use module methods for
    # for logging throughout the script. An alternative would be exporting
    # the logger, i.e. 'global logger; logger = logging.getLogger("<name>")'
    logger = logging.getLogger()

    # Set global log level to 'debug' (required for handler levels to work)
    logger.setLevel(logging.DEBUG)

    # Create console handler
    console_log_output = console_log_output.lower()
    if (console_log_output == "stdout"):
        console_log_output = sys.stdout
    elif (console_log_output == "stderr"):
        console_log_output = sys.stderr
    else:
        print("Failed to set console output: invalid output: '%s'" % console_log_output)
        return False
    console_handler = logging.StreamHandler(console_log_output)

    # Set console log level
    try:
        console_handler.setLevel(console_log_level.upper()) # only accepts uppercase level names
    except:
        print("Failed to set console log level: invalid level: '%s'" % console_log_level)
        return False

    # Create and set formatter, add console handler to logger
    console_formatter = LogFormatter(fmt=log_line_template, color=console_log_color)
    console_handler.setFormatter(console_formatter)
    logger.addHandler(console_handler)

    # Create log file handler
    try:
        logfile_handler = logging.FileHandler(logfile_file)
    except Exception as exception:
        print("Failed to set up log file: %s" % str(exception))
        return False

    # Set log file log level
    try:
        logfile_handler.setLevel(logfile_log_level.upper()) # only accepts uppercase level names
    except:
        print("Failed to set log file log level: invalid level: '%s'" % logfile_log_level)
        return False

    # Create and set formatter, add log file handler to logger
    logfile_formatter = LogFormatter(fmt=log_line_template, color=logfile_log_color)
    logfile_handler.setFormatter(logfile_formatter)
    logger.addHandler(logfile_handler)

    # Success
    return True

# Main function
def main():

    # Setup logging
    script_name = os.path.splitext(os.path.basename(sys.argv[0]))[0]
    if (not setup_logging(console_log_output="stdout", console_log_level="warning", console_log_color=True,
                        logfile_file=script_name + ".log", logfile_log_level="debug", logfile_log_color=False,
                        log_line_template="%(color_on)s[%(created)d] [%(threadName)s] [%(levelname)-8s] %(message)s%(color_off)s")):
        print("Failed to setup logging, aborting.")
        return 1

    # Log some messages
    logging.debug("Debug message")
    logging.info("Info message")
    logging.warning("Warning message")
    logging.error("Error message")
    logging.critical("Critical message")

# Call main function
if (__name__ == "__main__"):
    sys.exit(main())
Maksim
sumber
-4

Untuk 2.7, coba yang berikut ini:

fh = logging.handlers.RotatingFileHandler(LOGFILE, maxBytes=(1048576*5), backupCount=7)
JonM
sumber