Skip to content

Commit 3d770a3

Browse files
authored
[k2] add backtrace on error and warn (#1335)
1 parent 6ac7e25 commit 3d770a3

7 files changed

Lines changed: 191 additions & 121 deletions

File tree

runtime-light/k2-platform/k2-api.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <cstdint>
1010
#include <ctime>
1111
#include <expected>
12+
#include <format>
1213
#include <memory>
1314
#include <span>
1415
#include <string_view>
@@ -294,3 +295,16 @@ inline size_t backtrace(std::span<void*> buffer) noexcept {
294295
}
295296

296297
} // namespace k2
298+
299+
template<>
300+
struct std::formatter<k2::SymbolInfo> {
301+
template<typename ParseContext>
302+
constexpr auto parse(ParseContext& ctx) const noexcept {
303+
return ctx.begin();
304+
}
305+
306+
template<typename FmtContext>
307+
auto format(const k2::SymbolInfo& info, FmtContext& ctx) const noexcept {
308+
return std::format_to(ctx.out(), "{}\n\tat {}:{}", info.name.get(), info.filename.get(), info.lineno);
309+
}
310+
};

runtime-light/stdlib/diagnostics/backtrace.h

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
#include <cstddef>
88
#include <cstdint>
99
#include <expected>
10+
#include <format>
1011
#include <ranges>
1112
#include <span>
13+
#include <type_traits>
1214
#include <utility>
1315

1416
#include "runtime-light/k2-platform/k2-api.h"
@@ -54,3 +56,41 @@ inline auto backtrace_symbols(std::span<void* const> addresses) noexcept {
5456
}
5557

5658
} // namespace kphp::diagnostic
59+
60+
template<>
61+
struct std::formatter<std::invoke_result_t<decltype(kphp::diagnostic::backtrace_addresses), std::span<void* const>>> {
62+
using addresses_t = std::invoke_result_t<decltype(kphp::diagnostic::backtrace_addresses), std::span<void* const>>;
63+
template<typename ParseContext>
64+
constexpr auto parse(ParseContext& ctx) const noexcept {
65+
return ctx.begin();
66+
}
67+
68+
template<typename FmtContext>
69+
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);
73+
}
74+
75+
return ctx.out();
76+
}
77+
};
78+
79+
template<>
80+
struct std::formatter<std::invoke_result_t<decltype(kphp::diagnostic::backtrace_symbols), std::span<void* const>>> {
81+
using symbols_info_t = std::invoke_result_t<decltype(kphp::diagnostic::backtrace_symbols), std::span<void* const>>;
82+
template<typename ParseContext>
83+
constexpr auto parse(ParseContext& ctx) const noexcept {
84+
return ctx.begin();
85+
}
86+
87+
template<typename FmtContext>
88+
auto format(const symbols_info_t& symbols_info, FmtContext& ctx) const noexcept {
89+
size_t level{};
90+
for (const auto& symbol_info : symbols_info) {
91+
format_to(ctx.out(), "# {} : {}\n", level++, symbol_info);
92+
}
93+
94+
return ctx.out();
95+
}
96+
};

runtime-light/utils/logs.h

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,14 @@
77
#include <array>
88
#include <cstddef>
99
#include <format>
10+
#include <optional>
1011
#include <source_location>
12+
#include <span>
1113
#include <type_traits>
1214
#include <utility>
1315

1416
#include "runtime-light/k2-platform/k2-api.h"
15-
16-
enum class LogLevel : size_t {
17-
Error = 1,
18-
Warn = 2,
19-
Info = 3,
20-
Debug = 4,
21-
Trace = 5,
22-
};
17+
#include "runtime-light/stdlib/diagnostics/backtrace.h"
2318

