Làm cách nào để sao chép sys.stdout vào tệp nhật ký?


149

Chỉnh sửa: Vì có vẻ như không có giải pháp hoặc tôi đang làm điều gì đó không chuẩn mà không ai biết - Tôi sẽ sửa lại câu hỏi của mình để hỏi: Cách tốt nhất để thực hiện đăng nhập khi ứng dụng python đang thực hiện nhiều cuộc gọi hệ thống?

Ứng dụng của tôi có hai chế độ. Trong chế độ tương tác, tôi muốn tất cả đầu ra đi đến màn hình cũng như tệp nhật ký, bao gồm đầu ra từ bất kỳ cuộc gọi hệ thống nào. Trong chế độ daemon, tất cả đầu ra đi vào nhật ký. Chế độ Daemon hoạt động tuyệt vời bằng cách sử dụng os.dup2(). Tôi không thể tìm cách "phát" tất cả đầu ra sang nhật ký ở chế độ tương tác mà không sửa đổi từng cuộc gọi hệ thống.


Nói cách khác, tôi muốn chức năng của dòng lệnh 'tee' cho bất kỳ đầu ra nào được tạo bởi ứng dụng python, bao gồm cả đầu ra cuộc gọi hệ thống .

Làm rõ:

Để chuyển hướng tất cả đầu ra, tôi làm một cái gì đó như thế này và nó hoạt động rất tốt:

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

Điều tốt đẹp về điều này là nó không yêu cầu các cuộc gọi in đặc biệt từ phần còn lại của mã. Mã này cũng chạy một số lệnh shell, vì vậy thật tuyệt khi không phải xử lý từng đầu ra riêng lẻ.

Đơn giản, tôi muốn làm như vậy, ngoại trừ sao chép thay vì chuyển hướng.

Lúc đầu, tôi nghĩ rằng chỉ cần đảo ngược dup2là nên hoạt động. Tại sao không? Đây là bài kiểm tra của tôi:

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)

Tệp "a.log" phải giống hệt với tệp được hiển thị trên màn hình.


Nếu bạn nhìn vào trang man ( manpagez.com/man/2/dup2 ), đối số thứ 2 cho dup2 luôn bị đóng (nếu nó đã mở). Vì vậy, trong "giải pháp bị hỏng" của bạn, nó sẽ đóng và sau đó gán lại filenos của họ cho sys.stdout.
Jacob Gabrielson

1
Re: chỉnh sửa của bạn: điều này không phổ biến, tôi đã thực hiện tương tự một vài lần (trong các lang khác). Mặc dù Unix sẽ cho phép nhiều "bí danh" cho cùng một tệp xử lý, nhưng nó sẽ không "tách" một tệp xử lý (sao chép nó sang nhiều tệp khác). Vì vậy, bạn phải tự thực hiện "tee" (hoặc chỉ sử dụng "tee", xem câu trả lời thô thiển của tôi).
Jacob Gabrielson 17/03/2016

Tôi nghĩ rằng câu trả lời của JohnT tốt hơn câu trả lời thực tế. Bạn có thể muốn thay đổi câu trả lời được chấp nhận.
Phong

"Tôi đang làm một cái gì đó không chuẩn" - bạn thực sự, mọi người chỉ cần gửi nhật ký của họ đến thiết bị lỗi chuẩn và xử lý từ dòng lệnh.
khachik

Câu trả lời:


55

Vì bạn cảm thấy thoải mái khi sinh ra các quy trình bên ngoài từ mã của mình, bạn có thể sử dụng teechính nó. Tôi không biết bất kỳ cuộc gọi hệ thống Unix nào thực hiện chính xác những gì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)

Bạn cũng có thể mô phỏng teebằng cách sử dụng đa xử lý gói (hoặc sử dụng xử lý nếu bạn đang sử dụng Python 2.5 trở về trước).

Cập nhật

Đây là phiên bản tương thích 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
Vâng, câu trả lời này hoạt động, vì vậy tôi sẽ chấp nhận nó. Tuy nhiên, nó làm cho tôi cảm thấy bẩn.
drue 18/03/2016

