วิธีการทำซ้ำ sys.stdout ไปยังล็อกไฟล์


149

แก้ไข: เนื่องจากปรากฏว่าไม่มีวิธีแก้ปัญหาหรือฉันกำลังทำสิ่งที่ไม่ได้มาตรฐานซึ่งไม่มีใครรู้ - ฉันจะแก้ไขคำถามของฉันด้วยถาม: วิธีที่ดีที่สุดในการบันทึกให้สำเร็จคือเมื่อแอปหลามกำลังสร้าง การโทรของระบบจำนวนมากหรือไม่

แอพของฉันมีสองโหมด ในโหมดโต้ตอบฉันต้องการให้เอาต์พุตทั้งหมดไปที่หน้าจอเช่นเดียวกับไฟล์บันทึกรวมถึงเอาต์พุตจากการเรียกใช้ระบบ ในโหมด daemon เอาต์พุตทั้งหมดจะไปที่บันทึก โหมด Daemon ใช้งานได้ดีos.dup2()มาก ฉันไม่พบวิธีที่จะ "ที" เอาท์พุตทั้งหมดไปยังบันทึกในโหมดอินเทอร์แอคทีฟโดยไม่ต้องแก้ไขการเรียกแต่ละระบบและทุกครั้ง


ในคำอื่น ๆ ที่ฉันต้องการการทำงานของบรรทัดคำสั่ง 'ที' สำหรับการส่งออกใด ๆ ที่สร้างขึ้นโดยแอพพลิเคหลาม, รวมทั้งการส่งออกเรียกระบบ

เพื่อชี้แจง:

หากต้องการเปลี่ยนเส้นทางเอาต์พุตทั้งหมดฉันทำสิ่งนี้และมันใช้งานได้ดี:

# 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())

สิ่งที่ดีเกี่ยวกับเรื่องนี้ก็คือมันไม่จำเป็นต้องมีการโทรพิมพ์พิเศษจากส่วนที่เหลือของรหัส รหัสยังใช้คำสั่งเชลล์บางคำสั่งด้วยดังนั้นจึงไม่จำเป็นต้องจัดการกับแต่ละเอาต์พุตของแต่ละตัวเช่นกัน

เพียงแค่ฉันต้องการทำเช่นเดียวกันยกเว้นการทำซ้ำแทนการเปลี่ยนเส้นทาง

ตอนแรกฉันคิดว่าฉันdup2ควรจะย้อนกลับไปได้ ทำไมไม่เป็นเช่นนั้น? นี่คือการทดสอบของฉัน:

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)

ไฟล์ "a.log" ควรเหมือนกับไฟล์ที่แสดงบนหน้าจอ


หากคุณดูที่หน้า man ( manpagez.com/man/2/dup2 ) อาร์กิวเมนต์ที่ 2 ของ dup2 จะถูกปิดเสมอ (หากเปิดอยู่) ดังนั้นใน "วิธีการแก้ปัญหาที่ใช้ไม่ได้" ของคุณมันจะปิดและดูแล้วกำหนดไฟล์ของพวกเขาให้เป็น sys.stdout อีกครั้ง
344321 Gabriel Gabriel

1
Re: การแก้ไขของคุณ: นี่ไม่ใช่เรื่องแปลกฉันได้ทำคล้ายกันสองสามครั้ง (ในภาษาอื่น ๆ ) ในขณะที่ Unix จะอนุญาตให้ "นามแฝง" หลายตัวสำหรับการจัดการไฟล์เดียวกัน แต่จะไม่ "แยก" การจัดการไฟล์ (คัดลอกไปยังหลาย ๆ คน) ดังนั้นคุณต้องใช้ "tee" ด้วยตัวเอง (หรือใช้ "tee" ดูคำตอบที่หยาบคายของฉัน)
344209 Gabri Gabrielson

ฉันคิดว่าคำตอบของ JohnT ดีกว่าคำตอบที่ยอมรับจริง คุณอาจต้องการเปลี่ยนคำตอบที่ยอมรับได้
พงษ์