2419
namespace kphp::log {
2520

@@ -46,51 +41,78 @@ using wrapped_arg_t = std::invoke_result_t<decltype(impl::wrap_log_argument<T>),
4641
enum class level : size_t { error = 1, warn, info, debug, trace };
4742

4843
template<typename... Args>
49-
void log(level level, std::format_string<impl::wrapped_arg_t<Args>...> fmt, Args&&... args) noexcept {
44+
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;
5046
if (std::to_underlying(level) > k2::log_level_enabled()) {
5147
return;
5248
}
5349

54-
static constexpr size_t LOG_BUFFER_SIZE = 512;
55-
std::array<char, LOG_BUFFER_SIZE> log_buffer{};
56-
const auto [out, size]{std::format_to_n<decltype(log_buffer.data()), impl::wrapped_arg_t<Args>...>(log_buffer.data(), log_buffer.size() - 1, fmt,
57-
impl::wrap_log_argument(std::forward<Args>(args))...)};
50+
std::array<char, LOG_BUFFER_SIZE> log_buffer;
51+
auto [out, size]{std::format_to_n<decltype(log_buffer.data()), impl::wrapped_arg_t<Args>...>(log_buffer.data(), log_buffer.size() - 1, fmt,
52+
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+
}
63+
}
64+
5865
*out = '\0';
5966
k2::log(std::to_underlying(level), size, log_buffer.data());
6067
}
6168

62-
} // namespace impl
63-
6469
template<typename... Args>
65-
[[noreturn]] void error(std::format_string<impl::wrapped_arg_t<Args>...> fmt, Args&&... args) noexcept {
66-
impl::log(impl::level::error, fmt, std::forward<Args>(args)...);
67-
k2::exit(1);
70+
void log_with_backtrace(level level, std::format_string<impl::wrapped_arg_t<Args>...> fmt, Args&&... args) noexcept {
71+
static constexpr size_t MAX_BACKTRACE_SIZE = 64;
72+
if (std::to_underlying(level) > k2::log_level_enabled()) {
73+
return;
74+
}
75+
76+
std::array<void*, MAX_BACKTRACE_SIZE> backtrace{};
77+
const size_t num_frames{kphp::diagnostic::backtrace(backtrace)};
78+
const std::span<void* const> backtrace_view{backtrace.data(), num_frames};
79+
impl::log(level, backtrace_view, fmt, std::forward<Args>(args)...);
6880
}
81+
} // namespace impl
6982

83+
// The backtrace algorithm relies on the fact that assertion does not call backtrace.
84+
// If assertion is modified, the backtrace algorithm should be updated accordingly
7085
inline void assertion(bool condition, const std::source_location& location = std::source_location::current()) noexcept {
7186
if (!condition) [[unlikely]] {
72-
kphp::log::error("assertion failed at {}:{}", location.file_name(), location.line());
87+
impl::log(impl::level::error, std::nullopt, "assertion failed at {}:{}", location.file_name(), location.line());
88+
k2::exit(1);
7389
}
7490
}
7591

92+
template<typename... Args>
93+
[[noreturn]] void error(std::format_string<impl::wrapped_arg_t<Args>...> fmt, Args&&... args) noexcept {
94+
impl::log_with_backtrace(impl::level::error, fmt, std::forward<Args>(args)...);
95+
k2::exit(1);
96+
}
97+
7698
template<typename... Args>
7799
void warning(std::format_string<impl::wrapped_arg_t<Args>...> fmt, Args&&... args) noexcept {
78-
impl::log(impl::level::warn, fmt, std::forward<Args>(args)...);
100+
impl::log_with_backtrace(impl::level::warn, fmt, std::forward<Args>(args)...);
79101
}
80102

81103
template<typename... Args>
82104
void info(std::format_string<impl::wrapped_arg_t<Args>...> fmt, Args&&... args) noexcept {
83-
impl::log(impl::level::info, fmt, std::forward<Args>(args)...);
105+
impl::log(impl::level::info, std::nullopt, fmt, std::forward<Args>(args)...);
84106
}
85107

86108
template<typename... Args>
87109
void debug(std::format_string<impl::wrapped_arg_t<Args>...> fmt, Args&&... args) noexcept {
88-
impl::log(impl::level::debug, fmt, std::forward<Args>(args)...);
110+
impl::log(impl::level::debug, std::nullopt, fmt, std::forward<Args>(args)...);
89111
}
90112

