8000 simplify log printing in localstack start command (#9166) · codeperl/localstack@2524c99 · GitHub
[go: up one dir, main page]

Skip to content

Commit 2524c99

Browse files
authored
simplify log printing in localstack start command (localstack#9166)
1 parent 39b9bcc commit 2524c99

File tree

4 files changed

+54
-192
lines changed

4 files changed

+54
-192
lines changed

localstack/utils/bootstrap.py

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import threading
1111
import time
1212
from functools import wraps
13-
from typing import Any, Dict, Iterable, List, Optional, Set, Union
13+
from typing import Any, Callable, Dict, Iterable, List, Optional, Set, Union
1414

1515
from localstack import config, constants
1616
from localstack.config import HostAndPort, default_ip, is_env_true
@@ -38,7 +38,6 @@
3838
from localstack.utils.serving import Server
3939
from localstack.utils.strings import short_uid
4040
from localstack.utils.sync import poll_condition
41-
from localstack.utils.tail import FileListener
4241

4342
LOG = logging.getLogger(__name__)
4443

@@ -907,8 +906,47 @@ def stopped(self) -> Container:
907906
return Container(container_config=self.config, docker_client=self.container_client)
908907

909908

909+
class ContainerLogPrinter:
910+
"""
911+
Waits on a container to start and then uses ``stream_logs`` to print each line of the logs.
912+
"""
913+
914+
def __init__(self, container: Container, callback: Callable[[str], None] = print):
915+
self.container = container
916+
self.callback = callback
917+
918+
self._closed = threading.Event()
919+
self._stream: Optional[CancellableStream] = None
920+
921+
def _can_start_streaming(self):
922+
if self._closed.is_set():
923+
raise IOError("Already stopped")
924+
if not self.container.running_container:
925+
return False
926+
return self.container.running_container.is_running()
927+
928+
def run(self):
929+
try:
930+
poll_condition(self._can_start_streaming)
931+
except IOError:
932+
return
933+
self._stream = self.container.running_container.stream_logs()
934+
for line in self._stream:
935+
self.callback(line.rstrip(b"\r\n").decode("utf-8"))
936+
937+
def close(self):
938+
self._closed.set()
939+
if self._stream:
940+
self._stream.close()
941+
942+
910943
class LocalstackContainerServer(Server):
911-
def __init__(self, container_configuration: ContainerConfiguration | None = None) -> None:
944+
945+
container: Container | RunningContainer
946+
947+
def __init__(
948+
self, container_configuration: ContainerConfiguration | Container | None = None
949+
) -> None:
912950
super().__init__(config.EDGE_PORT, config.EDGE_BIND_HOST)
913951

914952
if container_configuration is None:
@@ -927,7 +965,10 @@ def __init__(self, container_configuration: ContainerConfiguration | None = None
927965
env_vars={},
928966
)
929967

930-
self.container: Container | RunningContainer = Container(container_configuration)
968+
if isinstance(container_configuration, Container):
969+
self.container = container_configuration
970+
else:
971+
self.container = Container(container_configuration)
931972

932973
def is_up(self) -> bool:
933974
"""
@@ -1098,10 +1139,7 @@ def _init_log_printer(line):
10981139
print(line)
10991140
log_printer.callback = print
11001141

1101-
logfile = get_container_default_logfile_location(container_config.name)
1102-
log_printer = FileListener(logfile, callback=_init_log_printer)
1103-
log_printer.truncate_file()
1104-
log_printer.start()
1142+
log_printer = ContainerLogPrinter(container, callback=_init_log_printer)
11051143

11061144
# Set up signal handler, to enable clean shutdown across different operating systems.
11071145
# There are subtle differences across operating systems and terminal emulators when it
@@ -1116,16 +1154,19 @@ def shutdown_handler(*args):
11161154
shutdown_event.set()
11171155
print("Shutting down...")
11181156
server.shutdown()
1119-
log_printer.close()
11201157

11211158
shutdown_event = threading.Event()
11221159
shutdown_event_lock = threading.RLock()
11231160
signal.signal(signal.SIGINT, shutdown_handler)
11241161

11251162
# start the Localstack container as a Server
1126-
server = LocalstackContainerServer(container_config)
1163+
server = LocalstackContainerServer(container)
1164+
log_printer_thread = threading.Thread(
1165+
target=log_printer.run, name="container-log-printer", daemon=True
1166+
)
11271167
try:
11281168
server.start()
1169+
log_printer_thread.start()
11291170
server.join()
11301171
error = server.get_error()
11311172
if error:
@@ -1134,6 +1175,8 @@ def shutdown_handler(*args):
11341175
except KeyboardInterrupt:
11351176
print("ok, bye!")
11361177
shutdown_handler()
1178+
finally:
1179+
log_printer.close()
11371180

11381181

11391182
def ensure_container_image(console, container: Container):

localstack/utils/common.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,9 +176,6 @@
176176
wait_until,
177177
)
178178

179-
# TODO: remove imports from here (need to update any client code that imports these from utils.common)
180-
from localstack.utils.tail import FileListener # noqa
181-
182179
# TODO: remove imports from here (need to update any client code that imports these from utils.common)
183180
from localstack.utils.threads import ( # noqa
184181
TMP_PROCESSES,

localstack/utils/tail.py

Lines changed: 0 additions & 97 deletions
This file was deleted.

tests/unit/utils/test_common.py

Lines changed: 1 addition & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
import os.path
21
import re
32
import threading
4-
import time
53
import unittest
64
from unittest.mock import MagicMock
75

@@ -18,8 +16,7 @@
1816
from localstack.utils.files import load_file, new_tmp_file, rm_rf
1917
from localstack.utils.json import FileMappedDocument
2018
from localstack.utils.run import run
21-
from localstack.utils.sync import poll_condition, synchronized
22-
from localstack.utils.tail import FileListener
19+
from localstack.utils.sync import synchronized
2320

2421

2522
class SynchronizedTest(unittest.TestCase):
@@ -77,84 +74,6 @@ def _run(cmd):
7774
_run(["echo 'foo bar 123'"])
7875

7976

80-
@pytest.mark.parametrize("tail_engine", ["command", "tailer"])
81-
class TestFileListener:
82-
def test_basic_usage(self, tail_engine, tmp_path):
83-
lines = []
84-
85-
file = tmp_path / "log.txt"
86-
file.touch()
87-
fd = open(file, "a")
88-
listener = FileListener(str(file), lines.append)
89-
listener.use_tail_command = tail_engine != "tailer"
90-
91-
try:
92-
listener.start()
93-
assert listener.started.is_set()
94-
fd.write("hello" + os.linesep)
95-
fd.write("pytest" + os.linesep)
96-
fd.flush()
97-
98-
assert poll_condition(lambda: len(lines) == 2, timeout=3), (
99-
"expected two lines to appear. %s" % lines
100-
)
101-
102-
assert lines[0] == "hello"
103-
assert lines[1] == "pytest"
104-
finally:
105-
listener.close()
106-
107-
try:
108-
fd.write("foobar" + os.linesep)
109-
time.sleep(0.5)
110-
assert len(lines) == 2, "expected listener.stop() to stop listening on new "
111-
finally:
112-
fd.close()
113-
114-
def test_callback_exception_ignored(self, tail_engine, tmp_path):
115-
lines = []
116-
117-
def callback(line):
118-
if "throw" in line:
119-
raise ValueError("oh noes")
120-
121-
lines.append(line)
122-
123-
file = tmp_path / "log.txt"
124-
file.touch()
125-
fd = open(file, "a")
126-
listener = FileListener(str(file), callback)
127-
listener.use_tail_command = tail_engine != "tailer"
128-
129-
try:
130-
listener.start()
131-
assert listener.started.is_set()
132-
fd.write("hello" + os.linesep)
133-
fd.flush()
134-
fd.write("throw" + os.linesep)
135-
fd.write("pytest" + os.linesep)
136-
fd.flush()
137-
138-
assert poll_condition(lambda: len(lines) == 2, timeout=3), (
139-
"expected two lines to appear. %s" % lines
140-
)
141-
142-
assert lines[0] == "hello"
143-
assert lines[1] == "pytest"
144-
finally:
145-
fd.close()
146-
listener.close()
147-
148-
def test_open_missing_file(self, tail_engine):
149-
lines = []
150-
151-
listener = FileListener("/tmp/does/not/exist", lines.append)
152-
listener.use_tail_command = tail_engine != "tailer"
153-
154-
with pytest.raises(FileNotFoundError):
155-
listener.start()
156-
157-
15877
class TestFileMappedDocument:
15978
def test_load_without_file_succeeds(self, tmp_path):
16079
path = tmp_path / "doc.json"

0 commit comments

Comments
 (0)
0