Bagaimana cara menduplikasi sys.stdout ke file log?

149

Sunting: Karena tampaknya tidak ada solusi, atau saya melakukan sesuatu yang sangat tidak standar sehingga tidak ada yang tahu - Saya akan merevisi pertanyaan saya untuk juga bertanya: Apa cara terbaik untuk menyelesaikan pencatatan ketika aplikasi python membuat banyak panggilan sistem?

Aplikasi saya memiliki dua mode. Dalam mode interaktif, saya ingin semua output masuk ke layar serta ke file log, termasuk output dari panggilan sistem apa pun. Dalam mode daemon, semua output masuk ke log. Mode daemon bekerja sangat baik menggunakan os.dup2(). Saya tidak dapat menemukan cara untuk "tee" semua output ke log dalam mode interaktif, tanpa memodifikasi setiap panggilan sistem.


Dengan kata lain, saya ingin fungsionalitas dari baris perintah 'tee' untuk setiap output yang dihasilkan oleh aplikasi python, termasuk output panggilan sistem .

Untuk memperjelas:

Untuk mengarahkan ulang semua output, saya melakukan sesuatu seperti ini, dan itu berfungsi dengan baik:

# open our log file
so = se = open("%s.log" % self.name, 'w', 0)

# re-open stdout without buffering
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

# redirect stdout and stderr to the log file opened above
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())

Yang menyenangkan tentang ini adalah tidak memerlukan panggilan cetak khusus dari sisa kode. Kode ini juga menjalankan beberapa perintah shell, jadi senang tidak harus berurusan dengan masing-masing output mereka secara terpisah juga.

Sederhananya, saya ingin melakukan hal yang sama, kecuali menduplikasi alih-alih mengarahkan.

Mula-mula saya berpikir, hanya membalikkan saja dup2seharusnya berhasil. Kenapa tidak? Inilah tes saya:

import os, sys

### my broken solution:
so = se = open("a.log", 'w', 0)
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

os.dup2(sys.stdout.fileno(), so.fileno())
os.dup2(sys.stderr.fileno(), se.fileno())
###

print("foo bar")

os.spawnve("P_WAIT", "/bin/ls", ["/bin/ls"], {})
os.execve("/bin/ls", ["/bin/ls"], os.environ)

File "a.log" harus identik dengan apa yang ditampilkan di layar.

mabuk
sumber
Jika Anda melihat halaman manual ( manpagez.com/man/2/dup2 ) argumen ke-2 untuk dup2 selalu ditutup (jika sudah terbuka). Jadi, di "solusi rusak" Anda, ia menutup begitu dan melihat kemudian memindahkan fileno mereka ke sys.stdout.
Jacob Gabrielson
1
Re: edit Anda: ini tidak biasa, saya sudah melakukan hal yang sama beberapa kali (di langs lain). Sementara Unix akan memungkinkan beberapa "alias" untuk menangani file yang sama, itu tidak akan "membagi" menangani file (salin ke beberapa orang lain). Jadi, Anda harus menerapkan "tee" sendiri (atau cukup gunakan "tee", lihat jawaban saya yang kasar).
Jacob Gabrielson
Saya pikir jawaban JohnT lebih baik daripada yang diterima sebenarnya. Anda mungkin ingin mengubah jawaban yang diterima.
Phong
"Saya melakukan sesuatu yang sangat tidak standar" - Anda benar-benar, orang hanya mengirim log mereka ke stderr dan menangani dari baris perintah.
khachik

Jawaban:

55

Karena Anda merasa nyaman memunculkan proses eksternal dari kode Anda, Anda bisa menggunakannya teesendiri. Saya tidak tahu adanya panggilan sistem Unix yang melakukan persis apa yang teedilakukannya.

# Note this version was written circa Python 2.6, see below for
# an updated 3.3+-compatible version.
import subprocess, os, sys

# Unbuffer output (this ensures the output is in the correct order)
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

tee = subprocess.Popen(["tee", "log.txt"], stdin=subprocess.PIPE)
os.dup2(tee.stdin.fileno(), sys.stdout.fileno())
os.dup2(tee.stdin.fileno(), sys.stderr.fileno())

print "\nstdout"
print >>sys.stderr, "stderr"
os.spawnve("P_WAIT", "/bin/ls", ["/bin/ls"], {})
os.execve("/bin/ls", ["/bin/ls"], os.environ)

Anda juga bisa meniru teemenggunakan paket multiprosesing (atau menggunakan pemrosesan jika Anda menggunakan Python 2.5 atau yang lebih lama).

Memperbarui

Berikut ini adalah versi yang kompatibel dengan Python 3.3 +:

import subprocess, os, sys

tee = subprocess.Popen(["tee", "log.txt"], stdin=subprocess.PIPE)
# Cause tee's stdin to get a copy of our stdin/stdout (as well as that
# of any child processes we spawn)
os.dup2(tee.stdin.fileno(), sys.stdout.fileno())
os.dup2(tee.stdin.fileno(), sys.stderr.fileno())

# The flush flag is needed to guarantee these lines are written before
# the two spawned /bin/ls processes emit any output
print("\nstdout", flush=True)
print("stderr", file=sys.stderr, flush=True)

# These child processes' stdin/stdout are 
os.spawnve("P_WAIT", "/bin/ls", ["/bin/ls"], {})
os.execve("/bin/ls", ["/bin/ls"], os.environ)
Jacob Gabrielson
sumber
28
Baiklah, jawaban ini berhasil, jadi saya akan menerimanya. Tetap saja, itu membuat saya merasa kotor.
Lelah
2
Saya baru saja memposting implementasi python murni dari tee (py2 / 3 kompatibel) yang dapat berjalan pada platform apa pun dan juga digunakan dalam konfigurasi logging yang berbeda. stackoverflow.com/questions/616645/…
sorin
8
Jika Python berjalan pada salah satu mesin saya dan solusi tidak, maka itu bukan solusi pythonic. Turun karena itu.
anatoly techtonik
2
Menurut posting ini , baris sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)tidak lagi berfungsi sejak python 3.3 (lihat PEP 3116)
Ken Myers
1
Mendapat kesalahan "sys: 1: ResourceWarning: file tidak tertutup <_io.BufferedWriter name = 5>", jadi saya harus menambahkan tee.stdin.close()di akhir program saya. Saya juga mendapatkan "ResourceWarning: subprocess 1842 masih berjalan", dan menambahkan sys.stdout.close(); sys.stderr.close()pada akhir program memperbaikinya.
matthieu
136

Saya pernah mengalami masalah yang sama sebelumnya dan merasa potongan ini sangat berguna:

