Add Logger observer and helper class
authoreric.carlson@apple.com <eric.carlson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 30 Aug 2017 19:06:49 +0000 (19:06 +0000)
committereric.carlson@apple.com <eric.carlson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 30 Aug 2017 19:06:49 +0000 (19:06 +0000)
https://bugs.webkit.org/show_bug.cgi?id=176106

Reviewed by Andy Estes.

Source/WebCore/PAL:

* pal/Logger.h:
(PAL::LogArgument::toString): Add new variants.
(PAL::Logger::Observer::~Observer):
(PAL::Logger::logAlways const): Constify.
(PAL::Logger::error const): Ditto.
(PAL::Logger::warning const): Ditto.
(PAL::Logger::notice const): Ditto.
(PAL::Logger::info const): Ditto.
(PAL::Logger::debug const): Ditto.
(PAL::Logger::willLog const): Ditto.
(PAL::Logger::MethodAndPointer::MethodAndPointer): Add class name, make object pointer const.
(PAL::Logger::addObserver): New.
(PAL::Logger::removeObserver): New.
(PAL::Logger::Logger):
(PAL::Logger::log):
(PAL::Logger::observers):
(PAL::LogHelper::willLog const):
(PAL::LogArgument<Logger::MethodAndPointer>::toString):

Tools:

* TestWebKitAPI/Tests/WebCore/Logging.cpp:
(TestWebKitAPI::LoggingTest::LoggingTest):
(TestWebKitAPI::LoggingTest::Logger::create):
(TestWebKitAPI::TEST_F):
(TestWebKitAPI::LogObserver::log):
(TestWebKitAPI::LogObserver::channel const):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@221388 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebCore/PAL/ChangeLog
Source/WebCore/PAL/pal/Logger.h
Tools/ChangeLog
Tools/TestWebKitAPI/Tests/WebCore/Logging.cpp

index 7b4b601..c2ceb94 100644 (file)
@@ -1,3 +1,29 @@
+2017-08-30  Eric Carlson  <eric.carlson@apple.com>
+
+        Add Logger observer and helper class
+        https://bugs.webkit.org/show_bug.cgi?id=176106
+
+        Reviewed by Andy Estes.
+
+        * pal/Logger.h:
+        (PAL::LogArgument::toString): Add new variants.
+        (PAL::Logger::Observer::~Observer):
+        (PAL::Logger::logAlways const): Constify.
+        (PAL::Logger::error const): Ditto.
+        (PAL::Logger::warning const): Ditto.
+        (PAL::Logger::notice const): Ditto.
+        (PAL::Logger::info const): Ditto.
+        (PAL::Logger::debug const): Ditto.
+        (PAL::Logger::willLog const): Ditto.
+        (PAL::Logger::MethodAndPointer::MethodAndPointer): Add class name, make object pointer const.
+        (PAL::Logger::addObserver): New.
+        (PAL::Logger::removeObserver): New.
+        (PAL::Logger::Logger):
+        (PAL::Logger::log):
+        (PAL::Logger::observers):
+        (PAL::LogHelper::willLog const):
+        (PAL::LogArgument<Logger::MethodAndPointer>::toString):
+
 2017-08-30  Andy Estes  <aestes@apple.com>
 
         [Mac] Upstream Accessibility-related WebKitSystemInterface functions