2
Tôi vừa đăng một triển khai python thuần của tee (tương thích py2 / 3) có thể chạy trên mọi nền tảng và cũng được sử dụng trong các cấu hình ghi nhật ký khác nhau. stackoverflow.com/questions/616645/
hy

8
Nếu Python chạy trên một trong các máy và giải pháp của tôi thì không, đó không phải là giải pháp pythonic. Bị hạ bệ vì điều đó.
anatoly techtonik

2
Theo bài đăng này , dòng sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)không còn hoạt động kể từ python 3.3 (xem PEP 3116)
Ken Myers

1
Đã xảy ra lỗi "sys: 1: ResourceWarning: tệp không được tiết lộ <_io.BufferedWriter name = 5>", vì vậy tôi phải thêm tee.stdin.close()vào cuối chương trình. Tôi cũng nhận được "ResourceWarning: sub process 1842 vẫn đang chạy" và thêm sys.stdout.close(); sys.stderr.close()vào cuối chương trình sửa nó.
matthieu

136

Tôi đã có vấn đề tương tự trước đây và thấy đoạn trích này rất hữu ích:

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

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


7
+1 để xử lý việc gán lại sys.stdout trong nội bộ để bạn có thể kết thúc đăng nhập bằng cách xóa đối tượng Tee
Ben Blank

12
Tôi sẽ thêm một tuôn ra điều đó. Ví dụ: 'self.file.flush ()'
Luke Stanley

4
Tôi không đồng ý về mô-đun đăng nhập. Tuyệt vời cho một số khó khăn. Ghi nhật ký là quá lớn cho điều đó.
Kobor42

4
Hãy chắc chắn lưu ý phiên bản sửa đổi trong phần tiếp theo của cuộc thảo luận được liên kết trong câu trả lời.
martineau

4
Rằng sẽ không làm việc. __del__không được gọi cho đến khi kết thúc thực hiện. Xem stackoverflow.com/questions/6104535/
Nux

77

Câu printlệnh sẽ gọi write()phương thức của bất kỳ đối tượng nào bạn gán cho sys.stdout.

Tôi sẽ quay một lớp nhỏ để viết đến hai nơi cùng một lúc ...

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

Bây giờ printcâu lệnh sẽ lặp lại cả màn hình và nối vào tệp nhật ký của bạn:

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

Điều này rõ ràng là nhanh chóng và bẩn thỉu. Một số lưu ý:

  • Bạn có lẽ nên tham số hóa tên tệp nhật ký.
  • Bạn có thể nên hoàn nguyên sys.stdout <stdout>nếu bạn sẽ không đăng nhập trong suốt thời gian của chương trình.
  • Bạn có thể muốn ghi vào nhiều tệp nhật ký cùng một lúc hoặc xử lý các cấp nhật ký khác nhau, v.v.

Tất cả đều đơn giản đến mức tôi cảm thấy thoải mái khi để chúng làm bài tập cho người đọc. Cái nhìn sâu sắc quan trọng ở đây là printchỉ cần gọi một "đối tượng giống như tệp" được gán cho sys.stdout.


Chính xác những gì tôi sẽ đăng, khá nhiều. +1 khi bạn khắc phục sự cố với ghi không có đối số tự. Ngoài ra, nó sẽ được thiết kế tốt hơn để có tệp mà bạn sẽ viết để gửi vào. Địa ngục, nó cũng có thể là thiết kế tốt hơn để có thiết bị xuất chuẩn.
Devin Jeanpierre

@Devin, vâng điều này nhanh và bẩn, tôi sẽ ghi chú lại những cải tiến ban đầu có thể.
Triptych

7
Tôi đã chọn câu trả lời này quá sớm. Nó hoạt động tuyệt vời cho "in", nhưng không nhiều cho đầu ra lệnh bên ngoài.
drue

2
Lớp Logger cũng nên định nghĩa một phương thức flush (), chẳng hạn như "def flush (): self.terminal.flush (); self.log.flush ()"
blo ở

5
Bạn nói The print statement will call the write() method of any object you assign to sys.stdout. Và những gì về các chức năng khác gửi dữ liệu đến thiết bị xuất chuẩn không sử dụng print. Chẳng hạn, nếu tôi tạo một tiến trình bằng cách sử dụng subprocess.callđầu ra của nó thì vào giao diện điều khiển nhưng không log.datlưu tệp ... có cách nào khắc phục điều đó không?
jpo38