class Tee(object):
    def __init__(self, name, mode):
        self.file = open(name, mode)
        self.stdout = sys.stdout
        sys.stdout = self
    def __del__(self):
        sys.stdout = self.stdout
        self.file.close()
    def write(self, data):
        self.file.write(data)
        self.stdout.write(data)
    def flush(self):
        self.file.flush()

dari: http://mail.python.org/pipermail/python-list/2007-May/438106.html

John T
sumber
7
1 untuk menangani penugasan kembali sys.stdout internal sehingga Anda dapat mengakhiri penebangan dengan menghapus Tee objek
Ben Kosong
12
Saya akan menambahkan flush untuk itu. Contoh: 'self.file.flush ()'
Luke Stanley
4
Saya tidak setuju tentang modul logging. Sangat baik untuk beberapa mengutak-atik. Penebangan terlalu besar untuk itu.
Kobor42
4
Pastikan untuk mencatat versi yang direvisi dalam tindak lanjut untuk diskusi terkait dalam jawabannya.
martineau
4
Itu TIDAK akan berhasil. __del__tidak dipanggil hingga akhir eksekusi. Lihat stackoverflow.com/questions/6104535/…
Nux
77

The printpernyataan akan memanggil write()metode dari setiap objek yang Anda tetapkan untuk sys.stdout.

Saya akan memutar kelas kecil untuk menulis ke dua tempat sekaligus ...

import sys

class Logger(object):
    def __init__(self):
        self.terminal = sys.stdout
        self.log = open("log.dat", "a")

    def write(self, message):
        self.terminal.write(message)
        self.log.write(message)  

sys.stdout = Logger()

Sekarang printpernyataan akan bergema ke layar dan menambahkan file log Anda:

# prints "1 2" to <stdout> AND log.dat
print "%d %d" % (1,2)

Ini jelas cepat dan kotor. Beberapa catatan:

  • Anda mungkin harus menentukan parameter nama file log.
  • Anda mungkin harus mengembalikan sys.stdout ke <stdout>jika Anda tidak akan masuk selama durasi program.
  • Anda mungkin ingin kemampuan untuk menulis ke beberapa file log sekaligus, atau menangani level log yang berbeda, dll.

Ini semua cukup mudah sehingga saya nyaman meninggalkannya sebagai latihan untuk pembaca. Wawasan utama di sini adalah bahwa printhanya memanggil "objek seperti file" yang ditugaskan sys.stdout.

Triptych
sumber
Apa yang akan saya posting, cukup banyak. +1 saat Anda memperbaiki masalah dengan write yang tidak memiliki argumen sendiri. Juga, itu akan menjadi desain yang lebih baik untuk memiliki file yang akan Anda tulis untuk diteruskan. Sial, mungkin juga desain yang lebih baik untuk melewati stdout.
Devin Jeanpierre
@ Devin, ya ini cepat dan kotor, saya akan membuat beberapa catatan untuk kemungkinan perbaikan awal.
Triptych
7
Saya memilih jawaban ini terlalu cepat. Ini berfungsi bagus untuk "mencetak", tetapi tidak terlalu banyak untuk output perintah eksternal.
Drue
2
Kelas Logger juga harus mendefinisikan metode flush () seperti "def flush (): self.terminal.flush (); self.log.flush ()"
blokeley
5
Anda mengatakan The print statement will call the write() method of any object you assign to sys.stdout. Dan bagaimana dengan fungsi lain mengirim data ke stdout tidak menggunakan print. Sebagai contoh, jika saya membuat proses menggunakan subprocess.calloutputnya pergi ke konsol tetapi tidak ke log.datfile ... apakah ada cara untuk memperbaikinya?
jpo38
64

Yang benar-benar Anda inginkan adalah logging modul dari pustaka standar. Buat logger dan lampirkan dua penangan, satu akan menulis ke file dan yang lainnya ke stdout atau stderr.

Lihat Masuk ke beberapa tujuan untuk detailnya

Alexander Lebedev
sumber
9
Modul pencatatan tidak mencatat pengecualian dan output penting lainnya ke stdout, yang dapat berguna saat menganalisis log pada server build (misalnya).
anatoly techtonik
2
loggingmodul tidak akan mengalihkan output dari panggilan sistem sepertios.write(1, b'stdout')
jfs
17

Berikut ini adalah solusi lain, yang lebih umum daripada yang lain - ini mendukung pemisahan output (ditulis untuk sys.stdout) ke sejumlah objek seperti file. Tidak ada persyaratan yang __stdout__disertakan.

import sys

class multifile(object):
    def __init__(self, files):
        self._files = files
    def __getattr__(self, attr, *args):
        return self._wrap(attr, *args)
    def _wrap(self, attr, *args):
        def g(*a, **kw):
            for f in self._files:
                res = getattr(f, attr, *args)(*a, **kw)
            return res
        return g

# for a tee-like behavior, use like this:
sys.stdout = multifile([ sys.stdout, open('myfile.txt', 'w') ])

# all these forms work:
print 'abc'
print >>sys.stdout, 'line2'
sys.stdout.write('line3\n')

CATATAN: Ini adalah bukti konsep. Implementasi di sini tidak lengkap, karena hanya membungkus metode objek seperti file (miswrite ), meninggalkan anggota / properti / setattr, dll. Namun, itu mungkin cukup baik bagi kebanyakan orang seperti saat ini berdiri.

Apa yang saya suka tentang hal itu, selain umum, adalah bahwa itu bersih dalam arti tidak membuat panggilan langsung ke write, flush, os.dup2, dll

shx2
sumber
3
Saya akan init mengambil file * bukan file, tetapi sebaliknya, ya, ini. Tidak ada solusi lain yang mengisolasi fungsionalitas "tee" tanpa mencoba memecahkan masalah lain. Jika Anda ingin meletakkan awalan pada semua yang Anda hasilkan, Anda bisa membungkus kelas ini dalam kelas awalan-penulis. (Jika Anda ingin menempatkan awalan hanya pada satu aliran, Anda membungkus aliran dan menyerahkannya ke kelas ini.) Yang ini juga memiliki keuntungan bahwa multifile ([]) membuat file yang mengabaikan segalanya (seperti open ('/ dev) /batal')).
Ben
Kenapa ada _wrapdi sini? Tidak bisakah Anda menyalin kode di sana ke dalam __getattr__dan itu bekerja sama?
timotree
@ Ben sebenarnya multifile([])membuat file yang membangkitkan UnboundLocalErrorsetiap kali Anda memanggil salah satu metodenya. ( resdikembalikan tanpa ditugaskan)
timotree
13

Seperti dijelaskan di tempat lain, mungkin solusi terbaik adalah dengan menggunakan modul logging secara langsung:

import logging

logging.basicConfig(level=logging.DEBUG, filename='mylog.log')
logging.info('this should to write to the log file')

