blob: 0dd47b865a86d2de372449e673fadde59e6554f0 [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 <fuchsia/logger/cpp/fidl.h>
#include <lib/syslog/cpp/log_level.h>
#include <cmath>
#include <memory>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "src/developer/forensics/feedback_data/system_log_recorder/encoding/identity_decoder.h"
#include "src/developer/forensics/feedback_data/system_log_recorder/encoding/identity_encoder.h"
#include "src/developer/forensics/feedback_data/system_log_recorder/encoding/production_encoding.h"
#include "src/developer/forensics/feedback_data/system_log_recorder/encoding/version.h"
#include "src/developer/forensics/feedback_data/system_log_recorder/reader.h"
#include "src/developer/forensics/feedback_data/system_log_recorder/system_log_recorder.h"
#include "src/developer/forensics/testing/log_message.h"
#include "src/developer/forensics/testing/scoped_memfs_manager.h"
#include "src/developer/forensics/utils/log_format.h"
#include "src/developer/forensics/utils/redact/redactor.h"
#include "src/lib/files/file.h"
#include "src/lib/files/path.h"
namespace forensics {
namespace feedback_data {
namespace system_log_recorder {
namespace {
::fpromise::result<fuchsia::logger::LogMessage, std::string> BuildLogMessage(
const int32_t severity, const std::string& text,
const zx::duration timestamp_offset = zx::duration(0),
const std::vector<std::string>& tags = {}) {
return ::fpromise::ok(testing::BuildLogMessage(severity, text, timestamp_offset, tags));
}
// Only change "X" for one character. i.e. X -> 12 is not allowed.
const auto kMaxLogLineSize =
StorageSize::Bytes(Format(BuildLogMessage(fuchsia_logging::LOG_INFO, "line X").value()).size());
const auto kMaxDecompressedSize = StorageSize::Kilobytes(256);
constexpr auto kRootDirectory = "/root";
constexpr auto kWriteDirectory = "/root/write";
constexpr auto kReadDirectory = "/read";
constexpr auto kOutputFile = "/read/output.txt";
class EncoderStub : public Encoder {
public:
EncoderStub() {}
virtual ~EncoderStub() {}
virtual EncodingVersion GetEncodingVersion() const { return EncodingVersion::kForTesting; }
virtual std::string Encode(const std::string& msg) {
input_.back() += msg;
return msg;
}
virtual void Reset() { input_.push_back(""); }
std::vector<std::string> GetInput() { return input_; }
private:
std::vector<std::string> input_ = {""};
};
class Decoder2x : public Decoder {
public:
Decoder2x() {}
virtual ~Decoder2x() {}
virtual EncodingVersion GetEncodingVersion() const { return EncodingVersion::kForTesting; }
virtual std::string Decode(const std::string& msg) { return msg + msg; }
virtual void Reset() {}
};
std::unique_ptr<Encoder> MakeIdentityEncoder() {
return std::unique_ptr<Encoder>(new IdentityEncoder());
}
std::unique_ptr<RedactorBase> MakeIdentityRedactor() {
return std::unique_ptr<RedactorBase>(new IdentityRedactor(inspect::BoolProperty()));
}
std::string MakeLogFilePath(const size_t file_num) {
return files::JoinPath(kWriteDirectory, std::to_string(file_num));
}
TEST(WriterTest, VerifyFileOrdering) {
// Set up the writer such that each file can fit 1 log message. When consuming a message the
// end of block signal will be sent and a new empty file will be produced from file rotation.
// From this behavior although we use 4 files, we only expect to retrieve the last 3 messages.
testing::ScopedMemFsManager memfs_manager;
memfs_manager.Create(kRootDirectory);
const StorageSize kBlockSize = kMaxLogLineSize;
const StorageSize kBufferSize = kMaxLogLineSize;
LogMessageStore store(kBlockSize, kBufferSize, MakeIdentityRedactor(), MakeIdentityEncoder());
store.TurnOnRateLimiting();
SystemLogWriter writer(kWriteDirectory, 4u, &store);
// Written to file 0
EXPECT_TRUE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 1")));
writer.Write();
// Written to file 1
EXPECT_TRUE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 2")));
writer.Write();
// Written to file 2
EXPECT_TRUE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 3")));
writer.Write();
// Written to file 3
EXPECT_TRUE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 4")));
writer.Write();
// Written to file 4
EXPECT_TRUE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 5")));
writer.Write();
memfs_manager.Create(kReadDirectory);
IdentityDecoder decoder;
std::string content;
ASSERT_TRUE(files::ReadFileToString(MakeLogFilePath(2u), &content));
EXPECT_EQ(content, R"([15604.000][07559][07687][] INFO: line 3
)");
ASSERT_TRUE(files::ReadFileToString(MakeLogFilePath(3u), &content));
EXPECT_EQ(content, R"([15604.000][07559][07687][] INFO: line 4
)");
ASSERT_TRUE(files::ReadFileToString(MakeLogFilePath(4u), &content));
EXPECT_EQ(content, R"([15604.000][07559][07687][] INFO: line 5
)");
float compression_ratio;
ASSERT_TRUE(Concatenate(kWriteDirectory, kMaxDecompressedSize, &decoder, kOutputFile,
&compression_ratio));
EXPECT_EQ(compression_ratio, 1.0);
std::string contents;
ASSERT_TRUE(files::ReadFileToString(kOutputFile, &contents));
EXPECT_EQ(contents, R"([15604.000][07559][07687][] INFO: line 3
[15604.000][07559][07687][] INFO: line 4
[15604.000][07559][07687][] INFO: line 5
)");
}
TEST(WriterTest, VerifyEncoderInput) {
// Set up the writer such that each file can fit 2 log messages. We will then write 4 messages
// and expect that the encoder receives 2 reset signals and encodes 2 log messages in each block.
testing::ScopedMemFsManager memfs_manager;
memfs_manager.Create(kRootDirectory);
const StorageSize kBlockSize = kMaxLogLineSize * 2;
const StorageSize kBufferSize = kMaxLogLineSize * 2;
auto encoder = std::unique_ptr<EncoderStub>(new EncoderStub());
auto encoder_ptr = encoder.get();
LogMessageStore store(kBlockSize, kBufferSize, MakeIdentityRedactor(), std::move(encoder));
store.TurnOnRateLimiting();
SystemLogWriter writer(kWriteDirectory, 2u, &store);
EXPECT_TRUE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 1")));
writer.Write();
EXPECT_TRUE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 2")));
writer.Write();
EXPECT_TRUE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 3")));
EXPECT_TRUE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 4")));
writer.Write();
std::vector<std::string> input = encoder_ptr->GetInput();
EXPECT_EQ(input.size(), (size_t)3);
EXPECT_EQ(input[0], R"([15604.000][07559][07687][] INFO: line 1
[15604.000][07559][07687][] INFO: line 2
)");
EXPECT_EQ(input[1], R"([15604.000][07559][07687][] INFO: line 3
[15604.000][07559][07687][] INFO: line 4
)");
}
TEST(WriterTest, WritesMessages) {
testing::ScopedMemFsManager memfs_manager;
memfs_manager.Create(kRootDirectory);
// Set up the writer such that each file can fit 2 log messages and the "!!! DROPPED..."
// string.
LogMessageStore store(kMaxLogLineSize * 2, kMaxLogLineSize * 2, MakeIdentityRedactor(),
MakeIdentityEncoder());
store.TurnOnRateLimiting();
SystemLogWriter writer(kWriteDirectory, 2u, &store);
EXPECT_TRUE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 0")));
EXPECT_TRUE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 1")));
EXPECT_FALSE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 2")));
writer.Write();
memfs_manager.Create(kReadDirectory);
IdentityDecoder decoder;
float compression_ratio;
ASSERT_TRUE(Concatenate(kWriteDirectory, kMaxDecompressedSize, &decoder, kOutputFile,
&compression_ratio));
EXPECT_EQ(compression_ratio, 1.0);
std::string contents;
ASSERT_TRUE(files::ReadFileToString(kOutputFile, &contents));
EXPECT_EQ(contents, R"([15604.000][07559][07687][] INFO: line 0
[15604.000][07559][07687][] INFO: line 1
!!! DROPPED 1 MESSAGES !!!
)");
EXPECT_TRUE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 3")));
EXPECT_TRUE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 4")));
writer.Write();
ASSERT_TRUE(Concatenate(kWriteDirectory, kMaxDecompressedSize, &decoder, kOutputFile,
&compression_ratio));
EXPECT_EQ(compression_ratio, 1.0);
ASSERT_TRUE(files::ReadFileToString(kOutputFile, &contents));
EXPECT_EQ(contents, R"([15604.000][07559][07687][] INFO: line 3
[15604.000][07559][07687][] INFO: line 4
)");
}
TEST(WriterTest, VerifyCompressionRatio) {
// Generate 2x data when decoding. The decoder data output is not useful, just its size.
testing::ScopedMemFsManager memfs_manager;
memfs_manager.Create(kRootDirectory);
LogMessageStore store(kMaxLogLineSize * 4, kMaxLogLineSize * 4, MakeIdentityRedactor(),
MakeIdentityEncoder());
store.TurnOnRateLimiting();
SystemLogWriter writer(kWriteDirectory, 2u, &store);
EXPECT_TRUE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 0")));
EXPECT_TRUE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 1")));
EXPECT_TRUE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 2")));
writer.Write();
memfs_manager.Create(kReadDirectory);
Decoder2x decoder;
float compression_ratio;
ASSERT_TRUE(Concatenate(kWriteDirectory, kMaxDecompressedSize, &decoder, kOutputFile,
&compression_ratio));
EXPECT_EQ(compression_ratio, 2.0);
}
TEST(WriterTest, VerifyProductionEcoding) {
testing::ScopedMemFsManager memfs_manager;
memfs_manager.Create(kRootDirectory);
// Set up the writer such that one file contains 5 log messages.
auto encoder = std::unique_ptr<Encoder>(new ProductionEncoder());
LogMessageStore store(kMaxLogLineSize * 5, kMaxLogLineSize * 5, MakeIdentityRedactor(),
std::move(encoder));
store.TurnOnRateLimiting();
SystemLogWriter writer(kWriteDirectory, 2u, &store);
EXPECT_TRUE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 0")));
EXPECT_TRUE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 1")));
EXPECT_TRUE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 2")));
EXPECT_TRUE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 3")));
EXPECT_TRUE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 4")));
writer.Write();
memfs_manager.Create(kReadDirectory);
ProductionDecoder decoder;
float compression_ratio;
ASSERT_TRUE(Concatenate(kWriteDirectory, kMaxDecompressedSize, &decoder, kOutputFile,
&compression_ratio));
EXPECT_FALSE(std::isnan(compression_ratio));
std::string contents;
ASSERT_TRUE(files::ReadFileToString(kOutputFile, &contents));
EXPECT_EQ(contents, R"([15604.000][07559][07687][] INFO: line 0
[15604.000][07559][07687][] INFO: line 1
[15604.000][07559][07687][] INFO: line 2
[15604.000][07559][07687][] INFO: line 3
[15604.000][07559][07687][] INFO: line 4
)");
}
TEST(WriterTest, FilesAlreadyPresent) {
testing::ScopedMemFsManager memfs_manager;
memfs_manager.Create(kRootDirectory);
{
// Set up the writer such that one file contains at most 5 log messages.
auto encoder = std::unique_ptr<Encoder>(new ProductionEncoder());
LogMessageStore store(kMaxLogLineSize * 5, kMaxLogLineSize * 5, MakeIdentityRedactor(),
std::move(encoder));
store.TurnOnRateLimiting();
SystemLogWriter writer(kWriteDirectory, 2u, &store);
EXPECT_TRUE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 0")));
EXPECT_TRUE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 1")));
writer.Write();
}
{
// Set up the writer such that one file contains at most 5 log messages.
auto encoder = std::unique_ptr<Encoder>(new ProductionEncoder());
LogMessageStore store(kMaxLogLineSize * 5, kMaxLogLineSize * 5, MakeIdentityRedactor(),
std::move(encoder));
store.TurnOnRateLimiting();
SystemLogWriter writer(kWriteDirectory, 2u, &store);
EXPECT_TRUE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 2")));
EXPECT_TRUE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 3")));
writer.Write();
}
memfs_manager.Create(kReadDirectory);
ProductionDecoder decoder;
float compression_ratio;
ASSERT_TRUE(Concatenate(kWriteDirectory, kMaxDecompressedSize, &decoder, kOutputFile,
&compression_ratio));
EXPECT_FALSE(std::isnan(compression_ratio));
std::string contents;
ASSERT_TRUE(files::ReadFileToString(kOutputFile, &contents));
EXPECT_EQ(contents, R"([15604.000][07559][07687][] INFO: line 0
[15604.000][07559][07687][] INFO: line 1
[15604.000][07559][07687][] INFO: line 2
[15604.000][07559][07687][] INFO: line 3
)");
}
TEST(WriterTest, FailCreateDirectory) {
// Don't set up kRootDirectory
testing::ScopedMemFsManager memfs_manager;
// Set up the writer such that each file can fit 2 log messages and the "!!! DROPPED..."
// string.
LogMessageStore store(kMaxLogLineSize * 2, kMaxLogLineSize * 2, MakeIdentityRedactor(),
MakeIdentityEncoder());
store.TurnOnRateLimiting();
SystemLogWriter writer(kWriteDirectory, 2u, &store);
// Create the kRootDirectory so kWriteDirectory can be made by |writer| after the next set of
// writes.
memfs_manager.Create(kRootDirectory);
EXPECT_TRUE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 0")));
EXPECT_TRUE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 1")));
EXPECT_FALSE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 2")));
writer.Write();
memfs_manager.Create(kReadDirectory);
IdentityDecoder decoder;
float compression_ratio;
EXPECT_FALSE(Concatenate(kWriteDirectory, kMaxDecompressedSize, &decoder, kOutputFile,
&compression_ratio));
std::string contents;
EXPECT_FALSE(files::ReadFileToString(kOutputFile, &contents));
EXPECT_TRUE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 3")));
EXPECT_TRUE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 4")));
writer.Write();
ASSERT_TRUE(Concatenate(kWriteDirectory, kMaxDecompressedSize, &decoder, kOutputFile,
&compression_ratio));
EXPECT_EQ(compression_ratio, 1.0);
ASSERT_TRUE(files::ReadFileToString(kOutputFile, &contents));
EXPECT_EQ(contents, R"([15604.000][07559][07687][] INFO: line 3
[15604.000][07559][07687][] INFO: line 4
)");
}
TEST(WriterTest, DirectoryDisappears) {
testing::ScopedMemFsManager memfs_manager;
memfs_manager.Create(kRootDirectory);
// Set up the writer such that each file can fit 2 log messages and the "!!! DROPPED..."
// string.
LogMessageStore store(kMaxLogLineSize * 2, kMaxLogLineSize * 2, MakeIdentityRedactor(),
MakeIdentityEncoder());
store.TurnOnRateLimiting();
SystemLogWriter writer(kWriteDirectory, 2u, &store);
// Destroy kWriteDirectory so the next set of writes fail and the directory is recreated.
ASSERT_TRUE(files::DeletePath(kWriteDirectory, /*recursive=*/true));
EXPECT_TRUE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 0")));
EXPECT_TRUE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 1")));
EXPECT_FALSE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 2")));
writer.Write();
memfs_manager.Create(kReadDirectory);
IdentityDecoder decoder;
float compression_ratio;
EXPECT_FALSE(Concatenate(kWriteDirectory, kMaxDecompressedSize, &decoder, kOutputFile,
&compression_ratio));
std::string contents;
EXPECT_FALSE(files::ReadFileToString(kOutputFile, &contents));
EXPECT_TRUE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 3")));
EXPECT_TRUE(store.Add(BuildLogMessage(fuchsia_logging::LOG_INFO, "line 4")));
writer.Write();
ASSERT_TRUE(Concatenate(kWriteDirectory, kMaxDecompressedSize, &decoder, kOutputFile,
&compression_ratio));
EXPECT_EQ(compression_ratio, 1.0);
ASSERT_TRUE(files::ReadFileToString(kOutputFile, &contents));
EXPECT_EQ(contents, R"([15604.000][07559][07687][] INFO: line 3
[15604.000][07559][07687][] INFO: line 4
)");
}
} // namespace
} // namespace system_log_recorder
} // namespace feedback_data
} // namespace forensics