64

Những gì bạn thực sự muốn là loggingmô-đun từ thư viện tiêu chuẩn. Tạo một logger và đính kèm hai trình xử lý, một sẽ ghi vào một tệp và cái còn lại vào thiết bị xuất chuẩn hoặc thiết bị xuất chuẩn.

Xem Đăng nhập vào nhiều điểm để biết chi tiết


9
Mô-đun ghi nhật ký không ghi lại các trường hợp ngoại lệ và đầu ra quan trọng khác vào thiết bị xuất chuẩn, có thể hữu ích khi phân tích nhật ký trên máy chủ xây dựng (ví dụ).
anatoly techtonik

2
loggingmô-đun sẽ không chuyển hướng đầu ra từ các cuộc gọi hệ thống, chẳng hạn nhưos.write(1, b'stdout')
jfs

17

Đây là một giải pháp khác, tổng quát hơn các giải pháp khác - nó hỗ trợ phân tách đầu ra (được ghi thành sys.stdout) cho bất kỳ số lượng đối tượng giống như tệp. Không có yêu cầu nào __stdout__được bao gồm.

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

LƯU Ý: Đây là một bằng chứng về khái niệm. Việc triển khai ở đây chưa hoàn tất, vì nó chỉ bao bọc các phương thức của các đối tượng giống như tệp (ví dụ write), loại bỏ các thành viên / property / setattr, v.v. Tuy nhiên, nó có thể đủ tốt cho hầu hết mọi người như hiện tại.

Những gì tôi thích về nó, trừ tổng quát của nó, là nó là sạch theo nghĩa nó không thực hiện bất kỳ cuộc gọi trực tiếp đến write, flush, os.dup2vv


3
Tôi sẽ có init tệp * không phải tệp, nhưng nếu không, vâng, cái này. Không có giải pháp nào khác tách biệt chức năng "tee" mà không cố gắng giải quyết các vấn đề khác. Nếu bạn muốn đặt tiền tố vào mọi thứ bạn xuất ra, bạn có thể bọc lớp này trong lớp người viết tiền tố. . /vô giá trị')).
Ben

Tại sao có _wrapở đây cả? Bạn không thể sao chép mã trong đó vào __getattr__và nó có hoạt động giống nhau không?
timotree

@Ben thực sự multifile([])tạo một tệp tăng UnboundLocalErrorbất cứ khi nào bạn gọi một trong các phương thức của nó. ( resđược trả lại mà không được chỉ định)
timotree

13

Như được mô tả ở nơi khác, có lẽ giải pháp tốt nhất là sử dụng mô đun ghi nhật ký trực tiếp:

import logging

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

Tuy nhiên, có một số dịp (hiếm) mà bạn thực sự muốn chuyển hướng thiết bị xuất chuẩn. Tôi đã gặp tình huống này khi tôi đang mở rộng lệnh máy chủ của django sử dụng bản in: Tôi không muốn hack nguồn django nhưng cần các câu lệnh in để đi đến một tệp.

Đây là một cách để chuyển hướng thiết bị xuất chuẩn và thiết bị xuất chuẩn khỏi vỏ bằng mô-đun đăng nhập:

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'

Bạn chỉ nên sử dụng triển khai LogFile này nếu bạn thực sự không thể sử dụng mô đun ghi nhật ký trực tiếp.


11

Tôi đã viết một tee()triển khai trong Python nên hoạt động trong hầu hết các trường hợp và nó cũng hoạt động trên Windows.

https://github.com/pycontribs/tendo

Ngoài ra, bạn có thể sử dụng nó kết hợp với loggingmô-đun từ Python nếu bạn muốn.


Hmm - liên kết đó không còn hoạt động - bất cứ nơi nào khác nó có thể được tìm thấy?
Daniel Staple

1
wow, gói của bạn làm rung chuyển, đặc biệt là nếu bạn biết văn hóa bảng điều khiển Windows cồng kềnh như thế nào nhưng không từ bỏ để làm cho nó hoạt động!
n611x007