Namun, ada beberapa (jarang) kesempatan di mana Anda benar-benar menginginkannya mengarahkan ulang stdout. Saya mengalami situasi ini ketika saya memperluas perintah runserver Django yang menggunakan print: Saya tidak ingin meretas sumber Django tetapi membutuhkan pernyataan cetak untuk pergi ke file.

Ini adalah cara untuk mengarahkan stdout dan stderr menjauh dari shell menggunakan modul logging:

import logging, sys

class LogFile(object):
    """File-like object to log text using the `logging` module."""

    def __init__(self, name=None):
        self.logger = logging.getLogger(name)

    def write(self, msg, level=logging.INFO):
        self.logger.log(level, msg)

    def flush(self):
        for handler in self.logger.handlers:
            handler.flush()

logging.basicConfig(level=logging.DEBUG, filename='mylog.log')

# Redirect stdout and stderr
sys.stdout = LogFile('stdout')
sys.stderr = LogFile('stderr')

print 'this should to write to the log file'

Anda hanya harus menggunakan implementasi LogFile ini jika Anda benar-benar tidak dapat menggunakan modul logging secara langsung.

blokeley
sumber
11

Saya menulis tee()implementasi dengan Python yang seharusnya bisa digunakan pada kebanyakan kasus, dan ini juga bisa digunakan di Windows.

https://github.com/pycontribs/tendo

Juga, Anda dapat menggunakannya dalam kombinasi dengan loggingmodul dari Python jika Anda mau.

Sorin
sumber
Hmm - tautan itu tidak lagi berfungsi - di mana saja itu dapat ditemukan?
Danny Staple
1
wow, paket Anda berbunyi, terutama jika Anda tahu betapa rumitnya budaya konsol Windows tetapi tidak menyerah untuk membuatnya berfungsi!
n611x007
8

(Ah, baca kembali pertanyaan Anda dan lihat ini tidak berlaku.)

Berikut adalah contoh program yang menggunakan modul python logging . Modul logging ini telah ada di semua versi sejak 2.3. Dalam sampel ini logging dapat dikonfigurasi oleh opsi baris perintah.

Dalam mode cukup itu hanya akan masuk ke file, dalam mode normal itu akan masuk ke file dan konsol.

import os
import sys
import logging
from optparse import OptionParser

def initialize_logging(options):
    """ Log information based upon users options"""

    logger = logging.getLogger('project')
    formatter = logging.Formatter('%(asctime)s %(levelname)s\t%(message)s')
    level = logging.__dict__.get(options.loglevel.upper(),logging.DEBUG)
    logger.setLevel(level)

    # Output logging information to screen
    if not options.quiet:
        hdlr = logging.StreamHandler(sys.stderr)
        hdlr.setFormatter(formatter)
        logger.addHandler(hdlr)

    # Output logging information to file
    logfile = os.path.join(options.logdir, "project.log")
    if options.clean and os.path.isfile(logfile):
        os.remove(logfile)
    hdlr2 = logging.FileHandler(logfile)
    hdlr2.setFormatter(formatter)
    logger.addHandler(hdlr2)

    return logger

def main(argv=None):
    if argv is None:
        argv = sys.argv[1:]

    # Setup command line options
    parser = OptionParser("usage: %prog [options]")
    parser.add_option("-l", "--logdir", dest="logdir", default=".", help="log DIRECTORY (default ./)")
    parser.add_option("-v", "--loglevel", dest="loglevel", default="debug", help="logging level (debug, info, error)")
    parser.add_option("-q", "--quiet", action="store_true", dest="quiet", help="do not log to console")
    parser.add_option("-c", "--clean", dest="clean", action="store_true", default=False, help="remove old log file")

    # Process command line options
    (options, args) = parser.parse_args(argv)

    # Setup logger format and output locations
    logger = initialize_logging(options)

    # Examples
    logger.error("This is an error message.")
    logger.info("This is an info message.")
    logger.debug("This is a debug message.")

if __name__ == "__main__":
    sys.exit(main())
Atlas1j
sumber
Jawaban yang bagus. Aku melihat beberapa cara yang benar-benar berbelit-belit mereplikasi penebangan ke konsol, tetapi membuat StreamHandler dengan stderr itu jawaban yang saya sudah mencari :)
meatvest
Kode ini bagus meletakkannya tidak menjawab pertanyaan - ini mengeluarkan log ke dalam file dan stderr, pertanyaan asli meminta untuk menduplikasi stderr ke file log.
emem
8

Untuk melengkapi jawaban John T: https://stackoverflow.com/a/616686/395687

Saya menambahkan __enter__dan __exit__metode untuk menggunakannya sebagai manajer konteks dengan withkata kunci, yang memberikan kode ini

class Tee(object):
    def __init__(self, name, mode):
        self.file = open(name, mode)
        self.stdout = sys.stdout
        sys.stdout = self

    def __del__(self):
        sys.stdout = self.stdout
        self.file.close()

    def write(self, data):
        self.file.write(data)
        self.stdout.write(data)

    def __enter__(self):
        pass

    def __exit__(self, _type, _value, _traceback):
        pass

Ini kemudian dapat digunakan sebagai

with Tee('outfile.log', 'w'):
    print('I am written to both stdout and outfile.log')
cladmi
sumber
1
Saya akan memindahkan __del__fungsionalitas ke__exit__
vontrapp
1
Memang, saya pikir menggunakan __del__adalah ide yang buruk. Itu harus dipindahkan ke fungsi 'tutup' yang dipanggil __exit__.
cladmi
7

Saya tahu pertanyaan ini telah dijawab berulang kali, tetapi untuk ini saya telah mengambil jawaban utama dari jawaban John T dan memodifikasinya sehingga berisi flush yang disarankan dan mengikuti versi revisi yang ditautkan. Saya juga menambahkan masuk dan keluar seperti yang disebutkan dalam cladmi's jawaban untuk digunakan dengan pernyataan with. Selain itu, dokumentasi menyebutkan untuk menggunakan file flush os.fsync()jadi saya telah menambahkannya juga. Saya tidak tahu apakah Anda benar - benar membutuhkannya tetapi ada di sana.

import sys, os

class Logger(object):
    "Lumberjack class - duplicates sys.stdout to a log file and it's okay"
    #source: https://stackoverflow.com/q/616645
    def __init__(self, filename="Red.Wood", mode="a", buff=0):
        self.stdout = sys.stdout
        self.file = open(filename, mode, buff)
        sys.stdout = self

    def __del__(self):
        self.close()

    def __enter__(self):
        pass

    def __exit__(self, *args):
        self.close()

    def write(self, message):
        self.stdout.write(message)
        self.file.write(message)

    def flush(self):
        self.stdout.flush()
        self.file.flush()
        os.fsync(self.file.fileno())

    def close(self):
        if self.stdout != None:
            sys.stdout = self.stdout
            self.stdout = None

        if self.file != None:
            self.file.close()
            self.file = None

