aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2023-05-09 14:39:01 +0200
committerGitHub <[email protected]>2023-05-09 14:39:01 +0200
commitf384c90daca485255c19dfda90939a76a121c530 (patch)
tree84473505995798108c4678694d946eba9d130b61 /src
parentadd ip and username to sentry reports if allowed in settings (#276) (diff)
downloadzen-f384c90daca485255c19dfda90939a76a121c530.tar.xz
zen-f384c90daca485255c19dfda90939a76a121c530.zip
implemented thread-local activity tracking
includes support for on-demand formatting of scope in error messages
Diffstat (limited to 'src')
-rw-r--r--src/zencore/include/zencore/logging.h83
-rw-r--r--src/zencore/logging.cpp111
2 files changed, 194 insertions, 0 deletions
diff --git a/src/zencore/include/zencore/logging.h b/src/zencore/include/zencore/logging.h
index 5cbe034cf..92db1b35b 100644
--- a/src/zencore/include/zencore/logging.h
+++ b/src/zencore/include/zencore/logging.h
@@ -134,3 +134,86 @@ LogIsErrorLevel(int level)
using namespace std::literals; \
ConsoleLog().info(fmtstr##sv, ##__VA_ARGS__); \
} while (false)
+
+//////////////////////////////////////////////////////////////////////////
+
+namespace zen {
+
+class StringBuilderBase;
+
+/** Thread-local diagnostics scope stack
+
+ Intended to be used in logging and such to provide context on demand.
+ A subclass should do as little work as possible at construction time
+ and ideally only do something when the Emit function is called.
+ */
+class ScopedActivityBase
+{
+public:
+ ScopedActivityBase();
+ virtual ~ScopedActivityBase();
+ virtual void Emit(StringBuilderBase& Target) = 0;
+
+ inline ScopedActivityBase* GetNext() { return m_NextScope; }
+
+private:
+ ScopedActivityBase* m_NextScope;
+};
+
+class ScopedActivityString : public ScopedActivityBase
+{
+public:
+ inline ScopedActivityString(const std::string_view& Text) : m_Text{Text} {}
+ virtual ~ScopedActivityString();
+ virtual void Emit(StringBuilderBase& Target) override;
+
+private:
+ const std::string_view m_Text;
+};
+
+template<typename Lambda>
+class ScopedLazyActivity : public ScopedActivityBase
+{
+public:
+ ScopedLazyActivity(Lambda&& InFunc) : m_CapturedFunc(std::move(InFunc)) {}
+ ~ScopedLazyActivity() = default;
+
+ virtual void Emit(StringBuilderBase& Target) override { m_CapturedFunc(Target); }
+
+private:
+ const Lambda m_CapturedFunc;
+};
+
+std::string_view EmitScopesForLogging(StringBuilderBase& OutString);
+
+#define ZEN_LOG_SCOPE(...) ScopedLazyActivity Activity$##__LINE__([&](StringBuilderBase& Out) { Out << fmt::format(__VA_ARGS__); })
+
+#define ZEN_SCOPED_ERROR(fmtstr, ...) \
+ do \
+ { \
+ ExtendableStringBuilder<256> ScopeString; \
+ const std::string_view Scopes = EmitScopesForLogging(ScopeString); \
+ ZEN_LOG_WITH_LOCATION(Log(), \
+ spdlog::source_loc(__FILE__, __LINE__, SPDLOG_FUNCTION), \
+ spdlog::level::err, \
+ fmtstr "{}"sv, \
+ ##__VA_ARGS__, \
+ Scopes); \
+ } while (false)
+
+#define ZEN_SCOPED_CRITICAL(fmtstr, ...) \
+ do \
+ { \
+ ExtendableStringBuilder<256> ScopeString; \
+ const std::string_view Scopes = EmitScopesForLogging(ScopeString); \
+ ZEN_LOG_WITH_LOCATION(Log(), \
+ spdlog::source_loc(__FILE__, __LINE__, SPDLOG_FUNCTION), \
+ spdlog::level::critical, \
+ fmtstr "{}"sv, \
+ ##__VA_ARGS__, \
+ Scopes); \
+ } while (false)
+
+ScopedActivityBase* GetThreadActivity();
+
+} // namespace zen
diff --git a/src/zencore/logging.cpp b/src/zencore/logging.cpp
index a6423e2dc..13a8ce9ef 100644
--- a/src/zencore/logging.cpp
+++ b/src/zencore/logging.cpp
@@ -2,6 +2,9 @@
#include "zencore/logging.h"
+#include <zencore/string.h>
+#include <zencore/testing.h>
+
#include <spdlog/sinks/stdout_color_sinks.h>
namespace zen {
@@ -83,3 +86,111 @@ ShutdownLogging()
}
} // namespace zen::logging
+
+namespace zen {
+
+thread_local ScopedActivityBase* t_ScopeStack = nullptr;
+
+ScopedActivityBase*
+GetThreadActivity()
+{
+ return t_ScopeStack;
+}
+
+ScopedActivityBase::ScopedActivityBase() : m_NextScope{t_ScopeStack}
+{
+ t_ScopeStack = this;
+}
+
+ScopedActivityBase::~ScopedActivityBase()
+{
+ ZEN_ASSERT(t_ScopeStack == this);
+ t_ScopeStack = m_NextScope;
+}
+
+std::string_view
+EmitActivitiesForLogging(StringBuilderBase& OutString)
+{
+ OutString.Reset();
+
+ for (auto Bread = GetThreadActivity(); Bread; Bread = Bread->GetNext())
+ {
+ OutString.Append("\n|>|>|> ");
+ Bread->Emit(OutString);
+ }
+
+ return OutString.ToView();
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+ScopedActivityString::~ScopedActivityString()
+{
+}
+
+void
+ScopedActivityString::Emit(StringBuilderBase& Target)
+{
+ Target.Append(m_Text);
+}
+
+#if ZEN_WITH_TESTS
+
+void
+logging_forcelink()
+{
+}
+
+using namespace std::literals;
+
+TEST_CASE("simple.bread")
+{
+ ExtendableStringBuilder<256> Crumbs;
+
+ auto EmitBreadcrumbs = [&] {
+ Crumbs.Reset();
+
+ for (auto Bread = GetThreadActivity(); Bread; Bread = Bread->GetNext())
+ {
+ Bread->Emit(Crumbs);
+ }
+
+ return Crumbs.ToView();
+ };
+
+ SUBCASE("single")
+ {
+ ScopedActivityString _{"hello"};
+ EmitBreadcrumbs();
+
+ CHECK_EQ(Crumbs.ToView(), "hello"sv);
+ }
+
+ SUBCASE("multi")
+ {
+ ScopedActivityString $1{"hello"};
+ ScopedActivityString $2{"world"};
+ ScopedActivityString $3{"amaze"};
+
+ CHECK_EQ(EmitBreadcrumbs(), "amazeworldhello"sv);
+ }
+
+ SUBCASE("multi_defer")
+ {
+ int n = 42;
+ ScopedActivityString $1{"hello"};
+ ScopedActivityString $2{"world"};
+ ScopedActivityString $3{"amaze"};
+ ScopedLazyActivity $4{[&](StringBuilderBase& Out) { Out << "plant"; }};
+ {
+ ScopedLazyActivity $5{[&](StringBuilderBase& Out) { Out << n; }};
+ ZEN_LOG_SCOPE("{}:{}", "abc", "def");
+ CHECK_EQ(EmitBreadcrumbs(), "abc:def42plantamazeworldhello"sv);
+ }
+ CHECK_EQ(EmitBreadcrumbs(), "amazeworldhello"sv);
+ }
+}
+
+#endif
+
+} // namespace zen