8

(Ah, chỉ cần đọc lại câu hỏi của bạn và thấy rằng điều này không hoàn toàn áp dụng.)

Đây là một chương trình mẫu sử dụng mô đun ghi nhật ký python . Mô-đun đăng nhập này đã có trong tất cả các phiên bản kể từ 2.3. Trong mẫu này, việc ghi nhật ký được cấu hình bởi các tùy chọn dòng lệnh.

Ở chế độ khá, nó sẽ chỉ đăng nhập vào một tệp, ở chế độ bình thường, nó sẽ đăng nhập vào cả tệp và bàn điều khiển.

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

Câu trả lời tốt. Tôi đã thấy một số cách thực sự khó khăn khi sao chép việc ghi nhật ký vào bảng điều khiển, nhưng tạo StreamHandler bằng stderr là câu trả lời tôi đang tìm kiếm :)
Meatvest

Mã này rất hay, nó không trả lời câu hỏi - điều này xuất ra nhật ký thành một tệp và thiết bị lỗi chuẩn, câu hỏi ban đầu được yêu cầu sao chép stderr thành tệp nhật ký.
emem

8

Để hoàn thành câu trả lời của John T: https://stackoverflow.com/a/616686/395687

Tôi đã thêm __enter____exit__các phương thức để sử dụng nó làm trình quản lý ngữ cảnh với withtừ khóa, cung cấp mã này

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

Sau đó nó có thể được sử dụng như

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

1
Tôi sẽ chuyển __del__chức năng vào__exit__
vontrapp

1
Thật vậy, tôi nghĩ rằng sử dụng __del__là một ý tưởng tồi. Nó nên được chuyển đến một chức năng 'đóng' được gọi là __exit__.
cladmi

7

Tôi biết câu hỏi này đã được trả lời nhiều lần, nhưng vì điều này tôi đã lấy câu trả lời chính từ John T's và sửa đổi nó để nó chứa gợi ý tuôn ra và tuân theo phiên bản sửa đổi được liên kết của nó. Tôi cũng đã thêm mục nhập và thoát như được đề cập trong câu trả lời của cladmi để sử dụng với câu lệnh with. Ngoài ra, tài liệu đề cập đến việc xóa các tập tin bằng cách sử dụng os.fsync()vì vậy tôi cũng đã thêm nó. Tôi không biết nếu bạn thực sự cần điều đó nhưng nó ở đó.

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

Sau đó bạn có thể sử dụng nó

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

hoặc là

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

Nhiều Thnaks @Status bạn đã giải quyết câu hỏi của tôi ( stackoverflow.com/questions/39143417/ mẹo ). Tôi sẽ đặt một liên kết đến giải pháp của bạn.
Mohammad ElNesr

1
@MohammadElNesr Tôi mới nhận ra một vấn đề với mã khi nó được sử dụng với câu lệnh with. Tôi đã sửa nó và bây giờ nó đóng lại chính xác ở cuối một khối có.
Trạng thái

1
Điều này rất tốt cho tôi, chỉ cần thay đổi chế độ thành mode="ab"và trong writechức năngself.file.write(message.encode("utf-8"))
ennetws

4

một giải pháp khác sử dụng mô đun đăng nhập:

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

Không có câu trả lời nào ở trên dường như thực sự trả lời cho vấn đề được đặt ra. Tôi biết đây là một chủ đề cũ, nhưng tôi nghĩ vấn đề này đơn giản hơn nhiều so với mọi người đang thực hiện:

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)

Bây giờ điều này sẽ lặp lại mọi thứ với trình xử lý sys.stderr bình thường và tệp của bạn. Tạo một lớp khác tee_outcho sys.stdout.


2
Một câu trả lời tương tự, tốt hơn đã được đăng hơn hai năm trước câu này: stackoverflow.com/a/616686 . Phương pháp của bạn rất tốn kém: Mỗi cuộc gọi để tee=tee_err();tee.write('');tee.write('');...mở + đóng một tệp cho mỗi cuộc gọi write. Xem stackoverflow.com/q/4867468stackoverflow.com/q/164053 để biết các đối số chống lại thực tiễn này.
Rob W