"ฉันกำลังทำอะไรที่ไม่ได้มาตรฐาน" - คุณเป็นคนจริง ๆ แค่ส่งบันทึกไปยัง stderr และจัดการจากบรรทัดคำสั่ง
khachik

คำตอบ:


55

เนื่องจากคุณสบายใจที่จะวางไข่กระบวนการภายนอกจากรหัสของคุณคุณสามารถใช้teeตัวเองได้ ฉันไม่รู้การเรียกระบบ Unix ใด ๆ ที่ทำสิ่งที่ถูกteeต้อง

# 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)

คุณสามารถเลียนแบบteeโดยใช้แพ็คเกจการประมวลผลหลายตัว (หรือใช้การประมวลผลหากคุณใช้ Python 2.5 หรือเก่ากว่า)

ปรับปรุง

นี่คือ 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)

28
คำตอบนี้ใช้ได้ผลดังนั้นฉันจะยอมรับมัน แต่ทว่ามันทำให้ฉันรู้สึกสกปรก
drue

2
ฉันเพิ่งโพสต์การนำ python ไปใช้จริงของ tee (เข้ากันได้กับ py2 / 3) ที่สามารถทำงานบนแพลตฟอร์มใดก็ได้และยังสามารถใช้ในการกำหนดค่าการบันทึกที่แตกต่างกัน stackoverflow.com/questions/616645/…
โซริน

8
ถ้า Python ทำงานบนหนึ่งในเครื่องของฉันและวิธีการแก้ปัญหาไม่ได้แสดงว่านั่นไม่ใช่วิธีการแบบ pythonic ลดลงเนื่องจากการที่
Anatoly techtonik

2
ตามโพสต์นี้บรรทัดsys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)ใช้งานไม่ได้อีกต่อไปตั้งแต่ไพ ธ อน 3.3 (ดู PEP 3116)
Ken Myers

1
มีข้อผิดพลาด "sys: 1: ResourceWarning: ไฟล์ unclosed <_io.BufferedWriter name = 5>" ดังนั้นฉันต้องเพิ่มtee.stdin.close()ที่ส่วนท้ายของโปรแกรม ฉันยังได้รับ "ResourceWarning: subprocess 1842 ยังคงทำงาน" และการเพิ่มsys.stdout.close(); sys.stderr.close()ที่ส่วนท้ายของโปรแกรมแก้ไข
matthieu

136

ฉันมีปัญหาเดียวกันนี้มาก่อนและพบว่าข้อมูลนี้มีประโยชน์มาก:

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()

จาก: http://mail.python.org/pipermail/python-list/2007-May/438106.html


7
+1 สำหรับการจัดการการโอนสิทธิ sys.stdout ภายในเพื่อให้คุณสามารถยุติการเข้าสู่ระบบโดยการลบวัตถุตี๋
เบนเปล่า

12
ฉันจะเพิ่มฟลัชให้กับสิ่งนั้น เช่น: 'self.file.flush ()'
ลุคสแตนลี่ย์

4
ฉันไม่เห็นด้วยเกี่ยวกับโมดูลการบันทึก ยอดเยี่ยมสำหรับเล่นซอ การบันทึกมีขนาดใหญ่เกินไปสำหรับสิ่งนั้น
Kobor42

4
ให้แน่ใจว่าได้บันทึกเวอร์ชันที่แก้ไขในการติดตามผลไปยังการสนทนาที่เชื่อมโยงในคำตอบ
martineau

4
นั่นจะไม่ทำงาน __del__จะไม่ถูกเรียกจนกระทั่งสิ้นสุดการประมวลผล ดูstackoverflow.com/questions/6104535/…
Nux

77

printคำสั่งจะเรียกwrite()วิธีการของวัตถุใด ๆ ที่คุณกำหนดให้ sys.stdout

ฉันจะหมุนชั้นเรียนขนาดเล็กเพื่อเขียนถึงสองที่ในครั้งเดียว ...

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()

ตอนนี้printคำสั่งทั้งสองจะสะท้อนไปที่หน้าจอและผนวกเข้ากับไฟล์บันทึกของคุณ:

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

เห็นได้ชัดว่ารวดเร็วและสกปรก หมายเหตุบางส่วน:

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

