Skip to content

Commit

Permalink
add a way to set a log consumer
Browse files Browse the repository at this point in the history
this is a private API to capture filament's logs.
  • Loading branch information
pixelflinger authored and bejado committed Dec 19, 2023
1 parent c81ece5 commit 9fa3cbf
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 7 deletions.
7 changes: 7 additions & 0 deletions libs/utils/include/utils/ostream.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ class UTILS_PUBLIC ostream : protected utils::PrivateImplementation<ostream_> {
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;

Expand Down
22 changes: 15 additions & 7 deletions libs/utils/src/Log.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
#include <emscripten/console.h>
#endif

#include <mutex>

namespace utils {
namespace io {

Expand All @@ -52,6 +54,12 @@ class LogStream : public ostream {
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) {
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand Down
27 changes: 27 additions & 0 deletions libs/utils/src/ostream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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_>;
Expand All @@ -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();
}

Expand Down
1 change: 1 addition & 0 deletions libs/utils/src/ostream_.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ namespace utils::io {
struct ostream_ {
std::mutex mLock;
ostream::Buffer mData;
std::pair<ostream::ConsumerCallback, void*> mConsumer{};
bool mShowHex = false;
};

Expand Down
25 changes: 25 additions & 0 deletions libs/utils/test/test_sstream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down

0 comments on commit 9fa3cbf

Please sign in to comment.