MCPcopy Create free account
hub / github.com/LangGraph-GUI/LangGraph-GUI-backend / ServerTee

Class ServerTee

src/ServerTee.py:8–102  ·  view source on GitHub ↗

A thread-safe logger that duplicates stdout to both console and file, timestamps all output lines, and supports real-time subscriber streaming.

Source from the content-addressed store, hash-verified

6from queue import Queue, Empty
7
8class ServerTee:
9 """
10 A thread-safe logger that duplicates stdout to both console and file,
11 timestamps all output lines, and supports real-time subscriber streaming.
12 """
13
14 def __init__(self, filename, mode='a'):
15 self.file = open(filename, mode, buffering=1) # line-buffered
16 self.stdout = sys.stdout
17 self.lock = Lock()
18 self.subscribers = []
19 self.buffer = "" # buffer for partial lines
20 sys.stdout = self # redirect global stdout
21
22 def write(self, message):
23 with self.lock:
24 self.buffer += message
25 while '\n' in self.buffer:
26 line, self.buffer = self.buffer.split('\n', 1)
27 # Skip completely empty lines
28 if not line.strip():
29 continue
30 timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
31 message_with_timestamp = f"{timestamp} - {line}\n"
32
33 # Write to original stdout
34 self.stdout.write(message_with_timestamp)
35 self.stdout.flush()
36
37 # Write to log file
38 self.file.write(message_with_timestamp)
39 self.file.flush()
40
41 # Notify subscribers
42 self.notify_subscribers(message_with_timestamp)
43
44 def flush(self):
45 with self.lock:
46 if self.buffer:
47 line = self.buffer.strip()
48 if line:
49 timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
50 message_with_timestamp = f"{timestamp} - {line}\n"
51 self.stdout.write(message_with_timestamp)
52 self.file.write(message_with_timestamp)
53 self.notify_subscribers(message_with_timestamp)
54 self.buffer = ""
55 self.stdout.flush()
56 self.file.flush()
57
58 def close(self):
59 with self.lock:
60 self.flush()
61 sys.stdout = self.stdout # restore original stdout
62 self.file.close()
63
64 # -------------------
65 # Subscriber methods

Callers 1

main.pyFile · 0.90

Calls

no outgoing calls

Tested by

no test coverage detected