ทั้งหมดนี้ตรงไปตรงมามากพอที่ฉันจะปล่อยให้พวกเขาเป็นแบบฝึกหัดสำหรับผู้อ่าน ความเข้าใจที่สำคัญที่นี่เป็นที่printเพียงแค่เรียกว่า "ไฟล์เหมือนวัตถุ" sys.stdoutที่ได้รับมอบหมายให้


สิ่งที่ฉันจะโพสต์ค่อนข้างมาก +1 เมื่อคุณแก้ไขปัญหาด้วยการเขียนไม่มีการโต้แย้งตนเอง นอกจากนี้การออกแบบที่ดีกว่าควรมีไฟล์ที่คุณกำลังจะเขียนเพื่อส่งผ่านเข้าไปในนรกมันอาจจะเป็นการออกแบบที่ดีกว่าถ้า stdout ผ่านเข้ามา
Devin Jeanpierre

@Devin ใช่แล้วนี่มันเร็วและสกปรกฉันจะทำโน้ตเพื่อปรับปรุงให้ดีขึ้น
Triptych

7
ฉันเลือกคำตอบนี้เร็วเกินไป มันใช้งานได้ดีสำหรับ "พิมพ์" แต่ไม่มากสำหรับเอาต์พุตคำสั่งภายนอก
drue

2
คลาส Logger ควรกำหนดวิธีการ flush () เช่น "def flush (): self.terminal.flush (); self.log.flush ()"
blokeley

5
The print statement will call the write() method of any object you assign to sys.stdoutคุณบอกว่า และสิ่งที่เกี่ยวกับฟังก์ชั่นอื่น ๆ ที่ส่งข้อมูลไปยัง stdout printไม่ได้ใช้ ตัวอย่างเช่นถ้าฉันสร้างกระบวนการโดยใช้subprocess.callผลลัพธ์ของมันจะไปที่คอนโซล แต่ไม่ใช่log.datไฟล์ ... มีวิธีแก้ไขไหม?
jpo38

64

สิ่งที่คุณต้องการจริงๆคือloggingโมดูลจากไลบรารีมาตรฐาน สร้างตัวบันทึกและแนบตัวจัดการสองตัวตัวหนึ่งจะเขียนลงไฟล์และอีกตัวหนึ่งไปที่ stdout หรือ stderr

ดูรายละเอียดเพิ่มเติมได้จาก การเข้าสู่ระบบสู่ปลายทางหลายแห่ง


9
โมดูลการบันทึกไม่ได้บันทึกข้อยกเว้นและเอาต์พุตที่สำคัญอื่น ๆ ไว้ที่ stdout ซึ่งอาจมีประโยชน์เมื่อวิเคราะห์ล็อกบนบิลด์เซิร์ฟเวอร์ (ตัวอย่าง)
Anatoly techtonik

2
loggingโมดูลจะไม่เปลี่ยนเส้นทางเอาต์พุตจากการเรียกของระบบเช่นos.write(1, b'stdout')
jfs

17

นี่คือวิธีแก้ไขปัญหาอื่นซึ่งโดยทั่วไปมากกว่าโซลูชันอื่น - รองรับการแยกเอาต์พุต (เขียนถึงsys.stdout) ไปยังวัตถุที่มีลักษณะคล้ายไฟล์จำนวนเท่าใดก็ได้ ไม่มีข้อกำหนดที่__stdout__รวมอยู่ในตัวเอง

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')

หมายเหตุ: นี่เป็นข้อพิสูจน์ถึงแนวคิด การใช้งานที่นี่ยังไม่เสร็จสมบูรณ์เพราะมันห่อหุ้มวิธีการของวัตถุที่มีลักษณะเหมือนไฟล์ (เช่นwrite) ทำให้สมาชิก / คุณสมบัติ / setattr ฯลฯ ไม่สามารถใช้งานได้อย่างไรก็ตามมันอาจจะดีพอสำหรับคนส่วนใหญ่

