folly/folly/logging/test/LogCategoryTest.cpp

/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <folly/logging/LogCategory.h>

#include <folly/Conv.h>
#include <folly/logging/Logger.h>
#include <folly/logging/LoggerDB.h>
#include <folly/logging/test/TestLogHandler.h>
#include <folly/portability/GTest.h>

using namespace folly;
using std::make_shared;
using std::shared_ptr;
using std::string;

TEST(LogCategory, effectiveLevel) {
  LoggerDB db{LoggerDB::TESTING};
  Logger foo{&db, "foo"};
  Logger foo2{&db, "..foo.."};
  EXPECT_EQ(foo.getCategory(), foo2.getCategory());

  EXPECT_EQ(kDefaultLogLevel, db.getCategory("")->getLevel());
  EXPECT_EQ(kDefaultLogLevel, db.getCategory("")->getEffectiveLevel());

  EXPECT_EQ(LogLevel::MAX_LEVEL, db.getCategory("foo.bar")->getLevel());
  EXPECT_EQ(kDefaultLogLevel, db.getCategory("foo.bar")->getEffectiveLevel());

  db.setLevel(".foo", LogLevel::DBG0);
  EXPECT_EQ(LogLevel::MAX_LEVEL, db.getCategory("foo.bar")->getLevel());
  EXPECT_EQ(LogLevel::DBG0, db.getCategory("foo.bar")->getEffectiveLevel());

  db.setLevel(".", LogLevel::DBG0);
  EXPECT_EQ(LogLevel::MAX_LEVEL, db.getCategory("foo.bar")->getLevel());
  EXPECT_EQ(LogLevel::DBG0, db.getCategory("foo.bar")->getEffectiveLevel());

  // Test a newly created category under .foo
  EXPECT_EQ(LogLevel::MAX_LEVEL, db.getCategory("foo.test.1234")->getLevel());
  EXPECT_EQ(
      LogLevel::DBG0, db.getCategory("foo.test.1234")->getEffectiveLevel());

  // Test a category that does not inherit its parent's log level
  auto noinherit = db.getCategory("foo.test.noinherit");
  EXPECT_EQ(LogLevel::MAX_LEVEL, noinherit->getLevel());
  EXPECT_EQ(LogLevel::DBG0, noinherit->getEffectiveLevel());
  noinherit->setLevel(LogLevel::CRITICAL, false);
  EXPECT_EQ(LogLevel::CRITICAL, noinherit->getEffectiveLevel());

  // Modify the root logger's level
  db.setLevel(".", LogLevel::ERR);
  EXPECT_EQ(LogLevel::MAX_LEVEL, db.getCategory("foo.test.1234")->getLevel());
  EXPECT_EQ(
      LogLevel::DBG0, db.getCategory("foo.test.1234")->getEffectiveLevel());
  EXPECT_EQ(LogLevel::MAX_LEVEL, db.getCategory("foo.test")->getLevel());
  EXPECT_EQ(LogLevel::DBG0, db.getCategory("foo.test")->getEffectiveLevel());
  EXPECT_EQ(LogLevel::DBG0, db.getCategory("foo")->getLevel());
  EXPECT_EQ(LogLevel::DBG0, db.getCategory("foo")->getEffectiveLevel());
  EXPECT_EQ(
      LogLevel::CRITICAL, db.getCategory("foo.test.noinherit")->getLevel());
  EXPECT_EQ(
      LogLevel::CRITICAL,
      db.getCategory("foo.test.noinherit")->getEffectiveLevel());

  EXPECT_EQ(LogLevel::MAX_LEVEL, db.getCategory("bar.foo.test")->getLevel());
  EXPECT_EQ(LogLevel::ERR, db.getCategory("bar.foo.test")->getEffectiveLevel());
}

