aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp/yt/logging
diff options
context:
space:
mode:
authormax42 <max42@yandex-team.com>2023-06-30 03:37:03 +0300
committermax42 <max42@yandex-team.com>2023-06-30 03:37:03 +0300
commitfac2bd72b4b31ec3238292caf8fb2a8aaa6d6c4a (patch)
treeb8cbc1deb00309c7f1a7ab6df520a76cf0b5c6d7 /library/cpp/yt/logging
parent7bf166b1a7ed0af927f230022b245af618e998c1 (diff)
downloadydb-fac2bd72b4b31ec3238292caf8fb2a8aaa6d6c4a.tar.gz
YT-19324: move YT provider to ydb/library/yql
This commit is formed by the following script: https://paste.yandex-team.ru/6f92e4b8-efc5-4d34-948b-15ee2accd7e7/text. This commit has zero effect on all projects that depend on YQL. The summary of changes: - `yql/providers/yt -> ydb/library/yql/providers/yt `- the whole implementation of YT provider is moved into YDB code base for further export as a part of YT YQL plugin shared library; - `yql/providers/stat/{expr_nodes,uploader} -> ydb/library/yql/providers/stat/{expr_nodes,uploader}` - a small interface without implementation and the description of stat expr nodes; - `yql/core/extract_predicate/ut -> ydb/library/yql/core/extract_predicate/ut`; - `yql/core/{ut,ut_common} -> ydb/library/yql/core/{ut,ut_common}`; - `yql/core` is gone; - `yql/library/url_preprocessing -> ydb/library/yql/core/url_preprocessing`. **NB**: all new targets inside `ydb/` are under `IF (NOT CMAKE_EXPORT)` clause which disables them from open-source cmake generation and ya make build. They will be enabled in the subsequent commits.
Diffstat (limited to 'library/cpp/yt/logging')
-rw-r--r--library/cpp/yt/logging/logger-inl.h303
-rw-r--r--library/cpp/yt/logging/logger.cpp289
-rw-r--r--library/cpp/yt/logging/logger.h351
-rw-r--r--library/cpp/yt/logging/public.h39
-rw-r--r--library/cpp/yt/logging/unittests/logger_ut.cpp38
-rw-r--r--library/cpp/yt/logging/unittests/ya.make18
-rw-r--r--library/cpp/yt/logging/ya.make20
7 files changed, 1058 insertions, 0 deletions
diff --git a/library/cpp/yt/logging/logger-inl.h b/library/cpp/yt/logging/logger-inl.h
new file mode 100644
index 0000000000..6f489da82d
--- /dev/null
+++ b/library/cpp/yt/logging/logger-inl.h
@@ -0,0 +1,303 @@
+#ifndef LOGGER_INL_H_
+#error "Direct inclusion of this file is not allowed, include logger.h"
+// For the sake of sane code completion.
+#include "logger.h"
+#endif
+#undef LOGGER_INL_H_
+
+#include <library/cpp/yt/yson_string/convert.h>
+#include <library/cpp/yt/yson_string/string.h>
+
+namespace NYT::NLogging {
+
+////////////////////////////////////////////////////////////////////////////////
+
+inline bool TLogger::IsAnchorUpToDate(const TLoggingAnchor& position) const
+{
+ return
+ !Category_ ||
+ position.CurrentVersion == Category_->ActualVersion->load(std::memory_order::relaxed);
+}
+
+template <class... TArgs>
+void TLogger::AddTag(const char* format, TArgs&&... args)
+{
+ AddRawTag(Format(format, std::forward<TArgs>(args)...));
+}
+
+template <class TType>
+void TLogger::AddStructuredTag(TStringBuf key, TType value)
+{
+ StructuredTags_.emplace_back(key, NYson::ConvertToYsonString(value));
+}
+
+template <class... TArgs>
+TLogger TLogger::WithTag(const char* format, TArgs&&... args) const
+{
+ auto result = *this;
+ result.AddTag(format, std::forward<TArgs>(args)...);
+ return result;
+}
+
+template <class TType>
+TLogger TLogger::WithStructuredTag(TStringBuf key, TType value) const
+{
+ auto result = *this;
+ result.AddStructuredTag(key, value);
+ return result;
+}
+
+Y_FORCE_INLINE bool TLogger::IsLevelEnabled(ELogLevel level) const
+{
+ // This is the first check which is intended to be inlined next to
+ // logging invocation point. Check below is almost zero-cost due
+ // to branch prediction (which requires inlining for proper work).
+ if (level < MinLevel_) {
+ return false;
+ }
+
+ // Next check is heavier and requires full log manager definition which
+ // is undesirable in -inl.h header file. This is why we extract it
+ // to a separate method which is implemented in cpp file.
+ return IsLevelEnabledHeavy(level);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NDetail {
+
+struct TMessageStringBuilderContext
+{
+ TSharedMutableRef Chunk;
+};
+
+struct TMessageBufferTag
+{ };
+
+class TMessageStringBuilder
+ : public TStringBuilderBase
+{
+public:
+ TSharedRef Flush();
+
+ // For testing only.
+ static void DisablePerThreadCache();
+
+protected:
+ void DoReset() override;
+ void DoReserve(size_t newLength) override;
+
+private:
+ struct TPerThreadCache
+ {
+ ~TPerThreadCache();
+
+ TSharedMutableRef Chunk;
+ size_t ChunkOffset = 0;
+ };
+
+ TSharedMutableRef Buffer_;
+
+ static thread_local TPerThreadCache* Cache_;
+ static thread_local bool CacheDestroyed_;
+ static TPerThreadCache* GetCache();
+
+ static constexpr size_t ChunkSize = 128_KB - 64;
+};
+
+inline bool HasMessageTags(
+ const TLoggingContext& loggingContext,
+ const TLogger& logger)
+{
+ if (logger.GetTag()) {
+ return true;
+ }
+ if (loggingContext.TraceLoggingTag) {
+ return true;
+ }
+ return false;
+}
+
+inline void AppendMessageTags(
+ TStringBuilderBase* builder,
+ const TLoggingContext& loggingContext,
+ const TLogger& logger)
+{
+ bool printComma = false;
+ if (const auto& loggerTag = logger.GetTag()) {
+ builder->AppendString(loggerTag);
+ printComma = true;
+ }
+ if (auto traceLoggingTag = loggingContext.TraceLoggingTag) {
+ if (printComma) {
+ builder->AppendString(TStringBuf(", "));
+ }
+ builder->AppendString(traceLoggingTag);
+ }
+}
+
+inline void AppendLogMessage(
+ TStringBuilderBase* builder,
+ const TLoggingContext& loggingContext,
+ const TLogger& logger,
+ TRef message)
+{
+ if (HasMessageTags(loggingContext, logger)) {
+ if (message.Size() >= 1 && message[message.Size() - 1] == ')') {
+ builder->AppendString(TStringBuf(message.Begin(), message.Size() - 1));
+ builder->AppendString(TStringBuf(", "));
+ } else {
+ builder->AppendString(TStringBuf(message.Begin(), message.Size()));
+ builder->AppendString(TStringBuf(" ("));
+ }
+ AppendMessageTags(builder, loggingContext, logger);
+ builder->AppendChar(')');
+ } else {
+ builder->AppendString(TStringBuf(message.Begin(), message.Size()));
+ }
+}
+
+template <class... TArgs>
+void AppendLogMessageWithFormat(
+ TStringBuilderBase* builder,
+ const TLoggingContext& loggingContext,
+ const TLogger& logger,
+ TStringBuf format,
+ TArgs&&... args)
+{
+ if (HasMessageTags(loggingContext, logger)) {
+ if (format.size() >= 2 && format[format.size() - 1] == ')') {
+ builder->AppendFormat(format.substr(0, format.size() - 1), std::forward<TArgs>(args)...);
+ builder->AppendString(TStringBuf(", "));
+ } else {
+ builder->AppendFormat(format, std::forward<TArgs>(args)...);
+ builder->AppendString(TStringBuf(" ("));
+ }
+ AppendMessageTags(builder, loggingContext, logger);
+ builder->AppendChar(')');
+ } else {
+ builder->AppendFormat(format, std::forward<TArgs>(args)...);
+ }
+}
+
+struct TLogMessage
+{
+ TSharedRef MessageRef;
+ TStringBuf Anchor;
+};
+
+template <size_t Length, class... TArgs>
+TLogMessage BuildLogMessage(
+ const TLoggingContext& loggingContext,
+ const TLogger& logger,
+ const char (&format)[Length],
+ TArgs&&... args)
+{
+ TMessageStringBuilder builder;
+ AppendLogMessageWithFormat(&builder, loggingContext, logger, format, std::forward<TArgs>(args)...);
+ return {builder.Flush(), format};
+}
+
+template <class T>
+TLogMessage BuildLogMessage(
+ const TLoggingContext& loggingContext,
+ const TLogger& logger,
+ const T& obj)
+{
+ TMessageStringBuilder builder;
+ FormatValue(&builder, obj, TStringBuf());
+ if (HasMessageTags(loggingContext, logger)) {
+ builder.AppendString(TStringBuf(" ("));
+ AppendMessageTags(&builder, loggingContext, logger);
+ builder.AppendChar(')');
+ }
+ return {builder.Flush(), TStringBuf()};
+}
+
+inline TLogMessage BuildLogMessage(
+ const TLoggingContext& loggingContext,
+ const TLogger& logger,
+ TStringBuf message)
+{
+ TMessageStringBuilder builder;
+ builder.AppendString(message);
+ if (HasMessageTags(loggingContext, logger)) {
+ builder.AppendString(TStringBuf(" ("));
+ AppendMessageTags(&builder, loggingContext, logger);
+ builder.AppendChar(')');
+ }
+ return {builder.Flush(), message};
+}
+
+template <size_t Length>
+TLogMessage BuildLogMessage(
+ const TLoggingContext& loggingContext,
+ const TLogger& logger,
+ const char (&message)[Length])
+{
+ return BuildLogMessage(
+ loggingContext,
+ logger,
+ TStringBuf(message));
+}
+
+inline TLogMessage BuildLogMessage(
+ const TLoggingContext& loggingContext,
+ const TLogger& logger,
+ TSharedRef&& message)
+{
+ if (HasMessageTags(loggingContext, logger)) {
+ TMessageStringBuilder builder;
+ AppendLogMessage(&builder, loggingContext, logger, message);
+ return {builder.Flush(), TStringBuf()};
+ } else {
+ return {std::move(message), TStringBuf()};
+ }
+}
+
+inline TLogEvent CreateLogEvent(
+ const TLoggingContext& loggingContext,
+ const TLogger& logger,
+ ELogLevel level)
+{
+ TLogEvent event;
+ event.Instant = loggingContext.Instant;
+ event.Category = logger.GetCategory();
+ event.Essential = logger.IsEssential();
+ event.Level = level;
+ event.ThreadId = loggingContext.ThreadId;
+ event.ThreadName = loggingContext.ThreadName;
+ event.FiberId = loggingContext.FiberId;
+ event.TraceId = loggingContext.TraceId;
+ event.RequestId = loggingContext.RequestId;
+ return event;
+}
+
+void OnCriticalLogEvent(
+ const TLogger& logger,
+ const TLogEvent& event);
+
+inline void LogEventImpl(
+ const TLoggingContext& loggingContext,
+ const TLogger& logger,
+ ELogLevel level,
+ ::TSourceLocation sourceLocation,
+ TSharedRef message)
+{
+ auto event = CreateLogEvent(loggingContext, logger, level);
+ event.MessageKind = ELogMessageKind::Unstructured;
+ event.MessageRef = std::move(message);
+ event.Family = ELogFamily::PlainText;
+ event.SourceFile = sourceLocation.File;
+ event.SourceLine = sourceLocation.Line;
+ logger.Write(std::move(event));
+ if (Y_UNLIKELY(event.Level >= ELogLevel::Alert)) {
+ OnCriticalLogEvent(logger, event);
+ }
+}
+
+} // namespace NDetail
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NLogging
diff --git a/library/cpp/yt/logging/logger.cpp b/library/cpp/yt/logging/logger.cpp
new file mode 100644
index 0000000000..4ee5c1a01b
--- /dev/null
+++ b/library/cpp/yt/logging/logger.cpp
@@ -0,0 +1,289 @@
+#include "logger.h"
+
+#include <library/cpp/yt/assert/assert.h>
+
+#include <library/cpp/yt/cpu_clock/clock.h>
+
+#include <library/cpp/yt/misc/thread_name.h>
+
+#include <util/system/compiler.h>
+#include <util/system/thread.h>
+
+namespace NYT::NLogging {
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NDetail {
+
+void OnCriticalLogEvent(
+ const TLogger& logger,
+ const TLogEvent& event)
+{
+ if (event.Level == ELogLevel::Fatal ||
+ event.Level == ELogLevel::Alert && logger.GetAbortOnAlert())
+ {
+ fprintf(stderr, "*** Aborting on critical log event\n");
+ fwrite(event.MessageRef.begin(), 1, event.MessageRef.size(), stderr);
+ fprintf(stderr, "\n");
+ YT_ABORT();
+ }
+}
+
+TSharedRef TMessageStringBuilder::Flush()
+{
+ return Buffer_.Slice(0, GetLength());
+}
+
+void TMessageStringBuilder::DisablePerThreadCache()
+{
+ Cache_ = nullptr;
+ CacheDestroyed_ = true;
+}
+
+void TMessageStringBuilder::DoReset()
+{
+ Buffer_.Reset();
+}
+
+void TMessageStringBuilder::DoReserve(size_t newCapacity)
+{
+ auto oldLength = GetLength();
+ newCapacity = FastClp2(newCapacity);
+
+ auto newChunkSize = std::max(ChunkSize, newCapacity);
+ // Hold the old buffer until the data is copied.
+ auto oldBuffer = std::move(Buffer_);
+ auto* cache = GetCache();
+ if (Y_LIKELY(cache)) {
+ auto oldCapacity = End_ - Begin_;
+ auto deltaCapacity = newCapacity - oldCapacity;
+ if (End_ == cache->Chunk.Begin() + cache->ChunkOffset &&
+ cache->ChunkOffset + deltaCapacity <= cache->Chunk.Size())
+ {
+ // Resize inplace.
+ Buffer_ = cache->Chunk.Slice(cache->ChunkOffset - oldCapacity, cache->ChunkOffset + deltaCapacity);
+ cache->ChunkOffset += deltaCapacity;
+ End_ = Begin_ + newCapacity;
+ return;
+ }
+
+ if (Y_UNLIKELY(cache->ChunkOffset + newCapacity > cache->Chunk.Size())) {
+ cache->Chunk = TSharedMutableRef::Allocate<TMessageBufferTag>(newChunkSize, {.InitializeStorage = false});
+ cache->ChunkOffset = 0;
+ }
+
+ Buffer_ = cache->Chunk.Slice(cache->ChunkOffset, cache->ChunkOffset + newCapacity);
+ cache->ChunkOffset += newCapacity;
+ } else {
+ Buffer_ = TSharedMutableRef::Allocate<TMessageBufferTag>(newChunkSize, {.InitializeStorage = false});
+ newCapacity = newChunkSize;
+ }
+ if (oldLength > 0) {
+ ::memcpy(Buffer_.Begin(), Begin_, oldLength);
+ }
+ Begin_ = Buffer_.Begin();
+ End_ = Begin_ + newCapacity;
+}
+
+TMessageStringBuilder::TPerThreadCache* TMessageStringBuilder::GetCache()
+{
+ if (Y_LIKELY(Cache_)) {
+ return Cache_;
+ }
+ if (CacheDestroyed_) {
+ return nullptr;
+ }
+ static thread_local TPerThreadCache Cache;
+ Cache_ = &Cache;
+ return Cache_;
+}
+
+TMessageStringBuilder::TPerThreadCache::~TPerThreadCache()
+{
+ TMessageStringBuilder::DisablePerThreadCache();
+}
+
+thread_local TMessageStringBuilder::TPerThreadCache* TMessageStringBuilder::Cache_;
+thread_local bool TMessageStringBuilder::CacheDestroyed_;
+
+} // namespace NDetail
+
+////////////////////////////////////////////////////////////////////////////////
+
+Y_WEAK TLoggingContext GetLoggingContext()
+{
+ return {
+ .Instant = GetCpuInstant(),
+ .ThreadId = TThread::CurrentThreadId(),
+ .ThreadName = GetCurrentThreadName(),
+ };
+}
+
+Y_WEAK ILogManager* GetDefaultLogManager()
+{
+ return nullptr;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+thread_local ELogLevel ThreadMinLogLevel = ELogLevel::Minimum;
+
+void SetThreadMinLogLevel(ELogLevel minLogLevel)
+{
+ ThreadMinLogLevel = minLogLevel;
+}
+
+ELogLevel GetThreadMinLogLevel()
+{
+ return ThreadMinLogLevel;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TLogger::TLogger(ILogManager* logManager, TStringBuf categoryName)
+ : LogManager_(logManager)
+ , Category_(LogManager_ ? LogManager_->GetCategory(categoryName) : nullptr)
+ , MinLevel_(LogManager_ ? LoggerDefaultMinLevel : NullLoggerMinLevel)
+{ }
+
+TLogger::TLogger(TStringBuf categoryName)
+ : TLogger(GetDefaultLogManager(), categoryName)
+{ }
+
+TLogger::operator bool() const
+{
+ return LogManager_;
+}
+
+const TLoggingCategory* TLogger::GetCategory() const
+{
+ return Category_;
+}
+
+bool TLogger::IsLevelEnabledHeavy(ELogLevel level) const
+{
+ // Note that we managed to reach this point, i.e. level >= MinLevel_,
+ // which implies that MinLevel_ != ELogLevel::Maximum, so this logger was not
+ // default constructed, thus it has non-trivial category.
+ YT_ASSERT(Category_);
+
+ if (Category_->CurrentVersion != Category_->ActualVersion->load(std::memory_order::relaxed)) {
+ LogManager_->UpdateCategory(const_cast<TLoggingCategory*>(Category_));
+ }
+
+ return
+ level >= Category_->MinPlainTextLevel &&
+ level >= ThreadMinLogLevel;
+}
+
+bool TLogger::GetAbortOnAlert() const
+{
+ return LogManager_->GetAbortOnAlert();
+}
+
+bool TLogger::IsEssential() const
+{
+ return Essential_;
+}
+
+void TLogger::UpdateAnchor(TLoggingAnchor* anchor) const
+{
+ LogManager_->UpdateAnchor(anchor);
+}
+
+void TLogger::RegisterStaticAnchor(TLoggingAnchor* anchor, ::TSourceLocation sourceLocation, TStringBuf message) const
+{
+ LogManager_->RegisterStaticAnchor(anchor, sourceLocation, message);
+}
+
+void TLogger::Write(TLogEvent&& event) const
+{
+ LogManager_->Enqueue(std::move(event));
+}
+
+void TLogger::AddRawTag(const TString& tag)
+{
+ if (!Tag_.empty()) {
+ Tag_ += ", ";
+ }
+ Tag_ += tag;
+}
+
+TLogger TLogger::WithRawTag(const TString& tag) const
+{
+ auto result = *this;
+ result.AddRawTag(tag);
+ return result;
+}
+
+TLogger TLogger::WithEssential(bool essential) const
+{
+ auto result = *this;
+ result.Essential_ = essential;
+ return result;
+}
+
+TLogger TLogger::WithStructuredValidator(TStructuredValidator validator) const
+{
+ auto result = *this;
+ result.StructuredValidators_.push_back(std::move(validator));
+ return result;
+}
+
+TLogger TLogger::WithMinLevel(ELogLevel minLevel) const
+{
+ auto result = *this;
+ if (result) {
+ result.MinLevel_ = minLevel;
+ }
+ return result;
+}
+
+const TString& TLogger::GetTag() const
+{
+ return Tag_;
+}
+
+const TLogger::TStructuredTags& TLogger::GetStructuredTags() const
+{
+ return StructuredTags_;
+}
+
+const TLogger::TStructuredValidators& TLogger::GetStructuredValidators() const
+{
+ return StructuredValidators_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void LogStructuredEvent(
+ const TLogger& logger,
+ NYson::TYsonString message,
+ ELogLevel level)
+{
+ YT_VERIFY(message.GetType() == NYson::EYsonType::MapFragment);
+
+ if (!logger.GetStructuredValidators().empty()) {
+ auto samplingRate = logger.GetCategory()->StructuredValidationSamplingRate.load();
+ auto p = RandomNumber<double>();
+ if (p < samplingRate) {
+ for (const auto& validator : logger.GetStructuredValidators()) {
+ validator(message);
+ }
+ }
+ }
+
+ auto loggingContext = GetLoggingContext();
+ auto event = NDetail::CreateLogEvent(
+ loggingContext,
+ logger,
+ level);
+ event.MessageKind = ELogMessageKind::Structured;
+ event.MessageRef = message.ToSharedRef();
+ event.Family = ELogFamily::Structured;
+ logger.Write(std::move(event));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NLogging
diff --git a/library/cpp/yt/logging/logger.h b/library/cpp/yt/logging/logger.h
new file mode 100644
index 0000000000..cdb5584d29
--- /dev/null
+++ b/library/cpp/yt/logging/logger.h
@@ -0,0 +1,351 @@
+#pragma once
+
+#include "public.h"
+
+#include <library/cpp/yt/string/format.h>
+
+#include <library/cpp/yt/memory/ref.h>
+
+#include <library/cpp/yt/cpu_clock/public.h>
+
+#include <library/cpp/yt/yson_string/string.h>
+
+#include <library/cpp/yt/misc/guid.h>
+
+#include <library/cpp/yt/misc/thread_name.h>
+
+#include <library/cpp/yt/memory/leaky_singleton.h>
+
+#include <util/system/src_location.h>
+
+#include <util/generic/size_literals.h>
+
+#include <atomic>
+
+namespace NYT::NLogging {
+
+////////////////////////////////////////////////////////////////////////////////
+
+constexpr double DefaultStructuredValidationSamplingRate = 0.01;
+
+struct TLoggingCategory
+{
+ TString Name;
+ //! This value is used for early dropping of plaintext events in order
+ //! to reduce load on logging thread for events which are definitely going
+ //! to be dropped due to rule setup.
+ //! NB: this optimization is used only for plaintext events since structured
+ //! logging rate is negligible comparing to the plaintext logging rate.
+ std::atomic<ELogLevel> MinPlainTextLevel;
+ std::atomic<int> CurrentVersion;
+ std::atomic<int>* ActualVersion;
+ std::atomic<double> StructuredValidationSamplingRate = DefaultStructuredValidationSamplingRate;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TLoggingAnchor
+{
+ std::atomic<bool> Registered = false;
+ ::TSourceLocation SourceLocation = {TStringBuf{}, 0};
+ TString AnchorMessage;
+ TLoggingAnchor* NextAnchor = nullptr;
+
+ std::atomic<int> CurrentVersion = 0;
+ std::atomic<bool> Enabled = false;
+
+ struct TCounter
+ {
+ std::atomic<i64> Current = 0;
+ i64 Previous = 0;
+ };
+
+ TCounter MessageCounter;
+ TCounter ByteCounter;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// Declare some type aliases to avoid circular dependencies.
+using TThreadId = size_t;
+using TFiberId = size_t;
+using TTraceId = TGuid;
+using TRequestId = TGuid;
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM(ELogMessageKind,
+ (Unstructured)
+ (Structured)
+);
+
+struct TLogEvent
+{
+ const TLoggingCategory* Category = nullptr;
+ ELogLevel Level = ELogLevel::Minimum;
+ ELogFamily Family = ELogFamily::PlainText;
+ bool Essential = false;
+
+ ELogMessageKind MessageKind = ELogMessageKind::Unstructured;
+ TSharedRef MessageRef;
+
+ TCpuInstant Instant = 0;
+
+ TThreadId ThreadId = {};
+ TThreadName ThreadName = {};
+
+ TFiberId FiberId = {};
+
+ TTraceId TraceId;
+ TRequestId RequestId;
+
+ TStringBuf SourceFile;
+ int SourceLine = -1;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct ILogManager
+{
+ virtual ~ILogManager() = default;
+
+ virtual void RegisterStaticAnchor(
+ TLoggingAnchor* position,
+ ::TSourceLocation sourceLocation,
+ TStringBuf anchorMessage) = 0;
+ virtual void UpdateAnchor(TLoggingAnchor* position) = 0;
+
+ virtual void Enqueue(TLogEvent&& event) = 0;
+
+ virtual const TLoggingCategory* GetCategory(TStringBuf categoryName) = 0;
+ virtual void UpdateCategory(TLoggingCategory* category) = 0;
+
+ virtual bool GetAbortOnAlert() const = 0;
+};
+
+ILogManager* GetDefaultLogManager();
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TLoggingContext
+{
+ TCpuInstant Instant;
+ TThreadId ThreadId;
+ TThreadName ThreadName;
+ TFiberId FiberId;
+ TTraceId TraceId;
+ TRequestId RequestId;
+ TStringBuf TraceLoggingTag;
+};
+
+TLoggingContext GetLoggingContext();
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Sets the minimum logging level for messages in current thread.
+// NB: In fiber environment, min log level is attached to a fiber,
+// so after context switch thread min log level might change.
+void SetThreadMinLogLevel(ELogLevel minLogLevel);
+ELogLevel GetThreadMinLogLevel();
+
+////////////////////////////////////////////////////////////////////////////////
+
+static constexpr auto NullLoggerMinLevel = ELogLevel::Maximum;
+
+// Min level for non-null logger depends on whether we are in debug or release build.
+// - For release mode default behavior is to omit trace logging,
+// this is done by setting logger min level to Debug by default.
+// - For debug mode logger min level is set to trace by default, so that trace logging is
+// allowed by logger, but still may be discarded by category min level.
+#ifdef NDEBUG
+static constexpr auto LoggerDefaultMinLevel = ELogLevel::Debug;
+#else
+static constexpr auto LoggerDefaultMinLevel = ELogLevel::Trace;
+#endif
+
+class TLogger
+{
+public:
+ using TStructuredValidator = std::function<void(const NYson::TYsonString&)>;
+ using TStructuredValidators = std::vector<TStructuredValidator>;
+
+ using TStructuredTag = std::pair<TString, NYson::TYsonString>;
+ // TODO(max42): switch to TCompactVector after YT-15430.
+ using TStructuredTags = std::vector<TStructuredTag>;
+
+ TLogger() = default;
+ TLogger(const TLogger& other) = default;
+ TLogger& operator=(const TLogger& other) = default;
+
+ TLogger(ILogManager* logManager, TStringBuf categoryName);
+ explicit TLogger(TStringBuf categoryName);
+
+ explicit operator bool() const;
+
+ const TLoggingCategory* GetCategory() const;
+
+ //! Validate that level is admitted by logger's own min level
+ //! and by category's min level.
+ bool IsLevelEnabled(ELogLevel level) const;
+
+ bool GetAbortOnAlert() const;
+
+ bool IsEssential() const;
+
+ bool IsAnchorUpToDate(const TLoggingAnchor& anchor) const;
+ void UpdateAnchor(TLoggingAnchor* anchor) const;
+ void RegisterStaticAnchor(TLoggingAnchor* anchor, ::TSourceLocation sourceLocation, TStringBuf message) const;
+
+ void Write(TLogEvent&& event) const;
+
+ void AddRawTag(const TString& tag);
+ template <class... TArgs>
+ void AddTag(const char* format, TArgs&&... args);
+
+ template <class TType>
+ void AddStructuredTag(TStringBuf key, TType value);
+
+ TLogger WithRawTag(const TString& tag) const;
+ template <class... TArgs>
+ TLogger WithTag(const char* format, TArgs&&... args) const;
+
+ template <class TType>
+ TLogger WithStructuredTag(TStringBuf key, TType value) const;
+
+ TLogger WithStructuredValidator(TStructuredValidator validator) const;
+
+ TLogger WithMinLevel(ELogLevel minLevel) const;
+
+ TLogger WithEssential(bool essential = true) const;
+
+ const TString& GetTag() const;
+ const TStructuredTags& GetStructuredTags() const;
+
+ const TStructuredValidators& GetStructuredValidators() const;
+
+protected:
+ // These fields are set only during logger creation, so they are effectively const
+ // and accessing them is thread-safe.
+ ILogManager* LogManager_ = nullptr;
+ const TLoggingCategory* Category_ = nullptr;
+ bool Essential_ = false;
+ ELogLevel MinLevel_ = NullLoggerMinLevel;
+ TString Tag_;
+ TStructuredTags StructuredTags_;
+ TStructuredValidators StructuredValidators_;
+
+private:
+ //! This method checks level against category's min level.
+ //! Refer to comment in TLogger::IsLevelEnabled for more details.
+ bool IsLevelEnabledHeavy(ELogLevel level) const;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+void LogStructuredEvent(
+ const TLogger& logger,
+ NYson::TYsonString message,
+ ELogLevel level);
+
+////////////////////////////////////////////////////////////////////////////////
+
+#ifdef YT_ENABLE_TRACE_LOGGING
+#define YT_LOG_TRACE(...) YT_LOG_EVENT(Logger, ::NYT::NLogging::ELogLevel::Trace, __VA_ARGS__)
+#define YT_LOG_TRACE_IF(condition, ...) if (condition) YT_LOG_TRACE(__VA_ARGS__)
+#define YT_LOG_TRACE_UNLESS(condition, ...) if (!(condition)) YT_LOG_TRACE(__VA_ARGS__)
+#else
+#define YT_LOG_UNUSED(...) if (true) { } else { YT_LOG_DEBUG(__VA_ARGS__); }
+#define YT_LOG_TRACE(...) YT_LOG_UNUSED(__VA_ARGS__)
+#define YT_LOG_TRACE_IF(condition, ...) YT_LOG_UNUSED(__VA_ARGS__)
+#define YT_LOG_TRACE_UNLESS(condition, ...) YT_LOG_UNUSED(__VA_ARGS__)
+#endif
+
+#define YT_LOG_DEBUG(...) YT_LOG_EVENT(Logger, ::NYT::NLogging::ELogLevel::Debug, __VA_ARGS__)
+#define YT_LOG_DEBUG_IF(condition, ...) if (condition) YT_LOG_DEBUG(__VA_ARGS__)
+#define YT_LOG_DEBUG_UNLESS(condition, ...) if (!(condition)) YT_LOG_DEBUG(__VA_ARGS__)
+
+#define YT_LOG_INFO(...) YT_LOG_EVENT(Logger, ::NYT::NLogging::ELogLevel::Info, __VA_ARGS__)
+#define YT_LOG_INFO_IF(condition, ...) if (condition) YT_LOG_INFO(__VA_ARGS__)
+#define YT_LOG_INFO_UNLESS(condition, ...) if (!(condition)) YT_LOG_INFO(__VA_ARGS__)
+
+#define YT_LOG_WARNING(...) YT_LOG_EVENT(Logger, ::NYT::NLogging::ELogLevel::Warning, __VA_ARGS__)
+#define YT_LOG_WARNING_IF(condition, ...) if (condition) YT_LOG_WARNING(__VA_ARGS__)
+#define YT_LOG_WARNING_UNLESS(condition, ...) if (!(condition)) YT_LOG_WARNING(__VA_ARGS__)
+
+#define YT_LOG_ERROR(...) YT_LOG_EVENT(Logger, ::NYT::NLogging::ELogLevel::Error, __VA_ARGS__)
+#define YT_LOG_ERROR_IF(condition, ...) if (condition) YT_LOG_ERROR(__VA_ARGS__)
+#define YT_LOG_ERROR_UNLESS(condition, ...) if (!(condition)) YT_LOG_ERROR(__VA_ARGS__)
+
+#define YT_LOG_ALERT(...) YT_LOG_EVENT(Logger, ::NYT::NLogging::ELogLevel::Alert, __VA_ARGS__);
+#define YT_LOG_ALERT_IF(condition, ...) if (condition) YT_LOG_ALERT(__VA_ARGS__)
+#define YT_LOG_ALERT_UNLESS(condition, ...) if (!(condition)) YT_LOG_ALERT(__VA_ARGS__)
+
+#define YT_LOG_FATAL(...) \
+ do { \
+ YT_LOG_EVENT(Logger, ::NYT::NLogging::ELogLevel::Fatal, __VA_ARGS__); \
+ Y_UNREACHABLE(); \
+ } while(false)
+#define YT_LOG_FATAL_IF(condition, ...) if (Y_UNLIKELY(condition)) YT_LOG_FATAL(__VA_ARGS__)
+#define YT_LOG_FATAL_UNLESS(condition, ...) if (!Y_LIKELY(condition)) YT_LOG_FATAL(__VA_ARGS__)
+
+#define YT_LOG_EVENT(logger, level, ...) \
+ YT_LOG_EVENT_WITH_ANCHOR(logger, level, nullptr, __VA_ARGS__)
+
+#define YT_LOG_EVENT_WITH_ANCHOR(logger, level, anchor, ...) \
+ do { \
+ const auto& logger__##__LINE__ = (logger); \
+ auto level__##__LINE__ = (level); \
+ \
+ if (!logger__##__LINE__.IsLevelEnabled(level__##__LINE__)) { \
+ break; \
+ } \
+ \
+ auto location__##__LINE__ = __LOCATION__; \
+ \
+ ::NYT::NLogging::TLoggingAnchor* anchor__##__LINE__ = (anchor); \
+ if (!anchor__##__LINE__) { \
+ static ::NYT::TLeakyStorage<::NYT::NLogging::TLoggingAnchor> staticAnchor__##__LINE__; \
+ anchor__##__LINE__ = staticAnchor__##__LINE__.Get(); \
+ } \
+ \
+ bool anchorUpToDate__##__LINE__ = logger__##__LINE__.IsAnchorUpToDate(*anchor__##__LINE__); \
+ if (anchorUpToDate__##__LINE__ && !anchor__##__LINE__->Enabled.load(std::memory_order::relaxed)) { \
+ break; \
+ } \
+ \
+ auto loggingContext__##__LINE__ = ::NYT::NLogging::GetLoggingContext(); \
+ auto message__##__LINE__ = ::NYT::NLogging::NDetail::BuildLogMessage(loggingContext__##__LINE__, logger__##__LINE__, __VA_ARGS__); \
+ \
+ if (!anchorUpToDate__##__LINE__) { \
+ logger__##__LINE__.RegisterStaticAnchor(anchor__##__LINE__, location__##__LINE__, message__##__LINE__.Anchor); \
+ logger__##__LINE__.UpdateAnchor(anchor__##__LINE__); \
+ } \
+ \
+ if (!anchor__##__LINE__->Enabled.load(std::memory_order::relaxed)) { \
+ break; \
+ } \
+ \
+ static thread_local i64 localByteCounter__##__LINE__; \
+ static thread_local ui8 localMessageCounter__##__LINE__; \
+ \
+ localByteCounter__##__LINE__ += message__##__LINE__.MessageRef.Size(); \
+ if (Y_UNLIKELY(++localMessageCounter__##__LINE__ == 0)) { \
+ anchor__##__LINE__->MessageCounter.Current += 256; \
+ anchor__##__LINE__->ByteCounter.Current += localByteCounter__##__LINE__; \
+ localByteCounter__##__LINE__ = 0; \
+ } \
+ \
+ ::NYT::NLogging::NDetail::LogEventImpl( \
+ loggingContext__##__LINE__, \
+ logger__##__LINE__, \
+ level__##__LINE__, \
+ location__##__LINE__, \
+ std::move(message__##__LINE__.MessageRef)); \
+ } while (false)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NLogging
+
+#define LOGGER_INL_H_
+#include "logger-inl.h"
+#undef LOGGER_INL_H_
diff --git a/library/cpp/yt/logging/public.h b/library/cpp/yt/logging/public.h
new file mode 100644
index 0000000000..1e2b59ca0d
--- /dev/null
+++ b/library/cpp/yt/logging/public.h
@@ -0,0 +1,39 @@
+#pragma once
+
+#include <library/cpp/yt/misc/enum.h>
+
+namespace NYT::NLogging {
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Any change to this enum must be also propagated to FormatLevel.
+DEFINE_ENUM(ELogLevel,
+ (Minimum)
+ (Trace)
+ (Debug)
+ (Info)
+ (Warning)
+ (Error)
+ (Alert)
+ (Fatal)
+ (Maximum)
+);
+
+DEFINE_ENUM(ELogFamily,
+ (PlainText)
+ (Structured)
+);
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TLoggingCategory;
+struct TLoggingAnchor;
+struct TLogEvent;
+struct TLoggingContext;
+
+class TLogger;
+struct ILogManager;
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NLogging
diff --git a/library/cpp/yt/logging/unittests/logger_ut.cpp b/library/cpp/yt/logging/unittests/logger_ut.cpp
new file mode 100644
index 0000000000..7696ea4a83
--- /dev/null
+++ b/library/cpp/yt/logging/unittests/logger_ut.cpp
@@ -0,0 +1,38 @@
+#include <library/cpp/testing/gtest/gtest.h>
+
+#include <library/cpp/yt/logging/logger.h>
+
+namespace NYT::NLogging {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TLogger, NullByDefault)
+{
+ {
+ TLogger logger;
+ EXPECT_FALSE(logger);
+ EXPECT_FALSE(logger.IsLevelEnabled(ELogLevel::Fatal));
+ }
+ {
+ TLogger logger{"Category"};
+ EXPECT_FALSE(logger);
+ EXPECT_FALSE(logger.IsLevelEnabled(ELogLevel::Fatal));
+ }
+}
+
+TEST(TLogger, CopyOfNullLogger)
+{
+ TLogger nullLogger{/*logManager*/ nullptr, "Category"};
+ ASSERT_FALSE(nullLogger);
+
+ auto logger = nullLogger.WithMinLevel(ELogLevel::Debug);
+
+ EXPECT_FALSE(logger);
+ EXPECT_FALSE(logger.IsLevelEnabled(ELogLevel::Fatal));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NLogging
diff --git a/library/cpp/yt/logging/unittests/ya.make b/library/cpp/yt/logging/unittests/ya.make
new file mode 100644
index 0000000000..42268d3db2
--- /dev/null
+++ b/library/cpp/yt/logging/unittests/ya.make
@@ -0,0 +1,18 @@
+GTEST(unittester-library-logging)
+
+INCLUDE(${ARCADIA_ROOT}/library/cpp/yt/ya_cpp.make.inc)
+
+IF (NOT OS_WINDOWS)
+ ALLOCATOR(YT)
+ENDIF()
+
+SRCS(
+ logger_ut.cpp
+)
+
+PEERDIR(
+ library/cpp/testing/gtest
+ library/cpp/yt/logging
+)
+
+END()
diff --git a/library/cpp/yt/logging/ya.make b/library/cpp/yt/logging/ya.make
new file mode 100644
index 0000000000..cf629a24b6
--- /dev/null
+++ b/library/cpp/yt/logging/ya.make
@@ -0,0 +1,20 @@
+LIBRARY()
+
+INCLUDE(${ARCADIA_ROOT}/library/cpp/yt/ya_cpp.make.inc)
+
+SRCS(
+ logger.cpp
+)
+
+PEERDIR(
+ library/cpp/yt/assert
+ library/cpp/yt/memory
+ library/cpp/yt/misc
+ library/cpp/yt/yson_string
+)
+
+END()
+
+RECURSE_FOR_TESTS(
+ unittests
+)