สิ่งที่ผมชอบเกี่ยวกับเรื่องอื่น ๆ กว่าทั่วๆไปของมันก็คือว่ามันมีความสะอาดในความรู้สึกมันไม่ได้ทำให้สายตรงไปยังwrite, flush, os.dup2ฯลฯ


3
ฉันจะเริ่มต้นใช้ไฟล์ * ไม่ใช่ไฟล์ แต่อย่างอื่นใช่นี่ ไม่มีวิธีการอื่นที่แยกฟังก์ชัน "tee" โดยไม่ลองแก้ไขปัญหาอื่น ๆ หากคุณต้องการใส่คำนำหน้ากับทุกสิ่งที่คุณส่งออกคุณสามารถล้อมคลาสนี้ในคลาส prefix-writer (ถ้าคุณต้องการใส่คำนำหน้าไว้ในสตรีมเดียวคุณจะต้องใส่ stream และส่งให้คลาสนี้) ข้อดีนี้ก็มีข้อดีที่ multifile ([]) สร้างไฟล์ที่ละเว้นทุกอย่าง (เช่น open ('/ dev) / null '))
Ben

ทำไมถึงมี_wrapที่นี่เลย? คุณไม่สามารถคัดลอกรหัสในนั้น__getattr__และทำงานเหมือนกันได้หรือไม่
timotree

@Ben multifile([])สร้างไฟล์ที่เพิ่มจริงUnboundLocalErrorทุกครั้งที่คุณเรียกใช้เมธอดอย่างใดอย่างหนึ่ง ( resถูกส่งคืนโดยไม่ได้รับมอบหมาย)
timotree

13

ตามที่อธิบายไว้ที่อื่นบางทีทางออกที่ดีที่สุดคือการใช้โมดูลการบันทึกโดยตรง:

import logging

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

อย่างไรก็ตามมีบางโอกาส (หายาก) ที่คุณต้องการเปลี่ยนเส้นทาง stdout ฉันมีสถานการณ์เช่นนี้เมื่อฉันขยายคำสั่ง runserver ของ django ซึ่งใช้การพิมพ์: ฉันไม่ต้องการแฮ็กแหล่ง django แต่ต้องการคำสั่งการพิมพ์เพื่อไปที่ไฟล์

นี่เป็นวิธีการเปลี่ยนเส้นทาง stdout และ stderr จากเชลล์โดยใช้โมดูลการบันทึก:

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'

คุณควรใช้การใช้งาน LogFile นี้หากคุณไม่สามารถใช้โมดูลการบันทึกโดยตรงเท่านั้น


11

ฉันเขียนtee()การนำไปใช้ใน Python ซึ่งควรจะใช้ในกรณีส่วนใหญ่และทำงานบน Windows ได้เช่นกัน

https://github.com/pycontribs/tendo

นอกจากนี้คุณสามารถใช้ร่วมกับloggingโมดูลจาก Python หากคุณต้องการ


อืม - ลิงค์นั้นใช้งานไม่ได้แล้วจะพบที่ไหนอีกไหม?
Danny Staple

1
ว้าวแพคเกจของคุณสั่นคลอนโดยเฉพาะอย่างยิ่งถ้าคุณรู้ว่าวัฒนธรรมคอนโซล Windows นั้นยุ่งยากแค่ไหน แต่ก็ไม่ยอมแพ้ที่จะทำให้มันใช้งานได้!
n611x007

8

(อ่าอ่านคำถามของคุณอีกครั้งแล้วดูว่ามันใช้ไม่ได้เลย)

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

ในโหมดค่อนข้างมันจะเข้าสู่ไฟล์เท่านั้นในโหมดปกติมันจะเข้าสู่ระบบทั้งไฟล์และคอนโซล

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())

คำตอบที่ดี. ผมเห็นวิธีการบางอย่างที่ซับซ้อนจริงๆของการจำลองการเข้าสู่ระบบไปยังคอนโซล แต่ทำให้ StreamHandler กับ stderr ถูกคำตอบที่ฉันได้รับการมองหา :)
meatvest

รหัสเป็นสิ่งที่ดีใส่มันไม่ตอบคำถาม - ผลลัพธ์นี้เข้าสู่ระบบไฟล์และ stderr, คำถามเดิมได้ขอให้ทำซ้ำ stderr ลงในไฟล์บันทึก
emem

