-
Notifications
You must be signed in to change notification settings - Fork 5.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[core] Use pipe to redirect stdout #49548
Changes from 1 commit
2dad6b1
9ddccdb
2160e0f
3129f18
5646d6f
687a1cd
5c9d352
9f9c4b5
9b89762
1f93a4f
cb9ac3d
a390078
ce368c3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
"""Implement log rotation with pipe.""" | ||
|
||
from typing import IO, AnyStr | ||
import sys | ||
import os | ||
import logging | ||
import threading | ||
import atexit | ||
from logging.handlers import RotatingFileHandler | ||
|
||
|
||
class PipeStreamWriteHandle(IO[AnyStr]): | ||
def __init__(self, write_fd: int, log_name: str): | ||
self.name = log_name | ||
self.write_fd = write_fd | ||
self.stream = os.fdopen(write_fd, "w") | ||
|
||
def name(self): | ||
return self.name | ||
|
||
def fileno(self): | ||
return self.write_fd | ||
|
||
def write(self, s: str) -> int: | ||
return self.stream.write(s) | ||
|
||
def flush(self): | ||
self.stream.flush() | ||
|
||
def close(self): | ||
self.stream.close() | ||
|
||
def isatty(self) -> bool: | ||
return False | ||
|
||
def readable(self) -> bool: | ||
return False | ||
|
||
def writable(self) -> bool: | ||
return True | ||
|
||
def seekable(self) -> bool: | ||
return False | ||
|
||
|
||
def open_pipe_with_rotation( | ||
fname: str, rotation_max_size: int = sys.maxsize, rotation_file_num: int = 1 | ||
) -> IO[AnyStr]: | ||
"""Stream content into pipe, which will be listened and dumped to [fname] with | ||
rotation.""" | ||
read_fd, write_fd = os.pipe() | ||
|
||
log_content = [] | ||
stopped = False | ||
lock = threading.Lock() | ||
cond = threading.Condition(lock) | ||
|
||
logger = logging.getLogger() | ||
logger.setLevel(logging.INFO) | ||
handler = RotatingFileHandler( | ||
dentiny marked this conversation as resolved.
Show resolved
Hide resolved
|
||
fname, maxBytes=rotation_max_size, backupCount=rotation_file_num | ||
) | ||
# Only logging message with nothing else. | ||
handler.setFormatter(logging.Formatter("%(message)s")) | ||
logger.addHandler(handler) | ||
|
||
# Setup read thread, which continuous read content out of pipe and append to buffer. | ||
def read_log_content_from_pipe(): | ||
with os.fdopen(read_fd, "r") as pipe_reader: | ||
while True: | ||
line = pipe_reader.readline() | ||
|
||
# An empty line is read over, which indicates write side has closed. | ||
if line == "": | ||
with cond: | ||
cond.notify() | ||
return | ||
|
||
# Only buffer new line when not empty. | ||
line = line.strip() | ||
if line: | ||
with cond: | ||
log_content.append(line) | ||
cond.notify() | ||
|
||
# Setup logging thread, which continues read content out of log buffer and persist | ||
# via logger. Two threads are used here to avoid blocking write from application. | ||
dentiny marked this conversation as resolved.
Show resolved
Hide resolved
|
||
def dump_log_content_to_buffer(): | ||
logger = logging.getLogger() | ||
dentiny marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
while True: | ||
with cond: | ||
while not log_content and not stopped: | ||
cond.wait() | ||
|
||
if log_content: | ||
content = log_content.pop(0) | ||
logger.info(content) | ||
continue | ||
|
||
# Thread requested to stop | ||
return | ||
|
||
read_thread = threading.Thread(target=read_log_content_from_pipe) | ||
dump_thread = threading.Thread(target=dump_log_content_to_buffer) | ||
read_thread.start() | ||
dump_thread.start() | ||
|
||
pipe_write_stream = PipeStreamWriteHandle(write_fd, fname) | ||
|
||
def cleanup(): | ||
nonlocal stopped | ||
with lock: | ||
stopped = True | ||
pipe_write_stream.close() | ||
|
||
atexit.register(cleanup) | ||
|
||
return pipe_write_stream | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How to you plan to provide There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just tried
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Code snippet:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems we can do https://stackoverflow.com/questions/60622854/how-to-instantiate-an-io-textiowrapper-object-with-a-name-attribute so we don't need to implement our own |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
configure_log_file
only requires this object hasfileno()
method not others?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
write, flush, name, fileno is definitely needed --- I have met issues without any of them;
Others are suggested by gpt, didn't verify one by one.
My personal opinion is it doesn't hurt to implement these overloads;