Anda kemudian dapat menggunakannya

with Logger('My_best_girlie_by_my.side'):
    print("we'd sing sing sing")

atau

Log=Logger('Sleeps_all.night')
print('works all day')
Log.close()
Status
sumber
Banyak Thnaks @ Status yang Anda pecahkan pertanyaan saya ( stackoverflow.com/questions/39143417/… ). Saya akan menaruh tautan ke solusi Anda.
Mohammad ElNesr
1
@MohammadElNesr Saya baru menyadari ada masalah dengan kode ketika digunakan dengan pernyataan with. Saya telah memperbaikinya dan sekarang ditutup dengan benar di akhir blok with.
Status
1
Ini bekerja sangat baik untuk saya, hanya perlu mengubah mode ke mode="ab"dan dalam writefungsiself.file.write(message.encode("utf-8"))
ennetws
4

solusi lain menggunakan modul logging:

import logging
import sys

log = logging.getLogger('stdxxx')

class StreamLogger(object):

    def __init__(self, stream, prefix=''):
        self.stream = stream
        self.prefix = prefix
        self.data = ''

    def write(self, data):
        self.stream.write(data)
        self.stream.flush()

        self.data += data
        tmp = str(self.data)
        if '\x0a' in tmp or '\x0d' in tmp:
            tmp = tmp.rstrip('\x0a\x0d')
            log.info('%s%s' % (self.prefix, tmp))
            self.data = ''


logging.basicConfig(level=logging.INFO,
                    filename='text.log',
                    filemode='a')

sys.stdout = StreamLogger(sys.stdout, '[stdout] ')

print 'test for stdout'
Denis Barmenkov
sumber
3

Tidak ada jawaban di atas yang benar-benar menjawab masalah yang diajukan. Saya tahu ini adalah utas lama, tetapi saya pikir masalah ini jauh lebih sederhana daripada yang dilakukan semua orang:

class tee_err(object):

 def __init__(self):
    self.errout = sys.stderr

    sys.stderr = self

    self.log = 'logfile.log'
    log = open(self.log,'w')
    log.close()

 def write(self, line):

    log = open(self.log,'a')
    log.write(line)
    log.close()   

    self.errout.write(line)

Sekarang ini akan mengulang semuanya ke handler sys.stderr normal dan file Anda. Buat kelas lain tee_outuntuk sys.stdout.

josianator
sumber
2
Jawaban serupa yang lebih baik telah diposting lebih dari dua tahun sebelum yang ini: stackoverflow.com/a/616686 . Metode Anda sangat mahal: Setiap panggilan untuk tee=tee_err();tee.write('');tee.write('');...membuka + menutup file untuk masing-masing write. Lihat stackoverflow.com/q/4867468 dan stackoverflow.com/q/164053 untuk argumen yang menentang praktik ini.
Rob W
3

Sesuai permintaan oleh @ user5359531 dalam komentar di bawah jawaban @John T , inilah salinan pos yang direferensikan ke versi revisi dari diskusi terkait dalam jawaban itu:

Issue of redirecting the stdout to both file and screen
Gabriel Genellina gagsl-py2 at yahoo.com.ar
Mon May 28 12:45:51 CEST 2007

    Previous message: Issue of redirecting the stdout to both file and screen
    Next message: Formal interfaces with Python
    Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]

En Mon, 28 May 2007 06:17:39 -0300, 人言落日是天涯,望极天涯不见家
<kelvin.you at gmail.com> escribió:

> I wanna print the log to both the screen and file, so I simulatered a
> 'tee'
>
> class Tee(file):
>
>     def __init__(self, name, mode):
>         file.__init__(self, name, mode)
>         self.stdout = sys.stdout
>         sys.stdout = self
>
>     def __del__(self):
>         sys.stdout = self.stdout
>         self.close()
>
>     def write(self, data):
>         file.write(self, data)
>         self.stdout.write(data)
>
> Tee('logfile', 'w')
> print >>sys.stdout, 'abcdefg'
>
> I found that it only output to the file, nothing to screen. Why?
> It seems the 'write' function was not called when I *print* something.

You create a Tee instance and it is immediately garbage collected. I'd
restore sys.stdout on Tee.close, not __del__ (you forgot to call the
inherited __del__ method, btw).
Mmm, doesn't work. I think there is an optimization somewhere: if it looks
like a real file object, it uses the original file write method, not yours.
The trick would be to use an object that does NOT inherit from file:

import sys
class TeeNoFile(object):
     def __init__(self, name, mode):
         self.file = open(name, mode)
         self.stdout = sys.stdout
         sys.stdout = self
     def close(self):
         if self.stdout is not None:
             sys.stdout = self.stdout
             self.stdout = None
         if self.file is not None:
             self.file.close()
             self.file = None
     def write(self, data):
         self.file.write(data)
         self.stdout.write(data)
     def flush(self):
         self.file.flush()
         self.stdout.flush()
     def __del__(self):
         self.close()

tee=TeeNoFile('logfile', 'w')
print 'abcdefg'
print 'another line'
tee.close()
print 'screen only'
del tee # should do nothing

--
Gabriel Genellina
martineau
sumber
1

Saya sedang menulis skrip untuk menjalankan skrip cmd-line. (Karena dalam beberapa kasus, tidak ada pengganti yang layak untuk perintah Linux - seperti kasus rsync.)

Apa yang saya benar-benar inginkan adalah menggunakan mekanisme logging python default dalam setiap kasus di mana dimungkinkan untuk melakukannya, tetapi untuk tetap menangkap kesalahan ketika ada sesuatu yang salah yang tidak terduga.

Kode ini sepertinya melakukan trik. Ini mungkin tidak terlalu elegan atau efisien (meskipun tidak menggunakan string + = string, jadi setidaknya tidak memiliki potensi leher botol tertentu). Saya mempostingnya kalau-kalau itu memberi orang lain ide yang berguna.

import logging
import os, sys
import datetime

# Get name of module, use as application name
try:
  ME=os.path.split(__file__)[-1].split('.')[0]
except:
  ME='pyExec_'

LOG_IDENTIFIER="uuu___( o O )___uuu "
LOG_IDR_LENGTH=len(LOG_IDENTIFIER)