void testNumHandlers(size_t numHandlers) {
  SCOPED_TRACE(folly::to<string>("num_handlers= ", numHandlers));
  LoggerDB db{LoggerDB::TESTING};
  db.setLevel("", LogLevel::DBG);

  // Create the requested number of handlers for the foo.bar category
  Logger foobar{&db, "foo.bar"};
  std::vector<shared_ptr<TestLogHandler>> handlers;
  for (size_t n = 0; n < numHandlers; ++n) {
    handlers.emplace_back(make_shared<TestLogHandler>());
    foobar.getCategory()->addHandler(handlers.back());
  }

  // Add a handler to the root category, to confirm that messages are
  // propagated up to the root correctly.
  auto rootHandler = make_shared<TestLogHandler>();
  auto rootCategory = db.getCategory("");
  rootCategory->addHandler(rootHandler);

  // Log a message to a child of the foobar category
  Logger childLogger{&db, "foo.bar.child"};
  FB_LOG(childLogger, WARN, "beware");

  // Make sure the message showed up at all of the handlers
  for (const auto& handler : handlers) {
    auto& messages = handler->getMessages();
    ASSERT_EQ(1, messages.size());
    EXPECT_EQ("beware", messages[0].first.getMessage());
    EXPECT_EQ(LogLevel::WARN, messages[0].first.getLevel());
    EXPECT_EQ(childLogger.getCategory(), messages[0].first.getCategory());
    EXPECT_EQ(foobar.getCategory(), messages[0].second);
  }
  {
    auto& messages = rootHandler->getMessages();
    ASSERT_EQ(1, messages.size());
    EXPECT_EQ("beware", messages[0].first.getMessage());
    EXPECT_EQ(LogLevel::WARN, messages[0].first.getLevel());
    EXPECT_EQ(childLogger.getCategory(), messages[0].first.getCategory());
    EXPECT_EQ(rootCategory, messages[0].second);
  }

  // Now log a message directly to foobar
  FB_LOG(foobar, DBG1, "just testing");
  for (const auto& handler : handlers) {
    auto& messages = handler->getMessages();
    ASSERT_EQ(2, messages.size());
    EXPECT_EQ("just testing", messages[1].first.getMessage());
    EXPECT_EQ(LogLevel::DBG1, messages[1].first.getLevel());
    EXPECT_EQ(foobar.getCategory(), messages[1].first.getCategory());
    EXPECT_EQ(foobar.getCategory(), messages[1].second);
  }
  {
    auto& messages = rootHandler->getMessages();
    ASSERT_EQ(2, messages.size());
    EXPECT_EQ("just testing", messages[1].first.getMessage());
    EXPECT_EQ(LogLevel::DBG1, messages[1].first.getLevel());
    EXPECT_EQ(foobar.getCategory(), messages[1].first.getCategory());
    EXPECT_EQ(rootCategory, messages[1].second);
  }

  // Log a message to a sibling of foobar
  Logger siblingLogger{&db, "foo.sibling"};
  FB_LOG(siblingLogger, ERR, "oh noes");
  for (const auto& handler : handlers) {
    auto& messages = handler->getMessages();
    EXPECT_EQ(2, messages.size());
  }
  {
    auto& messages = rootHandler->getMessages();
    ASSERT_EQ(3, messages.size());
    EXPECT_EQ("oh noes", messages[2].first.getMessage());
    EXPECT_EQ(LogLevel::ERR, messages[2].first.getLevel());
    EXPECT_EQ(siblingLogger.getCategory(), messages[2].first.getCategory());
    EXPECT_EQ(rootCategory, messages[2].second);
  }
}

TEST(LogCategory, numHandlers) {
  // The LogCategory code behaves differently when there are 5 or fewer
  // LogHandlers attached to a category vs when ther are more.
  //
  // Test with fewer than 5 handlers.
  testNumHandlers(1);
  testNumHandlers(2);

  // Test with exactly 5 handlers, as well as one fewer and one more, just
  // to make sure we catch any corner cases.
  testNumHandlers(4);
  testNumHandlers(5);
  testNumHandlers(6);

  // Test with significantly more than 5 handlers.
  testNumHandlers(15);
}