add a way to set a log consumer

this is a private API to capture filament's logs.
This commit is contained in:
Mathias Agopian
2023-12-13 23:16:10 -08:00
committed by Benjamin Doherty
parent c81ece5c3c
commit 9fa3cbfcde
5 changed files with 75 additions and 7 deletions

View File

@@ -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;

View File

@@ -31,6 +31,8 @@
#include <emscripten/console.h>
#endif
#include <mutex>
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;
}

View File

@@ -24,9 +24,12 @@
#include <utils/PrivateImplementation-impl.h>
#include <algorithm>
#include <mutex>
#include <string>
#include <string_view>
#include <utility>
#include <stdlib.h>
#include <stdarg.h>
template class utils::PrivateImplementation<utils::io::ostream_>;
@@ -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();
}

View File

@@ -25,6 +25,7 @@ namespace utils::io {
struct ostream_ {
std::mutex mLock;
ostream::Buffer mData;
std::pair<ostream::ConsumerCallback, void*> mConsumer{};
bool mShowHex = false;
};

View File

@@ -18,8 +18,33 @@
#include <utils/sstream.h>
#include <utils/Log.h>
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());