class PyExec(object):

  # Use this to capture all possible error / output to log
  class SuperTee(object):
      # Original reference: http://mail.python.org/pipermail/python-list/2007-May/442737.html
      def __init__(self, name, mode):
          self.fl = open(name, mode)
          self.fl.write('\n')
          self.stdout = sys.stdout
          self.stdout.write('\n')
          self.stderr = sys.stderr

          sys.stdout = self
          sys.stderr = self

      def __del__(self):
          self.fl.write('\n')
          self.fl.flush()
          sys.stderr = self.stderr
          sys.stdout = self.stdout
          self.fl.close()

      def write(self, data):
          # If the data to write includes the log identifier prefix, then it is already formatted
          if data[0:LOG_IDR_LENGTH]==LOG_IDENTIFIER:
            self.fl.write("%s\n" % data[LOG_IDR_LENGTH:])
            self.stdout.write(data[LOG_IDR_LENGTH:])

          # Otherwise, we can give it a timestamp
          else:

            timestamp=str(datetime.datetime.now())
            if 'Traceback' == data[0:9]:
              data='%s: %s' % (timestamp, data)
              self.fl.write(data)
            else:
              self.fl.write(data)

            self.stdout.write(data)


  def __init__(self, aName, aCmd, logFileName='', outFileName=''):

    # Using name for 'logger' (context?), which is separate from the module or the function
    baseFormatter=logging.Formatter("%(asctime)s \t %(levelname)s \t %(name)s:%(module)s:%(lineno)d \t %(message)s")
    errorFormatter=logging.Formatter(LOG_IDENTIFIER + "%(asctime)s \t %(levelname)s \t %(name)s:%(module)s:%(lineno)d \t %(message)s")

    if logFileName:
      # open passed filename as append
      fl=logging.FileHandler("%s.log" % aName)
    else:
      # otherwise, use log filename as a one-time use file
      fl=logging.FileHandler("%s.log" % aName, 'w')

    fl.setLevel(logging.DEBUG)
    fl.setFormatter(baseFormatter)

    # This will capture stdout and CRITICAL and beyond errors

    if outFileName:
      teeFile=PyExec.SuperTee("%s_out.log" % aName)
    else:
      teeFile=PyExec.SuperTee("%s_out.log" % aName, 'w')

    fl_out=logging.StreamHandler( teeFile )
    fl_out.setLevel(logging.CRITICAL)
    fl_out.setFormatter(errorFormatter)

    # Set up logging
    self.log=logging.getLogger('pyExec_main')
    log=self.log

    log.addHandler(fl)
    log.addHandler(fl_out)

    print "Test print statement."

    log.setLevel(logging.DEBUG)

    log.info("Starting %s", ME)
    log.critical("Critical.")

    # Caught exception
    try:
      raise Exception('Exception test.')
    except Exception,e:
      log.exception(str(e))

    # Uncaught exception
    a=2/0


PyExec('test_pyExec',None)

Jelas, jika Anda tidak tunduk pada imajinasi seperti saya, ganti LOG_IDENTIFIER dengan string lain yang Anda tidak ingin pernah melihat seseorang menulis ke log.

cognitiaclaeves
sumber
0

Jika Anda ingin mencatat semua output ke file DAN output ke file teks maka Anda dapat melakukan hal berikut. Agak gila tapi berhasil:

import logging
debug = input("Debug or not")
if debug == "1":
    logging.basicConfig(level=logging.DEBUG, filename='./OUT.txt')
    old_print = print
    def print(string):
        old_print(string)
        logging.info(string)
print("OMG it works!")

EDIT: Perhatikan bahwa ini tidak mencatat kesalahan kecuali Anda mengarahkan ulang sys.stderr ke sys.stdout

EDIT2: Masalah kedua adalah bahwa Anda harus melewati 1 argumen tidak seperti dengan fungsi builtin.

EDIT3: Lihat kode sebelum menulis stdin dan stdout ke konsol dan file dengan stderr hanya pergi ke file

import logging, sys
debug = input("Debug or not")
if debug == "1":
    old_input = input
    sys.stderr.write = logging.info
    def input(string=""):
        string_in = old_input(string)
        logging.info("STRING IN " + string_in)
        return string_in
    logging.basicConfig(level=logging.DEBUG, filename='./OUT.txt')
    old_print = print
    def print(string="", string2=""):
        old_print(string, string2)
        logging.info(string)
        logging.info(string2)
print("OMG")
b = input()
print(a) ## Deliberate error for testing
Jensen Taylor
sumber
-1

Aku menulis pengganti penuh untuk sys.stderrdan hanya digandakan kode mengubah nama stderruntuk stdoutuntuk membuatnya juga tersedia untuk menggantikan sys.stdout.

Untuk melakukan ini, saya membuat jenis objek yang sama dengan saat ini stderrdan stdout, dan meneruskan semua metode ke sistem asli stderrdan stdout:

import os
import sys
import logging

