blob: f8a9fe0721b24440ace95b1868864f6b6e0fb1c3 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <lib/syslog/cpp/log_settings.h>
#include <lib/syslog/cpp/macros.h>
#include <functional>
#include <mutex>
#include <optional>
#include <string>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "lib/syslog/cpp/log_level.h"
#ifndef __Fuchsia__
#include "host/encoder.h"
#endif
#include <zircon/types.h>
#include "src/lib/files/file.h"
#include "src/lib/files/scoped_temp_dir.h"
#include "src/lib/uuid/uuid.h"
#ifdef __Fuchsia__
#include <fuchsia/diagnostics/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async/dispatcher.h>
#include <lib/async/wait.h>
#include <lib/fpromise/promise.h>
#include <lib/syslog/cpp/logger.h>
#include <lib/syslog/structured_backend/cpp/log_connection.h>
#include <lib/zx/socket.h>
#include <cinttypes>
#include <rapidjson/document.h>
#include "fuchsia/logger/cpp/fidl.h"
#include "src/lib/diagnostics/fake-log-sink/cpp/fake_log_sink.h"
#include "src/lib/fxl/strings/join_strings.h"
#include "src/lib/fxl/strings/string_printf.h"
#endif
namespace fuchsia_logging {
namespace {
std::chrono::high_resolution_clock::time_point mock_time = std::chrono::steady_clock::now();
using ::testing::HasSubstr;
} // namespace
namespace {
class LoggingFixture : public ::testing::Test {
public:
LoggingFixture() : old_severity_(GetMinLogSeverity()), old_stderr_(dup(STDERR_FILENO)) {}
~LoggingFixture() {
LogSettingsBuilder builder;
builder.WithMinLogSeverity(old_severity_);
builder.BuildAndInitialize();
}
private:
RawLogSeverity old_severity_;
int old_stderr_;
};
using LoggingFixtureDeathTest = LoggingFixture;
#ifdef __Fuchsia__
constexpr std::string SeverityToString(const fuchsia_diagnostics_types::Severity severity) {
switch (severity) {
case fuchsia_diagnostics_types::Severity::kTrace:
return "TRACE";
case fuchsia_diagnostics_types::Severity::kDebug:
return "DEBUG";
case fuchsia_diagnostics_types::Severity::kInfo:
return "INFO";
case fuchsia_diagnostics_types::Severity::kWarn:
return "WARN";
case fuchsia_diagnostics_types::Severity::kError:
return "ERROR";
case fuchsia_diagnostics_types::Severity::kFatal:
return "FATAL";
default:
return "INVALID";
}
}
std::string Format(const std::optional<diagnostics::reader::LogsData>& data) {
if (!data) {
return "<invalid>\n";
}
std::string result = std::format(
"[{:05}.{:03}][{:05}][{:05}][{}] {}: [{}({})]", data->metadata().timestamp / 1000000000ULL,
(data->metadata().timestamp / 1000000ULL) % 1000ULL, data->metadata().pid.value_or(0),
data->metadata().tid.value_or(0), fxl::JoinStrings(data->metadata().tags, ", "),
SeverityToString(data->metadata().severity), data->metadata().file.value_or(""),
data->metadata().line.value_or(0));
if (!data->message().empty()) {
result.append(std::format(" {}", data->message()));
}
for (const auto& key : data->keys()) {
result.append(std::format(" {}=", key.name()));
switch (key.format()) {
case inspect::PropertyFormat::kInt:
result.append(std::format("{}", key.Get<inspect::IntPropertyValue>().value()));
break;
case inspect::PropertyFormat::kUint:
result.append(std::format("{}", key.Get<inspect::UintPropertyValue>().value()));
break;
case inspect::PropertyFormat::kDouble:
result.append(std::format("{}", key.Get<inspect::DoublePropertyValue>().value()));
break;
case inspect::PropertyFormat::kString:
result.push_back('"');
for (char c : key.Get<inspect::StringPropertyValue>().value()) {
if (c == '\"') {
result.push_back('\\');
}
result.push_back(c);
}
result.push_back('"');
break;
case inspect::PropertyFormat::kBool:
if (key.Get<inspect::BoolPropertyValue>().value()) {
result.append("true");
} else {
result.append("false");
}
break;
default:
ADD_FAILURE() << "Unexpected type for " << key.name() << ": "
<< static_cast<uint8_t>(key.format());
}
}
result.append("\n");
return result;
}
#endif
#ifdef __Fuchsia__
using LogState = FakeLogSink;
LogState SetupLogs(bool wait_for_initial_interest = true) { return FakeLogSink(); }
std::string ReadLogs(FakeLogSink& log_sink) {
std::string result;
while (log_sink.WaitForRecord(zx::time::infinite_past())) {
result.append(Format(log_sink.ReadLogsData()));
}
return result;
}
#else
struct TestLogState {
files::ScopedTempDir temp_dir;
std::string log_file;
};
using LogState = std::unique_ptr<TestLogState>;
LogState SetupLogs(bool wait_for_initial_interest = true) {
auto state = std::make_unique<TestLogState>();
{
std::string log_file_out;
state->temp_dir.NewTempFile(&log_file_out);
state->log_file = std::move(log_file_out);
}
LogSettingsBuilder builder;
builder.WithLogFile(state->log_file);
builder.BuildAndInitialize();
return state;
}
std::string ReadLogs(LogState& state) {
std::string log;
files::ReadFileToString(state->log_file, &log);
return log;
}
#endif
TEST_F(LoggingFixture, Log) {
LogState state = SetupLogs();
int error_line = __LINE__ + 1;
FX_LOGS(ERROR) << "something at error";
int info_line = __LINE__ + 1;
FX_LOGS(INFO) << "and some other at info level";
std::string log = ReadLogs(state);
EXPECT_THAT(log, HasSubstr("ERROR: [sdk/lib/syslog/cpp/logging_unittest.cc(" +
std::to_string(error_line) + ")] something at error"));
EXPECT_THAT(log, HasSubstr("INFO: [logging_unittest.cc(" + std::to_string(info_line) +
")] and some other at info level"));
}
TEST_F(LoggingFixture, LogFirstN) {
constexpr int kLimit = 5;
constexpr int kCycles = 20;
constexpr const char* kLogMessage = "Hello";
static_assert(kCycles > kLimit);
LogState state = SetupLogs();
for (int i = 0; i < kCycles; ++i) {
FX_LOGS_FIRST_N(ERROR, kLimit) << kLogMessage;
}
std::string log = ReadLogs(state);
int count = 0;
size_t pos = 0;
while ((pos = log.find(kLogMessage, pos)) != std::string::npos) {
++count;
++pos;
}
EXPECT_EQ(kLimit, count);
}
TEST_F(LoggingFixture, LogT) {
LogState state = SetupLogs();
int error_line = __LINE__ + 1;
FX_LOGST(ERROR, "first") << "something at error";
int info_line = __LINE__ + 1;
FX_LOGST(INFO, "second") << "and some other at info level";
std::string log = ReadLogs(state);
EXPECT_THAT(log, HasSubstr("first] ERROR: [sdk/lib/syslog/cpp/logging_unittest.cc(" +
std::to_string(error_line) + ")] something at error"));
EXPECT_THAT(log, HasSubstr("second] INFO: [logging_unittest.cc(" + std::to_string(info_line) +
")] and some other at info level"));
}
TEST_F(LoggingFixtureDeathTest, CheckFailed) { ASSERT_DEATH(FX_CHECK(false), ""); }
#if defined(__Fuchsia__)
TEST_F(LoggingFixture, Plog) {
FakeLogSink sink;
FX_PLOGS(ERROR, ZX_OK) << "should be ok";
FX_PLOGS(ERROR, ZX_ERR_ACCESS_DENIED) << "got access denied";
EXPECT_THAT(sink.ReadLogsData()->message(), HasSubstr("should be ok: 0 (ZX_OK)"));
EXPECT_THAT(sink.ReadLogsData()->message(),
HasSubstr("got access denied: -30 (ZX_ERR_ACCESS_DENIED)"));
}
TEST_F(LoggingFixture, PlogT) {
FakeLogSink sink;
int line1 = __LINE__ + 1;
FX_PLOGST(ERROR, "abcd", ZX_OK) << "should be ok";
int line2 = __LINE__ + 1;
FX_PLOGST(ERROR, "qwerty", ZX_ERR_ACCESS_DENIED) << "got access denied";
EXPECT_THAT(Format(sink.ReadLogsData()),
HasSubstr("abcd] ERROR: [sdk/lib/syslog/cpp/logging_unittest.cc(" +
std::to_string(line1) + ")] should be ok: 0 (ZX_OK)"));
EXPECT_THAT(
Format(sink.ReadLogsData()),
HasSubstr("qwerty] ERROR: [sdk/lib/syslog/cpp/logging_unittest.cc(" + std::to_string(line2) +
")] got access denied: -30 (ZX_ERR_ACCESS_DENIED)"));
}
#endif // defined(__Fuchsia__)
TEST_F(LoggingFixture, SLog) {
LogState state = SetupLogs(false);
std::string log_id = uuid::Generate();
int line1 = __LINE__ + 1;
FX_LOG_KV(ERROR, nullptr, FX_KV("some_msg", "String log"));
int line2 = __LINE__ + 1;
FX_LOG_KV(ERROR, nullptr, FX_KV("some_msg", 42));
int line4 = __LINE__ + 1;
FX_LOG_KV(ERROR, "msg", FX_KV("first", 42), FX_KV("second", "string"));
int line5 = __LINE__ + 1;
FX_LOG_KV(ERROR, "String log");
int line6 = __LINE__ + 1;
FX_LOG_KV(ERROR, nullptr, FX_KV("float", 0.25f));
int line7 = __LINE__ + 1;
FX_LOG_KV(ERROR, "String with quotes", FX_KV("value", "char is '\"'"));
std::string log = ReadLogs(state);
EXPECT_THAT(log, HasSubstr("ERROR: [" + std::string("sdk/lib/syslog/cpp/logging_unittest.cc") +
"(" + std::to_string(line1) + ")] some_msg=\"String log\""));
EXPECT_THAT(log, HasSubstr("ERROR: [" + std::string("sdk/lib/syslog/cpp/logging_unittest.cc") +
"(" + std::to_string(line2) + ")] some_msg=42"));
EXPECT_THAT(log, HasSubstr("ERROR: [" + std::string("sdk/lib/syslog/cpp/logging_unittest.cc") +
"(" + std::to_string(line4) + ")] msg first=42 second=\"string\""));
EXPECT_THAT(log, HasSubstr("ERROR: [" + std::string("sdk/lib/syslog/cpp/logging_unittest.cc") +
"(" + std::to_string(line5) + ")] String log"));
EXPECT_THAT(log, HasSubstr("ERROR: [" + std::string("sdk/lib/syslog/cpp/logging_unittest.cc") +
"(" + std::to_string(line6) + ")] float=0.25"));
EXPECT_THAT(log,
HasSubstr("ERROR: [" + std::string("sdk/lib/syslog/cpp/logging_unittest.cc") + "(" +
std::to_string(line7) + ")] String with quotes value=\"char is '\\\"'\""));
}
TEST_F(LoggingFixture, BackendDirect) {
LogState state = SetupLogs(false);
{
LogBufferBuilder builder(LogSeverity::Error);
auto buffer =
builder.WithFile("foo.cc", 42).WithMsg("Log message").WithCondition("condition").Build();
buffer.WriteKeyValue("tag", "fake tag");
buffer.Flush();
}
LogBufferBuilder builder(LogSeverity::Error);
auto buffer =
builder.WithMsg("fake message").WithCondition("condition").WithFile("foo.cc", 42).Build();
buffer.WriteKeyValue("tag", "fake tag");
buffer.WriteKeyValue("foo", static_cast<int64_t>(42));
buffer.Flush();
std::string log = ReadLogs(state);
EXPECT_THAT(log, HasSubstr("ERROR: [foo.cc(42)] Check failed: condition. Log message\n"));
EXPECT_THAT(log, HasSubstr("ERROR: [foo.cc(42)] Check failed: condition. fake message foo=42\n"));
}
TEST_F(LoggingFixture, MacroCompilationTest) {
uint8_t zero = 0;
FX_LOG_KV(DEBUG, "test log", FX_KV("key", static_cast<uint16_t>(zero)));
FX_LOG_KV(DEBUG, "test log", FX_KV("key", static_cast<uint32_t>(zero)));
FX_LOG_KV(DEBUG, "test log", FX_KV("key", static_cast<uint64_t>(zero)));
FX_LOG_KV(DEBUG, "test log", FX_KV("key", static_cast<size_t>(zero)));
FX_LOG_KV(DEBUG, "test log", FX_KV("key", static_cast<int16_t>(zero)));
FX_LOG_KV(DEBUG, "test log", FX_KV("key", static_cast<int32_t>(zero)));
FX_LOG_KV(DEBUG, "test log", FX_KV("key", static_cast<int64_t>(zero)));
}
TEST(StructuredLogging, LOGS) {
std::string str;
// 5mb log shouldn't crash
str.resize(1000 * 5000);
memset(str.data(), 's', str.size() - 1);
FX_LOGS(INFO) << str;
}
#ifndef __Fuchsia__
TEST(StructuredLogging, Remaining) {
LogSettingsBuilder builder;
std::string log_file;
files::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.NewTempFile(&log_file));
builder.WithLogFile(log_file);
builder.BuildAndInitialize();
LogBufferBuilder builder2(LogSeverity::Info);
auto buffer = builder2.WithFile("test", 5).WithMsg("test_msg").Build();
auto header = internal::MsgHeader::CreatePtr(&buffer);
auto initial = header->RemainingSpace();
header->WriteChar('t');
ASSERT_EQ(header->RemainingSpace(), initial - 1);
header->WriteString("est");
ASSERT_EQ(header->RemainingSpace(), initial - 4);
}
TEST(StructuredLogging, FlushAndReset) {
LogBufferBuilder builder(LogSeverity::Info);
auto buffer = builder.WithFile("test", 5).WithMsg("test_msg").Build();
auto header = internal::MsgHeader::CreatePtr(&buffer);
auto initial = header->RemainingSpace();
header->WriteString("test");
ASSERT_EQ(header->RemainingSpace(), initial - 4);
header->FlushAndReset();
ASSERT_EQ(header->RemainingSpace(),
LogBuffer::data_size() - 2); // last byte reserved for NULL terminator
}
#endif
#ifdef __Fuchsia__
TEST(LogConnection, Basic) {
auto endpoints = fidl::CreateEndpoints<fuchsia_logger::LogSink>();
FakeLogSink log_sink(FUCHSIA_LOG_INFO, std::move(endpoints->server));
auto connection = LogConnection::Create(std::move(endpoints->client));
ASSERT_EQ(connection.status_value(), ZX_OK);
ASSERT_TRUE(connection->is_valid());
LogBuffer buffer;
buffer.BeginRecord(FUCHSIA_LOG_INFO, {}, {}, "foo", 1, 2, 3);
ASSERT_EQ(connection->FlushBuffer(buffer).status_value(), ZX_OK);
std::vector<uint8_t> record = log_sink.ReadRecord();
std::span<const uint8_t> span = buffer.EndRecord();
ASSERT_EQ(record.size(), span.size());
EXPECT_EQ(memcmp(record.data(), span.data(), record.size()), 0);
}
TEST(LogConnection, BlockIfFull) {
auto endpoints = fidl::CreateEndpoints<fuchsia_logger::LogSink>();
FakeLogSink log_sink(FUCHSIA_LOG_INFO, std::move(endpoints->server));
auto connection = LogConnection::Create(std::move(endpoints->client));
ASSERT_EQ(connection.status_value(), ZX_OK);
ASSERT_TRUE(connection->is_valid());
// Keep logging and we should eventually get ZX_ERR_SHOULD_WAIT.
LogBuffer buffer;
buffer.BeginRecord(FUCHSIA_LOG_INFO, {}, {}, "foo", 1, 2, 3);
int count = 0;
for (;;) {
auto result = connection->FlushBuffer(buffer);
if (result.is_error()) {
ASSERT_EQ(result.status_value(), ZX_ERR_SHOULD_WAIT);
break;
}
++count;
}
zx::socket socket;
ASSERT_EQ(connection->socket().duplicate(ZX_RIGHT_SAME_RIGHTS, &socket), ZX_OK);
LogConnection connection2(std::move(socket), {.block_if_full = true});
std::thread thread([&] {
// Delay reading the message to make it more likely that we block when flushing the buffer.
usleep(10000);
for (int i = 0; i < count; ++i) {
ASSERT_FALSE(log_sink.ReadRecord().empty());
}
});
for (int i = 0; i < count; ++i) {
ASSERT_EQ(connection2.FlushBuffer(buffer).status_value(), ZX_OK);
}
thread.join();
}
TEST(LogConnection, EncodingError) {
zx::socket client, server;
ASSERT_EQ(zx::socket::create(0, &client, &server), ZX_OK);
LogConnection connection(std::move(client), {});
LogBuffer buffer;
std::string message;
message.resize(sizeof internal::LogBufferData::data, 'a');
// This should result in an invalid message because it's too big.
buffer.BeginRecord(FUCHSIA_LOG_INFO, {}, 0, message, 0, 1, 2);
EXPECT_EQ(connection.FlushBuffer(buffer).status_value(), ZX_ERR_INVALID_ARGS);
}
TEST(Logger, LocalLogger) {
Logger logger;
// `logger` should be destroyed after `loop`.
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
ASSERT_EQ(loop.StartThread(), ZX_OK);
auto endpoints = fidl::CreateEndpoints<fuchsia_logger::LogSink>();
FakeLogSink log_sink(FUCHSIA_LOG_INFO, std::move(endpoints->server));
// Unfortunately, these has to be static because `severity_change_callback` doesn't take a
// context.
static std::mutex mutex;
static std::condition_variable condition;
static RawLogSeverity severity = 0;
auto logger_result = Logger::Create(LogSettings{
.single_threaded_dispatcher = loop.dispatcher(),
.log_sink = endpoints->client.TakeChannel().release(),
.severity_change_callback =
+[](RawLogSeverity s) {
std::unique_lock lock(mutex);
severity = s;
condition.notify_all();
},
});
ASSERT_EQ(logger_result.status_value(), ZX_OK);
logger = *std::move(logger_result);
LogBuffer buffer;
buffer.BeginRecord(FUCHSIA_LOG_INFO, {}, {}, "foo", 1, 2, 3);
ASSERT_EQ(logger.FlushBuffer(buffer).status_value(), ZX_OK);
std::vector<uint8_t> record = log_sink.ReadRecord();
std::span<const uint8_t> span = buffer.EndRecord();
ASSERT_EQ(record.size(), span.size());
EXPECT_EQ(memcmp(record.data(), span.data(), record.size()), 0);
// Make sure a severity update gets through.
log_sink.set_severity(FUCHSIA_LOG_WARNING);
std::unique_lock lock(mutex);
condition.wait(lock, [&] { return severity == FUCHSIA_LOG_WARNING; });
EXPECT_EQ(logger.GetMinimumSeverity(), FUCHSIA_LOG_WARNING);
}
#endif
} // namespace
} // namespace fuchsia_logging