8

หากต้องการตอบ John T ให้สมบูรณ์: https://stackoverflow.com/a/616686/395687

ฉันเพิ่ม__enter__และ__exit__วิธีการที่จะใช้เป็นผู้จัดการบริบทกับwithคำหลักซึ่งให้รหัสนี้

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

มันสามารถใช้เป็น

with Tee('outfile.log', 'w'):
    print('I am written to both stdout and outfile.log')

1
ฉันจะย้าย__del__ฟังก์ชันการทำงานลงใน__exit__
vontrapp

1
แน่นอนฉันคิดว่าการใช้__del__เป็นความคิดที่ไม่ดี มันควรจะย้ายไปอยู่ที่ฟังก์ชั่น 'ปิด' __exit__ซึ่งถูกเรียกใน
cladmi

7

ฉันรู้ว่าคำถามนี้ได้รับคำตอบซ้ำแล้วซ้ำอีก แต่สำหรับเรื่องนี้ฉันได้รับคำตอบหลักจากคำตอบของJohn Tและแก้ไขเพื่อให้มี flush ที่แนะนำและปฏิบัติตามเวอร์ชันที่แก้ไขแล้วที่เชื่อมโยง ฉันยังได้เพิ่มการเข้าและออกตามที่กล่าวไว้ในคำตอบของ cladmiสำหรับใช้กับคำสั่ง with นอกจากนี้เอกสารกล่าวถึงการล้างไฟล์โดยใช้os.fsync()ดังนั้นฉันได้เพิ่มที่เช่นกัน ผมไม่ทราบว่าถ้าคุณจริงๆต้องว่า แต่มี

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

จากนั้นคุณสามารถใช้มัน

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

หรือ

Log=Logger('Sleeps_all.night')
print('works all day')
Log.close()

Thnaks จำนวนมาก @ สถานะที่คุณแก้ไขคำถามของฉัน ( stackoverflow.com/questions/39143417/… ) ฉันจะใส่ลิงค์ไปยังโซลูชันของคุณ
Mohammad ElNesr

1
@MohammadElNesr ฉันเพิ่งรู้ปัญหาเกี่ยวกับรหัสเมื่อมันใช้กับคำสั่งด้วย ฉันได้แก้ไขแล้วและตอนนี้ปิดอย่างถูกต้องในตอนท้ายของบล็อก
สถานะ

1
สิ่งนี้ใช้งานได้ดีสำหรับฉันเพียงต้องการเปลี่ยนโหมดเป็นmode="ab"และในwriteฟังก์ชั่นself.file.write(message.encode("utf-8"))
ennetws

4

โซลูชันอื่นโดยใช้โมดูลการบันทึก:

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'

3

ดูเหมือนว่าไม่มีคำตอบข้างต้นจริงๆที่จะตอบปัญหาที่ถูกวาง ฉันรู้ว่านี่เป็นหัวข้อเก่า แต่ฉันคิดว่าปัญหานี้ง่ายกว่าทุกคนที่ทำ:

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)

ตอนนี้สิ่งนี้จะทำซ้ำทุกอย่างเพื่อจัดการ sys.stderr ปกติและไฟล์ของคุณ สร้างชั้นเรียนอีกสำหรับtee_outsys.stdout


2
คล้ายกันดีกว่าคำตอบที่ถูกโพสต์ในช่วงสองปีก่อนนี้: stackoverflow.com/a/616686 วิธีการของคุณแพงมาก : แต่ละการโทรเพื่อtee=tee_err();tee.write('');tee.write('');...เปิด + ปิดไฟล์สำหรับแต่ละการwriteโทร ดู stackoverflow.com/q/4867468และstackoverflow.com/q/164053สำหรับข้อโต้แย้งต่อต้านการปฏิบัตินี้
Rob W

3

ตามคำขอโดย @ user5359531 ในความคิดเห็นภายใต้คำตอบของ @John T นี่เป็นสำเนาของโพสต์ที่อ้างอิงถึงเวอร์ชันที่แก้ไขของการสนทนาที่เชื่อมโยงในคำตอบนั้น:

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