class StdErrReplament(object):
    """
        How to redirect stdout and stderr to logger in Python
        /programming/19425736/how-to-redirect-stdout-and-stderr-to-logger-in-python

        Set a Read-Only Attribute in Python?
        /programming/24497316/set-a-read-only-attribute-in-python
    """
    is_active = False

    @classmethod
    def lock(cls, logger):
        """
            Attach this singleton logger to the `sys.stderr` permanently.
        """
        global _stderr_singleton
        global _stderr_default
        global _stderr_default_class_type

        # On Sublime Text, `sys.__stderr__` is set to None, because they already replaced `sys.stderr`
        # by some `_LogWriter()` class, then just save the current one over there.
        if not sys.__stderr__:
            sys.__stderr__ = sys.stderr

        try:
            _stderr_default
            _stderr_default_class_type

        except NameError:
            _stderr_default = sys.stderr
            _stderr_default_class_type = type( _stderr_default )

        # Recreate the sys.stderr logger when it was reset by `unlock()`
        if not cls.is_active:
            cls.is_active = True
            _stderr_write = _stderr_default.write

            logger_call = logger.debug
            clean_formatter = logger.clean_formatter

            global _sys_stderr_write
            global _sys_stderr_write_hidden

            if sys.version_info <= (3,2):
                logger.file_handler.terminator = '\n'

            # Always recreate/override the internal write function used by `_sys_stderr_write`
            def _sys_stderr_write_hidden(*args, **kwargs):
                """
                    Suppress newline in Python logging module
                    /programming/7168790/suppress-newline-in-python-logging-module
                """

                try:
                    _stderr_write( *args, **kwargs )
                    file_handler = logger.file_handler

                    formatter = file_handler.formatter
                    terminator = file_handler.terminator

                    file_handler.formatter = clean_formatter
                    file_handler.terminator = ""

                    kwargs['extra'] = {'_duplicated_from_file': True}
                    logger_call( *args, **kwargs )

                    file_handler.formatter = formatter
                    file_handler.terminator = terminator

                except Exception:
                    logger.exception( "Could not write to the file_handler: %s(%s)", file_handler, logger )
                    cls.unlock()

            # Only create one `_sys_stderr_write` function pointer ever
            try:
                _sys_stderr_write

            except NameError:

                def _sys_stderr_write(*args, **kwargs):
                    """
                        Hides the actual function pointer. This allow the external function pointer to
                        be cached while the internal written can be exchanged between the standard
                        `sys.stderr.write` and our custom wrapper around it.
                    """
                    _sys_stderr_write_hidden( *args, **kwargs )

        try:
            # Only create one singleton instance ever
            _stderr_singleton

        except NameError:

            class StdErrReplamentHidden(_stderr_default_class_type):
                """
                    Which special methods bypasses __getattribute__ in Python?
                    /programming/12872695/which-special-methods-bypasses-getattribute-in-python
                """

                if hasattr( _stderr_default, "__abstractmethods__" ):
                    __abstractmethods__ = _stderr_default.__abstractmethods__

                if hasattr( _stderr_default, "__base__" ):
                    __base__ = _stderr_default.__base__

                if hasattr( _stderr_default, "__bases__" ):
                    __bases__ = _stderr_default.__bases__

                if hasattr( _stderr_default, "__basicsize__" ):
                    __basicsize__ = _stderr_default.__basicsize__

                if hasattr( _stderr_default, "__call__" ):
                    __call__ = _stderr_default.__call__

                if hasattr( _stderr_default, "__class__" ):
                    __class__ = _stderr_default.__class__

                if hasattr( _stderr_default, "__delattr__" ):
                    __delattr__ = _stderr_default.__delattr__

                if hasattr( _stderr_default, "__dict__" ):
                    __dict__ = _stderr_default.__dict__

                if hasattr( _stderr_default, "__dictoffset__" ):
                    __dictoffset__ = _stderr_default.__dictoffset__

                if hasattr( _stderr_default, "__dir__" ):
                    __dir__ = _stderr_default.__dir__

                if hasattr( _stderr_default, "__doc__" ):
                    __doc__ = _stderr_default.__doc__

                if hasattr( _stderr_default, "__eq__" ):
                    __eq__ = _stderr_default.__eq__

                if hasattr( _stderr_default, "__flags__" ):
                    __flags__ = _stderr_default.__flags__

                if hasattr( _stderr_default, "__format__" ):
                    __format__ = _stderr_default.__format__

                if hasattr( _stderr_default, "__ge__" ):
                    __ge__ = _stderr_default.__ge__

                if hasattr( _stderr_default, "__getattribute__" ):
                    __getattribute__ = _stderr_default.__getattribute__

                if hasattr( _stderr_default, "__gt__" ):
                    __gt__ = _stderr_default.__gt__

                if hasattr( _stderr_default, "__hash__" ):
                    __hash__ = _stderr_default.__hash__

                if hasattr( _stderr_default, "__init__" ):
                    __init__ = _stderr_default.__init__

                if hasattr( _stderr_default, "__init_subclass__" ):
                    __init_subclass__ = _stderr_default.__init_subclass__

                if hasattr( _stderr_default, "__instancecheck__" ):
                    __instancecheck__ = _stderr_default.__instancecheck__

                if hasattr( _stderr_default, "__itemsize__" ):
                    __itemsize__ = _stderr_default.__itemsize__

                if hasattr( _stderr_default, "__le__" ):
                    __le__ = _stderr_default.__le__

                if hasattr( _stderr_default, "__lt__" ):
                    __lt__ = _stderr_default.__lt__

                if hasattr( _stderr_default, "__module__" ):
                    __module__ = _stderr_default.__module__

                if hasattr( _stderr_default, "__mro__" ):
                    __mro__ = _stderr_default.__mro__

                if hasattr( _stderr_default, "__name__" ):
                    __name__ = _stderr_default.__name__

                if hasattr( _stderr_default, "__ne__" ):
                    __ne__ = _stderr_default.__ne__

                if hasattr( _stderr_default, "__new__" ):
                    __new__ = _stderr_default.__new__

                if hasattr( _stderr_default, "__prepare__" ):
                    __prepare__ = _stderr_default.__prepare__

                if hasattr( _stderr_default, "__qualname__" ):
                    __qualname__ = _stderr_default.__qualname__

                if hasattr( _stderr_default, "__reduce__" ):
                    __reduce__ = _stderr_default.__reduce__

                if hasattr( _stderr_default, "__reduce_ex__" ):
                    __reduce_ex__ = _stderr_default.__reduce_ex__

                if hasattr( _stderr_default, "__repr__" ):
                    __repr__ = _stderr_default.__repr__

                if hasattr( _stderr_default, "__setattr__" ):
                    __setattr__ = _stderr_default.__setattr__

                if hasattr( _stderr_default, "__sizeof__" ):
                    __sizeof__ = _stderr_default.__sizeof__

                if hasattr( _stderr_default, "__str__" ):
                    __str__ = _stderr_default.__str__

                if hasattr( _stderr_default, "__subclasscheck__" ):
                    __subclasscheck__ = _stderr_default.__subclasscheck__

                if hasattr( _stderr_default, "__subclasses__" ):
                    __subclasses__ = _stderr_default.__subclasses__

                if hasattr( _stderr_default, "__subclasshook__" ):
                    __subclasshook__ = _stderr_default.__subclasshook__

                if hasattr( _stderr_default, "__text_signature__" ):
                    __text_signature__ = _stderr_default.__text_signature__

                if hasattr( _stderr_default, "__weakrefoffset__" ):
                    __weakrefoffset__ = _stderr_default.__weakrefoffset__

                if hasattr( _stderr_default, "mro" ):
                    mro = _stderr_default.mro

                def __init__(self):
                    """
                        Override any super class `type( _stderr_default )` constructor, so we can 
                        instantiate any kind of `sys.stderr` replacement object, in case it was already 
                        replaced by something else like on Sublime Text with `_LogWriter()`.

                        Assures all attributes were statically replaced just above. This should happen in case
                        some new attribute is added to the python language.

                        This also ignores the only two methods which are not equal, `__init__()` and `__getattribute__()`.
                    """
                    different_methods = ("__init__", "__getattribute__")
                    attributes_to_check = set( dir( object ) + dir( type ) )

                    for attribute in attributes_to_check:

                        if attribute not in different_methods \
                                and hasattr( _stderr_default, attribute ):

                            base_class_attribute = super( _stderr_default_class_type, self ).__getattribute__( attribute )
                            target_class_attribute = _stderr_default.__getattribute__( attribute )

                            if base_class_attribute != target_class_attribute:
                                sys.stderr.write( "    The base class attribute `%s` is different from the target class:\n%s\n%s\n\n" % (
                                        attribute, base_class_attribute, target_class_attribute ) )

                def __getattribute__(self, item):

                    if item == 'write':
                        return _sys_stderr_write

                    try:
                        return _stderr_default.__getattribute__( item )

                    except AttributeError:
                        return super( _stderr_default_class_type, _stderr_default ).__getattribute__( item )

            _stderr_singleton = StdErrReplamentHidden()
            sys.stderr = _stderr_singleton

        return cls

    @classmethod
    def unlock(cls):
        """
            Detach this `stderr` writer from `sys.stderr` and allow the next call to `lock()` create
            a new writer for the stderr.
        """

        if cls.is_active:
            global _sys_stderr_write_hidden

            cls.is_active = False
            _sys_stderr_write_hidden = _stderr_default.write



