aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorarkady-e1ppa <arkady-e1ppa@yandex-team.com>2024-05-30 21:48:06 +0300
committerarkady-e1ppa <arkady-e1ppa@yandex-team.com>2024-05-30 21:57:42 +0300
commitd6adb2d70464c9eb08b2b24076be43c44d5a8b74 (patch)
treefca4b31583b00c5d3a17d114a9923b67e72745ed
parentb5530daab1ea3757321436470f86ed65f30408c9 (diff)
downloadydb-d6adb2d70464c9eb08b2b24076be43c44d5a8b74.tar.gz
YT-21868: Static analysis of format string in logging
Added static analysis to format of YT_LOG_XXX macro's. We expect you to write format string as first or the second argument and follow the rules as if you are writing arguments for `NYT::Format`, which match those of printf: https://en.cppreference.com/w/cpp/io/c/fprintf plus few extra flags like 'v'. At the moment analyser checks if flags sequences is 1. Correctly terminated 2. Only contains specifiers valid for a given argument (if we are parsing nth argument of type T, then T must have all specifiers from its list of Conversion or Flag specifiers. (2) Also means that the number of flag sequences must match the number of arguments supplied to format. You can specialize `TFormatArg<T>` which is used to determine allowed Conversion and Flag specifiers to customise rules of static analysis. E.g. you can introduce new flags to the mix which you promise to parse in the related FormatValue function. If you feel like this produces to much overhead in terms of compile time, you are free to use macro YT_DISABLE_FORMAT_STATIC_ANALYSIS to turn the entire thing into a no-op. We have measured compile time to be affected by roughly 3-5% in a log intensive files. ae6def509474e8a42027bb4ed84ac040509b7c85
-rw-r--r--library/cpp/yt/logging/logger.h11
-rw-r--r--library/cpp/yt/logging/static_analysis-inl.h139
-rw-r--r--library/cpp/yt/logging/static_analysis.h22
-rw-r--r--library/cpp/yt/logging/unittests/static_analysis_ut.cpp41
-rw-r--r--library/cpp/yt/logging/unittests/ya.make1
-rw-r--r--library/cpp/yt/string/format_analyser-inl.h138
-rw-r--r--library/cpp/yt/string/format_analyser.h102
-rw-r--r--yt/cpp/mapreduce/client/client_reader.cpp6
-rw-r--r--yt/cpp/mapreduce/client/operation_preparer.cpp3
-rw-r--r--yt/yt/client/arrow/arrow_row_stream_encoder.cpp6
-rw-r--r--yt/yt/client/table_client/unversioned_row.h9
-rw-r--r--yt/yt/client/table_client/unversioned_value.h11
-rw-r--r--yt/yt/core/concurrency/new_fair_share_thread_pool.cpp4
-rw-r--r--yt/yt/core/concurrency/two_level_fair_share_thread_pool.cpp3
-rw-r--r--yt/yt/library/formats/arrow_writer.cpp6
15 files changed, 494 insertions, 8 deletions
diff --git a/library/cpp/yt/logging/logger.h b/library/cpp/yt/logging/logger.h
index 4f0ed44ab7..abeb9a1c9f 100644
--- a/library/cpp/yt/logging/logger.h
+++ b/library/cpp/yt/logging/logger.h
@@ -22,6 +22,10 @@
#include <atomic>
+#if !defined(NDEBUG) && !defined(YT_DISABLE_FORMAT_STATIC_ANALYSIS)
+ #include "static_analysis.h"
+#endif
+
namespace NYT::NLogging {
////////////////////////////////////////////////////////////////////////////////
@@ -296,6 +300,12 @@ void LogStructuredEvent(
#define YT_LOG_EVENT(logger, level, ...) \
YT_LOG_EVENT_WITH_ANCHOR(logger, level, nullptr, __VA_ARGS__)
+#if !defined(NDEBUG) && !defined(YT_DISABLE_FORMAT_STATIC_ANALYSIS)
+ #define YT_LOG_CHECK_FORMAT(...) STATIC_ANALYSIS_CHECK_LOG_FORMAT(__VA_ARGS__)
+#else
+ #define YT_LOG_CHECK_FORMAT(...)
+#endif
+
#define YT_LOG_EVENT_WITH_ANCHOR(logger, level, anchor, ...) \
do { \
const auto& logger__ = (logger)(); \
@@ -319,6 +329,7 @@ void LogStructuredEvent(
} \
\
auto loggingContext__ = ::NYT::NLogging::GetLoggingContext(); \
+ YT_LOG_CHECK_FORMAT(__VA_ARGS__); \
auto message__ = ::NYT::NLogging::NDetail::BuildLogMessage(loggingContext__, logger__, __VA_ARGS__); \
\
if (!anchorUpToDate__) { \
diff --git a/library/cpp/yt/logging/static_analysis-inl.h b/library/cpp/yt/logging/static_analysis-inl.h
new file mode 100644
index 0000000000..d4ec5343bc
--- /dev/null
+++ b/library/cpp/yt/logging/static_analysis-inl.h
@@ -0,0 +1,139 @@
+#ifndef STATIC_ANALYSIS_INL_H_
+#error "Direct inclusion of this file is not allowed, include static_analysis.h"
+// For the sake of sane code completion.
+#include "static_analysis.h"
+#endif
+
+#include <library/cpp/yt/misc/preprocessor.h>
+
+#include <library/cpp/yt/string/format_analyser.h>
+
+#include <string_view>
+#include <variant> // monostate
+
+namespace NYT::NLogging::NDetail {
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Tag for dispatching proper TFormatArg specialization.
+template <class T>
+struct TLoggerFormatArg
+{ };
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Stateless constexpr way of capturing arg types
+// without invoking any ctors. With the help of macros
+// can turn non-constexpr argument pack of arguments
+// into constexpr pack of types.
+template <class... TArgs>
+struct TLoggerFormatArgs
+{ };
+
+// Used for macro conversion. Purposefully undefined.
+template <class... TArgs>
+TLoggerFormatArgs<TArgs...> AsFormatArgs(TArgs&&...);
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <bool First, bool Second>
+struct TAnalyserDispatcher
+{
+ template <class... TArgs>
+ static consteval void Do(std::string_view, std::string_view, TLoggerFormatArgs<TArgs...>)
+ {
+ // Give up :(
+ // We can't crash here, because, for example, YT_LOG_ERROR(error) exists
+ // and we can't really check if error is actually TError or something else here.
+ // and probably shouldn't bother trying.
+ }
+};
+
+template <bool Second>
+struct TAnalyserDispatcher<true, Second>
+{
+ template <class TFirst, class... TArgs>
+ static consteval void Do(std::string_view str, std::string_view, TLoggerFormatArgs<TFirst, TArgs...>)
+ {
+ // Remove outer \"'s generated by PP_STRINGIZE.
+ auto stripped = std::string_view(std::begin(str) + 1, std::size(str) - 2);
+ ::NYT::NDetail::TFormatAnalyser::ValidateFormat<TLoggerFormatArg<TArgs>...>(stripped);
+ }
+};
+
+template <>
+struct TAnalyserDispatcher<false, true>
+{
+ template <class TFirst, class TSecond, class... TArgs>
+ static consteval void Do(std::string_view, std::string_view str, TLoggerFormatArgs<TFirst, TSecond, TArgs...>)
+ {
+ // Remove outer \"'s generated by PP_STRINGIZE.
+ auto stripped = std::string_view(std::begin(str) + 1, std::size(str) - 2);
+ ::NYT::NDetail::TFormatAnalyser::ValidateFormat<TLoggerFormatArg<TArgs>...>(stripped);
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+// This value is never read since homogenization works for unevaluated expressions.
+inline constexpr auto InvalidToken = std::monostate{};
+
+////////////////////////////////////////////////////////////////////////////////
+
+#define PP_VA_PICK_1_IMPL(N, ...) N
+#define PP_VA_PICK_2_IMPL(_1, N, ...) N
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Parameter pack parsing.
+
+#define STATIC_ANALYSIS_CAPTURE_TYPES(...) \
+ decltype(::NYT::NLogging::NDetail::AsFormatArgs(__VA_ARGS__)){}
+
+#define STATIC_ANALYSIS_FIRST_TOKEN(...) \
+ PP_STRINGIZE( \
+ PP_VA_PICK_1_IMPL(__VA_ARGS__ __VA_OPT__(,) ::NYT::NLogging::NDetail::InvalidToken))
+
+#define STATIC_ANALYSIS_SECOND_TOKEN(...) \
+ PP_STRINGIZE(\
+ PP_VA_PICK_2_IMPL( \
+ __VA_ARGS__ __VA_OPT__(,) \
+ ::NYT::NLogging::NDetail::InvalidToken, \
+ ::NYT::NLogging::NDetail::InvalidToken))
+
+#define STATIC_ANALYSIS_FIRST_TOKEN_COND(...) \
+ STATIC_ANALYSIS_FIRST_TOKEN(__VA_ARGS__)[0] == '\"'
+
+#define STATIC_ANALYSIS_SECOND_TOKEN_COND(...) \
+ STATIC_ANALYSIS_SECOND_TOKEN(__VA_ARGS__)[0] == '\"'
+
+#undef STATIC_ANALYSIS_CHECK_LOG_FORMAT
+#define STATIC_ANALYSIS_CHECK_LOG_FORMAT(...) \
+ ::NYT \
+ ::NLogging \
+ ::NDetail \
+ ::TAnalyserDispatcher< \
+ STATIC_ANALYSIS_FIRST_TOKEN_COND(__VA_ARGS__), \
+ STATIC_ANALYSIS_SECOND_TOKEN_COND(__VA_ARGS__) \
+ >::Do( \
+ STATIC_ANALYSIS_FIRST_TOKEN(__VA_ARGS__), \
+ STATIC_ANALYSIS_SECOND_TOKEN(__VA_ARGS__), \
+ STATIC_ANALYSIS_CAPTURE_TYPES(__VA_ARGS__))
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NLogging::NDetail
+
+template <class T>
+struct NYT::TFormatArg<NYT::NLogging::NDetail::TLoggerFormatArg<T>>
+ : public NYT::TFormatArgBase
+{
+ // We mix in '\"' and ' ' which is an artifact of logging stringize.
+ // We want to support YT_LOG_XXX("Value: %" PRIu64, 42)
+ // for plantform independent prints of numbers.
+ // String below may be converted to a token:
+ // "\"Value: %\" \"u\""
+ // Thus adding a \" \" sequence.
+ static constexpr auto FlagSpecifiers
+ = TFormatArgBase::ExtendFlags</*Hot*/ false, 2, std::array{'\"', ' '}, /*TFrom*/ NYT::TFormatArg<T>>();
+};
diff --git a/library/cpp/yt/logging/static_analysis.h b/library/cpp/yt/logging/static_analysis.h
new file mode 100644
index 0000000000..a335d8c6cc
--- /dev/null
+++ b/library/cpp/yt/logging/static_analysis.h
@@ -0,0 +1,22 @@
+#pragma once
+
+namespace NYT::NLogging {
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Performs a compile-time check of log arguments validity.
+// Valid argument lists are:
+// 1. (format, args...)
+// 2. (error, format, args...)
+// If format is not a string literal or argument list
+// is not valid, no check is made -- macro turns to
+// a no-op.
+#define STATIC_ANALYSIS_CHECK_LOG_FORMAT(...)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NLogging
+
+#define STATIC_ANALYSIS_INL_H_
+#include "static_analysis-inl.h"
+#undef STATIC_ANALYSIS_INL_H_
diff --git a/library/cpp/yt/logging/unittests/static_analysis_ut.cpp b/library/cpp/yt/logging/unittests/static_analysis_ut.cpp
new file mode 100644
index 0000000000..1c705dc967
--- /dev/null
+++ b/library/cpp/yt/logging/unittests/static_analysis_ut.cpp
@@ -0,0 +1,41 @@
+#include <library/cpp/testing/gtest/gtest.h>
+
+#include <library/cpp/yt/logging/logger.h>
+
+namespace NYT::NLogging {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TStaticAnalysis, ValidFormats)
+{
+ // Mock for actual error -- we only care that
+ // it is some runtime object.
+ [[maybe_unused]] struct TError
+ { } error;
+
+ YT_LOG_CHECK_FORMAT("Hello");
+ YT_LOG_CHECK_FORMAT("Hello %v", "World!");
+ YT_LOG_CHECK_FORMAT("Hello %qv", "World!");
+ YT_LOG_CHECK_FORMAT(error);
+ YT_LOG_CHECK_FORMAT(error, "Hello");
+ YT_LOG_CHECK_FORMAT(error, "Hello %Qhs", "World!");
+ YT_LOG_CHECK_FORMAT("Hello %%");
+ YT_LOG_CHECK_FORMAT("Hello %" PRIu64, 42);
+}
+
+// Uncomment this test to see that we don't have false negatives!
+// TEST(TStaticAnalysis, InvalidFormats)
+// {
+// YT_LOG_CHECK_FORMAT("Hello", 1);
+// YT_LOG_CHECK_FORMAT("Hello %");
+// YT_LOG_CHECK_FORMAT("Hello %false");
+// YT_LOG_CHECK_FORMAT("Hello ", "World");
+// YT_LOG_CHECK_FORMAT("Hello ", "(World: %v)", 42);
+// YT_LOG_CHECK_FORMAT("Hello %lbov", 42); // There is no 'b' flag.
+// }
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NLogging
diff --git a/library/cpp/yt/logging/unittests/ya.make b/library/cpp/yt/logging/unittests/ya.make
index 42268d3db2..021b0d09d6 100644
--- a/library/cpp/yt/logging/unittests/ya.make
+++ b/library/cpp/yt/logging/unittests/ya.make
@@ -8,6 +8,7 @@ ENDIF()
SRCS(
logger_ut.cpp
+ static_analysis_ut.cpp
)
PEERDIR(
diff --git a/library/cpp/yt/string/format_analyser-inl.h b/library/cpp/yt/string/format_analyser-inl.h
new file mode 100644
index 0000000000..e205fc754e
--- /dev/null
+++ b/library/cpp/yt/string/format_analyser-inl.h
@@ -0,0 +1,138 @@
+#ifndef FORMAT_ANALYSER_INL_H_
+#error "Direct inclusion of this file is not allowed, include format_analyser.h"
+// For the sake of sane code completion.
+#include "format_analyser.h"
+#endif
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NDetail {
+
+template <class... TArgs>
+consteval void TFormatAnalyser::ValidateFormat(std::string_view fmt)
+{
+ std::array<std::string_view, sizeof...(TArgs)> markers = {};
+ std::array<TSpecifiers, sizeof...(TArgs)> specifiers{GetSpecifiers<TArgs>()...};
+
+ int markerCount = 0;
+ int currentMarkerStart = -1;
+
+ for (int idx = 0; idx < std::ssize(fmt); ++idx) {
+ auto symbol = fmt[idx];
+
+ // Parse verbatim text.
+ if (currentMarkerStart == -1) {
+ if (symbol == IntroductorySymbol) {
+ // Marker maybe begins.
+ currentMarkerStart = idx;
+ }
+ continue;
+ }
+
+ // NB: We check for %% first since
+ // in order to verify if symbol is a specifier
+ // we need markerCount to be within range of our
+ // specifier array.
+ if (symbol == IntroductorySymbol) {
+ if (currentMarkerStart + 1 != idx) {
+ // '%a% detected'
+ CrashCompilerWrongTermination("You may not terminate flag sequence other than %% with \'%\' symbol");
+ return;
+ }
+ // '%%' detected --- skip
+ currentMarkerStart = -1;
+ continue;
+ }
+
+ // We are inside of marker.
+ if (markerCount == std::ssize(markers)) {
+ // To many markers
+ CrashCompilerNotEnoughArguments("Number of arguments supplied to format is smaller than the number of flag sequences");
+ return;
+ }
+
+ if (specifiers[markerCount].Conversion.contains(symbol)) {
+ // Marker has finished.
+
+ markers[markerCount]
+ = std::string_view(fmt.begin() + currentMarkerStart, idx - currentMarkerStart + 1);
+ currentMarkerStart = -1;
+ ++markerCount;
+
+ continue;
+ }
+
+ if (!specifiers[markerCount].Flags.contains(symbol)) {
+ CrashCompilerWrongFlagSpecifier("Symbol is not a valid flag specifier; See FlagSpecifiers");
+ }
+ }
+
+ if (currentMarkerStart != -1) {
+ // Runaway marker.
+ CrashCompilerMissingTermination("Unterminated flag sequence detected; Use \'%%\' to type plain %");
+ return;
+ }
+
+ if (markerCount < std::ssize(markers)) {
+ // Missing markers.
+ CrashCompilerTooManyArguments("Number of arguments supplied to format is greater than the number of flag sequences");
+ return;
+ }
+
+ // TODO(arkady-e1ppa): Consider per-type verification
+ // of markers.
+}
+
+template <class TArg>
+consteval auto TFormatAnalyser::GetSpecifiers()
+{
+ static_assert(CFormattable<TArg>, "Your specialization of TFormatArg is broken");
+
+ return TSpecifiers{
+ .Conversion = std::string_view{
+ std::data(TFormatArg<TArg>::ConversionSpecifiers),
+ std::size(TFormatArg<TArg>::ConversionSpecifiers)},
+ .Flags = std::string_view{
+ std::data(TFormatArg<TArg>::FlagSpecifiers),
+ std::size(TFormatArg<TArg>::FlagSpecifiers)},
+ };
+}
+
+} // namespace NDetail
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <bool Hot, size_t N, std::array<char, N> List, class TFrom>
+consteval auto TFormatArgBase::ExtendConversion()
+{
+ static_assert(CFormattable<TFrom>);
+ return AppendArrays<Hot, N, List, &TFrom::ConversionSpecifiers>();
+}
+
+template <bool Hot, size_t N, std::array<char, N> List, class TFrom>
+consteval auto TFormatArgBase::ExtendFlags()
+{
+ static_assert(CFormattable<TFrom>);
+ return AppendArrays<Hot, N, List, &TFrom::FlagSpecifiers>();
+}
+
+template <bool Hot, size_t N, std::array<char, N> List, auto* From>
+consteval auto TFormatArgBase::AppendArrays()
+{
+ auto& from = *From;
+ return [] <size_t... I, size_t... J> (
+ std::index_sequence<I...>,
+ std::index_sequence<J...>) {
+ if constexpr (Hot) {
+ return std::array{List[J]..., from[I]...};
+ } else {
+ return std::array{from[I]..., List[J]...};
+ }
+ } (
+ std::make_index_sequence<std::size(from)>(),
+ std::make_index_sequence<N>());
+ }
+
+} // namespace NYT::NDetail
diff --git a/library/cpp/yt/string/format_analyser.h b/library/cpp/yt/string/format_analyser.h
new file mode 100644
index 0000000000..4afe3f1ca8
--- /dev/null
+++ b/library/cpp/yt/string/format_analyser.h
@@ -0,0 +1,102 @@
+#pragma once
+
+#include <array>
+#include <string_view>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NDetail {
+
+struct TFormatAnalyser
+{
+public:
+ template <class... TArgs>
+ static consteval void ValidateFormat(std::string_view fmt);
+
+private:
+ // Non-constexpr function call will terminate compilation.
+ // Purposefully undefined and non-constexpr/consteval
+ static void CrashCompilerNotEnoughArguments(std::string_view msg);
+ static void CrashCompilerTooManyArguments(std::string_view msg);
+ static void CrashCompilerWrongTermination(std::string_view msg);
+ static void CrashCompilerMissingTermination(std::string_view msg);
+ static void CrashCompilerWrongFlagSpecifier(std::string_view msg);
+
+ struct TSpecifiers
+ {
+ std::string_view Conversion;
+ std::string_view Flags;
+ };
+ template <class TArg>
+ static consteval auto GetSpecifiers();
+
+ static constexpr char IntroductorySymbol = '%';
+};
+
+} // namespace NDetail
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Base used for flag checks for each type independently.
+// Use it for overrides.
+struct TFormatArgBase
+{
+public:
+ // TODO(arkady-e1ppa): Consider more strict formatting rules.
+ static constexpr std::array ConversionSpecifiers = {
+ 'v', '1', 'c', 's', 'd', 'i', 'o',
+ 'x', 'X', 'u', 'f', 'F', 'e', 'E',
+ 'a', 'A', 'g', 'G', 'n', 'p'
+ };
+
+ static constexpr std::array FlagSpecifiers = {
+ '-', '+', ' ', '#', '0',
+ '1', '2', '3', '4', '5',
+ '6', '7', '8', '9',
+ '*', '.', 'h', 'l', 'j',
+ 'z', 't', 'L', 'q', 'Q'
+ };
+
+ template <class T>
+ static constexpr bool IsSpecifierList = requires (T t) {
+ [] <size_t N> (std::array<char, N>) { } (t);
+ };
+
+ // Hot = |true| adds specifiers to the beggining
+ // of a new array.
+ template <bool Hot, size_t N, std::array<char, N> List, class TFrom = TFormatArgBase>
+ static consteval auto ExtendConversion();
+
+ template <bool Hot, size_t N, std::array<char, N> List, class TFrom = TFormatArgBase>
+ static consteval auto ExtendFlags();
+
+private:
+ template <bool Hot, size_t N, std::array<char, N> List, auto* From>
+ static consteval auto AppendArrays();
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+struct TFormatArg
+ : public TFormatArgBase
+{ };
+
+// Semantic requirement:
+// Said field must be constexpr.
+template <class T>
+concept CFormattable = requires {
+ TFormatArg<T>::ConversionSpecifiers;
+ requires TFormatArgBase::IsSpecifierList<decltype(TFormatArg<T>::ConversionSpecifiers)>;
+
+ TFormatArg<T>::FlagSpecifiers;
+ requires TFormatArgBase::IsSpecifierList<decltype(TFormatArg<T>::FlagSpecifiers)>;
+};
+
+} // namespace NYT
+
+#define FORMAT_ANALYSER_INL_H_
+#include "format_analyser-inl.h"
+#undef FORMAT_ANALYSER_INL_H_
diff --git a/yt/cpp/mapreduce/client/client_reader.cpp b/yt/cpp/mapreduce/client/client_reader.cpp
index e1051fda96..0efa06ed3a 100644
--- a/yt/cpp/mapreduce/client/client_reader.cpp
+++ b/yt/cpp/mapreduce/client/client_reader.cpp
@@ -216,7 +216,11 @@ void TClientReader::CreateRequest(const TMaybe<ui32>& rangeIndex, const TMaybe<u
Input_ = Response_->GetResponseStream();
- YT_LOG_DEBUG("RSP %v - table stream (RequestId: %v, RangeIndex: %v, RowIndex: %v)", requestId, rangeIndex, rowIndex);
+ YT_LOG_DEBUG(
+ "RSP %v - table stream (RangeIndex: %v, RowIndex: %v)",
+ requestId,
+ rangeIndex,
+ rowIndex);
return;
} catch (const TErrorResponse& e) {
diff --git a/yt/cpp/mapreduce/client/operation_preparer.cpp b/yt/cpp/mapreduce/client/operation_preparer.cpp
index 181d8def03..07d00e88d6 100644
--- a/yt/cpp/mapreduce/client/operation_preparer.cpp
+++ b/yt/cpp/mapreduce/client/operation_preparer.cpp
@@ -575,7 +575,8 @@ TMaybe<TString> TJobPreparer::GetItemFromCypressCache(const TString& md5Signatur
GetCachePath(),
TGetFileFromCacheOptions());
if (maybePath) {
- YT_LOG_DEBUG("File is already in cache (FileName: %v)",
+ YT_LOG_DEBUG(
+ "File is already in cache (FileName: %v, FilePath: %v)",
fileName,
*maybePath);
}
diff --git a/yt/yt/client/arrow/arrow_row_stream_encoder.cpp b/yt/yt/client/arrow/arrow_row_stream_encoder.cpp
index 65701a56db..5413729fdc 100644
--- a/yt/yt/client/arrow/arrow_row_stream_encoder.cpp
+++ b/yt/yt/client/arrow/arrow_row_stream_encoder.cpp
@@ -457,7 +457,8 @@ void SerializeDoubleColumn(
YT_VERIFY(column->Values->BaseValue == 0);
YT_VERIFY(!column->Values->ZigZagEncoded);
- YT_LOG_DEBUG("Adding double column (ColumnId: %v, StartIndex: %v, ValueCount: %v)",
+ YT_LOG_DEBUG(
+ "Adding double column (ColumnId: %v, StartIndex: %v, ValueCount: %v, Rle: %v)",
column->Id,
column->StartIndex,
column->ValueCount,
@@ -486,7 +487,8 @@ void SerializeFloatColumn(
YT_VERIFY(column->Values->BaseValue == 0);
YT_VERIFY(!column->Values->ZigZagEncoded);
- YT_LOG_DEBUG("Adding float column (ColumnId: %v, StartIndex: %v, ValueCount: %v)",
+ YT_LOG_DEBUG(
+ "Adding float column (ColumnId: %v, StartIndex: %v, ValueCount: %v, Rle: %v)",
column->Id,
column->StartIndex,
column->ValueCount,
diff --git a/yt/yt/client/table_client/unversioned_row.h b/yt/yt/client/table_client/unversioned_row.h
index 24fd6a9288..148b49799c 100644
--- a/yt/yt/client/table_client/unversioned_row.h
+++ b/yt/yt/client/table_client/unversioned_row.h
@@ -985,3 +985,12 @@ struct THash<NYT::NTableClient::TUnversionedRow>
return NYT::NTableClient::TDefaultUnversionedRowHash()(row);
}
};
+
+template <class T>
+ requires std::derived_from<std::remove_cvref_t<T>, NYT::NTableClient::TUnversionedRow>
+struct NYT::TFormatArg<T>
+ : public NYT::TFormatArgBase
+{
+ static constexpr auto FlagSpecifiers
+ = TFormatArgBase::ExtendFlags</*Hot*/ true, 1, std::array{'k'}>();
+};
diff --git a/yt/yt/client/table_client/unversioned_value.h b/yt/yt/client/table_client/unversioned_value.h
index d4fdc40c9d..6bca3e490f 100644
--- a/yt/yt/client/table_client/unversioned_value.h
+++ b/yt/yt/client/table_client/unversioned_value.h
@@ -4,6 +4,8 @@
#include <library/cpp/yt/farmhash/farm_hash.h>
+#include <library/cpp/yt/string/format_analyser.h>
+
#include <util/system/defaults.h>
namespace NYT::NTableClient {
@@ -115,3 +117,12 @@ struct THash<NYT::NTableClient::TUnversionedValue>
return NYT::NTableClient::TDefaultUnversionedValueHash()(value);
}
};
+
+template <class T>
+ requires std::derived_from<std::remove_cvref_t<T>, NYT::NTableClient::TUnversionedValue>
+struct NYT::TFormatArg<T>
+ : public NYT::TFormatArgBase
+{
+ static constexpr auto FlagSpecifiers
+ = TFormatArgBase::ExtendFlags</*Hot*/ true, 1, std::array{'k'}>();
+};
diff --git a/yt/yt/core/concurrency/new_fair_share_thread_pool.cpp b/yt/yt/core/concurrency/new_fair_share_thread_pool.cpp
index 585665cdf6..7a2a067093 100644
--- a/yt/yt/core/concurrency/new_fair_share_thread_pool.cpp
+++ b/yt/yt/core/concurrency/new_fair_share_thread_pool.cpp
@@ -977,7 +977,9 @@ private:
{
VERIFY_SPINLOCK_AFFINITY(MainLock_);
- YT_LOG_DEBUG_IF(VerboseLogging_, "Buckets: %v",
+ YT_LOG_DEBUG_IF(
+ VerboseLogging_,
+ "Buckets: %v",
MakeFormattableView(
xrange(size_t(0), ActivePoolsHeap_.GetSize()),
[&] (auto* builder, auto index) {
diff --git a/yt/yt/core/concurrency/two_level_fair_share_thread_pool.cpp b/yt/yt/core/concurrency/two_level_fair_share_thread_pool.cpp
index 3c192b6ce1..be940455d6 100644
--- a/yt/yt/core/concurrency/two_level_fair_share_thread_pool.cpp
+++ b/yt/yt/core/concurrency/two_level_fair_share_thread_pool.cpp
@@ -538,7 +538,8 @@ private:
}
}
- YT_LOG_TRACE("Buckets: %v",
+ YT_LOG_TRACE(
+ "Buckets: %v",
MakeFormattableView(
xrange(size_t(0), IdToPool_.size()),
[&] (auto* builder, auto index) {
diff --git a/yt/yt/library/formats/arrow_writer.cpp b/yt/yt/library/formats/arrow_writer.cpp
index 15faaa27a7..56aa36c99b 100644
--- a/yt/yt/library/formats/arrow_writer.cpp
+++ b/yt/yt/library/formats/arrow_writer.cpp
@@ -696,7 +696,8 @@ void SerializeDoubleColumn(
YT_VERIFY(column->Values->BaseValue == 0);
YT_VERIFY(!column->Values->ZigZagEncoded);
- YT_LOG_DEBUG("Adding double column (ColumnId: %v, StartIndex: %v, ValueCount: %v)",
+ YT_LOG_DEBUG(
+ "Adding double column (ColumnId: %v, StartIndex: %v, ValueCount: %v, Rle: %v)",
column->Id,
column->StartIndex,
column->ValueCount,
@@ -725,7 +726,8 @@ void SerializeFloatColumn(
YT_VERIFY(column->Values->BaseValue == 0);
YT_VERIFY(!column->Values->ZigZagEncoded);
- YT_LOG_DEBUG("Adding float column (ColumnId: %v, StartIndex: %v, ValueCount: %v)",
+ YT_LOG_DEBUG(
+ "Adding float column (ColumnId: %v, StartIndex: %v, ValueCount: %v, Rle: %v)",
column->Id,
column->StartIndex,
column->ValueCount,