3

Theo yêu cầu của @ user5359531 trong các bình luận dưới câu trả lời của @John T , đây là bản sao của bài đăng được tham chiếu đến phiên bản sửa đổi của cuộc thảo luận được liên kết trong câu trả lời đó:

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

Tôi đang viết một tập lệnh để chạy các tập lệnh cmd-line. (Bởi vì trong một số trường hợp, không có sự thay thế khả thi nào cho lệnh Linux - chẳng hạn như trường hợp rsync.)

Điều tôi thực sự muốn là sử dụng cơ chế ghi nhật ký python mặc định trong mọi trường hợp có thể làm như vậy, nhưng vẫn nắm bắt được bất kỳ lỗi nào khi có sự cố xảy ra mà không lường trước được.

Mã này dường như để thực hiện các mẹo. Nó có thể không đặc biệt thanh lịch hoặc hiệu quả (mặc dù nó không sử dụng chuỗi + = chuỗi, vì vậy ít nhất nó không có cổ chai tiềm năng cụ thể đó). Tôi đang đăng nó trong trường hợp nó cung cấp cho người khác bất kỳ ý tưởng hữu ích.

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)

Rõ ràng, nếu bạn không phải là người hay thay đổi như tôi, hãy thay thế LOG_IDENTIFIER bằng một chuỗi khác mà bạn không muốn thấy ai đó viết vào nhật ký.


0

Nếu bạn muốn đăng nhập tất cả đầu ra vào một tệp VÀ xuất nó thành một tệp văn bản thì bạn có thể làm như sau. Đó là một chút hack nhưng nó hoạt động:

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: Lưu ý rằng điều này không ghi lại lỗi trừ khi bạn chuyển hướng sys.stderr sang sys.stdout

EDIT2: Vấn đề thứ hai là bạn phải vượt qua 1 đối số không giống như hàm dựng sẵn.

EDIT3: Xem mã trước để ghi stdin và stdout vào bàn điều khiển và tệp với stderr chỉ đi đến tệp

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

Tôi đã viết một thay thế hoàn toàn cho sys.stderrvà chỉ cần sao chép mã đổi tên stderrđể stdoutlàm cho nó cũng có sẵn để thay thế sys.stdout.

Để làm điều này, tôi tạo cùng loại đối tượng như hiện tại stderrstdout, và chuyển tiếp tất cả các phương thức đến hệ thống ban đầu stderrstdout:

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

Để sử dụng điều này, bạn có thể chỉ cần gọi StdErrReplament::lock(logger)StdOutReplament::lock(logger) chuyển bộ ghi mà bạn muốn sử dụng để gửi văn bản đầu ra. Ví dụ:

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

Chạy mã này, bạn sẽ thấy trên màn hình:

nhập mô tả hình ảnh ở đây

Và trên nội dung tập tin:

nhập mô tả hình ảnh ở đây

Nếu bạn cũng muốn xem nội dung của các log.debugcuộc gọi trên màn hình, bạn sẽ cần thêm một trình xử lý luồng vào logger của mình. Trong trường hợp này, nó sẽ như thế này:

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

Mà sẽ xuất ra như thế này khi chạy:

nhập mô tả hình ảnh ở đây

Trong khi nó vẫn sẽ lưu tệp này vào tệp my_log_file.txt:

nhập mô tả hình ảnh ở đây

Khi vô hiệu hóa điều này với StdErrReplament:unlock(), nó sẽ chỉ khôi phục hành vi tiêu chuẩn của stderrluồng, vì trình ghi nhật ký đính kèm không thể tách rời vì người khác có thể có tham chiếu đến phiên bản cũ hơn. Đây là lý do tại sao nó là một singleton toàn cầu không bao giờ có thể chết. Do đó, trong trường hợp tải lại mô-đun này bằng imphoặc một cái gì đó khác, nó sẽ không bao giờ lấy lại dòng điện sys.stderrvì nó đã được tiêm vào nó và lưu nó vào bên trong.


5
một mức độ đáng kinh ngạc của sự phức tạp ngẫu nhiên để sao chép một luồng.
Attila Lendvai
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.