class StdOutReplament(object):
    """
        How to redirect stdout and stderr to logger in Python
        /programming/19425736/how-to-redirect-stdout-and-stderr-to-logger-in-python

        Set a Read-Only Attribute in Python?
        /programming/24497316/set-a-read-only-attribute-in-python
    """
    is_active = False

    @classmethod
    def lock(cls, logger):
        """
            Attach this singleton logger to the `sys.stdout` permanently.
        """
        global _stdout_singleton
        global _stdout_default
        global _stdout_default_class_type

        # On Sublime Text, `sys.__stdout__` is set to None, because they already replaced `sys.stdout`
        # by some `_LogWriter()` class, then just save the current one over there.
        if not sys.__stdout__:
            sys.__stdout__ = sys.stdout

        try:
            _stdout_default
            _stdout_default_class_type

        except NameError:
            _stdout_default = sys.stdout
            _stdout_default_class_type = type( _stdout_default )

        # Recreate the sys.stdout logger when it was reset by `unlock()`
        if not cls.is_active:
            cls.is_active = True
            _stdout_write = _stdout_default.write

            logger_call = logger.debug
            clean_formatter = logger.clean_formatter

            global _sys_stdout_write
            global _sys_stdout_write_hidden

            if sys.version_info <= (3,2):
                logger.file_handler.terminator = '\n'

            # Always recreate/override the internal write function used by `_sys_stdout_write`
            def _sys_stdout_write_hidden(*args, **kwargs):
                """
                    Suppress newline in Python logging module
                    /programming/7168790/suppress-newline-in-python-logging-module
                """

                try:
                    _stdout_write( *args, **kwargs )
                    file_handler = logger.file_handler

                    formatter = file_handler.formatter
                    terminator = file_handler.terminator

                    file_handler.formatter = clean_formatter
                    file_handler.terminator = ""

                    kwargs['extra'] = {'_duplicated_from_file': True}
                    logger_call( *args, **kwargs )

                    file_handler.formatter = formatter
                    file_handler.terminator = terminator

                except Exception:
                    logger.exception( "Could not write to the file_handler: %s(%s)", file_handler, logger )
                    cls.unlock()

            # Only create one `_sys_stdout_write` function pointer ever
            try:
                _sys_stdout_write

            except NameError:

                def _sys_stdout_write(*args, **kwargs):
                    """
                        Hides the actual function pointer. This allow the external function pointer to
                        be cached while the internal written can be exchanged between the standard
                        `sys.stdout.write` and our custom wrapper around it.
                    """
                    _sys_stdout_write_hidden( *args, **kwargs )

        try:
            # Only create one singleton instance ever
            _stdout_singleton

        except NameError:

            class StdOutReplamentHidden(_stdout_default_class_type):
                """
                    Which special methods bypasses __getattribute__ in Python?
                    /programming/12872695/which-special-methods-bypasses-getattribute-in-python
                """

                if hasattr( _stdout_default, "__abstractmethods__" ):
                    __abstractmethods__ = _stdout_default.__abstractmethods__

                if hasattr( _stdout_default, "__base__" ):
                    __base__ = _stdout_default.__base__

                if hasattr( _stdout_default, "__bases__" ):
                    __bases__ = _stdout_default.__bases__

                if hasattr( _stdout_default, "__basicsize__" ):
                    __basicsize__ = _stdout_default.__basicsize__

                if hasattr( _stdout_default, "__call__" ):
                    __call__ = _stdout_default.__call__

                if hasattr( _stdout_default, "__class__" ):
                    __class__ = _stdout_default.__class__

                if hasattr( _stdout_default, "__delattr__" ):
                    __delattr__ = _stdout_default.__delattr__

                if hasattr( _stdout_default, "__dict__" ):
                    __dict__ = _stdout_default.__dict__

                if hasattr( _stdout_default, "__dictoffset__" ):
                    __dictoffset__ = _stdout_default.__dictoffset__

                if hasattr( _stdout_default, "__dir__" ):
                    __dir__ = _stdout_default.__dir__

                if hasattr( _stdout_default, "__doc__" ):
                    __doc__ = _stdout_default.__doc__

                if hasattr( _stdout_default, "__eq__" ):
                    __eq__ = _stdout_default.__eq__

                if hasattr( _stdout_default, "__flags__" ):
                    __flags__ = _stdout_default.__flags__

                if hasattr( _stdout_default, "__format__" ):
                    __format__ = _stdout_default.__format__

                if hasattr( _stdout_default, "__ge__" ):
                    __ge__ = _stdout_default.__ge__

                if hasattr( _stdout_default, "__getattribute__" ):
                    __getattribute__ = _stdout_default.__getattribute__

                if hasattr( _stdout_default, "__gt__" ):
                    __gt__ = _stdout_default.__gt__

                if hasattr( _stdout_default, "__hash__" ):
                    __hash__ = _stdout_default.__hash__

                if hasattr( _stdout_default, "__init__" ):
                    __init__ = _stdout_default.__init__

                if hasattr( _stdout_default, "__init_subclass__" ):
                    __init_subclass__ = _stdout_default.__init_subclass__

                if hasattr( _stdout_default, "__instancecheck__" ):
                    __instancecheck__ = _stdout_default.__instancecheck__

                if hasattr( _stdout_default, "__itemsize__" ):
                    __itemsize__ = _stdout_default.__itemsize__

                if hasattr( _stdout_default, "__le__" ):
                    __le__ = _stdout_default.__le__

                if hasattr( _stdout_default, "__lt__" ):
                    __lt__ = _stdout_default.__lt__

                if hasattr( _stdout_default, "__module__" ):
                    __module__ = _stdout_default.__module__

                if hasattr( _stdout_default, "__mro__" ):
                    __mro__ = _stdout_default.__mro__

                if hasattr( _stdout_default, "__name__" ):
                    __name__ = _stdout_default.__name__

                if hasattr( _stdout_default, "__ne__" ):
                    __ne__ = _stdout_default.__ne__

                if hasattr( _stdout_default, "__new__" ):
                    __new__ = _stdout_default.__new__

                if hasattr( _stdout_default, "__prepare__" ):
                    __prepare__ = _stdout_default.__prepare__

                if hasattr( _stdout_default, "__qualname__" ):
                    __qualname__ = _stdout_default.__qualname__

                if hasattr( _stdout_default, "__reduce__" ):
                    __reduce__ = _stdout_default.__reduce__

                if hasattr( _stdout_default, "__reduce_ex__" ):
                    __reduce_ex__ = _stdout_default.__reduce_ex__

                if hasattr( _stdout_default, "__repr__" ):
                    __repr__ = _stdout_default.__repr__

                if hasattr( _stdout_default, "__setattr__" ):
                    __setattr__ = _stdout_default.__setattr__

                if hasattr( _stdout_default, "__sizeof__" ):
                    __sizeof__ = _stdout_default.__sizeof__

                if hasattr( _stdout_default, "__str__" ):
                    __str__ = _stdout_default.__str__

                if hasattr( _stdout_default, "__subclasscheck__" ):
                    __subclasscheck__ = _stdout_default.__subclasscheck__

                if hasattr( _stdout_default, "__subclasses__" ):
                    __subclasses__ = _stdout_default.__subclasses__

                if hasattr( _stdout_default, "__subclasshook__" ):
                    __subclasshook__ = _stdout_default.__subclasshook__

                if hasattr( _stdout_default, "__text_signature__" ):
                    __text_signature__ = _stdout_default.__text_signature__

                if hasattr( _stdout_default, "__weakrefoffset__" ):
                    __weakrefoffset__ = _stdout_default.__weakrefoffset__

                if hasattr( _stdout_default, "mro" ):
                    mro = _stdout_default.mro

                def __init__(self):
                    """
                        Override any super class `type( _stdout_default )` constructor, so we can 
                        instantiate any kind of `sys.stdout` replacement object, in case it was already 
                        replaced by something else like on Sublime Text with `_LogWriter()`.

                        Assures all attributes were statically replaced just above. This should happen in case
                        some new attribute is added to the python language.

                        This also ignores the only two methods which are not equal, `__init__()` and `__getattribute__()`.
                    """
                    different_methods = ("__init__", "__getattribute__")
                    attributes_to_check = set( dir( object ) + dir( type ) )

                    for attribute in attributes_to_check:

                        if attribute not in different_methods \
                                and hasattr( _stdout_default, attribute ):

                            base_class_attribute = super( _stdout_default_class_type, self ).__getattribute__( attribute )
                            target_class_attribute = _stdout_default.__getattribute__( attribute )

                            if base_class_attribute != target_class_attribute:
                                sys.stdout.write( "    The base class attribute `%s` is different from the target class:\n%s\n%s\n\n" % (
                                        attribute, base_class_attribute, target_class_attribute ) )

                def __getattribute__(self, item):

                    if item == 'write':
                        return _sys_stdout_write

                    try:
                        return _stdout_default.__getattribute__( item )

                    except AttributeError:
                        return super( _stdout_default_class_type, _stdout_default ).__getattribute__( item )

            _stdout_singleton = StdOutReplamentHidden()
            sys.stdout = _stdout_singleton

        return cls

    @classmethod
    def unlock(cls):
        """
            Detach this `stdout` writer from `sys.stdout` and allow the next call to `lock()` create
            a new writer for the stdout.
        """

        if cls.is_active:
            global _sys_stdout_write_hidden

            cls.is_active = False
            _sys_stdout_write_hidden = _stdout_default.write

