Skip to content

Commit

Permalink
feat: ok, let it be an abstraction
Browse files Browse the repository at this point in the history
  • Loading branch information
3Hren committed Sep 14, 2017
1 parent ca19495 commit 2c974e0
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 51 deletions.
9 changes: 7 additions & 2 deletions include/blackhole/sink/file.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,13 @@ class builder<sink::file_t> {
auto flush_every(std::size_t events) && -> builder&&;

/// Speficies whether a sink should check file exists before attempt writing.
auto should_stat(bool flag) & -> builder&;
auto should_stat(bool flag) && -> builder&&;
///
/// In case the file doesn't exist the sink will create it. If it exists, but its inode differs
/// from its initial value the sink changes the target to that file automatically.
///
/// Note, that this check significantly slows down sink's performance.
auto rotate_checking_stat() & -> builder&;
auto rotate_checking_stat() && -> builder&&;

/// Consumes this builder, returning a newly created file sink with the options configured.
auto build() && -> std::unique_ptr<sink_t>;
Expand Down
64 changes: 29 additions & 35 deletions src/sink/file.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
#include "blackhole/sink/file.hpp"

#include <sys/stat.h>

#include <cctype>

#include <boost/lexical_cast.hpp>
Expand All @@ -16,6 +14,8 @@
#include "file.hpp"
#include "file/flusher/bytecount.hpp"
#include "file/flusher/repeat.hpp"
#include "file/rotate/null.hpp"
#include "file/rotate/stat.hpp"
#include "file/stream.hpp"

namespace blackhole {
Expand Down Expand Up @@ -106,11 +106,11 @@ auto ofstream_factory_t::create(const std::string& filename, std::ios_base::open

file_t::file_t(const std::string& path,
std::unique_ptr<file::stream_factory_t> stream_factory,
std::unique_ptr<file::flusher_factory_t> flusher_factory,
bool should_stat) :
std::unique_ptr<file::rotate_factory_t> rotate_factory,
std::unique_ptr<file::flusher_factory_t> flusher_factory) :
stream_factory(std::move(stream_factory)),
flusher_factory(std::move(flusher_factory)),
should_stat(should_stat)
rotate_factory(std::move(rotate_factory)),
flusher_factory(std::move(flusher_factory))
{
data.path = path;
}
Expand All @@ -124,40 +124,29 @@ auto file_t::filename(const record_t&) const -> std::string {
return {data.path};
}

auto file_t::exists(const std::string& filename) const -> bool {
struct stat buf = {};
return ::stat(filename.c_str(), &buf) == 0;
}

auto file_t::backend(const std::string& filename) -> file::backend_t& {
const auto it = data.backends.find(filename);

if (it == data.backends.end()) {
return create_backend(filename);
}

if (should_stat && !exists(filename)) {
auto& backend = it->second;
if (backend.should_rotate()) {
data.backends.erase(filename);
return create_backend(filename);
}

return it->second;
return backend;
}

auto file_t::create_backend(const std::string& filename) -> file::backend_t& {
auto stream = stream_factory->create(filename, std::ios_base::app);
auto flusher = flusher_factory->create();
return data.backends.insert(
std::make_pair(filename, file::backend_t(std::move(stream), std::move(flusher)))).first->second;
}

template<typename T>
auto file_t::create_backend(const std::string& filename, T it) -> file::backend_t& {
auto stream = stream_factory->create(filename, std::ios_base::app);
auto stream = stream_factory->create(filename, std::ios_base::app | std::ios_base::out);
auto rotate = rotate_factory->create(filename);
auto flusher = flusher_factory->create();
auto backend = file::backend_t(std::move(stream), std::move(rotate), std::move(flusher));

return data.backends.insert(it,
std::make_pair(filename, file::backend_t(std::move(stream), std::move(flusher))))->second;
return data.backends.insert(std::make_pair(filename, std::move(backend))).first->second;
}

auto file_t::emit(const record_t& record, const string_view& formatted) -> void {
Expand All @@ -172,13 +161,14 @@ auto file_t::emit(const record_t& record, const string_view& formatted) -> void
class builder<sink::file_t>::inner_t {
public:
std::string filename;
std::unique_ptr<sink::file::rotate_factory_t> rfactory;
std::unique_ptr<sink::file::flusher_factory_t> ffactory;
bool should_stat;
};

builder<sink::file_t>::builder(const std::string& path) :
p(new inner_t{path, nullptr, false}, deleter_t())
p(new inner_t{path, nullptr, nullptr}, deleter_t())
{
p->rfactory = blackhole::make_unique<sink::file::rotate::null_factory_t>();
p->ffactory = blackhole::make_unique<sink::file::flusher::repeat_factory_t>(std::size_t(0));
}

Expand All @@ -200,21 +190,21 @@ auto builder<sink::file_t>::flush_every(std::size_t events) && -> builder&& {
return std::move(flush_every(events));
}

auto builder<sink::file_t>::should_stat(bool flag) & -> builder& {
p->should_stat = flag;
auto builder<sink::file_t>::rotate_checking_stat() & -> builder& {
p->rfactory = blackhole::make_unique<sink::file::rotate::stat_factory_t>();
return *this;
}

auto builder<sink::file_t>::should_stat(bool flag) && -> builder&& {
return std::move(should_stat(flag));
auto builder<sink::file_t>::rotate_checking_stat() && -> builder&& {
return std::move(rotate_checking_stat());
}

auto builder<sink::file_t>::build() && -> std::unique_ptr<sink_t> {
return blackhole::make_unique<sink::file_t>(
std::move(p->filename),
blackhole::make_unique<sink::file::ofstream_factory_t>(),
std::move(p->ffactory),
p->should_stat
std::move(p->rfactory),
std::move(p->ffactory)
);
}

Expand Down Expand Up @@ -243,9 +233,13 @@ auto factory<sink::file_t>::from(const config::node_t& config) const -> std::uni
}
}

if (auto should_stat = config["should_stat"]) {
if (should_stat.unwrap()->is_bool()) {
builder.should_stat(should_stat.unwrap()->to_bool());
if (auto rotate = config["rotate"]) {
if (auto type = rotate["type"].to_string()) {
if (*type == "stat") {
builder.rotate_checking_stat();
} else {
throw std::invalid_argument("rotate type \"" + *type + "\" is not registered");
}
}
}

Expand Down
22 changes: 12 additions & 10 deletions src/sink/file.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

#include "../memory.hpp"
#include "file/flusher.hpp"
#include "file/rotate.hpp"
#include "file/stream.hpp"

namespace blackhole {
Expand All @@ -21,15 +22,22 @@ namespace sink {
namespace file {

class backend_t {
std::string name;
std::unique_ptr<std::ostream> stream;
std::unique_ptr<rotate_t> rotate;
std::unique_ptr<flusher_t> flusher;

public:
backend_t(std::unique_ptr<std::ostream> stream, std::unique_ptr<flusher_t> flusher) :
backend_t(std::unique_ptr<std::ostream> stream, std::unique_ptr<rotate_t> rotate, std::unique_ptr<flusher_t> flusher) :
stream(std::move(stream)),
rotate(std::move(rotate)),
flusher(std::move(flusher))
{}

auto should_rotate() const -> bool {
return rotate->should_rotate();
}

auto write(const string_view& message) -> void {
stream->write(message.data(), static_cast<std::streamsize>(message.size()));
stream->put('\n');
Expand All @@ -43,24 +51,23 @@ class backend_t {

class file_t : public sink_t {
std::unique_ptr<file::stream_factory_t> stream_factory;
std::unique_ptr<file::rotate_factory_t> rotate_factory;
std::unique_ptr<file::flusher_factory_t> flusher_factory;

struct {
std::string path;
std::map<std::string, file::backend_t> backends;
} data;

bool should_stat;

mutable std::mutex mutex;

public:
/// \param path a path with final destination file to open. All files are opened with append
/// mode by default.
file_t(const std::string& path,
std::unique_ptr<file::stream_factory_t> stream_factory,
std::unique_ptr<file::flusher_factory_t> flusher_factory,
bool should_stat);
std::unique_ptr<file::rotate_factory_t> rotate_factory,
std::unique_ptr<file::flusher_factory_t> flusher_factory);

/// Returns a const lvalue reference to destination path pattern.
///
Expand All @@ -71,15 +78,10 @@ class file_t : public sink_t {

auto filename(const record_t& record) const -> std::string;

auto exists(const std::string& filename) const -> bool;

auto backend(const std::string& filename) -> file::backend_t&;

auto create_backend(const std::string& filename) -> file::backend_t&;

template<typename T>
auto create_backend(const std::string& filename, T it) -> file::backend_t&;

/// Outputs the formatted message with its associated record to the file.
///
/// Depending on the filename pattern it is possible to write into multiple destinations.
Expand Down
23 changes: 23 additions & 0 deletions src/sink/file/rotate.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#pragma once

namespace blackhole {
inline namespace v1 {
namespace sink {
namespace file {

class rotate_t {
public:
virtual ~rotate_t() = default;
virtual auto should_rotate() -> bool = 0;
};

class rotate_factory_t {
public:
virtual ~rotate_factory_t() = default;
virtual auto create(const std::string& filename) const -> std::unique_ptr<rotate_t> = 0;
};

} // namespace file
} // namespace sink
} // namespace v1
} // namespace blackhole
30 changes: 30 additions & 0 deletions src/sink/file/rotate/null.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#pragma once

#include "../../../memory.hpp"
#include "../rotate.hpp"

namespace blackhole {
inline namespace v1 {
namespace sink {
namespace file {
namespace rotate {

class null_rotate_t : public rotate_t {
public:
auto should_rotate() -> bool override {
return false;
}
};

class null_factory_t : public rotate_factory_t {
public:
auto create(const std::string&) const -> std::unique_ptr<rotate_t> override {
return blackhole::make_unique<null_rotate_t>();
}
};

} // namespace rotate
} // namespace file
} // namespace sink
} // namespace v1
} // namespace blackhole
53 changes: 53 additions & 0 deletions src/sink/file/rotate/stat.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#pragma once

#include <sys/stat.h>

#include <string>
#include <system_error>

#include "../rotate.hpp"

namespace blackhole {
inline namespace v1 {
namespace sink {
namespace file {
namespace rotate {

class stat_rotate_t : public rotate_t {
std::string filename;
std::int64_t inode;

public:
explicit stat_rotate_t(std::string filename) :
filename(std::move(filename)),
inode(0)
{
struct stat buf = {};
auto rc = ::stat(this->filename.c_str(), &buf);
if (rc == -1) {
throw std::system_error(errno, std::system_category());
}

inode = std::int64_t(buf.st_ino);
}

auto should_rotate() -> bool override {
struct stat buf = {};
auto rc = ::stat(filename.c_str(), &buf);

return !(rc == 0 && std::int64_t(buf.st_ino) == inode);
}
};

class stat_factory_t : public rotate_factory_t {
public:
auto create(const std::string& filename) const -> std::unique_ptr<rotate_t> override {
return blackhole::make_unique<stat_rotate_t>(filename);
}
};

} // namespace rotate
} // namespace file
} // namespace sink
} // namespace v1
} // namespace blackhole
14 changes: 10 additions & 4 deletions tests/src/unit/sink/file.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,16 @@ class flusher_t : public file::flusher_t {
MOCK_METHOD1(update, file::flusher_t::result_t(std::size_t nwritten));
};

class rotate_t : public file::rotate_t {
public:
MOCK_METHOD0(should_rotate, bool());
};

} // namespace mock

TEST(backend_t, Write) {
std::unique_ptr<std::stringstream> stream(new std::stringstream);
std::unique_ptr<mock::rotate_t> rotate(new mock::rotate_t);
std::unique_ptr<mock::flusher_t> flusher(new mock::flusher_t);

auto& stream_ = *stream;
Expand All @@ -40,7 +46,7 @@ TEST(backend_t, Write) {
.Times(1)
.WillOnce(Return(flusher_t::result_t::idle));

backend_t backend(std::move(stream), std::move(flusher));
backend_t backend(std::move(stream), std::move(rotate), std::move(flusher));
backend.write("le message");

EXPECT_EQ("le message\n", stream_.str());
Expand Down Expand Up @@ -92,7 +98,7 @@ TEST(factory, PatternFromConfig) {
.Times(1)
.WillOnce(Return(nullptr));

EXPECT_CALL(config, subscript_key("should_stat"))
EXPECT_CALL(config, subscript_key("rotate"))
.Times(1)
.WillOnce(Return(nullptr));

Expand Down Expand Up @@ -133,7 +139,7 @@ TEST(factory, FlushIntervalFromConfig) {
.Times(1)
.WillOnce(Return(false));

EXPECT_CALL(config, subscript_key("should_stat"))
EXPECT_CALL(config, subscript_key("rotate"))
.Times(1)
.WillOnce(Return(nullptr));

Expand Down Expand Up @@ -171,7 +177,7 @@ TEST(factory, BinaryUnitFlushIntervalFromConfig) {
.Times(1)
.WillOnce(Return("100MB"));

EXPECT_CALL(config, subscript_key("should_stat"))
EXPECT_CALL(config, subscript_key("rotate"))
.Times(1)
.WillOnce(Return(nullptr));

Expand Down

0 comments on commit 2c974e0

Please sign in to comment.