diff --git a/locust/runners.py b/locust/runners.py index 5c183d11b4..7a90c1396f 100644 --- a/locust/runners.py +++ b/locust/runners.py @@ -1422,9 +1422,19 @@ def stats_reporter(self) -> NoReturn: logger.error(f"Temporary connection lost to master server: {e}, will retry later.") gevent.sleep(WORKER_REPORT_INTERVAL) - def logs_reporter(self) -> NoReturn: + def logs_reporter(self) -> None: while True: - self._send_logs() + current_logs = get_logs() + + if (len(current_logs) - len(self.logs)) > 10: + current_logs = [*self.logs, "The worker experienced an issue and will stop outputting logs"] + self.send_message("logs", {"worker_id": self.client_id, "logs": current_logs}) + self._send_logs(current_logs) + break + if len(current_logs) > len(self.logs): + self._send_logs(current_logs) + + self.logs = current_logs gevent.sleep(WORKER_LOG_REPORT_INTERVAL) def send_message(self, msg_type: str, data: dict[str, Any] | None = None, client_id: str | None = None) -> None: @@ -1443,12 +1453,8 @@ def _send_stats(self) -> None: self.environment.events.report_to_master.fire(client_id=self.client_id, data=data) self.client.send(Message("stats", data, self.client_id)) - def _send_logs(self) -> None: - current_logs = get_logs() - - if len(current_logs) > len(self.logs): - self.logs = current_logs - self.send_message("logs", {"worker_id": self.client_id, "logs": current_logs}) + def _send_logs(self, current_logs) -> None: + self.send_message("logs", {"worker_id": self.client_id, "logs": current_logs}) def connect_to_master(self): self.retry += 1 diff --git a/locust/test/test_runners.py b/locust/test/test_runners.py index 185d8a03d9..19a0689269 100644 --- a/locust/test/test_runners.py +++ b/locust/test/test_runners.py @@ -3992,6 +3992,39 @@ def my_task(self): self.assertEqual(worker.client_id, client.outbox[3].data.get("worker_id")) worker.quit() + def test_quit_worker_logs(self): + class MyUser(User): + wait_time = constant(1) + + @task + def my_task(self): + pass + + with mock.patch("locust.rpc.rpc.Client", mocked_rpc()) as client: + short_time = 0.05 + + log_handler = LogReader() + log_handler.name = "log_reader" + log_handler.setLevel(logging.INFO) + logger = logging.getLogger("root") + logger.addHandler(log_handler) + log_line = "spamming log" + + for _ in range(11): + logger.info(log_line) + + worker = self.get_runner(environment=Environment(), user_classes=[MyUser], client=client) + + gevent.sleep(short_time) + + self.assertEqual("logs", client.outbox[3].type) + self.assertEqual( + "The worker experienced an issue and will stop outputting logs", + client.outbox[3].data.get("logs", [])[0], + ) + self.assertEqual(worker.client_id, client.outbox[3].data.get("worker_id")) + worker.quit() + class TestMessageSerializing(unittest.TestCase): def test_message_serialize(self): diff --git a/locust/web.py b/locust/web.py index 3c5f16ea57..69610256a4 100644 --- a/locust/web.py +++ b/locust/web.py @@ -490,7 +490,9 @@ def tasks() -> dict[str, dict[str, dict[str, float]]]: @app.route("/logs") @self.auth_required_if_enabled def logs(): - return jsonify({"master": get_logs(), "workers": self.environment.worker_logs}) + # truncate to get the tail of the logs + master_logs = get_logs()[-500:] + return jsonify({"master": master_logs, "workers": self.environment.worker_logs}) @app.route("/login") def login():