Untuk menggunakan ini, Anda bisa memanggil StdErrReplament::lock(logger)dan StdOutReplament::lock(logger) melewati logger yang ingin Anda gunakan untuk mengirim teks output. Sebagai contoh:

import os
import sys
import logging

current_folder = os.path.dirname( os.path.realpath( __file__ ) )
log_file_path = os.path.join( current_folder, "my_log_file.txt" )

file_handler = logging.FileHandler( log_file_path, 'a' )
file_handler.formatter = logging.Formatter( "%(asctime)s %(name)s %(levelname)s - %(message)s", "%Y-%m-%d %H:%M:%S" )

log = logging.getLogger( __name__ )
log.setLevel( "DEBUG" )
log.addHandler( file_handler )

log.file_handler = file_handler
log.clean_formatter = logging.Formatter( "", "" )

StdOutReplament.lock( log )
StdErrReplament.lock( log )

log.debug( "I am doing usual logging debug..." )
sys.stderr.write( "Tests 1...\n" )
sys.stdout.write( "Tests 2...\n" )

Menjalankan kode ini, Anda akan melihat di layar:

masukkan deskripsi gambar di sini

Dan pada isi file:

masukkan deskripsi gambar di sini

Jika Anda juga ingin melihat konten log.debugpanggilan di layar, Anda perlu menambahkan stream handler ke logger Anda. Dalam hal ini akan menjadi seperti ini:

import os
import sys
import logging

class ContextFilter(logging.Filter):
    """ This filter avoids duplicated information to be displayed to the StreamHandler log. """
    def filter(self, record):
        return not "_duplicated_from_file" in record.__dict__

current_folder = os.path.dirname( os.path.realpath( __file__ ) )
log_file_path = os.path.join( current_folder, "my_log_file.txt" )

stream_handler = logging.StreamHandler()
file_handler = logging.FileHandler( log_file_path, 'a' )

formatter = logging.Formatter( "%(asctime)s %(name)s %(levelname)s - %(message)s", "%Y-%m-%d %H:%M:%S" )
file_handler.formatter = formatter
stream_handler.formatter = formatter
stream_handler.addFilter( ContextFilter() )

log = logging.getLogger( __name__ )
log.setLevel( "DEBUG" )
log.addHandler( file_handler )
log.addHandler( stream_handler )

log.file_handler = file_handler
log.stream_handler = stream_handler
log.clean_formatter = logging.Formatter( "", "" )

StdOutReplament.lock( log )
StdErrReplament.lock( log )

log.debug( "I am doing usual logging debug..." )
sys.stderr.write( "Tests 1...\n" )
sys.stdout.write( "Tests 2...\n" )

Yang akan ditampilkan seperti ini saat menjalankan:

masukkan deskripsi gambar di sini

Meskipun masih menyimpan ini ke file my_log_file.txt:

masukkan deskripsi gambar di sini

Saat menonaktifkan ini dengan StdErrReplament:unlock(), itu hanya akan mengembalikan perilaku standar stderraliran, karena logger terlampir tidak dapat dilepaskan karena orang lain dapat memiliki referensi ke versi yang lebih lama. Inilah sebabnya mengapa itu adalah singleton global yang tidak pernah bisa mati. Oleh karena itu, dalam kasus memuat kembali modul ini dengan impatau sesuatu yang lain, itu tidak akan pernah merebut kembali arus sys.stderrkarena sudah disuntikkan padanya dan disimpan secara internal.

pengguna
sumber
5
tingkat kerumitan yang tidak disengaja untuk menduplikasi aliran.
Attila Lendvai