Skip to content

Commit f1356bc

Browse files
authored
[k2] add trace as tag in log (#1361)
1 parent 4411313 commit f1356bc

7 files changed

Lines changed: 104 additions & 20 deletions

File tree

runtime-light/stdlib/diagnostics/backtrace.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <cstdint>
99
#include <expected>
1010
#include <format>
11+
#include <iterator>
1112
#include <ranges>
1213
#include <span>
1314
#include <type_traits>
@@ -67,11 +68,12 @@ struct std::formatter<std::invoke_result_t<decltype(kphp::diagnostic::backtrace_
6768

6869
template<typename FmtContext>
6970
auto format(const addresses_t& addresses, FmtContext& ctx) const noexcept {
70-
size_t level{};
71-
for (const auto* addr : addresses) {
72-
format_to(ctx.out(), "# {} : {:p}\n", level++, addr);
71+
format_to(ctx.out(), "[");
72+
if (!addresses.empty()) {
73+
std::ranges::for_each(addresses | std::views::take(addresses.size() - 1), [&ctx](void* addr) noexcept { format_to(ctx.out(), "\"{:p}\", ", addr); });
74+
format_to(ctx.out(), "\"{:p}\"", *std::prev(addresses.end()));
7375
}
74-
76+
format_to(ctx.out(), "]");
7577
return ctx.out();
7678
}
7779
};

runtime-light/utils/logs.h

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <optional>
1111
#include <source_location>
1212
#include <span>
13+
#include <string_view>
1314
#include <type_traits>
1415
#include <utility>
1516

@@ -42,28 +43,37 @@ enum class level : size_t { error = 1, warn, info, debug, trace };
4243

4344
template<typename... Args>
4445
void log(level level, std::optional<std::span<void* const>> trace, std::format_string<impl::wrapped_arg_t<Args>...> fmt, Args&&... args) noexcept {
45-
static constexpr size_t LOG_BUFFER_SIZE = 1024UZ * 4UZ;
4646
if (std::to_underlying(level) > k2::log_level_enabled()) {
4747
return;
4848
}
4949

50+
static constexpr size_t LOG_BUFFER_SIZE = 512UZ;
5051
std::array<char, LOG_BUFFER_SIZE> log_buffer;
5152
auto [out, size]{std::format_to_n<decltype(log_buffer.data()), impl::wrapped_arg_t<Args>...>(log_buffer.data(), log_buffer.size() - 1, fmt,
5253
impl::wrap_log_argument(std::forward<Args>(args))...)};
53-
if (trace.has_value()) {
54-
if (auto backtrace_symbols{kphp::diagnostic::backtrace_symbols(*trace)}; !backtrace_symbols.empty()) {
55-
const auto [trace_out, trace_size]{std::format_to_n(out, std::distance(out, log_buffer.end()) - 1, "\nBacktrace\n{}", backtrace_symbols)};
56-
out = trace_out;
57-
size += trace_size;
58-
} else if (auto backtrace_addresses{kphp::diagnostic::backtrace_addresses(*trace)}; !backtrace_addresses.empty()) {
59-
const auto [trace_out, trace_size]{std::format_to_n(out, std::distance(out, log_buffer.end()) - 1, "\nBacktrace\n{}", backtrace_addresses)};
60-
out = trace_out;
61-
size += trace_size;
62-
}
54+
*out = '\0';
55+
auto message{std::string_view{log_buffer.data(), static_cast<std::string_view::size_type>(size)}};
56+
if (!trace.has_value()) {
57+
k2::log(std::to_underlying(level), message, std::nullopt);
58+
return;
6359
}
6460

65-
*out = '\0';
66-
k2::log(std::to_underlying(level), std::string_view{log_buffer.data(), static_cast<std::string_view::size_type>(size)}, std::nullopt);
61+
static constexpr std::string_view backtrace_key = "trace";
62+
static constexpr size_t BACKTRACE_BUFFER_SIZE = 1024UZ * 4UZ;
63+
std::array<char, BACKTRACE_BUFFER_SIZE> backtrace_buffer;
64+
std::string_view backtrace{"[]"};
65+
if (auto backtrace_symbols{kphp::diagnostic::backtrace_symbols(*trace)}; !backtrace_symbols.empty()) {
66+
const auto [trace_out, trace_size]{std::format_to_n(backtrace_buffer.data(), backtrace_buffer.size() - 1, "\n{}", backtrace_symbols)};
67+
*trace_out = '\0';
68+
backtrace = std::string_view{backtrace_buffer.data(), static_cast<std::string_view::size_type>(trace_size)};
69+
} else if (auto backtrace_addresses{kphp::diagnostic::backtrace_addresses(*trace)}; !backtrace_addresses.empty()) {
70+
const auto [trace_out, trace_size]{std::format_to_n(backtrace_buffer.data(), backtrace_buffer.size() - 1, "{}", backtrace_addresses)};
71+
*trace_out = '\0';
72+
backtrace = std::string_view{backtrace_buffer.data(), static_cast<std::string_view::size_type>(trace_size)};
73+
}
74+
std::array<k2::LogTaggedEntry, 1> tagged_entries{
75+
{k2::LogTaggedEntry{.key = backtrace_key.data(), .value = backtrace.data(), .key_len = backtrace_key.size(), .value_len = backtrace.size()}}};
76+
k2::log(std::to_underlying(level), message, std::span(tagged_entries.data(), tagged_entries.size()));
6777
}
6878

6979
template<typename... Args>

tests/python/lib/file_utils.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import re
44
import sys
55
import shutil
6+
import time
67

78
_SUPPORTED_PHP_VERSIONS = ["php7.4", "php8", "php8.1", "php8.2", "php8.3"]
89

@@ -120,5 +121,13 @@ def search_php_bin(php_version: str):
120121

121122
return None
122123

124+
123125
def search_k2_bin():
124126
return os.getenv("K2_BIN")
127+
128+
129+
def wait_for_file_creation(file_path, check_interval=0.1, attempts=20):
130+
for i in range(attempts):
131+
if os.path.exists(file_path):
132+
break
133+
time.sleep(check_interval)

tests/python/lib/k2_server.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,15 @@ def start(self, start_msgs=None):
3939
else:
4040
start_msgs = start_msgs or []
4141
start_msgs.append("Starting to accept clients.")
42+
4243
super(K2Server, self).start(start_msgs)
4344

45+
if self._is_json_log_enabled():
46+
self.assert_json_log_tags(expect=[
47+
{"msg": "Starting to accept clients.", "tags": set()}
48+
])
49+
50+
4451
def stop(self):
4552
super(K2Server, self).stop()
4653

tests/python/lib/web_server.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from .engine import Engine
66
from .http_client import send_http_request, send_http_request_raw
77
from .port_generator import get_port
8+
from .file_utils import wait_for_file_creation
89

910

1011
class WebServer(Engine):
@@ -23,11 +24,11 @@ def __init__(self, web_server_bin, working_dir, options=None):
2324
self._json_log_file = None
2425
self._json_logs = []
2526

26-
2727
def start(self, start_msgs=None):
2828
super(WebServer, self).start(start_msgs)
2929
self._json_logs = []
3030
if (self._json_log_file is not None):
31+
wait_for_file_creation(self._json_log_file)
3132
self._json_log_file_read_fd = open(self._json_log_file, 'r')
3233

3334
def stop(self):
@@ -91,6 +92,36 @@ def _read_new_json_logs(self):
9192
def _process_json_log(self, log_record):
9293
return log_record
9394

95+
def assert_json_log_tags(self, expect, message="Can't wait expected json log", timeout=60):
96+
"""
97+
Check web server json log contains tags
98+
:param expect: Expected json record
99+
:param message: Error message in case of failure
100+
:param timeout: Json records waiting time
101+
"""
102+
start = time.time()
103+
expected_records = expect[:]
104+
105+
while expected_records:
106+
self._assert_availability()
107+
self._read_new_json_logs()
108+
self._json_logs = list(filter(None, self._json_logs))
109+
for index, json_log_record in enumerate(self._json_logs):
110+
if not expected_records:
111+
return
112+
expected_record = expected_records[0]
113+
expected_msg = expected_record["msg"]
114+
got_msg = json_log_record["msg"]
115+
if re.search(expected_msg, got_msg):
116+
if expected_record["tags"].issubset(json_log_record.keys()):
117+
expected_records.pop(0)
118+
self._json_logs[index] = None
119+
120+
time.sleep(0.05)
121+
if time.time() - start > timeout:
122+
expected_str = json.dumps(obj=expected_records, indent=2)
123+
raise RuntimeError("{}; Missed messages: {}".format(message, expected_str))
124+
94125
def assert_json_log(self, expect, message="Can't wait expected json log", timeout=60):
95126
"""
96127
Check kphp server json log

tests/python/tests/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
import os
22
import pytest
33

4-
from python.lib.conftest_impl import skip_k2_unsupported_test, skip_k2_unsupported_test_suite
4+
from python.lib.conftest_impl import skip_k2_unsupported_test, skip_k2_unsupported_test_suite, skip_kphp_unsupported_test, skip_kphp_unsupported_test_suite

tests/python/tests/json_logs/test_warnings.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,31 @@
1+
import os
12
import socket
23
import pytest
34

45
from python.lib.testcase import WebServerAutoTestCase
56
from python.lib.kphp_server import KphpServer
67

78

8-
@pytest.mark.k2_skip_suite
99
class TestJsonLogsWarnings(WebServerAutoTestCase):
1010
@classmethod
1111
def extra_class_setup(cls):
1212
cls.web_server.ignore_log_errors()
13+
if cls.should_use_k2():
14+
cls.web_server.update_options({"--log-file": os.path.join(cls.web_server._working_dir, "data/log-file")})
1315

16+
@pytest.mark.kphp_skip
17+
def test_warning_backtrace(self):
18+
resp = self.web_server.http_post(
19+
json=[
20+
{"op": "warning", "msg": "hello"},
21+
])
22+
self.assertEqual(resp.text, "ok")
23+
self.web_server.assert_json_log_tags(
24+
expect=[
25+
{"msg": "hello", "tags": {"trace"}}
26+
])
27+
28+
@pytest.mark.k2_skip
1429
def test_warning_no_context(self):
1530
resp = self.web_server.http_post(
1631
json=[
@@ -24,6 +39,7 @@ def test_warning_no_context(self):
2439
{"version": 0, "hostname": socket.gethostname(), "type": 2, "env": "", "msg": "world", "tags": {"uncaught": False}}
2540
])
2641

42+
@pytest.mark.k2_skip
2743
def test_warning_with_special_chars(self):
2844
resp = self.web_server.http_post(json=[{"op": "warning", "msg": 'aaa"bbb"\nccc'}])
2945
self.assertEqual(resp.text, "ok")
@@ -33,6 +49,7 @@ def test_warning_with_special_chars(self):
3349
"tags": {"uncaught": False}
3450
}])
3551

52+
@pytest.mark.k2_skip
3653
def test_warning_with_tags(self):
3754
resp = self.web_server.http_post(
3855
json=[
@@ -46,6 +63,7 @@ def test_warning_with_tags(self):
4663
"tags": {"uncaught": False, "a": "b"}
4764
}])
4865

66+
@pytest.mark.k2_skip
4967
def test_warning_with_extra_info(self):
5068
resp = self.web_server.http_post(
5169
json=[
@@ -59,6 +77,7 @@ def test_warning_with_extra_info(self):
5977
"tags": {"uncaught": False}, "extra_info": {"a": "b"}
6078
}])
6179

80+
@pytest.mark.k2_skip
6281
def test_warning_with_env(self):
6382
resp = self.web_server.http_post(
6483
json=[
@@ -69,6 +88,7 @@ def test_warning_with_env(self):
6988
self.web_server.assert_json_log(
7089
expect=[{"version": 0, "hostname": socket.gethostname(), "type": 2, "msg": "aaa", "env": "abc", "tags": {"uncaught": False}}])
7190

91+
@pytest.mark.k2_skip
7292
def test_warning_with_env_special_chars(self):
7393
resp = self.web_server.http_post(
7494
json=[
@@ -79,6 +99,7 @@ def test_warning_with_env_special_chars(self):
7999
self.web_server.assert_json_log(
80100
expect=[{"version": 0, "hostname": socket.gethostname(), "type": 2, "msg": "aaa", "env": "a b c/d\\e?f", "tags": {"uncaught": False}}])
81101

102+
@pytest.mark.k2_skip
82103
def test_warning_with_long_env(self):
83104
resp = self.web_server.http_post(
84105
json=[
@@ -89,6 +110,7 @@ def test_warning_with_long_env(self):
89110
self.web_server.assert_json_log(
90111
expect=[{"version": 0, "hostname": socket.gethostname(), "type": 2, "msg": "aaa", "env": "", "tags": {"uncaught": False}}])
91112

113+
@pytest.mark.k2_skip
92114
def test_warning_with_full_context(self):
93115
resp = self.web_server.http_post(
94116
json=[
@@ -102,6 +124,7 @@ def test_warning_with_full_context(self):
102124
"tags": {"uncaught": False, "a": "b"}, "extra_info": {"c": "d"}
103125
}])
104126

127+
@pytest.mark.k2_skip
105128
def test_warning_override_context(self):
106129
resp = self.web_server.http_post(
107130
json=[
@@ -132,6 +155,7 @@ def test_warning_override_context(self):
132155
}
133156
])
134157

158+
@pytest.mark.k2_skip
135159
def test_warning_vector_context(self):
136160
resp = self.web_server.http_post(
137161
json=[
@@ -145,6 +169,7 @@ def test_warning_vector_context(self):
145169
"tags": {"uncaught": False, "0": "a", "1": "b"}, "extra_info": {"0": "c", "1": "d"}
146170
}], timeout=1)
147171

172+
@pytest.mark.k2_skip
148173
def test_error_tag_context(self):
149174
if isinstance(self.web_server, KphpServer):
150175
self.web_server.set_error_tag(100500)

0 commit comments

Comments
 (0)