index da37d43..47b91c7 100644 (file)
@@ -37,10 +37,12 @@ namespace PAL {
 
 template<typename T>
 struct LogArgument {
+    template<typename U = T> static typename std::enable_if<std::is_same<U, bool>::value, String>::type toString(bool argument) { return argument ? ASCIILiteral("true") : ASCIILiteral("false"); }
     template<typename U = T> static typename std::enable_if<std::is_same<U, int>::value, String>::type toString(int argument) { return String::number(argument); }
     template<typename U = T> static typename std::enable_if<std::is_same<U, float>::value, String>::type toString(float argument) { return String::number(argument); }
     template<typename U = T> static typename std::enable_if<std::is_same<U, double>::value, String>::type toString(double argument) { return String::number(argument); }
-    template<typename U = T> static typename std::enable_if<std::is_same<U, String>::value, String>::type toString(String argument) { return argument; }
+    template<typename U = T> static typename std::enable_if<std::is_same<typename std::remove_reference<U>::type, AtomicString>::value, String>::type toString(AtomicString argument) { return argument.string(); }
+    template<typename U = T> static typename std::enable_if<std::is_same<typename std::remove_reference<U>::type, String>::value, String>::type toString(String argument) { return argument; }
     template<typename U = T> static typename std::enable_if<std::is_same<U, const char*>::value, String>::type toString(const char* argument) { return String(argument); }
     template<size_t length> static String toString(const char (&argument)[length]) { return String(argument); }
 };
@@ -48,13 +50,20 @@ struct LogArgument {
 class Logger : public RefCounted<Logger> {
     WTF_MAKE_NONCOPYABLE(Logger);
 public:
+
+    class Observer {
+    public:
+        virtual ~Observer() { }
+        virtual void didLogMessage(const WTFLogChannel&, const String&) = 0;
+    };
+
     static Ref<Logger> create(const void* owner)
     {
         return adoptRef(*new Logger(owner));
     }
 
     template<typename... Arguments>
-    inline void logAlways(WTFLogChannel& channel, const Arguments&... arguments)
+    inline void logAlways(WTFLogChannel& channel, const Arguments&... arguments) const
     {
 #if RELEASE_LOG_DISABLED
         // "Standard" WebCore logging goes to stderr, which is captured in layout test output and can generally be a problem
@@ -69,7 +78,7 @@ public:
     }
 
     template<typename... Arguments>
-    inline void error(WTFLogChannel& channel, const Arguments&... arguments)
+    inline void error(WTFLogChannel& channel, const Arguments&... arguments) const
     {
         if (!willLog(channel, WTFLogLevelError))
             return;
@@ -78,7 +87,7 @@ public:
     }
 
     template<typename... Arguments>
-    inline void warning(WTFLogChannel& channel, const Arguments&... arguments)
+    inline void warning(WTFLogChannel& channel, const Arguments&... arguments) const
     {
         if (!willLog(channel, WTFLogLevelWarning))
             return;
@@ -87,7 +96,7 @@ public:
     }
 
     template<typename... Arguments>
-    inline void notice(WTFLogChannel& channel, const Arguments&... arguments)
+    inline void notice(WTFLogChannel& channel, const Arguments&... arguments) const
     {
         if (!willLog(channel, WTFLogLevelNotice))
             return;
@@ -96,7 +105,7 @@ public:
     }
 
     template<typename... Arguments>
-    inline void info(WTFLogChannel& channel, const Arguments&... arguments)
+    inline void info(WTFLogChannel& channel, const Arguments&... arguments) const
     {
         if (!willLog(channel, WTFLogLevelInfo))
             return;
@@ -105,7 +114,7 @@ public:
     }
 
     template<typename... Arguments>
-    inline void debug(WTFLogChannel& channel, const Arguments&... arguments)
+    inline void debug(WTFLogChannel& channel, const Arguments&... arguments) const
     {
         if (!willLog(channel, WTFLogLevelDebug))
             return;
@@ -113,7 +122,7 @@ public:
         log(channel, arguments...);
     }
 
-    inline bool willLog(WTFLogChannel& channel, WTFLogLevel level) const
+    inline bool willLog(const WTFLogChannel& channel, WTFLogLevel level) const
     {
         if (level != WTFLogLevelAlways && level > channel.level)
             return false;
@@ -133,40 +142,95 @@ public:
     }
 
     struct MethodAndPointer {
-        MethodAndPointer(const char* methodName, void* objectPtr)
-            : methodName(methodName)
-            , objectPtr(reinterpret_cast<uintptr_t>(objectPtr))
+        MethodAndPointer(const char* methodName, const void* objectPtr)
+            : methodName { methodName }
+            , objectPtr { reinterpret_cast<uintptr_t>(objectPtr) }
         {
         }
 
-        const char* methodName;
-        uintptr_t objectPtr;
+        MethodAndPointer(const char* className, const char* methodName, const void* objectPtr)
+            : className { className }
+            , methodName { methodName }
+            , objectPtr { reinterpret_cast<uintptr_t>(objectPtr) }
+        {
+        }
+
+        const char* className { nullptr };
+        const char* methodName { nullptr };
+        const uintptr_t objectPtr { 0 };
     };
 
+    static inline void addObserver(Observer& observer)
+    {
+        observers().append(observer);
+    }
+    static inline void removeObserver(Observer& observer)
+    {
+        observers().removeFirstMatching([&observer](auto anObserver) {
+            return &anObserver.get() == &observer;
+        });
+    }
+
 private:
-    Logger(const void* owner) { m_owner = owner; }
+    Logger(const void* owner)
+        : m_owner { owner }
+    {
+    }
 
     template<typename... Argument>
     static inline void log(WTFLogChannel& channel, const Argument&... arguments)
     {
-        String string = makeString(LogArgument<Argument>::toString(arguments)...);
+        String logMessage = makeString(LogArgument<Argument>::toString(arguments)...);
 
 #if RELEASE_LOG_DISABLED
-        WTFLog(&channel, "%s", string.utf8().data());
+        WTFLog(&channel, "%s", logMessage.utf8().data());
 #else
-        os_log(channel.osLogChannel, "%{public}s", string.utf8().data());
+        os_log(channel.osLogChannel, "%{public}s", logMessage.utf8().data());
 #endif
+
+        for (Observer& observer : observers())
+            observer.didLogMessage(channel, logMessage);
+    }
+
+    static Vector<std::reference_wrapper<Observer>>& observers()
+    {
+        static NeverDestroyed<Vector<std::reference_wrapper<Observer>>> observers;
+        return observers;
     }
 
     const void* m_owner;
     bool m_enabled { true };
 };
 
+class LogHelper {
+public:
+    virtual ~LogHelper() = default;
+
+    virtual const Logger& logger() const = 0;
+    virtual const char* className() const = 0;
+    virtual WTFLogChannel& logChannel() const = 0;
+
+    inline bool willLog(WTFLogLevel level) const { return logger().willLog(logChannel(), level); }
+
+#define ALWAYS_LOG(...)     logger().logAlways(logChannel(), Logger::MethodAndPointer(className(), __func__, this), ##__VA_ARGS__)
+#define ERROR_LOG(...)      logger().error(logChannel(), Logger::MethodAndPointer(className(), __func__, this), ##__VA_ARGS__)
+#define WARNING_LOG(...)    logger().warning(logChannel(), Logger::MethodAndPointer(className(), __func__, this), ##__VA_ARGS__)
+#define NOTICE_LOG(...)     logger().notice(logChannel(), Logger::MethodAndPointer(className(), __func__, this), ##__VA_ARGS__)
+#define INFO_LOG(...)       logger().info(logChannel(), Logger::MethodAndPointer(className(), __func__, this), ##__VA_ARGS__)
+#define DEBUG_LOG(...)      logger().debug(logChannel(), Logger::MethodAndPointer(className(), __func__, this), ##__VA_ARGS__)
+
+};
+
 template <>
 struct LogArgument<Logger::MethodAndPointer> {
     static String toString(const Logger::MethodAndPointer& value)
     {
         StringBuilder builder;
+
+        if (value.className) {
+            builder.append(value.className);
+            builder.appendLiteral("::");
+        }
         builder.append(value.methodName);
         builder.appendLiteral("(0x");
         appendUnsigned64AsHex(value.objectPtr, builder);
@@ -176,4 +240,3 @@ struct LogArgument<Logger::MethodAndPointer> {
 };
 
 } // namespace PAL
-
index 729c97c..c0348d3 100644 (file)
@@ -1,3 +1,17 @@
+2017-08-30  Eric Carlson  <eric.carlson@apple.com>
+
+        Add Logger observer and helper class
+        https://bugs.webkit.org/show_bug.cgi?id=176106
+
+        Reviewed by Andy Estes.
+
+        * TestWebKitAPI/Tests/WebCore/Logging.cpp:
+        (TestWebKitAPI::LoggingTest::LoggingTest):
+        (TestWebKitAPI::LoggingTest::Logger::create):
+        (TestWebKitAPI::TEST_F):
+        (TestWebKitAPI::LogObserver::log):
+        (TestWebKitAPI::LogObserver::channel const):
+
 2017-08-29  Filip Pizlo  <fpizlo@apple.com>
 
         Initial reference implementation scaffolding for ArrayLang
index f42735d..c3e053a 100644 (file)
@@ -55,8 +55,13 @@ static const size_t logChannelCount = sizeof(testLogChannels) / sizeof(testLogCh
 
 namespace TestWebKitAPI {
 
-class LoggingTest : public testing::Test {
+class LoggingTest : public testing::Test, public LogHelper {
 public:
+    LoggingTest()
+        : m_logger { Logger::create(this) }
+    {
+    }
+
     void SetUp() final
     {
         WTF::initializeMainThread();
@@ -94,7 +99,13 @@ public:
         return result.toString();
     }
 
+    const Logger& logger() const final { return m_logger.get(); }
+    const char* className() const final { return "LoggingTest"; }
+    WTFLogChannel& logChannel() const final { return TestChannel1; }
+
 private:
+
+    Ref<Logger> m_logger;
     int m_descriptors[2];
     FILE* m_stderr;
 };
@@ -267,6 +278,7 @@ TEST_F(LoggingTest, Logger)
     EXPECT_TRUE(logger->enabled());
 
     WTFSetLogChannelLevel(&TestChannel1, WTFLogLevelError);
+    EXPECT_TRUE(logger->willLog(TestChannel1, WTFLogLevelError));
     logger->error(TestChannel1, "You're using coconuts!");
     EXPECT_TRUE(output().contains("You're using coconuts!", false));
     logger->warning(TestChannel1, "You're using coconuts!");
@@ -302,10 +314,107 @@ TEST_F(LoggingTest, Logger)
     EXPECT_TRUE(output().contains("I shall taunt you a second time!", false));
 
     logger->setEnabled(this, false);
+    EXPECT_FALSE(logger->willLog(TestChannel1, WTFLogLevelError));
+    EXPECT_FALSE(logger->willLog(TestChannel1, WTFLogLevelWarning));
+    EXPECT_FALSE(logger->willLog(TestChannel1, WTFLogLevelNotice));
+    EXPECT_FALSE(logger->willLog(TestChannel1, WTFLogLevelInfo));
+    EXPECT_FALSE(logger->willLog(TestChannel1, WTFLogLevelDebug));
     EXPECT_FALSE(logger->enabled());
     logger->logAlways(TestChannel1, "You've got two empty halves of coconuts");
     EXPECT_EQ(0u, output().length());
+
+    logger->setEnabled(this, true);
+    AtomicString string1("AtomicString", AtomicString::ConstructFromLiteral);
+    const AtomicString string2("const AtomicString", AtomicString::ConstructFromLiteral);
+    logger->logAlways(TestChannel1, string1, " and ", string2);
+    EXPECT_TRUE(output().contains("AtomicString and const AtomicString", false));
+
+    String string3("String");
+    const String string4("const String");
+    logger->logAlways(TestChannel1, string3, " and ", string4);
+    EXPECT_TRUE(output().contains("String and const String", false));
+}
+
+TEST_F(LoggingTest, LogHelper)
+{
+    EXPECT_TRUE(logger().enabled());
+
+    StringBuilder builder;
+    builder.appendLiteral("LoggingTest::TestBody(0x");
+    appendUnsigned64AsHex(reinterpret_cast<uintptr_t>(this), builder);
+    builder.appendLiteral(")");
+    String signature = builder.toString();
+
+    ALWAYS_LOG();
+    EXPECT_TRUE(this->output().contains(signature, false));
+
+    ALWAYS_LOG("Welcome back", " my friends", " to the show", " that never ends");
+    String result = this->output();
+    EXPECT_TRUE(result.contains(signature, false));
+    EXPECT_TRUE(result.contains("to the show that never", false));
+
+    WTFSetLogChannelLevel(&TestChannel1, WTFLogLevelWarning);
+    EXPECT_TRUE(willLog(WTFLogLevelWarning));
+
+    ERROR_LOG("We're so glad you could attend");
+    EXPECT_TRUE(output().contains("We're so glad you could attend", false));
+
+    WARNING_LOG("Come inside! ", "Come inside!");
+    EXPECT_TRUE(output().contains("Come inside! Come inside!", false));
+
+    NOTICE_LOG("There behind a glass is a real blade of grass");
+    EXPECT_EQ(0u, output().length());
+
+    INFO_LOG("be careful as you pass.");
+    EXPECT_EQ(0u, output().length());
+
+    DEBUG_LOG("Move along! Move along!");
+    EXPECT_EQ(0u, output().length());
 }
+
+class LogObserver : public Logger::Observer {
+public:
+    LogObserver() = default;
+
+    String log()
+    {
+        String log = m_logBuffer.toString();
+        m_logBuffer.clear();
+
+        return log;
+    }
+
+    WTFLogChannel channel() const { return m_lastChannel; }
+
+private:
+    void didLogMessage(const WTFLogChannel& channel, const String& logMessage) final
+    {
+        m_logBuffer.append(logMessage);
+        m_lastChannel = channel;
+    }
+
+    StringBuilder m_logBuffer;
+    WTFLogChannel m_lastChannel;
+};
+
+TEST_F(LoggingTest, LogObserver)
+{
+    LogObserver observer;
+
+    EXPECT_TRUE(logger().enabled());
+
+    logger().addObserver(observer);
+    ALWAYS_LOG("testing 1, 2, 3");
+    EXPECT_TRUE(this->output().contains("testing 1, 2, 3", false));
+    EXPECT_TRUE(observer.log().contains("testing 1, 2, 3", false));
+    EXPECT_STREQ(observer.channel().name, logChannel().name);
+
+    logger().removeObserver(observer);
+    ALWAYS_LOG("testing ", 1, ", ", 2, ", 3");
+    EXPECT_TRUE(this->output().contains("testing 1, 2, 3", false));
+    EXPECT_EQ(0u, observer.log().length());
+}
+
 #endif
 
 } // namespace TestWebKitAPI