From 9fa3cbfcdeb49f0c7660d47f054fdad931bac383 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Wed, 13 Dec 2023 23:16:10 -0800 Subject: [PATCH] add a way to set a log consumer this is a private API to capture filament's logs. --- libs/utils/include/utils/ostream.h | 7 +++++++ libs/utils/src/Log.cpp | 22 +++++++++++++++------- libs/utils/src/ostream.cpp | 27 +++++++++++++++++++++++++++ libs/utils/src/ostream_.h | 1 + libs/utils/test/test_sstream.cpp | 25 +++++++++++++++++++++++++ 5 files changed, 75 insertions(+), 7 deletions(-) diff --git a/libs/utils/include/utils/ostream.h b/libs/utils/include/utils/ostream.h index c8308578df..d4bf80c360 100644 --- a/libs/utils/include/utils/ostream.h +++ b/libs/utils/include/utils/ostream.h @@ -69,6 +69,13 @@ public: ostream& dec() noexcept; ostream& hex() noexcept; + /*! @cond PRIVATE */ + // Sets a consumer of the log. The consumer is invoked on flush() and replaces the default. + // Thread safe and reentrant. + using ConsumerCallback = void(*)(void*, char const*); + void setConsumer(ConsumerCallback consumer, void* user) noexcept; + /*! @endcond */ + protected: ostream& print(const char* format, ...) noexcept; diff --git a/libs/utils/src/Log.cpp b/libs/utils/src/Log.cpp index be52c2acde..86eaa5531c 100644 --- a/libs/utils/src/Log.cpp +++ b/libs/utils/src/Log.cpp @@ -31,6 +31,8 @@ #include #endif +#include + namespace utils { namespace io { @@ -52,6 +54,12 @@ private: ostream& LogStream::flush() noexcept { std::lock_guard const lock(mImpl->mLock); Buffer& buf = getBuffer(); + char const* const str = buf.get(); + if (UTILS_UNLIKELY(!str)) { + // this can happen if the log hasn't been written ever + return *this; + } + #ifdef __ANDROID__ int prio = ANDROID_LOG_UNKNOWN; switch (mPriority) { @@ -61,20 +69,20 @@ ostream& LogStream::flush() noexcept { case LOG_INFO: prio = ANDROID_LOG_INFO; break; case LOG_VERBOSE: prio = ANDROID_LOG_VERBOSE; break; } - __android_log_write(prio, UTILS_LOG_TAG, buf.get()); + __android_log_write(prio, UTILS_LOG_TAG, str); #elif defined(__EMSCRIPTEN__) switch (mPriority) { case LOG_DEBUG: case LOG_WARNING: case LOG_INFO: - _emscripten_out(buf.get()); + _emscripten_out(str); break; case LOG_ERROR: - _emscripten_err(buf.get()); + _emscripten_err(str); break; case LOG_VERBOSE: #ifndef NFIL_DEBUG - _emscripten_out(buf.get()); + _emscripten_out(str); #endif break; } @@ -83,14 +91,14 @@ ostream& LogStream::flush() noexcept { case LOG_DEBUG: case LOG_WARNING: case LOG_INFO: - fprintf(stdout, "%s", buf.get()); + fprintf(stdout, "%s", str); break; case LOG_ERROR: - fprintf(stderr, "%s", buf.get()); + fprintf(stderr, "%s", str); break; case LOG_VERBOSE: #ifndef NDEBUG - fprintf(stdout, "%s", buf.get()); + fprintf(stdout, "%s", str); #endif break; } diff --git a/libs/utils/src/ostream.cpp b/libs/utils/src/ostream.cpp index 32904c0e51..5b0fb4781c 100644 --- a/libs/utils/src/ostream.cpp +++ b/libs/utils/src/ostream.cpp @@ -24,9 +24,12 @@ #include #include +#include #include #include +#include +#include #include template class utils::PrivateImplementation; @@ -35,7 +38,31 @@ namespace utils::io { ostream::~ostream() = default; +void ostream::setConsumer(ConsumerCallback consumer, void* user) noexcept { + auto* const pImpl = mImpl; + std::lock_guard const lock(pImpl->mLock); + pImpl->mConsumer = { consumer, user }; +} + ostream& flush(ostream& s) noexcept { + auto* const pImpl = s.mImpl; + pImpl->mLock.lock(); + auto const callback = pImpl->mConsumer; + if (UTILS_UNLIKELY(callback.first)) { + auto& buf = s.getBuffer(); + char const* const data = buf.get(); + if (UTILS_LIKELY(data)) { + char* const str = strdup(data); + buf.reset(); + pImpl->mLock.unlock(); + // call ConsumerCallback without lock held + callback.first(callback.second, str); + ::free(str); + return s; + } + } + pImpl->mLock.unlock(); + return s.flush(); } diff --git a/libs/utils/src/ostream_.h b/libs/utils/src/ostream_.h index a417cde70b..19f2957287 100644 --- a/libs/utils/src/ostream_.h +++ b/libs/utils/src/ostream_.h @@ -25,6 +25,7 @@ namespace utils::io { struct ostream_ { std::mutex mLock; ostream::Buffer mData; + std::pair mConsumer{}; bool mShowHex = false; }; diff --git a/libs/utils/test/test_sstream.cpp b/libs/utils/test/test_sstream.cpp index 9886f9d96e..fbac624279 100644 --- a/libs/utils/test/test_sstream.cpp +++ b/libs/utils/test/test_sstream.cpp @@ -18,8 +18,33 @@ #include +#include + +using namespace utils; using namespace utils::io; +TEST(ostream, setConsumer) { + slog.d.setConsumer(+[](void*, char const*) { + GTEST_FAIL(); + }, nullptr); + // we test that we don't crash if the log is empty and that we don't call the consumer. + flush(slog.d); + + slog.d.setConsumer(nullptr, nullptr); + slog.d << "hello world"; + // we test that after resetting the consumer, it's not called on flush. + flush(slog.d); + + const char* str = "hello world!"; + slog.d.setConsumer(+[](void* user, char const* str) { + ASSERT_STREQ(str, (const char*)user); + }, (void*)str); + slog.d << str; + // we test that the comsumer is called with the right string + flush(slog.d); + slog.d.setConsumer(nullptr, nullptr); +} + TEST(sstream, EmptyString) { sstream ss; EXPECT_STREQ("", ss.c_str());