1

ฉันกำลังเขียนสคริปต์เพื่อเรียกใช้สคริปต์ cmd-line (เพราะในบางกรณีไม่มีเพียงคำสั่ง Linux แทนคำสั่งที่ใช้งานได้ - เช่นกรณีของ rsync)

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

รหัสนี้ดูเหมือนจะทำเคล็ดลับ อาจไม่สวยงามหรือมีประสิทธิภาพโดยเฉพาะอย่างยิ่ง (แม้ว่าจะไม่ได้ใช้ string + = string ดังนั้นอย่างน้อยก็ไม่มีขวดที่มีศักยภาพนั้น) ฉันโพสต์ไว้ในกรณีที่ให้ความคิดที่เป็นประโยชน์กับคนอื่น

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)

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


0

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

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!")

แก้ไข: โปรดทราบว่านี่ไม่ได้บันทึกข้อผิดพลาดยกเว้นว่าคุณเปลี่ยนเส้นทาง sys.stderr ไปยัง sys.stdout

แก้ไข 2: ปัญหาที่สองคือคุณต้องผ่าน 1 อาร์กิวเมนต์ซึ่งแตกต่างจากฟังก์ชั่น builtin

EDIT3: ดูรหัสก่อนที่จะเขียน stdin และ stdout เพื่อคอนโซลและไฟล์ที่มี stderr เพียงไปที่ไฟล์

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

-1

ผมเขียนทดแทนเต็มรูปแบบสำหรับsys.stderrและเพียงแค่ทำซ้ำรหัสการเปลี่ยนชื่อstderrเพื่อstdoutที่จะทำให้มันยังมีการเปลี่ยนsys.stdoutที่จะทำให้มันยังมีการเปลี่ยน

เมื่อต้องการทำเช่นนี้ฉันจะสร้างวัตถุชนิดเดียวกันเป็นปัจจุบันstderrและstdoutและส่งต่อวิธีการทั้งหมดไปยังระบบเดิมstderrและ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

หากต้องการใช้สิ่งนี้คุณสามารถโทรStdErrReplament::lock(logger)และStdOutReplament::lock(logger) ส่งผ่านตัวบันทึกที่คุณต้องการใช้เพื่อส่งข้อความเอาต์พุต ตัวอย่างเช่น:

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" )

ใช้รหัสนี้คุณจะเห็นบนหน้าจอ:

ป้อนคำอธิบายรูปภาพที่นี่

และในเนื้อหาไฟล์:

ป้อนคำอธิบายรูปภาพที่นี่

หากคุณต้องการที่จะเห็นเนื้อหาของการlog.debugโทรบนหน้าจอคุณจะต้องเพิ่มสตรีมตัวจัดการลงในเครื่องบันทึกของคุณ ในกรณีนี้มันจะเป็นเช่นนี้:

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" )

สิ่งที่จะออกเช่นนี้เมื่อทำงาน:

ป้อนคำอธิบายรูปภาพที่นี่

ในขณะที่มันจะยังคงบันทึกสิ่งนี้ลงในไฟล์my_log_file.txt:

ป้อนคำอธิบายรูปภาพที่นี่

เมื่อปิดใช้งานสิ่งนี้ด้วยStdErrReplament:unlock()จะเป็นการคืนค่าลักษณะการทำงานมาตรฐานของstderrสตรีมเท่านั้นเนื่องจากตัวบันทึกที่แนบมาจะไม่สามารถแยกออกได้เนื่องจากมีบุคคลอื่นสามารถอ้างอิงถึงเวอร์ชันที่เก่ากว่าได้ นี่คือเหตุผลว่าทำไมมันถึงเป็นซิงเกิลระดับโลกที่ไม่อาจตายได้ ดังนั้นในกรณีของการโหลดโมดูลนี้ด้วยimpหรืออย่างอื่นมันจะไม่กลับคืนกระแสในsys.stderrขณะที่มันถูกฉีดเข้าไปแล้วและมีการบันทึกไว้ภายใน


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