91113
template<typename... Args>
92114
void trace(std::format_string<impl::wrapped_arg_t<Args>...> fmt, Args&&... args) noexcept {
93-
impl::log(impl::level::trace, fmt, std::forward<Args>(args)...);
115+
impl::log(impl::level::trace, std::nullopt, fmt, std::forward<Args>(args)...);
94116
}
95117

96118
} // namespace kphp::log

runtime-light/utils/panic.cpp

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

runtime-light/utils/php-assert.cpp

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Compiler for PHP (aka KPHP)
2+
// Copyright (c) 2024 LLC «V Kontakte»
3+
// Distributed under the GPL v3 License, see LICENSE.notice.txt
4+
5+
#include <array>
6+
#include <cstdarg>
7+
#include <cstddef>
8+
#include <cstdio>
9+
#include <utility>
10+
11+
#include "runtime-common/core/utils/kphp-assert-core.h"
12+
#include "runtime-light/k2-platform/k2-api.h"
13+
#include "runtime-light/utils/logs.h"
14+
15+
namespace {
16+
17+
enum class php_log_level : size_t { error = 1, warn, info, debug };
18+
19+
void php_warning_impl(php_log_level level, char const* message, va_list args) noexcept {
20+
if (std::to_underlying(level) > k2::log_level_enabled()) {
21+
return;
22+
}
23+
24+
constexpr size_t LOG_BUFFER_SIZE = 512;
25+
std::array<char, LOG_BUFFER_SIZE> log_buffer;
26+
const auto recorded{std::vsnprintf(log_buffer.data(), log_buffer.size(), message, args)};
27+
if (recorded <= 0) {
28+
return;
29+
}
30+
31+
switch (level) {
32+
case php_log_level::debug:
33+
kphp::log::debug("{}", log_buffer.data());
34+
break;
35+
case php_log_level::info:
36+
kphp::log::info("{}", log_buffer.data());
37+
break;
38+
case php_log_level::warn:
39+
kphp::log::warning("{}", log_buffer.data());
40+
break;
41+
case php_log_level::error:
42+
kphp::log::error("{}", log_buffer.data());
43+
break;
44+
}
45+
}
46+
} // namespace
47+
48+
void php_debug(char const* message, ...) {
49+
va_list args;
50+
va_start(args, message);
51+
php_warning_impl(php_log_level::debug, message, args);
52+
va_end(args);
53+
}
54+
55+
void php_notice(char const* message, ...) {
56+
va_list args;
57+
va_start(args, message);
58+
php_warning_impl(php_log_level::info, message, args);
59+
va_end(args);
60+
}
61+
62+
void php_warning(char const* message, ...) {
63+
va_list args;
64+
va_start(args, message);
65+
php_warning_impl(php_log_level::warn, message, args);
66+
va_end(args);
67+
}
68+
69+
void php_error(char const* message, ...) {
70+
va_list args;
71+
va_start(args, message);
72+
php_warning_impl(php_log_level::error, message, args);
73+
va_end(args);
74+
}
75+
76+
void runtime_error(char const* message, ...) {
77+
va_list args;
78+
va_start(args, message);
79+
php_warning_impl(php_log_level::error, message, args); // TODO: fix error code and think about internal / user errors separation in K2
80+
va_end(args);
81+
}
82+
83+
void php_assert__(const char* msg, const char* file, int line) {
84+
php_error("Assertion \"%s\" failed in file %s on line %d", msg, file, line);
85+
critical_error_handler();
86+
k2::exit(1);
87+
}
88+
89+
void critical_error_handler() {
90+
k2::exit(1);
91+
}

runtime-light/utils/php_assert.cpp

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

runtime-light/utils/utils.cmake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
prepend(RUNTIME_LIGHT_UTILS_SRC utils/ panic.cpp php_assert.cpp)
1+
prepend(RUNTIME_LIGHT_UTILS_SRC utils/ php-assert.cpp)

0 commit comments

Comments
 (0)