├── examples ├── .gitkeep ├── 3.config.json ├── 3.config-tskv.json ├── 1.hello.cpp ├── 3.config.cpp ├── 4.config_facade.cpp └── 2.simple.cpp ├── debian ├── compat ├── docs ├── libblackhole1.dirs ├── source │ └── format ├── blackhole-dev.dirs ├── blackhole-dev.install ├── libblackhole1.install ├── blackhole-dev.links ├── blackhole-migration-dev.dirs ├── rules ├── blackhole-migration-dev.links ├── copyright └── control ├── .gitignore ├── bench ├── main.cpp ├── mod.hpp ├── record.cpp ├── system │ └── thread.cpp ├── recordbuf.cpp ├── queue.cpp ├── cpp14formatter.cpp ├── attribute.cpp ├── clock.cpp └── formatter │ └── tskv.cpp ├── src ├── handler │ ├── dev.hpp │ ├── blocking.hpp │ └── blocking.cpp ├── spinlock.hpp ├── datetime.hpp ├── handler.cpp ├── formatter │ ├── mod.cpp │ ├── string │ │ ├── grammar.hpp │ │ ├── error.hpp │ │ ├── error.cpp │ │ ├── parser.hpp │ │ ├── grammar.inl.hpp │ │ └── token.cpp │ └── json.hpp ├── essentials.hpp ├── logger.cpp ├── config │ ├── factory.hpp │ ├── node.cpp │ ├── factory.cpp │ ├── json.cpp │ └── option.cpp ├── util │ ├── deleter.hpp │ ├── unimplemented.hpp │ ├── optional.hpp │ └── time.hpp ├── scope │ ├── manager.cpp │ ├── holder.cpp │ └── watcher.cpp ├── memory.hpp ├── filter │ ├── zen.hpp │ └── severity.cpp ├── procname.hpp ├── sink │ ├── file │ │ ├── rotate.hpp │ │ ├── rotate │ │ │ ├── null.hpp │ │ │ └── stat.hpp │ │ ├── stream.hpp │ │ ├── flusher.hpp │ │ └── flusher │ │ │ ├── repeat.hpp │ │ │ └── bytecount.hpp │ ├── null.cpp │ ├── console.hpp │ ├── socket │ │ ├── tcp.hpp │ │ ├── udp.hpp │ │ └── udp.cpp │ ├── syslog.hpp │ ├── asynchronous.hpp │ ├── syslog.cpp │ ├── asynchronous.cpp │ └── file.hpp ├── record.hpp ├── spinlock.osx.hpp ├── attribute.hpp ├── datetime │ ├── generator.other.hpp │ ├── generator.linux.hpp │ ├── stream.hpp │ └── generator.linux.cpp ├── spinlock.linux.hpp ├── wrapper.cpp ├── procname.cpp ├── essentials.cpp ├── hack │ └── addressof.hpp ├── termcolor.cpp └── record.cpp ├── libblackhole1.version ├── tests ├── severity.cpp ├── src │ ├── mocks │ │ ├── handler.cpp │ │ ├── logger.cpp │ │ ├── sink.cpp │ │ └── formatter.cpp │ └── unit │ │ ├── formatter │ │ └── string │ │ │ └── token.cpp │ │ ├── stdext │ │ └── string_view.cpp │ │ ├── sink │ │ ├── file │ │ │ ├── stream.cpp │ │ │ └── flusher │ │ │ │ ├── repeat.cpp │ │ │ │ └── bytecount.cpp │ │ ├── null.cpp │ │ ├── syslog.cpp │ │ ├── console.cpp │ │ ├── udp.cpp │ │ └── asynchronous.cpp │ │ ├── detail │ │ ├── mpsc.cpp │ │ └── handler │ │ │ └── blocking.cpp │ │ ├── termcolor.cpp │ │ └── time.cpp ├── include │ └── mocks │ │ ├── handler.hpp │ │ ├── sink.hpp │ │ ├── formatter.hpp │ │ ├── scope │ │ └── manager.hpp │ │ ├── logger.hpp │ │ ├── registry.hpp │ │ └── node.hpp ├── rc │ ├── filter.cpp │ └── assign.cpp ├── wrapper.cpp ├── cmake │ └── PrepareGoogleTesting.cmake ├── registry.cpp └── record.cpp ├── .gitmodules ├── include └── blackhole │ ├── severity.hpp │ ├── filter │ └── severity.hpp │ ├── config │ ├── factory.hpp │ ├── json.hpp │ ├── option.hpp │ └── node.hpp │ ├── handler.hpp │ ├── factory.hpp │ ├── sink │ ├── syslog.hpp │ ├── null.hpp │ ├── socket │ │ ├── udp.hpp │ │ └── tcp.hpp │ ├── console.hpp │ └── asynchronous.hpp │ ├── extensions │ ├── writer.hpp │ └── facade.inl.hpp │ ├── filter.hpp │ ├── formatter.hpp │ ├── sink.hpp │ ├── wrapper.hpp │ ├── forward.hpp │ ├── scope │ ├── manager.hpp │ ├── holder.hpp │ └── watcher.hpp │ ├── handler │ ├── dev.hpp │ └── blocking.hpp │ ├── message.hpp │ ├── formatter │ └── tskv.hpp │ ├── termcolor.hpp │ ├── attributes.hpp │ ├── record.hpp │ ├── logger.hpp │ ├── root.hpp │ └── registry.hpp ├── .shippable.yml ├── docker ├── vivid │ ├── gcc │ │ └── Dockerfile │ └── clang │ │ └── Dockerfile └── xenial │ └── gcc │ └── Dockerfile ├── Dockerfile ├── .travis.yml └── LICENSE /examples/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 8 2 | -------------------------------------------------------------------------------- /debian/docs: -------------------------------------------------------------------------------- 1 | README.md 2 | -------------------------------------------------------------------------------- /debian/libblackhole1.dirs: -------------------------------------------------------------------------------- 1 | usr/lib 2 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /debian/blackhole-dev.dirs: -------------------------------------------------------------------------------- 1 | usr/include 2 | -------------------------------------------------------------------------------- /debian/blackhole-dev.install: -------------------------------------------------------------------------------- 1 | usr/include/* 2 | -------------------------------------------------------------------------------- /debian/libblackhole1.install: -------------------------------------------------------------------------------- 1 | usr/lib/lib*.so.* 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | bench/*.py 3 | docs 4 | 5 | .clang_complete 6 | -------------------------------------------------------------------------------- /bench/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | BENCHMARK_MAIN(); 4 | -------------------------------------------------------------------------------- /debian/blackhole-dev.links: -------------------------------------------------------------------------------- 1 | usr/lib/libblackhole.so.1 usr/lib/libblackhole.so 2 | -------------------------------------------------------------------------------- /debian/blackhole-migration-dev.dirs: -------------------------------------------------------------------------------- 1 | usr/include/blackhole/v1 2 | usr/include/blackhole/v1/formatter 3 | -------------------------------------------------------------------------------- /src/handler/dev.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace blackhole { 4 | inline namespace v1 { 5 | 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /libblackhole1.version: -------------------------------------------------------------------------------- 1 | { 2 | global: 3 | *; 4 | local: 5 | *rapidjson*; 6 | *boost4asio6detail*; 7 | }; 8 | -------------------------------------------------------------------------------- /src/spinlock.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __linux__ 4 | # include "spinlock.linux.hpp" 5 | #elif __APPLE__ 6 | # include "spinlock.osx.hpp" 7 | #endif 8 | -------------------------------------------------------------------------------- /src/datetime.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __linux__ 4 | #include "datetime/generator.linux.hpp" 5 | #else 6 | #include "datetime/generator.other.hpp" 7 | #endif 8 | -------------------------------------------------------------------------------- /src/handler.cpp: -------------------------------------------------------------------------------- 1 | #include "blackhole/handler.hpp" 2 | 3 | namespace blackhole { 4 | inline namespace v1 { 5 | 6 | handler_t::~handler_t() = default; 7 | 8 | } // namespace v1 9 | } // namespace blackhole 10 | -------------------------------------------------------------------------------- /src/formatter/mod.cpp: -------------------------------------------------------------------------------- 1 | #include "blackhole/formatter.hpp" 2 | 3 | namespace blackhole { 4 | inline namespace v1 { 5 | 6 | formatter_t::~formatter_t() = default; 7 | 8 | } // namespace v1 9 | } // namespace blackhole 10 | -------------------------------------------------------------------------------- /src/essentials.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "blackhole/forward.hpp" 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | 8 | auto essentials(registry_t& registry) -> void; 9 | 10 | } // namespace v1 11 | } // namespace blackhole 12 | -------------------------------------------------------------------------------- /src/logger.cpp: -------------------------------------------------------------------------------- 1 | #include "blackhole/logger.hpp" 2 | 3 | #include "blackhole/attribute.hpp" 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | 8 | logger_t::~logger_t() = default; 9 | 10 | } // namespace v1 11 | } // namespace blackhole 12 | -------------------------------------------------------------------------------- /src/config/factory.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace blackhole { 4 | inline namespace v1 { 5 | namespace config { 6 | 7 | template 8 | class factory; 9 | 10 | } // namespace config 11 | } // namespace v1 12 | } // namespace blackhole 13 | -------------------------------------------------------------------------------- /src/config/node.cpp: -------------------------------------------------------------------------------- 1 | #include "blackhole/config/node.hpp" 2 | 3 | namespace blackhole { 4 | inline namespace v1 { 5 | namespace config { 6 | 7 | node_t::~node_t() = default; 8 | 9 | } // namespace config 10 | } // namespace v1 11 | } // namespace blackhole 12 | -------------------------------------------------------------------------------- /src/util/deleter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace blackhole { 4 | inline namespace v1 { 5 | 6 | template 7 | auto deleter_t::operator()(T* value) -> void { 8 | delete value; 9 | } 10 | 11 | } // namespace v1 12 | } // namespace blackhole 13 | -------------------------------------------------------------------------------- /src/scope/manager.cpp: -------------------------------------------------------------------------------- 1 | #include "blackhole/scope/manager.hpp" 2 | 3 | namespace blackhole { 4 | inline namespace v1 { 5 | namespace scope { 6 | 7 | manager_t::~manager_t() = default; 8 | 9 | } // namespace scope 10 | } // namespace v1 11 | } // namespace blackhole 12 | -------------------------------------------------------------------------------- /src/util/unimplemented.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /// Three ways of ignorance. 6 | #define BLACKHOLE_UNIMPLEMENTED() \ 7 | BOOST_ASSERT(false && "not implemented yet"); \ 8 | throw std::runtime_error("not implemented yet"); \ 9 | std::terminate() 10 | -------------------------------------------------------------------------------- /tests/severity.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | namespace blackhole { 6 | namespace testing { 7 | 8 | TEST(severity_t, FromInt) { 9 | severity_t severity(4); 10 | EXPECT_EQ(4, severity); 11 | } 12 | 13 | } // namespace testing 14 | } // namespace blackhole 15 | -------------------------------------------------------------------------------- /tests/src/mocks/handler.cpp: -------------------------------------------------------------------------------- 1 | #include "mocks/handler.hpp" 2 | 3 | #include 4 | 5 | namespace blackhole { 6 | namespace testing { 7 | namespace mock { 8 | 9 | handler_t::handler_t() {} 10 | handler_t::~handler_t() {} 11 | 12 | } // namespace mock 13 | } // namespace testing 14 | } // namespace blackhole 15 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "foreign/modules"] 2 | path = foreign/modules 3 | url = https://github.com/3Hren/modules.git 4 | [submodule "foreign/rapidjson"] 5 | path = foreign/rapidjson 6 | url = https://github.com/miloyip/rapidjson.git 7 | [submodule "foreign/libcds"] 8 | path = foreign/libcds 9 | url = https://github.com/khizmax/libcds.git 10 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # -*- makefile -*- 3 | DH_VERBOSE = 1 4 | 5 | %: 6 | dh $@ 7 | 8 | override_dh_auto_configure: 9 | cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_VERBOSE_MAKEFILE=ON 10 | 11 | .PHONY: override_dh_strip 12 | override_dh_strip: 13 | dh_strip --dbg-package=blackhole-dbg 14 | -------------------------------------------------------------------------------- /tests/src/mocks/logger.cpp: -------------------------------------------------------------------------------- 1 | #include "mocks/logger.hpp" 2 | 3 | #include 4 | 5 | namespace blackhole { 6 | namespace testing { 7 | namespace mock { 8 | 9 | logger_t::logger_t() = default; 10 | logger_t::~logger_t() = default; 11 | 12 | } // namespace mock 13 | } // namespace testing 14 | } // namespace blackhole 15 | -------------------------------------------------------------------------------- /bench/mod.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Some private magic, but it's okay, since I manage the library version myself. 4 | #define NBENCHMARK(name, n) \ 5 | BENCHMARK_PRIVATE_DECLARE(n) = \ 6 | (::benchmark::internal::RegisterBenchmarkInternal( \ 7 | new ::benchmark::internal::FunctionBenchmark(name, n))) 8 | -------------------------------------------------------------------------------- /src/memory.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | 8 | template 9 | auto make_unique(Args&&... args) -> std::unique_ptr { 10 | return std::unique_ptr(new T(std::forward(args)...)); 11 | } 12 | 13 | } // namespace v1 14 | } // namespace blackhole 15 | -------------------------------------------------------------------------------- /tests/src/mocks/sink.cpp: -------------------------------------------------------------------------------- 1 | #include "mocks/sink.hpp" 2 | 3 | #include 4 | #include 5 | 6 | namespace blackhole { 7 | namespace testing { 8 | namespace mock { 9 | 10 | sink_t::sink_t() {} 11 | sink_t::~sink_t() {} 12 | 13 | } // namespace mock 14 | } // namespace testing 15 | } // namespace blackhole 16 | -------------------------------------------------------------------------------- /tests/src/mocks/formatter.cpp: -------------------------------------------------------------------------------- 1 | #include "mocks/formatter.hpp" 2 | 3 | #include 4 | #include 5 | 6 | namespace blackhole { 7 | namespace testing { 8 | namespace mock { 9 | 10 | formatter_t::formatter_t() {} 11 | formatter_t::~formatter_t() {} 12 | 13 | } // namespace mock 14 | } // namespace testing 15 | } // namespace blackhole 16 | -------------------------------------------------------------------------------- /debian/blackhole-migration-dev.links: -------------------------------------------------------------------------------- 1 | usr/include/blackhole/attribute.hpp usr/include/blackhole/v1/attribute.hpp 2 | usr/include/blackhole/formatter.hpp usr/include/blackhole/v1/formatter.hpp 3 | usr/include/blackhole/formatter/string.hpp usr/include/blackhole/v1/formatter/string.hpp 4 | usr/include/blackhole/logger.hpp usr/include/blackhole/v1/logger.hpp 5 | usr/include/blackhole/record.hpp usr/include/blackhole/v1/record.hpp 6 | -------------------------------------------------------------------------------- /include/blackhole/severity.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace blackhole { 4 | inline namespace v1 { 5 | 6 | class severity_t { 7 | int value; 8 | 9 | public: 10 | constexpr severity_t(int value) noexcept : 11 | value(value) 12 | {} 13 | 14 | constexpr operator int() const noexcept { 15 | return value; 16 | } 17 | }; 18 | 19 | } // namespace v1 20 | } // namespace blackhole 21 | -------------------------------------------------------------------------------- /src/filter/zen.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "blackhole/filter.hpp" 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | namespace filter { 8 | 9 | class zen_t : public filter_t { 10 | public: 11 | auto filter(const record_t&) -> filter_t::action_t override { 12 | return filter_t::action_t::neutral; 13 | } 14 | }; 15 | 16 | } // namespace filter 17 | } // namespace v1 18 | } // namespace blackhole 19 | -------------------------------------------------------------------------------- /src/procname.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | namespace stdext { 8 | 9 | template 10 | class basic_string_view; 11 | 12 | typedef basic_string_view> string_view; 13 | 14 | } // namespace stdext 15 | 16 | auto procname() -> stdext::string_view; 17 | 18 | } // namespace v1 19 | } // namespace blackhole 20 | -------------------------------------------------------------------------------- /src/config/factory.cpp: -------------------------------------------------------------------------------- 1 | #include "blackhole/config/factory.hpp" 2 | 3 | #include "blackhole/forward.hpp" 4 | 5 | #include "../util/deleter.hpp" 6 | 7 | namespace blackhole { 8 | inline namespace v1 { 9 | namespace config { 10 | 11 | factory_t::~factory_t() = default; 12 | 13 | } // namespace config 14 | 15 | template auto deleter_t::operator()(config::factory_t* value) -> void; 16 | 17 | } // namespace v1 18 | } // namespace blackhole 19 | -------------------------------------------------------------------------------- /examples/3.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": [ 3 | { 4 | "type": "blocking", 5 | "formatter": { 6 | "type": "string", 7 | "sevmap": ["D", "I", "W", "E"], 8 | "pattern": "{severity}, [{timestamp}]: {message}" 9 | }, 10 | "sinks": [ 11 | { 12 | "type": "console" 13 | } 14 | ] 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /src/formatter/string/grammar.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "token.hpp" 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | namespace formatter { 8 | namespace string { 9 | 10 | auto parse_leftover(const std::string& pattern) -> ph::leftover_t; 11 | auto parse_optional(const std::string& name, const std::string& pattern) -> ph::generic; 12 | 13 | } // namespace string 14 | } // namespace formatter 15 | } // namespace v1 16 | } // namespace blackhole 17 | -------------------------------------------------------------------------------- /tests/include/mocks/handler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace blackhole { 8 | namespace testing { 9 | namespace mock { 10 | 11 | class handler_t : public ::blackhole::handler_t { 12 | public: 13 | handler_t(); 14 | ~handler_t(); 15 | 16 | MOCK_METHOD1(handle, void(const record_t&)); 17 | }; 18 | 19 | } // namespace mock 20 | } // namespace testing 21 | } // namespace blackhole 22 | -------------------------------------------------------------------------------- /tests/include/mocks/sink.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace blackhole { 8 | namespace testing { 9 | namespace mock { 10 | 11 | class sink_t : public ::blackhole::sink_t { 12 | public: 13 | sink_t(); 14 | ~sink_t(); 15 | 16 | MOCK_METHOD2(emit, void(const record_t&, const string_view&)); 17 | }; 18 | 19 | } // namespace mock 20 | } // namespace testing 21 | } // namespace blackhole 22 | -------------------------------------------------------------------------------- /tests/include/mocks/formatter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace blackhole { 8 | namespace testing { 9 | namespace mock { 10 | 11 | class formatter_t : public ::blackhole::formatter_t { 12 | public: 13 | formatter_t(); 14 | ~formatter_t(); 15 | 16 | MOCK_METHOD2(format, void(const record_t&, writer_t&)); 17 | }; 18 | 19 | } // namespace mock 20 | } // namespace testing 21 | } // namespace blackhole 22 | -------------------------------------------------------------------------------- /tests/src/unit/formatter/string/token.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | namespace formatter { 8 | namespace string { 9 | namespace { 10 | 11 | TEST(name, SurroundSpecWithBraces) { 12 | EXPECT_EQ("{:<20}", ph::attribute("<20").format); 13 | } 14 | 15 | } // namespace 16 | } // namespace string 17 | } // namespace formatter 18 | } // namespace v1 19 | } // namespace blackhole 20 | -------------------------------------------------------------------------------- /.shippable.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | 3 | build: 4 | ci: 5 | - sudo apt-get update 6 | - sudo apt-get install git 7 | - sudo apt-get install cmake 8 | - sudo apt-get install g++ 9 | - sudo apt-get install libboost-dev 10 | - sudo apt-get install libboost-thread-dev 11 | - sudo apt-get install libboost-system-dev 12 | - sudo apt-get install valgrind 13 | - mkdir -p build && cd build 14 | - cmake -DENABLE_TESTING=ON .. 15 | - make 16 | - valgrind ./blackhole-tests 17 | -------------------------------------------------------------------------------- /tests/src/unit/stdext/string_view.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | namespace stdext { 8 | namespace { 9 | 10 | TEST(string_view, CompareWithString) { 11 | EXPECT_EQ(string_view("message"), std::string("message")); 12 | EXPECT_EQ(std::string("message"), string_view("message")); 13 | } 14 | 15 | } // namespace 16 | } // namespace stdext 17 | } // namespace v1 18 | } // namespace blackhole 19 | -------------------------------------------------------------------------------- /include/blackhole/filter/severity.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "blackhole/factory.hpp" 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | namespace filter { 8 | 9 | class severity_t; 10 | 11 | } // namespace filter 12 | 13 | template<> 14 | class factory : public factory { 15 | public: 16 | auto type() const noexcept -> const char* override; 17 | auto from(const config::node_t& config) const -> std::unique_ptr override; 18 | }; 19 | 20 | } // namespace v1 21 | } // namespace blackhole 22 | -------------------------------------------------------------------------------- /tests/include/mocks/scope/manager.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace blackhole { 8 | namespace testing { 9 | namespace mock { 10 | namespace scope { 11 | 12 | class manager_t : public ::blackhole::scope::manager_t { 13 | public: 14 | MOCK_CONST_METHOD0(get, ::blackhole::scope::watcher_t*()); 15 | MOCK_METHOD1(reset, void(::blackhole::scope::watcher_t*)); 16 | }; 17 | 18 | } // namespace scope 19 | } // namespace mock 20 | } // namespace testing 21 | } // namespace blackhole 22 | -------------------------------------------------------------------------------- /src/sink/file/rotate.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace blackhole { 4 | inline namespace v1 { 5 | namespace sink { 6 | namespace file { 7 | 8 | class rotate_t { 9 | public: 10 | virtual ~rotate_t() = default; 11 | virtual auto should_rotate() -> bool = 0; 12 | }; 13 | 14 | class rotate_factory_t { 15 | public: 16 | virtual ~rotate_factory_t() = default; 17 | virtual auto create(const std::string& filename) const -> std::unique_ptr = 0; 18 | }; 19 | 20 | } // namespace file 21 | } // namespace sink 22 | } // namespace v1 23 | } // namespace blackhole 24 | -------------------------------------------------------------------------------- /src/record.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "blackhole/record.hpp" 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | 8 | struct record_t::inner_t { 9 | std::reference_wrapper message; 10 | std::reference_wrapper formatted; 11 | 12 | severity_t severity; 13 | time_point timestamp; 14 | 15 | std::uint64_t lwp; 16 | std::thread::native_handle_type tid; 17 | char __pad[8]; 18 | 19 | std::reference_wrapper attributes; 20 | }; 21 | 22 | } // namespace v1 23 | } // namespace blackhole 24 | -------------------------------------------------------------------------------- /tests/src/unit/sink/file/stream.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace blackhole { 8 | inline namespace v1 { 9 | namespace sink { 10 | namespace file { 11 | namespace { 12 | 13 | TEST(ofstream_factory_t, ThrowsIfUnableToOpenStream) { 14 | ofstream_factory_t factory; 15 | EXPECT_THROW(factory.create("/__mythic/file.log", std::ios_base::app), std::system_error); 16 | } 17 | 18 | } // namespace 19 | } // namespace file 20 | } // namespace sink 21 | } // namespace v1 22 | } // namespace blackhole 23 | -------------------------------------------------------------------------------- /src/util/optional.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | namespace util { 8 | 9 | /// This hack is required because of `boost::optional` 1.46, which I have to support and which 10 | /// can not map on none value. 11 | template 12 | auto value_or(const boost::optional& optional, F fn) -> T { 13 | if (optional) { 14 | return optional.get(); 15 | } else { 16 | return fn(); 17 | } 18 | } 19 | 20 | } // namespace util 21 | } // namespace v1 22 | } // namespace blackhole 23 | -------------------------------------------------------------------------------- /src/spinlock.osx.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | 8 | class spinlock_t { 9 | os_unfair_lock mutex; 10 | 11 | public: 12 | constexpr spinlock_t() noexcept : mutex({0}) {} 13 | 14 | auto lock() noexcept -> void { 15 | os_unfair_lock_lock(&mutex); 16 | } 17 | 18 | auto unlock() noexcept -> void { 19 | os_unfair_lock_unlock(&mutex); 20 | } 21 | 22 | auto trylock() noexcept -> bool { 23 | return os_unfair_lock_trylock(&mutex); 24 | } 25 | }; 26 | 27 | } // namespace v1 28 | } // namespace blackhole 29 | -------------------------------------------------------------------------------- /include/blackhole/config/factory.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../forward.hpp" 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | namespace config { 8 | 9 | class node_t; 10 | 11 | /// Represents an interface for logger configuration factory. 12 | class factory_t { 13 | public: 14 | /// Destroys the factory with freeing all its associated resources. 15 | virtual ~factory_t() = 0; 16 | 17 | /// Returns a const lvalue reference to the root configuration. 18 | virtual auto config() const -> const node_t& = 0; 19 | }; 20 | 21 | } // namespace config 22 | } // namespace v1 23 | } // namespace blackhole 24 | -------------------------------------------------------------------------------- /bench/record.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "mod.hpp" 7 | 8 | namespace blackhole { 9 | namespace benchmark { 10 | 11 | static void record(::benchmark::State& state) { 12 | const string_view message("GET /porn.png HTTP/1.1"); 13 | const attribute_pack pack; 14 | 15 | while (state.KeepRunning()) { 16 | record_t(42, message, pack); 17 | } 18 | 19 | state.SetItemsProcessed(state.iterations()); 20 | } 21 | 22 | NBENCHMARK("record[ctor]", record); 23 | 24 | } // namespace benchmark 25 | } // namespace blackhole 26 | -------------------------------------------------------------------------------- /bench/system/thread.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "mod.hpp" 6 | 7 | namespace blackhole { 8 | namespace benchmark { 9 | namespace { 10 | 11 | /// Cheap operation on OS X, relatively expensive on linux. 12 | void thread_name(::benchmark::State& state) { 13 | while (state.KeepRunning()) { 14 | char buffer[16]; 15 | ::pthread_getname_np(::pthread_self(), buffer, 16); 16 | } 17 | 18 | state.SetItemsProcessed(state.iterations()); 19 | } 20 | 21 | NBENCHMARK("thread.name", thread_name); 22 | 23 | } // namespace 24 | } // namespace benchmark 25 | } // namespace blackhole 26 | -------------------------------------------------------------------------------- /docker/vivid/gcc/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:vivid 2 | 3 | RUN apt-get update 4 | 5 | RUN apt-get -y install cmake 6 | RUN apt-get -y install g++ 7 | RUN apt-get -y install git 8 | RUN apt-get -y install libboost-dev 9 | RUN apt-get -y install libboost-system-dev 10 | RUN apt-get -y install libboost-thread-dev 11 | RUN apt-get -y install make 12 | RUN apt-get -y install valgrind 13 | 14 | COPY . /code/blackhole/ 15 | 16 | RUN dpkg -l | grep g++ 17 | RUN dpkg -l | grep boost 18 | 19 | RUN mkdir -p /code/blackhole/build 20 | WORKDIR /code/blackhole/build 21 | 22 | RUN rm -rf * 23 | RUN cmake -DENABLE_TESTING=ON .. 24 | RUN make -j1 25 | RUN valgrind ./blackhole-tests 26 | -------------------------------------------------------------------------------- /docker/xenial/gcc/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:xenial 2 | 3 | RUN apt-get update 4 | 5 | RUN apt-get -y install cmake 6 | RUN apt-get -y install g++ 7 | RUN apt-get -y install git 8 | RUN apt-get -y install libboost-dev 9 | RUN apt-get -y install libboost-system-dev 10 | RUN apt-get -y install libboost-thread-dev 11 | RUN apt-get -y install make 12 | RUN apt-get -y install valgrind 13 | 14 | COPY . /code/blackhole/ 15 | 16 | RUN dpkg -l | grep g++ 17 | RUN dpkg -l | grep boost 18 | 19 | RUN mkdir -p /code/blackhole/build 20 | WORKDIR /code/blackhole/build 21 | 22 | RUN rm -rf * 23 | RUN cmake -DENABLE_TESTING=ON .. 24 | RUN make -j1 25 | RUN valgrind ./blackhole-tests 26 | -------------------------------------------------------------------------------- /include/blackhole/handler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | 8 | class record_t; 9 | 10 | /// Represents logging handler interface. 11 | class handler_t { 12 | public: 13 | virtual ~handler_t() = 0; 14 | 15 | /// Handles the given record. 16 | /// 17 | /// By handling a record we usually mean doing at least three actions over it: filtering, 18 | /// formatting and emitting to the targets. 19 | /// 20 | /// \warning must be thread-safe. 21 | virtual auto handle(const record_t& record) -> void = 0; 22 | }; 23 | 24 | } // namespace v1 25 | } // namespace blackhole 26 | -------------------------------------------------------------------------------- /include/blackhole/factory.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "forward.hpp" 6 | 7 | namespace blackhole { 8 | inline namespace v1 { 9 | 10 | class factory_t { 11 | public: 12 | virtual ~factory_t() = default; 13 | 14 | /// Returns factory type as a string literal, that is used to identity one factory of a given 15 | /// type from another. 16 | virtual auto type() const noexcept -> const char* = 0; 17 | }; 18 | 19 | template 20 | class factory : public factory_t { 21 | public: 22 | virtual auto from(const config::node_t& config) const -> std::unique_ptr = 0; 23 | }; 24 | 25 | } // namespace v1 26 | } // namespace blackhole 27 | -------------------------------------------------------------------------------- /src/handler/blocking.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "blackhole/handler.hpp" 7 | #include "blackhole/forward.hpp" 8 | 9 | namespace blackhole { 10 | inline namespace v1 { 11 | namespace handler { 12 | 13 | class blocking_t : public handler_t { 14 | std::unique_ptr formatter; 15 | std::vector> sinks; 16 | 17 | public: 18 | blocking_t(std::unique_ptr formatter, std::vector> sinks); 19 | 20 | virtual auto handle(const record_t& record) -> void override; 21 | }; 22 | 23 | } // namespace handler 24 | } // namespace v1 25 | } // namespace blackhole 26 | -------------------------------------------------------------------------------- /include/blackhole/sink/syslog.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "blackhole/factory.hpp" 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | namespace sink { 8 | 9 | class syslog_t; 10 | 11 | } // namespace sink 12 | 13 | template<> 14 | class factory : public factory { 15 | const registry_t& registry; 16 | 17 | public: 18 | constexpr explicit factory(const registry_t& registry) noexcept : 19 | registry(registry) 20 | {} 21 | 22 | auto type() const noexcept -> const char* override; 23 | auto from(const config::node_t& config) const -> std::unique_ptr override; 24 | }; 25 | 26 | } // namespace v1 27 | } // namespace blackhole 28 | -------------------------------------------------------------------------------- /src/attribute.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | namespace attribute { 8 | 9 | struct value_t::inner_t { 10 | typedef boost::make_variant_over::type type; 11 | 12 | type value; 13 | 14 | template 15 | inner_t(T&& value) : value(std::forward(value)) {} 16 | }; 17 | 18 | struct view_t::inner_t { 19 | typedef boost::make_variant_over::type type; 20 | 21 | type value; 22 | 23 | template 24 | inner_t(T&& value) : value(std::forward(value)) {} 25 | }; 26 | 27 | } // namespace attribute 28 | } // namespace v1 29 | } // namespace blackhole 30 | -------------------------------------------------------------------------------- /tests/include/mocks/logger.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace blackhole { 9 | namespace testing { 10 | namespace mock { 11 | 12 | class logger_t : public ::blackhole::logger_t { 13 | public: 14 | logger_t(); 15 | ~logger_t(); 16 | 17 | MOCK_METHOD2(log, void(severity_t, const message_t&)); 18 | MOCK_METHOD3(log, void(severity_t, const message_t&, attribute_pack&)); 19 | MOCK_METHOD3(log, void(severity_t, const lazy_message_t&, attribute_pack&)); 20 | MOCK_METHOD0(manager, scope::manager_t&()); 21 | }; 22 | 23 | } // namespace mock 24 | } // namespace testing 25 | } // namespace blackhole 26 | -------------------------------------------------------------------------------- /src/sink/file/rotate/null.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../../../memory.hpp" 4 | #include "../rotate.hpp" 5 | 6 | namespace blackhole { 7 | inline namespace v1 { 8 | namespace sink { 9 | namespace file { 10 | namespace rotate { 11 | 12 | class null_rotate_t : public rotate_t { 13 | public: 14 | auto should_rotate() -> bool override { 15 | return false; 16 | } 17 | }; 18 | 19 | class null_factory_t : public rotate_factory_t { 20 | public: 21 | auto create(const std::string&) const -> std::unique_ptr override { 22 | return blackhole::make_unique(); 23 | } 24 | }; 25 | 26 | } // namespace rotate 27 | } // namespace file 28 | } // namespace sink 29 | } // namespace v1 30 | } // namespace blackhole 31 | -------------------------------------------------------------------------------- /src/sink/null.cpp: -------------------------------------------------------------------------------- 1 | #include "blackhole/sink/null.hpp" 2 | 3 | #include "blackhole/stdext/string_view.hpp" 4 | #include "blackhole/record.hpp" 5 | #include "blackhole/sink.hpp" 6 | 7 | namespace blackhole { 8 | inline namespace v1 { 9 | namespace sink { 10 | 11 | class null_t : public sink_t { 12 | public: 13 | auto emit(const record_t&, const string_view&) -> void override {} 14 | }; 15 | 16 | } // namespace sink 17 | 18 | auto factory::type() const noexcept -> const char* { 19 | return "null"; 20 | } 21 | 22 | auto factory::from(const config::node_t&) const -> std::unique_ptr { 23 | return std::unique_ptr(new sink::null_t); 24 | } 25 | 26 | } // namespace v1 27 | } // namespace blackhole 28 | -------------------------------------------------------------------------------- /examples/3.config-tskv.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": [ 3 | { 4 | "type": "blocking", 5 | "formatter": { 6 | "type": "tskv", 7 | "create": { 8 | "tskv_format": "YT" 9 | }, 10 | "rename": { 11 | "severity": "verbosity" 12 | }, 13 | "mutate": { 14 | "unixtime": { 15 | "strftime": "%s.%f" 16 | } 17 | }, 18 | "remove": ["timestamp"] 19 | }, 20 | "sinks": [ 21 | { 22 | "type": "console" 23 | } 24 | ] 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/sink/file/stream.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace blackhole { 7 | inline namespace v1 { 8 | namespace sink { 9 | namespace file { 10 | 11 | class stream_factory_t { 12 | public: 13 | virtual ~stream_factory_t() = default; 14 | virtual auto create(const std::string& filename, std::ios_base::openmode mode) const -> 15 | std::unique_ptr = 0; 16 | }; 17 | 18 | class ofstream_factory_t : public stream_factory_t { 19 | public: 20 | virtual auto create(const std::string& filename, std::ios_base::openmode mode) const -> 21 | std::unique_ptr override; 22 | }; 23 | 24 | } // namespace file 25 | } // namespace sink 26 | } // namespace v1 27 | } // namespace blackhole 28 | -------------------------------------------------------------------------------- /tests/src/unit/sink/null.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include "mocks/node.hpp" 8 | 9 | namespace blackhole { 10 | inline namespace v1 { 11 | namespace sink { 12 | namespace { 13 | 14 | using ::testing::StrictMock; 15 | 16 | TEST(null_t, type) { 17 | EXPECT_EQ(std::string("null"), factory().type()); 18 | } 19 | 20 | TEST(null_t, factory) { 21 | StrictMock config; 22 | 23 | // NOTE: Actually does nothing, none of mock methods should be called. 24 | factory().from(config); 25 | } 26 | 27 | } // namespace 28 | } // namespace sink 29 | } // namespace v1 30 | } // namespace blackhole 31 | -------------------------------------------------------------------------------- /include/blackhole/extensions/writer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "blackhole/stdext/string_view.hpp" 4 | #include "blackhole/extensions/format.hpp" 5 | 6 | namespace blackhole { 7 | inline namespace v1 { 8 | 9 | using stdext::string_view; 10 | 11 | /// Represents stream writer backed up by cppformat. 12 | class writer_t { 13 | public: 14 | fmt::MemoryWriter inner; 15 | 16 | /// Formats the given arguments using the underlying formatter. 17 | template 18 | inline auto write(const Args&... args) -> void { 19 | inner.write(args...); 20 | } 21 | 22 | auto result() const noexcept -> string_view { 23 | return string_view(inner.data(), inner.size()); 24 | } 25 | }; 26 | 27 | } // namespace v1 28 | } // namespace blackhole 29 | -------------------------------------------------------------------------------- /include/blackhole/filter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | 8 | class record_t; 9 | 10 | /// Represents logging filter interface. 11 | /// 12 | /// Filters allow log events to be evaluated to determine if or how they should be published. 13 | class filter_t { 14 | public: 15 | enum class action_t { 16 | /// Don't know what to do. Pass to other filters, if there are some. 17 | neutral, 18 | /// Accept record immediately. 19 | accept, 20 | /// Deny record immediately. 21 | deny 22 | }; 23 | 24 | public: 25 | virtual ~filter_t() = default; 26 | virtual auto filter(const record_t& record) -> action_t = 0; 27 | }; 28 | 29 | } // namespace v1 30 | } // namespace blackhole 31 | -------------------------------------------------------------------------------- /bench/recordbuf.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "mod.hpp" 8 | 9 | namespace blackhole { 10 | namespace benchmark { 11 | 12 | static void record(::benchmark::State& state) { 13 | const string_view message("GET /porn.png HTTP/1.1"); 14 | const attribute_list attributes{{"key#1", "value#1"}}; 15 | const attribute_pack pack{attributes}; 16 | 17 | record_t record(42, message, pack); 18 | 19 | while (state.KeepRunning()) { 20 | detail::recordbuf_t owned(record); 21 | } 22 | 23 | state.SetItemsProcessed(state.iterations()); 24 | } 25 | 26 | NBENCHMARK("record[into_owned]", record); 27 | 28 | } // namespace benchmark 29 | } // namespace blackhole 30 | -------------------------------------------------------------------------------- /src/datetime/generator.other.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "blackhole/extensions/format.hpp" 7 | 8 | namespace blackhole { 9 | inline namespace v1 { 10 | namespace datetime { 11 | 12 | struct context_t; 13 | typedef void (*token_t)(context_t&); 14 | typedef fmt::MemoryWriter writer_type; 15 | 16 | class generator_t { 17 | std::vector tokens; 18 | std::vector literals; 19 | 20 | public: 21 | generator_t(std::vector tokens, std::vector literals); 22 | 23 | auto operator()(writer_type& stream, const std::tm& tm, std::uint64_t usec = 0) const -> void; 24 | }; 25 | 26 | auto make_generator(const std::string& pattern) -> generator_t; 27 | 28 | } // namespace datetime 29 | } // namespace v1 30 | } // namespace blackhole 31 | -------------------------------------------------------------------------------- /tests/src/unit/detail/mpsc.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | namespace { 8 | 9 | TEST(mpsc_queue_t, EnqueueDequeue) { 10 | cds::container::VyukovMPSCCycleQueue queue(1024); 11 | 12 | EXPECT_EQ(0, queue.size()); 13 | 14 | queue.enqueue_with([&](int &cell) { 15 | cell = 100500; 16 | }); 17 | 18 | // We didn't enable `empty_item_counter` feature, so no counting occurs. 19 | EXPECT_EQ(0, queue.size()); 20 | 21 | int result = 0; 22 | const auto dequeued = queue.dequeue(result); 23 | 24 | ASSERT_TRUE(dequeued); 25 | EXPECT_EQ(100500, result); 26 | EXPECT_EQ(0, queue.size()); 27 | } 28 | 29 | } // namespace 30 | } // namespace v1 31 | } // namespace blackhole 32 | -------------------------------------------------------------------------------- /src/scope/holder.cpp: -------------------------------------------------------------------------------- 1 | #include "blackhole/scope/holder.hpp" 2 | 3 | namespace blackhole { 4 | inline namespace v1 { 5 | namespace scope { 6 | 7 | namespace { 8 | 9 | // TODO: Move somewhere near `view_of`. 10 | auto transform(const attributes_t& source) -> attribute_list { 11 | attribute_list result; 12 | for (const auto& attribute : source) { 13 | result.emplace_back(attribute); 14 | } 15 | 16 | return result; 17 | } 18 | 19 | } // namespace 20 | 21 | holder_t::holder_t(logger_t& logger, attributes_t attributes): 22 | watcher_t(logger), 23 | storage(std::move(attributes)), 24 | list(transform(storage)) 25 | {} 26 | 27 | auto holder_t::attributes() const -> const attribute_list& { 28 | return list; 29 | } 30 | 31 | } // namespace scope 32 | } // namespace v1 33 | } // namespace blackhole 34 | -------------------------------------------------------------------------------- /include/blackhole/formatter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace blackhole { 4 | inline namespace v1 { 5 | 6 | class record_t; 7 | 8 | class writer_t; 9 | 10 | /// Represents an interface that every formatter must implement. 11 | /// 12 | /// Formatters are responsible for formatting the input logging record using the specified writer. 13 | class formatter_t { 14 | public: 15 | formatter_t() = default; 16 | formatter_t(const formatter_t& other) = default; 17 | formatter_t(formatter_t&& other) = default; 18 | 19 | virtual ~formatter_t() = 0; 20 | 21 | /// Formats the specified logging event record by invoking formatter renderers and writing the 22 | /// result into the given writer. 23 | virtual auto format(const record_t& record, writer_t& writer) -> void = 0; 24 | }; 25 | 26 | } // namespace v1 27 | } // namespace blackhole 28 | -------------------------------------------------------------------------------- /include/blackhole/sink/null.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "blackhole/factory.hpp" 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | namespace sink { 8 | 9 | /// A null sink merely exists, it never outputs a message to any device. 10 | /// 11 | /// This class exists primarily for benchmarking reasons to measure the entire logging processing 12 | /// pipeline. It never fails and never throws, because it does nothing. 13 | /// 14 | /// \remark All methods of this class are thread safe. 15 | class null_t; 16 | 17 | } // namespace sink 18 | 19 | template<> 20 | class factory : public factory { 21 | public: 22 | auto type() const noexcept -> const char* override; 23 | auto from(const config::node_t& config) const -> std::unique_ptr override; 24 | }; 25 | 26 | } // namespace v1 27 | } // namespace blackhole 28 | -------------------------------------------------------------------------------- /docker/vivid/clang/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:vivid 2 | 3 | RUN apt-get update 4 | 5 | RUN apt-get -y install cmake 6 | RUN apt-get -y install g++ 7 | RUN apt-get -y install git 8 | RUN apt-get -y install libboost-dev 9 | RUN apt-get -y install libboost-system-dev 10 | RUN apt-get -y install libboost-thread-dev 11 | RUN apt-get -y install make 12 | RUN apt-get -y install valgrind 13 | 14 | RUN apt-get -y install clang++-3.6 15 | RUN update-alternatives --install /usr/bin/cc cc /usr/bin/clang-3.6 100 16 | RUN update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++-3.6 100 17 | 18 | COPY . /code/blackhole/ 19 | 20 | RUN dpkg -l | grep clang 21 | RUN dpkg -l | grep boost 22 | 23 | RUN mkdir -p /code/blackhole/build 24 | WORKDIR /code/blackhole/build 25 | 26 | RUN rm -rf * 27 | RUN cmake -DENABLE_TESTING=ON .. 28 | RUN make -j1 29 | RUN valgrind ./blackhole-tests 30 | -------------------------------------------------------------------------------- /include/blackhole/sink/socket/udp.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../../factory.hpp" 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | namespace sink { 8 | namespace socket { 9 | 10 | /// The UDP sink is a sink that writes its output to a remote destination specified by a host and 11 | /// port. 12 | class udp_t; 13 | 14 | } // namespace socket 15 | } // namespace sink 16 | 17 | template<> 18 | class factory : public factory { 19 | const registry_t& registry; 20 | 21 | public: 22 | constexpr explicit factory(const registry_t& registry) noexcept : 23 | registry(registry) 24 | {} 25 | 26 | auto type() const noexcept -> const char* override; 27 | auto from(const config::node_t& config) const -> std::unique_ptr override; 28 | }; 29 | 30 | } // namespace v1 31 | } // namespace blackhole 32 | -------------------------------------------------------------------------------- /include/blackhole/sink/socket/tcp.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../../factory.hpp" 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | namespace sink { 8 | namespace socket { 9 | 10 | /// The TCP socket sink is a sink that writes its output to a remote destination specified by a host 11 | /// and port. 12 | class tcp_t; 13 | 14 | } // namespace socket 15 | } // namespace sink 16 | 17 | template<> 18 | class factory : public factory { 19 | const registry_t& registry; 20 | 21 | public: 22 | constexpr explicit factory(const registry_t& registry) noexcept : 23 | registry(registry) 24 | {} 25 | 26 | auto type() const noexcept -> const char* override; 27 | auto from(const config::node_t& config) const -> std::unique_ptr override; 28 | }; 29 | 30 | } // namespace v1 31 | } // namespace blackhole 32 | -------------------------------------------------------------------------------- /include/blackhole/sink.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | namespace stdext { 8 | 9 | template 10 | class basic_string_view; 11 | 12 | typedef basic_string_view> string_view; 13 | 14 | } // namespace stdext 15 | 16 | using stdext::string_view; 17 | 18 | class record_t; 19 | 20 | class sink_t { 21 | public: 22 | sink_t() = default; 23 | sink_t(const sink_t& other) = default; 24 | sink_t(sink_t&& other) = default; 25 | 26 | virtual ~sink_t() {} 27 | 28 | auto operator=(const sink_t& other) -> sink_t& = default; 29 | auto operator=(sink_t&& other) -> sink_t& = default; 30 | 31 | virtual auto emit(const record_t& record, const string_view& message) -> void = 0; 32 | }; 33 | 34 | } // namespace v1 35 | } // namespace blackhole 36 | -------------------------------------------------------------------------------- /bench/queue.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include "mod.hpp" 9 | 10 | namespace blackhole { 11 | namespace benchmark { 12 | 13 | static void io_post(::benchmark::State& state) { 14 | boost::asio::io_service loop; 15 | std::unique_ptr work(new boost::asio::io_service::work(loop)); 16 | 17 | std::uint64_t counter = 0; 18 | std::thread thread([&] { 19 | loop.run(); 20 | }); 21 | 22 | while (state.KeepRunning()) { 23 | loop.post([&] { 24 | ++counter; 25 | }); 26 | } 27 | 28 | work.reset(); 29 | thread.join(); 30 | 31 | state.SetItemsProcessed(state.iterations()); 32 | } 33 | 34 | NBENCHMARK("I/O post", io_post); 35 | 36 | } // namespace benchmark 37 | } // namespace blackhole 38 | -------------------------------------------------------------------------------- /src/spinlock.linux.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | 8 | class spinlock_t { 9 | pthread_spinlock_t mutex; 10 | 11 | public: 12 | spinlock_t() { 13 | const int rc = pthread_spin_init(&mutex, PTHREAD_PROCESS_PRIVATE); 14 | if (rc != 0) { 15 | throw std::system_error(errno, std::system_category()); 16 | } 17 | } 18 | 19 | ~spinlock_t() { 20 | pthread_spin_destroy(&mutex); 21 | } 22 | 23 | // These methods may fail only if the mutex is invalid or on attempt to use it recursively. We 24 | // don't do this. 25 | auto lock() noexcept -> void { 26 | pthread_spin_lock(&mutex); 27 | } 28 | 29 | auto unlock() noexcept -> void { 30 | pthread_spin_unlock(&mutex); 31 | } 32 | }; 33 | 34 | } // namespace v1 35 | } // namespace blackhole 36 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:trusty 2 | 3 | RUN apt-get -y -qq update && \ 4 | apt-get -y -qq install build-essential devscripts equivs 5 | 6 | COPY . /build/blackhole 7 | RUN cd /build/blackhole && \ 8 | DEBIAN_FRONTEND=noninteractive mk-build-deps -ir -t "apt-get -qq --no-install-recommends" 9 | RUN cd /build/blackhole && \ 10 | yes | debuild -e CC -e CXX -uc -us -j$(cat /proc/cpuinfo | fgrep -c processor) && \ 11 | debi 12 | 13 | # Test. 14 | RUN cd /tmp && mkdir build && cd build && \ 15 | cmake -DENABLE_TESTING=ON /build/blackhole && make && \ 16 | ./blackhole-tests 17 | 18 | # Cleanup 19 | RUN DEBIAN_FRONTEND=noninteractive apt-get -qq purge blackhole-build-deps && \ 20 | DEBIAN_FRONTEND=noninteractive apt-get -qq purge build-essential devscripts equivs && \ 21 | DEBIAN_FRONTEND=noninteractive apt-get -qq autoremove --purge && \ 22 | rm -rf build && \ 23 | rm -rf /tmp 24 | -------------------------------------------------------------------------------- /include/blackhole/wrapper.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "blackhole/logger.hpp" 4 | 5 | #include "blackhole/attribute.hpp" 6 | 7 | namespace blackhole { 8 | inline namespace v1 { 9 | 10 | class wrapper_t : public logger_t { 11 | logger_t& inner; 12 | attributes_t storage; 13 | view_of::type attributes_view; 14 | 15 | public: 16 | wrapper_t(logger_t& log, attributes_t attributes); 17 | 18 | auto attributes() const noexcept -> const view_of::type& { 19 | return attributes_view; 20 | } 21 | 22 | auto log(severity_t severity, const message_t& message) -> void; 23 | auto log(severity_t severity, const message_t& message, attribute_pack& pack) -> void; 24 | auto log(severity_t severity, const lazy_message_t& message, attribute_pack& pack) -> void; 25 | 26 | auto manager() -> scope::manager_t&; 27 | }; 28 | 29 | } // namespace v1 30 | } // namespace blackhole 31 | -------------------------------------------------------------------------------- /src/sink/console.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "blackhole/sink.hpp" 7 | #include "blackhole/sink/console.hpp" 8 | 9 | namespace blackhole { 10 | inline namespace v1 { 11 | namespace sink { 12 | 13 | class console_t : public sink_t { 14 | std::ostream& stream_; 15 | std::unique_ptr filter; 16 | std::function mapping_; 17 | 18 | public: 19 | console_t(); 20 | console_t(std::unique_ptr filter); 21 | console_t(std::ostream& stream, std::function mapping); 22 | 23 | auto stream() noexcept -> std::ostream&; 24 | auto mapping(const record_t& record) const -> termcolor_t; 25 | 26 | auto emit(const record_t& record, const string_view& formatted) -> void override; 27 | }; 28 | 29 | } // namespace sink 30 | } // namespace v1 31 | } // namespace blackhole 32 | -------------------------------------------------------------------------------- /src/sink/socket/tcp.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "blackhole/sink.hpp" 9 | 10 | namespace blackhole { 11 | inline namespace v1 { 12 | namespace sink { 13 | namespace socket { 14 | 15 | class tcp_t : public sink_t { 16 | std::string host_; 17 | std::uint16_t port_; 18 | 19 | boost::asio::io_service io_service; 20 | std::unique_ptr socket; 21 | 22 | mutable std::mutex mutex; 23 | 24 | public: 25 | tcp_t(std::string host, std::uint16_t port); 26 | 27 | auto host() const noexcept -> const std::string&; 28 | auto port() const noexcept -> std::uint16_t; 29 | 30 | auto emit(const record_t& record, const string_view& message) -> void override; 31 | }; 32 | 33 | } // namespace socket 34 | } // namespace sink 35 | } // namespace v1 36 | } // namespace blackhole 37 | -------------------------------------------------------------------------------- /src/datetime/generator.linux.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "blackhole/extensions/format.hpp" 10 | 11 | namespace blackhole { 12 | inline namespace v1 { 13 | namespace datetime { 14 | 15 | typedef fmt::MemoryWriter writer_type; 16 | 17 | struct literal_t { 18 | std::string value; 19 | }; 20 | 21 | struct epoch_t {}; 22 | struct usecond_t {}; 23 | 24 | class generator_t { 25 | typedef boost::variant token_t; 26 | 27 | std::vector tokens; 28 | 29 | public: 30 | explicit generator_t(std::string pattern); 31 | 32 | void operator()(writer_type& stream, const std::tm& tm, std::uint64_t usec = 0) const; 33 | }; 34 | 35 | auto make_generator(const std::string& pattern) -> generator_t; 36 | 37 | } // namespace datetime 38 | } // namespace v1 39 | } // namespace blackhole 40 | -------------------------------------------------------------------------------- /include/blackhole/forward.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace blackhole { 4 | inline namespace v1 { 5 | 6 | struct deleter_t { 7 | template 8 | auto operator()(T* value) -> void; 9 | }; 10 | 11 | class record_t; 12 | class writer_t; 13 | class severity_t; 14 | 15 | class formatter_t; 16 | class handler_t; 17 | class filter_t; 18 | class sink_t; 19 | 20 | class registry_t; 21 | 22 | class logger_t; 23 | class root_logger_t; 24 | 25 | template 26 | class builder; 27 | 28 | template 29 | class factory; 30 | 31 | namespace config { 32 | 33 | class node_t; 34 | class factory_t; 35 | 36 | /// Must implement `construct` method, which accepts anything required for factory construction and 37 | /// returns an `std::unique_ptr`. 38 | template 39 | class factory_traits; 40 | 41 | } // namespace config 42 | 43 | class termcolor_t; 44 | 45 | } // namespace v1 46 | } // namespace blackhole 47 | -------------------------------------------------------------------------------- /tests/include/mocks/registry.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace blackhole { 8 | inline namespace v1 { 9 | 10 | class mock_registry_t : public registry_t { 11 | public: 12 | MOCK_CONST_METHOD1(sink, registry_t::sink_factory(const std::string& type)); 13 | MOCK_CONST_METHOD1(filter, registry_t::filter_factory(const std::string& type)); 14 | MOCK_CONST_METHOD1(handler, registry_t::handler_factory(const std::string& type)); 15 | MOCK_CONST_METHOD1(formatter, registry_t::formatter_factory(const std::string& type)); 16 | MOCK_METHOD1(add, void(std::shared_ptr> factory)); 17 | MOCK_METHOD1(add, void(std::shared_ptr> factory)); 18 | MOCK_METHOD1(add, void(std::shared_ptr> factory)); 19 | MOCK_METHOD1(add, void(std::shared_ptr> factory)); 20 | }; 21 | 22 | } // namespace v1 23 | } // namespace blackhole 24 | -------------------------------------------------------------------------------- /include/blackhole/scope/manager.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace blackhole { 4 | inline namespace v1 { 5 | namespace scope { 6 | 7 | class watcher_t; 8 | 9 | /// Represents a thread local storage of a scoped attributes watcher pointer. 10 | class manager_t { 11 | public: 12 | virtual ~manager_t() = 0; 13 | 14 | /// Returns the current scoped attributes watcher pointer value, nullptr otherwise. 15 | /// 16 | /// \note it's valid to return a nullptr as a scoped attributes watcher, meaning that there are 17 | /// no scoped attributes registered before this call. 18 | virtual auto get() const -> watcher_t* = 0; 19 | 20 | /// Resets the current scoped attributes watch with the specified value. 21 | /// 22 | /// \param value scoped attributes watcher, may be nullptr. 23 | /// 24 | /// \post this->get() == value; 25 | virtual auto reset(watcher_t* value) -> void = 0; 26 | }; 27 | 28 | } // namespace scope 29 | } // namespace v1 30 | } // namespace blackhole 31 | -------------------------------------------------------------------------------- /include/blackhole/handler/dev.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../factory.hpp" 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | namespace experimental { 8 | namespace handler { 9 | 10 | class dev_t; 11 | 12 | } // namespace handler 13 | } // namespace experimental 14 | 15 | template<> 16 | class builder { 17 | class inner_t; 18 | std::unique_ptr d; 19 | 20 | public: 21 | builder(); 22 | 23 | auto build() && -> std::unique_ptr; 24 | }; 25 | 26 | template<> 27 | class factory : public factory { 28 | const registry_t& registry; 29 | 30 | public: 31 | constexpr explicit factory(const registry_t& registry) noexcept : 32 | registry(registry) 33 | {} 34 | 35 | auto type() const noexcept -> const char* override; 36 | auto from(const config::node_t& config) const -> std::unique_ptr override; 37 | }; 38 | 39 | } // namespace v1 40 | } // namespace blackhole 41 | -------------------------------------------------------------------------------- /bench/cpp14formatter.cpp: -------------------------------------------------------------------------------- 1 | #if (__GNUC__ >= 6 || defined(__clang__)) && defined(__cpp_constexpr) && __cpp_constexpr >= 201304 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "mod.hpp" 9 | 10 | namespace blackhole { 11 | namespace benchmark { 12 | 13 | static 14 | void 15 | cpp14formatter(::benchmark::State& state) { 16 | while (state.KeepRunning()) { 17 | constexpr auto formatter = blackhole::detail::formatter< 18 | blackhole::detail::literal_count("{} - {} [{}] 'GET {} HTTP/1.0' {} {}") 19 | >("{} - {} [{}] 'GET {} HTTP/1.0' {} {}"); 20 | 21 | fmt::MemoryWriter wr; 22 | formatter.format(wr, "[::]", "esafronov", "10/Oct/2000:13:55:36 -0700", "/porn.png", 200, 2326); 23 | } 24 | 25 | state.SetItemsProcessed(state.iterations()); 26 | } 27 | 28 | NBENCHMARK("c++14::fmt", cpp14formatter); 29 | 30 | } // namespace benchmark 31 | } // namespace blackhole 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /tests/rc/filter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | using namespace blackhole; 8 | 9 | int main(int, char** argv) { 10 | const auto thread_num = boost::lexical_cast(argv[1]); 11 | const auto iterations = boost::lexical_cast(argv[2]); 12 | 13 | root_logger_t logger({}); 14 | 15 | std::vector threads; 16 | 17 | for (int tid = 0; tid < thread_num; ++tid) { 18 | threads.emplace_back([&] { 19 | for (int i = 0; i < iterations; ++i) { 20 | logger.log(0, "GET /porn.png HTTP/1.1"); 21 | } 22 | }); 23 | } 24 | 25 | bool allow = false; 26 | for (int i = 0; i < iterations; ++i) { 27 | allow = !allow; 28 | logger.filter([=](const record_t&) -> bool { 29 | return allow; 30 | }); 31 | } 32 | 33 | for (auto& thread : threads) { 34 | thread.join(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | language: cpp 4 | 5 | matrix: 6 | include: 7 | - compiler: gcc 8 | - compiler: gcc 9 | addons: 10 | apt: 11 | sources: 12 | - ubuntu-toolchain-r-test 13 | packages: 14 | - lcov 15 | env: 16 | - BUILD_TYPE=Coverage 17 | - compiler: gcc 18 | addons: 19 | apt: 20 | sources: 21 | - ubuntu-toolchain-r-test 22 | packages: 23 | - g++-4.9 24 | env: COMPILER=g++-4.9 25 | before_install: 26 | - sudo apt-get update -qq 27 | - sudo apt-get install -y libboost-dev libboost-thread-dev libboost-system-dev 28 | script: 29 | - mkdir -p build 30 | - cd build 31 | - cmake -DCMAKE_CXX_COMPILER=$COMPILER -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DENABLE_TESTING=ON .. 32 | - | 33 | if [[ "${BUILD_TYPE}" == "Coverage" ]]; then 34 | make coverage; 35 | bash <(curl -s https://codecov.io/bash); 36 | else 37 | make; 38 | ./blackhole-tests; 39 | fi 40 | -------------------------------------------------------------------------------- /src/scope/watcher.cpp: -------------------------------------------------------------------------------- 1 | #include "blackhole/scope/watcher.hpp" 2 | 3 | #include 4 | 5 | #include "blackhole/attribute.hpp" 6 | #include "blackhole/logger.hpp" 7 | #include "blackhole/scope/manager.hpp" 8 | 9 | namespace blackhole { 10 | inline namespace v1 { 11 | namespace scope { 12 | 13 | watcher_t::watcher_t(logger_t& logger) : 14 | manager(logger.manager()), 15 | prev(manager.get().get()) 16 | { 17 | manager.get().reset(this); 18 | } 19 | 20 | watcher_t::~watcher_t() { 21 | BOOST_ASSERT(manager.get().get() == this); 22 | manager.get().reset(prev); 23 | } 24 | 25 | auto watcher_t::collect(attribute_pack& pack) const -> void { 26 | pack.emplace_back(attributes()); 27 | 28 | if (prev) { 29 | prev->collect(pack); 30 | } 31 | } 32 | 33 | auto watcher_t::rebind(manager_t& manager) -> void { 34 | this->manager = manager; 35 | 36 | if (prev) { 37 | prev->rebind(manager); 38 | } 39 | } 40 | 41 | } // namespace scope 42 | } // namespace v1 43 | } // namespace blackhole 44 | -------------------------------------------------------------------------------- /src/sink/file/flusher.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace blackhole { 7 | inline namespace v1 { 8 | namespace sink { 9 | namespace file { 10 | 11 | /// Flush suggest policy. 12 | class flusher_t { 13 | public: 14 | enum result_t { 15 | /// No flush required. 16 | idle, 17 | /// It's time to flush. 18 | flush 19 | }; 20 | 21 | public: 22 | virtual ~flusher_t() = default; 23 | 24 | /// Resets the current flusher state. 25 | virtual auto reset() -> void = 0; 26 | 27 | /// Updates the flusher, incrementing its counters. 28 | /// 29 | /// \param nwritten bytes consumed during previous write operation. 30 | virtual auto update(std::size_t nwritten) -> result_t = 0; 31 | }; 32 | 33 | class flusher_factory_t { 34 | public: 35 | virtual ~flusher_factory_t() = default; 36 | virtual auto create() const -> std::unique_ptr = 0; 37 | }; 38 | 39 | } // namespace file 40 | } // namespace sink 41 | } // namespace v1 42 | } // namespace blackhole 43 | -------------------------------------------------------------------------------- /src/sink/socket/udp.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "blackhole/sink.hpp" 7 | 8 | namespace blackhole { 9 | inline namespace v1 { 10 | namespace sink { 11 | namespace socket { 12 | 13 | class udp_t : public sink_t { 14 | public: 15 | /// The endpoint type. 16 | typedef boost::asio::ip::basic_endpoint endpoint_type; 17 | 18 | private: 19 | boost::asio::io_service io_service; 20 | boost::asio::ip::udp::socket socket; 21 | boost::asio::ip::udp::endpoint endpoint_; 22 | 23 | public: 24 | udp_t(const std::string& host, std::uint16_t port); 25 | 26 | /// Returns a const lvalue reference to the destination endpoint. 27 | auto endpoint() const -> const endpoint_type&; 28 | 29 | /// Emits a datagram to the specified endpoint. 30 | auto emit(const record_t& record, const string_view& message) -> void override; 31 | }; 32 | 33 | } // namespace socket 34 | } // namespace sink 35 | } // namespace v1 36 | } // namespace blackhole 37 | -------------------------------------------------------------------------------- /include/blackhole/scope/holder.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "blackhole/scope/watcher.hpp" 4 | 5 | #include "blackhole/attribute.hpp" 6 | #include "blackhole/attributes.hpp" 7 | 8 | namespace blackhole { 9 | inline namespace v1 { 10 | namespace scope { 11 | 12 | /// Implementation of scoped attributes guard that keeps attributes provided on construction and 13 | /// provides them each time on demand. 14 | class holder_t : public watcher_t { 15 | attributes_t storage; 16 | attribute_list list; 17 | 18 | public: 19 | /// Constructs a scoped guard which will attach the given attributes to the specified logger on 20 | /// construction making every further log event to contain them until keeped alive. 21 | /// 22 | /// \note creating multiple scoped watch objects results in attributes stacking. 23 | holder_t(logger_t& logger, attributes_t attributes); 24 | 25 | /// Returns an immutable reference to the internal attribute list. 26 | auto attributes() const -> const attribute_list&; 27 | }; 28 | 29 | } // namespace scoped 30 | } // namespace v1 31 | } // namespace blackhole 32 | -------------------------------------------------------------------------------- /include/blackhole/config/json.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "factory.hpp" 6 | 7 | namespace blackhole { 8 | inline namespace v1 { 9 | namespace config { 10 | 11 | class json_t; 12 | 13 | template<> 14 | class factory_traits { 15 | public: 16 | /// Constructs and initializes the JSON config factory by reading the given stream reference 17 | /// until EOF and parsing its content. 18 | /// 19 | /// The content should be valid JSON object. 20 | /// 21 | /// \param stream lvalue reference to the input stream. 22 | static auto construct(std::istream& stream) -> std::unique_ptr; 23 | 24 | /// Constructs and initializes the JSON config factory by reading the given stream until EOF 25 | /// and parsing its content. 26 | /// 27 | /// The content should be valid JSON object. 28 | /// 29 | /// \overload 30 | /// \param stream rvalue reference to the input stream. 31 | static auto construct(std::istream&& stream) -> std::unique_ptr; 32 | }; 33 | 34 | } // namespace config 35 | } // namespace v1 36 | } // namespace blackhole 37 | -------------------------------------------------------------------------------- /src/sink/syslog.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "blackhole/sink.hpp" 7 | 8 | namespace blackhole { 9 | inline namespace v1 { 10 | namespace sink { 11 | 12 | class syslog_t : public sink_t { 13 | struct { 14 | int option; 15 | int facility; 16 | std::string identity; 17 | std::vector priorities; 18 | } data; 19 | 20 | public: 21 | syslog_t(); 22 | syslog_t(const syslog_t& other) = delete; 23 | syslog_t(syslog_t&& other) = default; 24 | ~syslog_t(); 25 | 26 | auto operator=(const syslog_t& other) -> syslog_t& = delete; 27 | auto operator=(syslog_t&& other) -> syslog_t& = default; 28 | 29 | auto option() const noexcept -> int; 30 | auto facility() const noexcept -> int; 31 | auto identity() const noexcept -> const std::string&; 32 | auto priorities() const -> std::vector; 33 | auto priorities(std::vector priorities) -> void; 34 | 35 | auto emit(const record_t& record, const string_view& formatted) -> void override; 36 | }; 37 | 38 | } // namespace sink 39 | } // namespace v1 40 | } // namespace blackhole 41 | -------------------------------------------------------------------------------- /src/wrapper.cpp: -------------------------------------------------------------------------------- 1 | #include "blackhole/wrapper.hpp" 2 | 3 | namespace blackhole { 4 | inline namespace v1 { 5 | 6 | using attribute::view_t; 7 | 8 | wrapper_t::wrapper_t(logger_t& log, attributes_t attributes): 9 | inner(log), 10 | storage(std::move(attributes)) 11 | { 12 | // TODO: Replace somewhere near `view_of`. 13 | for (const auto& attribute : storage) { 14 | attributes_view.emplace_back(attribute); 15 | } 16 | } 17 | 18 | auto wrapper_t::log(severity_t severity, const message_t& message) -> void { 19 | attribute_pack pack{attributes()}; 20 | inner.log(severity, message, pack); 21 | } 22 | 23 | auto wrapper_t::log(severity_t severity, const message_t& message, attribute_pack& pack) -> void { 24 | pack.push_back(attributes()); 25 | inner.log(severity, message, pack); 26 | } 27 | 28 | auto wrapper_t::log(severity_t severity, const lazy_message_t& message, attribute_pack& pack) -> void { 29 | pack.push_back(attributes()); 30 | inner.log(severity, message, pack); 31 | } 32 | 33 | auto wrapper_t::manager() -> scope::manager_t& { 34 | return inner.manager(); 35 | } 36 | 37 | } // namespace v1 38 | } // namespace blackhole 39 | -------------------------------------------------------------------------------- /src/procname.cpp: -------------------------------------------------------------------------------- 1 | #include "procname.hpp" 2 | 3 | #ifdef __linux__ 4 | # include 5 | #elif __APPLE__ 6 | # include 7 | #endif 8 | 9 | #include 10 | 11 | #include 12 | 13 | #include "blackhole/stdext/string_view.hpp" 14 | 15 | namespace blackhole { 16 | inline namespace v1 { 17 | 18 | namespace { 19 | 20 | auto procname(pid_t pid) -> stdext::string_view { 21 | #ifdef __linux__ 22 | (void)pid; 23 | return stdext::string_view(program_invocation_short_name, ::strlen(program_invocation_short_name)); 24 | #elif __APPLE__ 25 | static char path[PROC_PIDPATHINFO_MAXSIZE]; 26 | 27 | if (::proc_name(pid, path, sizeof(path)) > 0) { 28 | return stdext::string_view(path, ::strlen(path)); 29 | } else { 30 | const auto nwritten = ::snprintf(path, sizeof(path), "%d", pid); 31 | return stdext::string_view(path, static_cast(nwritten)); 32 | } 33 | #endif 34 | } 35 | 36 | } // namespace 37 | 38 | auto procname() -> stdext::string_view { 39 | static const stdext::string_view name = procname(::getpid()); 40 | return name; 41 | } 42 | 43 | } // namespace v1 44 | } // namespace blackhole 45 | -------------------------------------------------------------------------------- /src/formatter/json.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "blackhole/formatter.hpp" 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | namespace formatter { 8 | 9 | class json_t : public formatter_t { 10 | public: 11 | class properties_t; 12 | 13 | private: 14 | class inner_t; 15 | 16 | std::unique_ptr inner; 17 | 18 | public: 19 | /// Constructs a defaultly configured JSON formatter, which will produce plain trees with no 20 | /// filtering without adding a separator character at the end. 21 | json_t(); 22 | json_t(properties_t properties); 23 | 24 | /// Returns true if there will be newline sequence added after each formatted message. 25 | auto newline() const noexcept -> bool; 26 | 27 | /// Returns true if the filtering policy is enabled. 28 | auto unique() const noexcept -> bool; 29 | 30 | /// Formats the given record by constructing a JSON tree with further serializing into the 31 | /// specified writer. 32 | /// 33 | /// \remark this method is thread safe. 34 | auto format(const record_t& record, writer_t& writer) -> void; 35 | }; 36 | 37 | } // namespace formatter 38 | } // namespace v1 39 | } // namespace blackhole 40 | -------------------------------------------------------------------------------- /src/formatter/string/error.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace blackhole { 8 | inline namespace v1 { 9 | namespace formatter { 10 | namespace string { 11 | 12 | class parser_error_t : public std::runtime_error { 13 | const std::size_t pos; 14 | const std::string inspect; 15 | 16 | public: 17 | parser_error_t(std::size_t pos, const std::string& pattern, const std::string& reason); 18 | parser_error_t(const parser_error_t& other) = default; 19 | 20 | ~parser_error_t() noexcept; 21 | 22 | auto position() const noexcept -> std::size_t; 23 | auto detail() const noexcept -> const std::string&; 24 | }; 25 | 26 | class broken_t : public parser_error_t { 27 | public: 28 | broken_t(std::size_t pos, const std::string& pattern); 29 | }; 30 | 31 | class illformed_t : public parser_error_t { 32 | public: 33 | illformed_t(std::size_t pos, const std::string& pattern); 34 | }; 35 | 36 | class invalid_placeholder_t : public parser_error_t { 37 | public: 38 | invalid_placeholder_t(std::size_t pos, const std::string& pattern); 39 | }; 40 | 41 | } // namespace string 42 | } // namespace formatter 43 | } // namespace v1 44 | } // namespace blackhole 45 | -------------------------------------------------------------------------------- /include/blackhole/handler/blocking.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../factory.hpp" 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | namespace handler { 8 | 9 | class blocking_t; 10 | 11 | } // namespace handler 12 | 13 | template<> 14 | class builder { 15 | class inner_t; 16 | std::unique_ptr d; 17 | 18 | public: 19 | builder(); 20 | 21 | auto set(std::unique_ptr formatter) & -> builder&; 22 | auto set(std::unique_ptr formatter) && -> builder&&; 23 | auto add(std::unique_ptr sink) & -> builder&; 24 | auto add(std::unique_ptr sink) && -> builder&&; 25 | 26 | auto build() && -> std::unique_ptr; 27 | }; 28 | 29 | template<> 30 | class factory : public factory { 31 | const registry_t& registry; 32 | 33 | public: 34 | constexpr explicit factory(const registry_t& registry) noexcept : 35 | registry(registry) 36 | {} 37 | 38 | virtual auto type() const noexcept -> const char* override; 39 | virtual auto from(const config::node_t& config) const -> std::unique_ptr override; 40 | }; 41 | 42 | } // namespace v1 43 | } // namespace blackhole 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2013-2017 Evgeny Safronov 4 | Copyright (c) 2013-2017 Other contributors as noted in the AUTHORS file. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /tests/rc/assign.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace blackhole; 10 | 11 | int main(int, char** argv) { 12 | const auto thread_num = boost::lexical_cast(argv[1]); 13 | const auto iterations = boost::lexical_cast(argv[2]); 14 | 15 | root_logger_t logger({}); 16 | 17 | std::vector threads; 18 | 19 | for (int tid = 0; tid < thread_num; ++tid) { 20 | threads.emplace_back([&] { 21 | const scope::holder_t scoped(logger, {{"key#1", {42}}}); 22 | for (int i = 0; i < iterations; ++i) { 23 | logger.log(0, "GET /porn.png HTTP/1.1"); 24 | } 25 | }); 26 | } 27 | 28 | bool allow = false; 29 | for (int i = 0; i < iterations; ++i) { 30 | allow = !allow; 31 | logger = root_logger_t([=](const record_t&) -> bool { 32 | return allow; 33 | }, {}); 34 | const scope::holder_t scoped(logger, {{"key#2", {100}}}); 35 | } 36 | 37 | for (auto& thread : threads) { 38 | thread.join(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/src/unit/termcolor.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | namespace blackhole { 7 | inline namespace v1 { 8 | namespace { 9 | 10 | TEST(termcolor_t, Default) { 11 | std::ostringstream stream; 12 | stream << termcolor_t(); 13 | 14 | EXPECT_EQ("\033[39m", stream.str()); 15 | } 16 | 17 | TEST(termcolor_t, Red) { 18 | std::ostringstream stream; 19 | stream << termcolor_t::red(); 20 | 21 | EXPECT_EQ("\033[31m", stream.str()); 22 | } 23 | 24 | TEST(termcolor_t, Green) { 25 | std::ostringstream stream; 26 | stream << termcolor_t::green(); 27 | 28 | EXPECT_EQ("\033[32m", stream.str()); 29 | } 30 | 31 | TEST(termcolor_t, Yellow) { 32 | std::ostringstream stream; 33 | stream << termcolor_t::yellow(); 34 | 35 | EXPECT_EQ("\033[33m", stream.str()); 36 | } 37 | 38 | TEST(termcolor_t, Blue) { 39 | std::ostringstream stream; 40 | stream << termcolor_t::blue(); 41 | 42 | EXPECT_EQ("\033[34m", stream.str()); 43 | } 44 | 45 | TEST(termcolor_t, Reset) { 46 | std::ostringstream stream; 47 | stream << termcolor_t::reset(); 48 | 49 | EXPECT_EQ("\033[0m", stream.str()); 50 | } 51 | 52 | } // namespace 53 | } // namespace v1 54 | } // namespace blackhole 55 | -------------------------------------------------------------------------------- /src/formatter/string/error.cpp: -------------------------------------------------------------------------------- 1 | #include "error.hpp" 2 | 3 | namespace blackhole { 4 | inline namespace v1 { 5 | namespace formatter { 6 | namespace string { 7 | 8 | parser_error_t::parser_error_t(std::size_t pos, const std::string& pattern, const std::string& reason) : 9 | std::runtime_error("parser error: " + reason), 10 | pos(pos), 11 | inspect("parser error: " + reason + "\n" + 12 | pattern + "\n" + 13 | std::string(pos, '~') + "^" 14 | ) 15 | {} 16 | 17 | parser_error_t::~parser_error_t() noexcept {} 18 | 19 | auto 20 | parser_error_t::position() const noexcept -> std::size_t { 21 | return pos; 22 | } 23 | 24 | auto 25 | parser_error_t::detail() const noexcept -> const std::string& { 26 | return inspect; 27 | } 28 | 29 | broken_t::broken_t(std::size_t pos, const std::string& pattern) : 30 | parser_error_t(pos, pattern, "broken parser") 31 | {} 32 | 33 | illformed_t::illformed_t(std::size_t pos, const std::string& pattern) : 34 | parser_error_t(pos, pattern, "illformed pattern") 35 | {} 36 | 37 | invalid_placeholder_t::invalid_placeholder_t(std::size_t pos, const std::string& pattern) : 38 | parser_error_t(pos, pattern, "invalid placeholder name (only [a-zA-Z0-9_] allowed)") 39 | {} 40 | 41 | } // namespace string 42 | } // namespace formatter 43 | } // namespace v1 44 | } // namespace blackhole 45 | -------------------------------------------------------------------------------- /include/blackhole/message.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "blackhole/stdext/string_view.hpp" 6 | 7 | namespace blackhole { 8 | inline namespace v1 { 9 | 10 | /// Represents a message that can be logged. 11 | typedef string_view message_t; 12 | 13 | /// Represents a message that can be logged and can be instantiated lazily on demand. 14 | struct lazy_message_t { 15 | /// Initial unformatted message pattern. 16 | string_view pattern; 17 | 18 | /// Message supplier callback, that should be called only when the logging event passes 19 | /// filtering to obtain the final formatted message. 20 | /// 21 | /// Sometimes obtaining a logging message requires heavyweight operation to be performed and 22 | /// there is no guarantee that the result of this operation won't be immediately dropped because 23 | /// of failed filtering for example. For these cases we allow the client code to provide 24 | /// messages lazily. 25 | /// Note, that string view semantics requires the real message storage that is pointed by view 26 | /// to outlive the function call. Default implementation in the facade just allocates large 27 | /// buffer on stack and fills it on function invocation. 28 | std::function string_view> supplier; 29 | }; 30 | 31 | } // namespace v1 32 | } // namespace blackhole 33 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2017, Evgeny Safronov 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL EVGENY SAFRONOV BE LIABLE FOR ANY 17 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /src/filter/severity.cpp: -------------------------------------------------------------------------------- 1 | #include "blackhole/filter/severity.hpp" 2 | 3 | #include 4 | 5 | #include "blackhole/config/node.hpp" 6 | #include "blackhole/config/option.hpp" 7 | #include "blackhole/filter.hpp" 8 | #include "blackhole/record.hpp" 9 | 10 | #include "../memory.hpp" 11 | 12 | namespace blackhole { 13 | inline namespace v1 { 14 | namespace filter { 15 | 16 | class severity_t : public filter_t { 17 | std::int64_t threshold; 18 | 19 | public: 20 | severity_t(std::int64_t threshold) noexcept : threshold(threshold) {} 21 | 22 | auto filter(const record_t& record) -> filter_t::action_t override { 23 | if (record.severity() >= threshold) { 24 | return filter_t::action_t::neutral; 25 | } else { 26 | return filter_t::action_t::deny; 27 | } 28 | } 29 | }; 30 | 31 | } // namespace filter 32 | 33 | auto factory::type() const noexcept -> const char* { 34 | return "severity"; 35 | } 36 | 37 | auto factory::from(const config::node_t& config) const -> 38 | std::unique_ptr 39 | { 40 | if (auto threshold = config["threshold"].to_sint64()) { 41 | return blackhole::make_unique(*threshold); 42 | } 43 | 44 | throw std::invalid_argument("field 'threshold' is required"); 45 | } 46 | 47 | } // namespace v1 48 | } // namespace blackhole 49 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: blackhole 2 | Priority: extra 3 | Maintainer: Evgeny Safronov 4 | Build-Depends: debhelper (>= 8.0.0), cmake, 5 | libboost-dev | libboost1.48-dev, 6 | libboost-thread-dev | libboost-thread1.48-dev 7 | Standards-Version: 3.9.3 8 | Section: libs 9 | Homepage: https://github.com/3Hren/blackhole 10 | Vcs-Git: git://github.com/3Hren/blackhole.git 11 | Vcs-Browser: https://github.com/3Hren/blackhole.git 12 | 13 | Package: blackhole-dev 14 | Section: libdevel 15 | Architecture: any 16 | Depends: ${misc:Depends}, libblackhole1 (= ${binary:Version}) 17 | Description: Blackhole C++ Logger - Development Headers 18 | Development files for Blackhole C++ logging library. 19 | 20 | Package: libblackhole1 21 | Section: libs 22 | Architecture: any 23 | Depends: ${shlibs:Depends}, ${misc:Depends} 24 | Description: Blackhole C++ Logger 25 | Fast C++ logging library with dynamic attributes. 26 | 27 | Package: blackhole-dbg 28 | Section: debug 29 | Architecture: any 30 | Depends: ${shlibs:Depends}, ${misc:Depends}, libblackhole1 (= ${binary:Version}) 31 | Description: Blackhole - Debug Symbols 32 | Blackhole debug files and symbols. 33 | 34 | Package: blackhole-migration-dev 35 | Section: libdevel 36 | Architecture: any 37 | Depends: ${misc:Depends}, blackhole-dev 38 | Description: Blackhole C++ Logger - Development Headers 39 | Development files with migration symlinks for Blackhole C++ logging library. 40 | -------------------------------------------------------------------------------- /src/sink/file/rotate/stat.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "../rotate.hpp" 9 | 10 | namespace blackhole { 11 | inline namespace v1 { 12 | namespace sink { 13 | namespace file { 14 | namespace rotate { 15 | 16 | class stat_rotate_t : public rotate_t { 17 | std::string filename; 18 | std::int64_t inode; 19 | 20 | public: 21 | explicit stat_rotate_t(std::string filename) : 22 | filename(std::move(filename)), 23 | inode(0) 24 | { 25 | struct stat buf = {}; 26 | auto rc = ::stat(this->filename.c_str(), &buf); 27 | if (rc == -1) { 28 | throw std::system_error(errno, std::system_category()); 29 | } 30 | 31 | inode = std::int64_t(buf.st_ino); 32 | } 33 | 34 | auto should_rotate() -> bool override { 35 | struct stat buf = {}; 36 | auto rc = ::stat(filename.c_str(), &buf); 37 | 38 | return !(rc == 0 && std::int64_t(buf.st_ino) == inode); 39 | } 40 | }; 41 | 42 | class stat_factory_t : public rotate_factory_t { 43 | public: 44 | auto create(const std::string& filename) const -> std::unique_ptr override { 45 | return blackhole::make_unique(filename); 46 | } 47 | }; 48 | 49 | } // namespace rotate 50 | } // namespace file 51 | } // namespace sink 52 | } // namespace v1 53 | } // namespace blackhole 54 | -------------------------------------------------------------------------------- /bench/attribute.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "mod.hpp" 6 | 7 | namespace blackhole { 8 | namespace benchmark { 9 | 10 | static void view_ctor_get_int64(::benchmark::State& state) { 11 | while (state.KeepRunning()) { 12 | blackhole::attribute::view_t v(42); 13 | blackhole::attribute::get(v); 14 | } 15 | 16 | state.SetItemsProcessed(state.iterations()); 17 | } 18 | 19 | static void from_owned(::benchmark::State& state) { 20 | blackhole::attribute::value_t owned(42); 21 | 22 | while (state.KeepRunning()) { 23 | blackhole::attribute::view_t view(owned); 24 | ::benchmark::DoNotOptimize(view); 25 | } 26 | 27 | state.SetItemsProcessed(state.iterations()); 28 | } 29 | 30 | static void from_owned_string(::benchmark::State& state) { 31 | blackhole::attribute::value_t owned("le message"); 32 | 33 | while (state.KeepRunning()) { 34 | blackhole::attribute::view_t view(owned); 35 | ::benchmark::DoNotOptimize(view); 36 | } 37 | 38 | state.SetItemsProcessed(state.iterations()); 39 | } 40 | 41 | NBENCHMARK("attribute.view_t[ctor + get]", view_ctor_get_int64); 42 | NBENCHMARK("attribute.view_t[ctor + from owned]", from_owned); 43 | NBENCHMARK("attribute.view_t[ctor + from owned string]", from_owned_string); 44 | 45 | } // namespace benchmark 46 | } // namespace blackhole 47 | -------------------------------------------------------------------------------- /src/essentials.cpp: -------------------------------------------------------------------------------- 1 | #include "essentials.hpp" 2 | 3 | #include "blackhole/filter/severity.hpp" 4 | #include "blackhole/formatter/json.hpp" 5 | #include "blackhole/formatter/string.hpp" 6 | #include "blackhole/formatter/tskv.hpp" 7 | #include "blackhole/handler/blocking.hpp" 8 | #include "blackhole/handler/dev.hpp" 9 | #include "blackhole/registry.hpp" 10 | #include "blackhole/sink/asynchronous.hpp" 11 | #include "blackhole/sink/console.hpp" 12 | #include "blackhole/sink/file.hpp" 13 | #include "blackhole/sink/null.hpp" 14 | #include "blackhole/sink/socket/tcp.hpp" 15 | #include "blackhole/sink/socket/udp.hpp" 16 | #include "blackhole/sink/syslog.hpp" 17 | 18 | namespace blackhole { 19 | inline namespace v1 { 20 | 21 | auto essentials(registry_t& registry) -> void { 22 | registry.add(); 23 | 24 | registry.add(); 25 | registry.add(); 26 | registry.add(); 27 | 28 | registry.add(registry); 29 | registry.add(registry); 30 | registry.add(registry); 31 | registry.add(); 32 | registry.add(registry); 33 | registry.add(registry); 34 | registry.add(registry); 35 | 36 | registry.add(registry); 37 | // registry.add(registry); 38 | } 39 | 40 | } // namespace v1 41 | } // namespace blackhole 42 | -------------------------------------------------------------------------------- /src/config/json.cpp: -------------------------------------------------------------------------------- 1 | #include "json.hpp" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "../memory.hpp" 10 | 11 | namespace blackhole { 12 | inline namespace v1 { 13 | namespace config { 14 | 15 | auto factory_traits::construct(std::istream& stream) -> std::unique_ptr { 16 | return blackhole::make_unique>(stream); 17 | } 18 | 19 | auto factory_traits::construct(std::istream&& stream) -> std::unique_ptr { 20 | return blackhole::make_unique>(std::move(stream)); 21 | } 22 | 23 | factory::factory(std::istream& stream) : 24 | node(doc) 25 | { 26 | initialize(stream); 27 | } 28 | 29 | factory::factory(std::istream&& stream) : 30 | node(doc) 31 | { 32 | initialize(stream); 33 | } 34 | 35 | auto factory::config() const noexcept -> const node_t& { 36 | return node; 37 | } 38 | 39 | auto factory::initialize(std::istream& stream) -> void { 40 | rapidjson::IStreamWrapper wrapper(stream); 41 | doc.ParseStream(wrapper); 42 | 43 | if (doc.HasParseError()) { 44 | throw std::invalid_argument("parse error at offset " + 45 | boost::lexical_cast(doc.GetErrorOffset()) + 46 | ": " + rapidjson::GetParseError_En(doc.GetParseError())); 47 | } 48 | } 49 | 50 | } // namespace config 51 | } // namespace v1 52 | } // namespace blackhole 53 | -------------------------------------------------------------------------------- /bench/clock.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #ifdef __linux__ 6 | # include 7 | #endif 8 | 9 | #include "mod.hpp" 10 | 11 | namespace blackhole { 12 | namespace benchmark { 13 | 14 | #ifdef __linux__ 15 | static 16 | void 17 | monotonic_coarse(::benchmark::State& state) { 18 | while (state.KeepRunning()) { 19 | struct timespec ts; 20 | ::clock_gettime(CLOCK_MONOTONIC_COARSE, &ts); 21 | } 22 | 23 | state.SetItemsProcessed(state.iterations()); 24 | } 25 | 26 | static 27 | void 28 | monotonic_precise(::benchmark::State& state) { 29 | while (state.KeepRunning()) { 30 | struct timespec ts; 31 | ::clock_gettime(CLOCK_MONOTONIC, &ts); 32 | } 33 | 34 | state.SetItemsProcessed(state.iterations()); 35 | } 36 | #endif 37 | 38 | static 39 | void 40 | system_clock(::benchmark::State& state) { 41 | while (state.KeepRunning()) { 42 | std::chrono::system_clock::now(); 43 | } 44 | 45 | state.SetItemsProcessed(state.iterations()); 46 | } 47 | 48 | static 49 | void 50 | high_resolution_clock(::benchmark::State& state) { 51 | while (state.KeepRunning()) { 52 | std::chrono::high_resolution_clock::now(); 53 | } 54 | 55 | state.SetItemsProcessed(state.iterations()); 56 | } 57 | 58 | #ifdef __linux__ 59 | NBENCHMARK("clock.coarse", monotonic_coarse); 60 | NBENCHMARK("clock.precise", monotonic_precise); 61 | #endif 62 | 63 | NBENCHMARK("clock.system", system_clock); 64 | NBENCHMARK("clock.high_resolution", high_resolution_clock); 65 | 66 | } // namespace benchmark 67 | } // namespace blackhole 68 | -------------------------------------------------------------------------------- /include/blackhole/formatter/tskv.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../factory.hpp" 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | namespace formatter { 8 | 9 | class tskv_t; 10 | 11 | } // namespace formatter 12 | 13 | template<> 14 | class builder { 15 | class inner_t; 16 | std::unique_ptr p; 17 | 18 | public: 19 | explicit builder(); 20 | 21 | auto create(const std::string& name, const std::string& value) & -> builder&; 22 | auto create(const std::string& name, const std::string& value) && -> builder&&; 23 | 24 | auto rename(const std::string& from, const std::string& to) & -> builder&; 25 | auto rename(const std::string& from, const std::string& to) && -> builder&&; 26 | 27 | auto remove(const std::string& name) & -> builder&; 28 | auto remove(const std::string& name) && -> builder&&; 29 | 30 | auto timestamp(const std::string& name, const std::string& pattern) & -> builder&; 31 | auto timestamp(const std::string& name, const std::string& pattern) && -> builder&&; 32 | 33 | auto timestamp(const std::string& name, const std::string& pattern, bool gmtime) & -> builder&; 34 | auto timestamp(const std::string& name, const std::string& pattern, bool gmtime) && -> builder&&; 35 | 36 | auto build() && -> std::unique_ptr; 37 | }; 38 | 39 | template<> 40 | class factory : public factory { 41 | public: 42 | auto type() const noexcept -> const char* override final; 43 | auto from(const config::node_t& config) const -> std::unique_ptr override final; 44 | }; 45 | 46 | } // namespace v1 47 | } // namespace blackhole 48 | -------------------------------------------------------------------------------- /bench/formatter/tskv.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "mod.hpp" 12 | 13 | namespace blackhole { 14 | namespace benchmark { 15 | 16 | using formatter::tskv_t; 17 | 18 | static void format_message(::benchmark::State& state) { 19 | auto formatter = builder() 20 | .build(); 21 | 22 | const string_view message("value"); 23 | const attribute_pack pack; 24 | record_t record(0, message, pack); 25 | writer_t writer; 26 | 27 | while (state.KeepRunning()) { 28 | formatter->format(record, writer); 29 | writer.inner.clear(); 30 | } 31 | 32 | state.SetItemsProcessed(state.iterations()); 33 | } 34 | 35 | static void format_leftover(::benchmark::State& state) { 36 | auto formatter = builder() 37 | .build(); 38 | 39 | const string_view message("-"); 40 | const attribute_list attributes{{"key#1", {42}}, {"key#2", {"value#2"}}}; 41 | const attribute_pack pack{attributes}; 42 | record_t record(0, message, pack); 43 | writer_t writer; 44 | 45 | while (state.KeepRunning()) { 46 | formatter->format(record, writer); 47 | writer.inner.clear(); 48 | } 49 | 50 | state.SetItemsProcessed(state.iterations()); 51 | } 52 | 53 | NBENCHMARK("formatter.tskv", format_message); 54 | NBENCHMARK("formatter.tskv[...]", format_leftover); 55 | 56 | } // namespace benchmark 57 | } // namespace blackhole 58 | -------------------------------------------------------------------------------- /examples/1.hello.cpp: -------------------------------------------------------------------------------- 1 | //! This example demonstrates the a very basic the Blackhole logging library usage and its features, 2 | //! like: 3 | //! - Logger construction using builder pattern. 4 | //! - Logging using logger methods. 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | auto main() -> int { 17 | // Here we are going to configure our development handler and to build the logger. 18 | auto lg = blackhole::builder() 19 | // Add the development handler. 20 | .add(blackhole::builder() 21 | .build()) 22 | // Build the logger. 23 | .build(); 24 | auto log = blackhole::logger_facade(*lg); 25 | 26 | log.log(0, "add {} quads for {}, weight: {:.2f}%, {}/{}", 64, "nginx", 34.375, 22, 64, 27 | blackhole::attribute_list { 28 | {"source", "locator"}, 29 | {"group", "nginx_group1466158150"}, 30 | {"service", "locator"}, 31 | {"endpoint", "[::]:38502"} 32 | } 33 | ); 34 | 35 | log.log(1, "core has been terminated"); 36 | log.log(2, "client stopped connection before send body completed", { 37 | {"uuid", "a7c345fa-2034-439e-b941-44321419725e"}, 38 | {"endpoint", "[::]:59010"} 39 | }); 40 | log.log(3, "file does not exist: {}", "/var/www/favicon.ico"); 41 | 42 | return 0; 43 | } 44 | -------------------------------------------------------------------------------- /tests/src/unit/detail/handler/blocking.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "mocks/formatter.hpp" 10 | #include "mocks/sink.hpp" 11 | 12 | namespace blackhole { 13 | inline namespace v1 { 14 | namespace handler { 15 | namespace { 16 | 17 | using ::testing::Invoke; 18 | using ::testing::_; 19 | 20 | using namespace testing; 21 | 22 | TEST(blocking_t, Handle) { 23 | std::unique_ptr formatter_(new mock::formatter_t); 24 | mock::formatter_t& formatter = *formatter_; 25 | 26 | std::unique_ptr sink_(new mock::sink_t); 27 | mock::sink_t& sink = *sink_; 28 | 29 | std::vector> sinks; 30 | sinks.emplace_back(std::move(sink_)); 31 | 32 | blocking_t handler(std::move(formatter_), std::move(sinks)); 33 | 34 | EXPECT_CALL(formatter, format(_, _)) 35 | .Times(1) 36 | .WillOnce(Invoke([](const record_t& record, writer_t& writer) { 37 | EXPECT_EQ(42, record.severity()); 38 | EXPECT_EQ("-", record.message().to_string()); 39 | EXPECT_EQ(0, record.attributes().size()); 40 | 41 | writer.write("---"); 42 | })); 43 | 44 | EXPECT_CALL(sink, emit(_, _)) 45 | .Times(1); 46 | 47 | const string_view message("-"); 48 | const attribute_pack pack; 49 | record_t record(42, message, pack); 50 | 51 | handler.handle(record); 52 | } 53 | 54 | } // namespace 55 | } // namespace handler 56 | } // namespace v1 57 | } // namespace blackhole 58 | -------------------------------------------------------------------------------- /tests/src/unit/time.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include 7 | 8 | namespace blackhole { 9 | inline namespace v1 { 10 | namespace { 11 | 12 | TEST(time, RandomGreenwichTime) { 13 | std::random_device rd; 14 | std::uniform_int_distribution dist(0l, std::time(nullptr)); 15 | for (auto i = 0; i < 10000; ++i) { 16 | auto time = dist(rd); 17 | 18 | std::tm tm1; 19 | ::gmtime_r(&time, &tm1); 20 | 21 | std::tm tm2; 22 | blackhole::gmtime(&time, &tm2); 23 | 24 | ASSERT_EQ(tm2.tm_sec, tm1.tm_sec); 25 | ASSERT_EQ(tm2.tm_min, tm1.tm_min); 26 | ASSERT_EQ(tm2.tm_hour, tm1.tm_hour); 27 | ASSERT_EQ(tm2.tm_mday, tm1.tm_mday); 28 | ASSERT_EQ(tm2.tm_mon, tm1.tm_mon); 29 | ASSERT_EQ(tm2.tm_year, tm1.tm_year); 30 | ASSERT_EQ(tm2.tm_year, tm1.tm_year); 31 | ASSERT_EQ(tm2.tm_yday, tm1.tm_yday); 32 | ASSERT_EQ(tm2.tm_isdst, tm1.tm_isdst); 33 | } 34 | } 35 | 36 | TEST(time, RandomLocalTime) { 37 | auto time = std::time(nullptr); 38 | 39 | std::tm tm1; 40 | ::localtime_r(&time, &tm1); 41 | 42 | std::tm tm2; 43 | blackhole::localtime(&time, &tm2); 44 | 45 | ASSERT_EQ(tm2.tm_sec, tm1.tm_sec); 46 | ASSERT_EQ(tm2.tm_min, tm1.tm_min); 47 | ASSERT_EQ(tm2.tm_hour, tm1.tm_hour); 48 | ASSERT_EQ(tm2.tm_mday, tm1.tm_mday); 49 | ASSERT_EQ(tm2.tm_mon, tm1.tm_mon); 50 | ASSERT_EQ(tm2.tm_year, tm1.tm_year); 51 | ASSERT_EQ(tm2.tm_year, tm1.tm_year); 52 | ASSERT_EQ(tm2.tm_yday, tm1.tm_yday); 53 | ASSERT_EQ(tm2.tm_isdst, tm1.tm_isdst); 54 | } 55 | 56 | } // namespace 57 | } // namespace v1 58 | } // namespace blackhole 59 | -------------------------------------------------------------------------------- /examples/3.config.cpp: -------------------------------------------------------------------------------- 1 | /// This example demonstrates how to initialize Blackhole from configuration file using JSON 2 | /// builder. 3 | /// In this case the entire logging pipeline is initialized from file including severity mapping. 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace blackhole; 14 | 15 | /// As always specify severity enumeration. 16 | enum severity { 17 | debug = 0, 18 | info = 1, 19 | warning = 2, 20 | error = 3 21 | }; 22 | 23 | auto main(int argc, char** argv) -> int { 24 | if (argc != 2) { 25 | std::cerr << "Usage: 3.config PATH" << std::endl; 26 | return 1; 27 | } 28 | 29 | /// Here we are going to build the logger using registry. The registry's responsibility is to 30 | /// track registered handlers, formatter and sinks, but for now we're not going to register 31 | /// anything else, since there are predefined types. 32 | auto log = blackhole::registry::configured() 33 | /// Specify the concrete builder type we want to use. It may be JSON, XML, YAML or whatever 34 | /// else. 35 | ->builder(std::ifstream(argv[1])) 36 | /// Build the logger named "root". 37 | .build("root"); 38 | 39 | log.log(severity::debug, "GET /static/image.png HTTP/1.1 404 347"); 40 | log.log(severity::info, "nginx/1.6 configured"); 41 | log.log(severity::warning, "client stopped connection before send body completed"); 42 | log.log(severity::error, "file does not exist: /var/www/favicon.ico"); 43 | 44 | return 0; 45 | } 46 | -------------------------------------------------------------------------------- /src/sink/file/flusher/repeat.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../flusher.hpp" 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | namespace sink { 8 | namespace file { 9 | namespace flusher { 10 | 11 | class repeat_t : public flusher_t { 12 | public: 13 | typedef std::size_t threshold_type; 14 | 15 | private: 16 | threshold_type counter; 17 | threshold_type threshold_; 18 | 19 | public: 20 | constexpr repeat_t(threshold_type threshold) noexcept : 21 | counter(0), 22 | threshold_(threshold > 0 ? threshold : std::numeric_limits::max()) 23 | {} 24 | 25 | auto threshold() const noexcept -> threshold_type { 26 | return threshold_; 27 | } 28 | 29 | auto count() const noexcept -> threshold_type { 30 | return counter; 31 | } 32 | 33 | auto reset() -> void override { 34 | counter = 0; 35 | } 36 | 37 | auto update(std::size_t nwritten) -> flusher_t::result_t override { 38 | if (nwritten != 0) { 39 | counter = (counter + 1) % threshold(); 40 | 41 | if (counter == 0) { 42 | return flusher_t::flush; 43 | } 44 | } 45 | 46 | return flusher_t::idle; 47 | } 48 | }; 49 | 50 | class repeat_factory_t : public flusher_factory_t { 51 | public: 52 | typedef typename repeat_t::threshold_type threshold_type; 53 | 54 | private: 55 | threshold_type value; 56 | 57 | public: 58 | explicit repeat_factory_t(threshold_type threshold) noexcept; 59 | 60 | auto threshold() const noexcept -> threshold_type; 61 | auto create() const -> std::unique_ptr override; 62 | }; 63 | 64 | } // namespace flusher 65 | } // namespace file 66 | } // namespace sink 67 | } // namespace v1 68 | } // namespace blackhole 69 | -------------------------------------------------------------------------------- /src/datetime/stream.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | namespace datetime { 8 | 9 | template> 10 | class basic_ostreambuf_adapter : public std::basic_streambuf { 11 | typedef std::basic_streambuf base_type; 12 | 13 | public: 14 | typedef Stream stream_type; 15 | typedef typename base_type::int_type int_type; 16 | typedef typename base_type::char_type char_type; 17 | typedef typename base_type::traits_type traits_type; 18 | 19 | private: 20 | stream_type& stream; 21 | 22 | public: 23 | basic_ostreambuf_adapter(stream_type& stream) : 24 | stream(stream) 25 | { 26 | base_type::setp(nullptr, nullptr); 27 | } 28 | 29 | auto overflow(int_type ch) -> int_type { 30 | if (!traits_type::eq_int_type(ch, traits_type::eof())) { 31 | stream << traits_type::to_char_type(ch); 32 | return ch; 33 | } 34 | 35 | return traits_type::not_eof(ch); 36 | } 37 | 38 | auto inner() -> stream_type& { 39 | return stream; 40 | } 41 | }; 42 | 43 | template 44 | class ostream_adapter : public std::basic_ostream { 45 | typedef Stream stream_type; 46 | 47 | basic_ostreambuf_adapter streambuf; 48 | 49 | public: 50 | ostream_adapter(stream_type& stream) : 51 | std::basic_ostream(&streambuf), 52 | streambuf(stream) 53 | {} 54 | 55 | template 56 | auto operator<<(const T& value) -> ostream_adapter& { 57 | streambuf.inner() << value; 58 | return *this; 59 | } 60 | }; 61 | 62 | } // namespace datetime 63 | } // namespace v1 64 | } // namespace blackhole 65 | -------------------------------------------------------------------------------- /src/hack/addressof.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | //! Workaround (honestly, a hack) for cases when Blackhole is being compiled with clang on systems 10 | //! with boost 1.55 on board. 11 | //! 12 | //! Stolen from https://svn.boost.org/trac/boost/ticket/5487. 13 | //! 14 | //! \note must be included before or . 15 | 16 | #if defined(__clang__) && (BOOST_VERSION / 100000 == 1 && BOOST_VERSION / 100 % 1000 == 55) 17 | 18 | #ifndef BOOST_NO_CXX11_NULLPTR 19 | 20 | namespace boost { 21 | namespace detail { 22 | 23 | #if defined(__clang__) && !defined(_LIBCPP_VERSION) && !defined(BOOST_NO_CXX11_DECLTYPE) 24 | typedef decltype(nullptr) addr_nullptr_t; 25 | #else 26 | typedef std::nullptr_t addr_nullptr_t; 27 | #endif 28 | 29 | template<> 30 | struct addressof_impl { 31 | typedef addr_nullptr_t T; 32 | 33 | constexpr static T* f(T& v, int) { 34 | return &v; 35 | } 36 | }; 37 | 38 | template<> 39 | struct addressof_impl { 40 | typedef addr_nullptr_t const T; 41 | 42 | constexpr static T* f(T& v, int) { 43 | return &v; 44 | } 45 | }; 46 | 47 | template<> 48 | struct addressof_impl { 49 | typedef addr_nullptr_t volatile T; 50 | 51 | constexpr static T* f(T& v, int) { 52 | return &v; 53 | } 54 | }; 55 | 56 | template<> 57 | struct addressof_impl { 58 | typedef addr_nullptr_t const volatile T; 59 | 60 | constexpr static T* f( T& v, int) { 61 | return &v; 62 | } 63 | }; 64 | 65 | } // namespace detail 66 | } // namespace boost 67 | 68 | #endif // BOOST_NO_CXX11_NULLPTR 69 | 70 | #endif 71 | -------------------------------------------------------------------------------- /tests/wrapper.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include "mocks/logger.hpp" 16 | #include "mocks/scope/manager.hpp" 17 | 18 | namespace blackhole { 19 | namespace testing { 20 | 21 | using ::testing::AtLeast; 22 | using ::testing::Invoke; 23 | using ::testing::Return; 24 | using ::testing::ReturnRef; 25 | using ::testing::SaveArg; 26 | using ::testing::_; 27 | 28 | TEST(Wrapper, Constructor) { 29 | mock::logger_t logger; 30 | 31 | wrapper_t wrapper(logger, { 32 | {"key#0", {0}}, 33 | {"key#1", {"value#1"}} 34 | }); 35 | 36 | const view_of::type expected = { 37 | {"key#0", {0}}, 38 | {"key#1", {"value#1"}} 39 | }; 40 | 41 | EXPECT_EQ(expected, wrapper.attributes()); 42 | } 43 | 44 | TEST(wrapper_t, DelegatesScopeManager) { 45 | mock::scope::manager_t manager; 46 | 47 | mock::logger_t logger; 48 | wrapper_t wrapper(logger, {}); 49 | 50 | EXPECT_CALL(logger, manager()) 51 | .Times(1) 52 | .WillOnce(ReturnRef(manager)); 53 | 54 | scope::watcher_t* watcher = nullptr; 55 | 56 | EXPECT_CALL(manager, get()) 57 | .Times(AtLeast(1)) 58 | .WillOnce(Return(nullptr)) 59 | .WillOnce(Invoke([&]() -> scope::watcher_t* { 60 | return watcher; 61 | })); 62 | 63 | EXPECT_CALL(manager, reset(_)) 64 | .Times(1) 65 | .WillOnce(SaveArg<0>(&watcher)); 66 | 67 | EXPECT_CALL(manager, reset(nullptr)) 68 | .Times(1); 69 | 70 | const scope::holder_t scoped(wrapper, {}); 71 | } 72 | 73 | } // namespace testing 74 | } // namespace blackhole 75 | -------------------------------------------------------------------------------- /src/sink/file/flusher/bytecount.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../flusher.hpp" 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | namespace sink { 8 | namespace file { 9 | namespace flusher { 10 | 11 | class bytecount_t : public flusher_t { 12 | public: 13 | typedef std::uint64_t threshold_type; 14 | 15 | private: 16 | threshold_type counter; 17 | threshold_type threshold_; 18 | 19 | public: 20 | constexpr bytecount_t(threshold_type threshold) noexcept : 21 | counter(0), 22 | threshold_(threshold > 0 ? threshold : std::numeric_limits::max()) 23 | {} 24 | 25 | auto threshold() const noexcept -> threshold_type { 26 | return threshold_; 27 | } 28 | 29 | auto count() const noexcept -> threshold_type { 30 | return counter; 31 | } 32 | 33 | auto reset() -> void override { 34 | counter = 0; 35 | } 36 | 37 | auto update(std::size_t nwritten) -> flusher_t::result_t override { 38 | auto result = flusher_t::result_t::idle; 39 | 40 | if (nwritten != 0) { 41 | if (counter + nwritten >= threshold()) { 42 | result = flusher_t::result_t::flush; 43 | } 44 | 45 | counter = (counter + nwritten) % threshold(); 46 | } 47 | 48 | return result; 49 | } 50 | }; 51 | 52 | class bytecount_factory_t : public flusher_factory_t { 53 | public: 54 | typedef typename bytecount_t::threshold_type threshold_type; 55 | 56 | private: 57 | threshold_type value; 58 | 59 | public: 60 | explicit bytecount_factory_t(threshold_type threshold) noexcept; 61 | 62 | auto threshold() const noexcept -> threshold_type; 63 | auto create() const -> std::unique_ptr override; 64 | }; 65 | 66 | auto parse_dunit(const std::string& encoded) -> std::uint64_t; 67 | 68 | } // namespace flusher 69 | } // namespace file 70 | } // namespace sink 71 | } // namespace v1 72 | } // namespace blackhole 73 | -------------------------------------------------------------------------------- /src/sink/socket/udp.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "blackhole/config/node.hpp" 5 | #include "blackhole/config/option.hpp" 6 | #include "blackhole/stdext/string_view.hpp" 7 | #include "blackhole/sink/socket/udp.hpp" 8 | 9 | #include "../../memory.hpp" 10 | #include "../../util/optional.hpp" 11 | #include "udp.hpp" 12 | 13 | namespace blackhole { 14 | inline namespace v1 { 15 | namespace sink { 16 | namespace socket { 17 | 18 | udp_t::udp_t(const std::string& host, std::uint16_t port) : 19 | socket(io_service) 20 | { 21 | boost::asio::ip::udp::resolver resolver(io_service); 22 | boost::asio::ip::udp::resolver::query query(host, boost::lexical_cast(port), 23 | boost::asio::ip::udp::resolver::query::flags::numeric_service); 24 | endpoint_ = *resolver.resolve(query); 25 | 26 | socket.open(endpoint_.protocol()); 27 | } 28 | 29 | auto udp_t::endpoint() const -> const endpoint_type& { 30 | return endpoint_; 31 | } 32 | 33 | auto udp_t::emit(const record_t&, const string_view& formatted) -> void { 34 | socket.send_to(boost::asio::buffer(formatted.data(), formatted.size()), endpoint_); 35 | } 36 | 37 | } // namespace socket 38 | } // namespace sink 39 | 40 | using sink::socket::udp_t; 41 | 42 | using util::value_or; 43 | 44 | auto factory::type() const noexcept -> const char* { 45 | return "udp"; 46 | } 47 | 48 | auto factory::from(const config::node_t& config) const -> std::unique_ptr { 49 | (void)registry; 50 | const auto host = value_or(config["host"].to_string(), []() -> std::string { 51 | throw std::invalid_argument(R"(parameter "host" is required)"); 52 | }); 53 | 54 | const auto port = value_or(config["port"].to_uint64(), []() -> std::uint64_t { 55 | throw std::invalid_argument(R"(parameter "port" is required)"); 56 | }); 57 | 58 | return blackhole::make_unique(host, static_cast(port)); 59 | } 60 | 61 | } // namespace v1 62 | } // namespace blackhole 63 | -------------------------------------------------------------------------------- /tests/src/unit/sink/syslog.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include "mocks/node.hpp" 13 | #include "mocks/registry.hpp" 14 | 15 | namespace blackhole { 16 | inline namespace v1 { 17 | namespace sink { 18 | namespace { 19 | 20 | using ::testing::_; 21 | using ::testing::Invoke; 22 | using ::testing::Return; 23 | using ::testing::StrictMock; 24 | 25 | TEST(syslog_t, Type) { 26 | EXPECT_EQ(std::string("syslog"), factory(mock_registry_t()).type()); 27 | } 28 | 29 | TEST(syslog_t, Factory) { 30 | using config::testing::mock::node_t; 31 | 32 | StrictMock config; 33 | 34 | auto priorities_node = new node_t; 35 | EXPECT_CALL(config, subscript_key("priorities")) 36 | .Times(1) 37 | .WillOnce(Return(priorities_node)); 38 | 39 | node_t priority_item_node; 40 | EXPECT_CALL(*priorities_node, each(_)) 41 | .Times(1) 42 | .WillOnce(Invoke([&](const node_t::each_function& fn) { 43 | for (int i = 0; i < 4; ++i) { 44 | fn(priority_item_node); 45 | } 46 | })); 47 | 48 | EXPECT_CALL(priority_item_node, to_sint64()) 49 | .Times(4) 50 | .WillOnce(Return(0)) 51 | .WillOnce(Return(2)) 52 | .WillOnce(Return(5)) 53 | .WillOnce(Return(9)); 54 | 55 | auto sink = factory(mock_registry_t()).from(config); 56 | const auto& cast = dynamic_cast(*sink); 57 | 58 | EXPECT_EQ((std::vector{0, 2, 5, 9}), cast.priorities()); 59 | } 60 | 61 | TEST(syslog_t, Default) { 62 | syslog_t syslog; 63 | 64 | EXPECT_EQ(procname().to_string(), syslog.identity()); 65 | EXPECT_EQ(LOG_PID, syslog.option()); 66 | EXPECT_EQ(LOG_USER, syslog.facility()); 67 | } 68 | 69 | } // namespace 70 | } // namespace sink 71 | } // namespace v1 72 | } // namespace blackhole 73 | -------------------------------------------------------------------------------- /include/blackhole/termcolor.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | 8 | /// Terminal color manipulator. 9 | class termcolor_t { 10 | int attr; 11 | int code; 12 | 13 | public: 14 | /// Constructs a default terminal color. 15 | termcolor_t() noexcept; 16 | 17 | static auto gray() -> termcolor_t; 18 | 19 | /// Creates a terminal color stream manipulator, which equals the blue color. 20 | /// 21 | /// Usually useful for marking information or debug events. 22 | static auto blue() -> termcolor_t; 23 | 24 | /// Creates a terminal color stream manipulator, which equals the green color. 25 | /// 26 | /// Usually useful for marking information events. 27 | static auto green() -> termcolor_t; 28 | 29 | /// Creates a terminal color stream manipulator, which equals the yellow color. 30 | /// 31 | /// Usually useful for marking warning events. 32 | static auto yellow() -> termcolor_t; 33 | 34 | /// Creates a terminal color stream manipulator, which equals the red color. 35 | /// 36 | /// Usually useful for marking error events. 37 | static auto red() -> termcolor_t; 38 | 39 | /// Creates a terminal color stream manipulator, which resets the color. 40 | static auto reset() -> termcolor_t; 41 | 42 | /// Lightens the current color. 43 | auto lighter() -> termcolor_t&; 44 | 45 | /// Applies the current color to the given stream and writes data specified to it. 46 | auto write(std::ostream& stream, const char* data, std::size_t size) -> void; 47 | 48 | /// Checks whether this terminal color differs from the default one. 49 | auto colored() const noexcept -> bool; 50 | 51 | auto operator==(const termcolor_t& other) const noexcept -> bool; 52 | auto operator!=(const termcolor_t& other) const noexcept -> bool; 53 | 54 | friend auto operator<<(std::ostream& stream, const termcolor_t& color) -> std::ostream&; 55 | 56 | private: 57 | termcolor_t(int attr, int code) noexcept; 58 | }; 59 | 60 | } // namespace v1 61 | } // namespace blackhole 62 | -------------------------------------------------------------------------------- /include/blackhole/attributes.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #ifdef __has_include 7 | #if __has_include() 8 | #define BLACKHOLE_HAVE_SMALL_VECTOR 9 | #endif 10 | #endif 11 | 12 | #ifdef BLACKHOLE_HAVE_SMALL_VECTOR 13 | #include 14 | #else 15 | #include 16 | #endif 17 | 18 | #include "blackhole/stdext/string_view.hpp" 19 | 20 | namespace blackhole { 21 | inline namespace v1 { 22 | 23 | template 24 | struct view_of; 25 | 26 | } // namespace v1 27 | } // namespace blackhole 28 | 29 | namespace blackhole { 30 | inline namespace v1 { 31 | namespace attribute { 32 | 33 | class value_t; 34 | class view_t; 35 | 36 | } // namespace attribute 37 | } // namespace v1 38 | } // namespace blackhole 39 | 40 | namespace blackhole { 41 | inline namespace v1 { 42 | 43 | typedef std::pair attribute_t; 44 | 45 | #ifdef BLACKHOLE_HAVE_SMALL_VECTOR 46 | typedef boost::container::small_vector attributes_t; 47 | #else 48 | typedef std::vector attributes_t; 49 | #endif 50 | 51 | template<> 52 | struct view_of { 53 | typedef string_view key_type; 54 | typedef attribute::view_t value_type; 55 | 56 | typedef std::pair type; 57 | }; 58 | 59 | template<> 60 | struct view_of { 61 | typedef view_of::type value_type; 62 | 63 | #ifdef BLACKHOLE_HAVE_SMALL_VECTOR 64 | typedef boost::container::small_vector type; 65 | #else 66 | typedef std::vector type; 67 | #endif 68 | }; 69 | 70 | /// Convenient typedef for attributes set view. 71 | typedef view_of::type attribute_list; 72 | 73 | #ifdef BLACKHOLE_HAVE_SMALL_VECTOR 74 | typedef boost::container::small_vector::type>, 16> attribute_pack; 75 | #else 76 | typedef std::vector::type>> attribute_pack; 77 | #endif 78 | 79 | } // namespace v1 80 | } // namespace blackhole 81 | -------------------------------------------------------------------------------- /tests/src/unit/sink/file/flusher/repeat.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | namespace sink { 8 | namespace file { 9 | namespace flusher { 10 | namespace { 11 | 12 | TEST(repeat_t, Default) { 13 | repeat_t flusher(3); 14 | 15 | EXPECT_EQ(3, flusher.threshold()); 16 | EXPECT_EQ(0, flusher.count()); 17 | } 18 | 19 | TEST(repeat_t, Zero) { 20 | repeat_t flusher(0); 21 | 22 | EXPECT_EQ(std::numeric_limits::max(), flusher.threshold()); 23 | } 24 | 25 | TEST(repeat_t, Update) { 26 | repeat_t flusher(3); 27 | 28 | EXPECT_EQ(flusher_t::idle, flusher.update(1)); 29 | EXPECT_EQ(flusher_t::idle, flusher.update(1)); 30 | EXPECT_EQ(flusher_t::flush, flusher.update(1)); 31 | EXPECT_EQ(flusher_t::idle, flusher.update(1)); 32 | EXPECT_EQ(flusher_t::idle, flusher.update(1)); 33 | EXPECT_EQ(flusher_t::flush, flusher.update(1)); 34 | } 35 | 36 | TEST(repeat_t, CounterOverflow) { 37 | repeat_t flusher(3); 38 | 39 | flusher.update(1); 40 | EXPECT_EQ(1, flusher.count()); 41 | 42 | flusher.update(42); 43 | EXPECT_EQ(2, flusher.count()); 44 | 45 | flusher.update(1); 46 | EXPECT_EQ(0, flusher.count()); 47 | } 48 | 49 | TEST(repeat_t, Reset) { 50 | repeat_t flusher(3); 51 | 52 | flusher.update(1); 53 | flusher.update(1); 54 | EXPECT_EQ(2, flusher.count()); 55 | 56 | flusher.reset(); 57 | EXPECT_EQ(0, flusher.count()); 58 | } 59 | 60 | TEST(repeat_t, ZeroWrite) { 61 | repeat_t flusher(3); 62 | 63 | flusher.update(0); 64 | EXPECT_EQ(0, flusher.count()); 65 | } 66 | 67 | TEST(repeat_factory_t, Init) { 68 | EXPECT_EQ(42, repeat_factory_t(42).threshold()); 69 | } 70 | 71 | TEST(repeat_factory_t, CreatesRepeatFlusher) { 72 | auto factory = repeat_factory_t(42); 73 | auto flusher = factory.create(); 74 | auto repeat = dynamic_cast(*flusher); 75 | 76 | EXPECT_EQ(42, repeat.threshold()); 77 | } 78 | 79 | } // namespace 80 | } // flusher 81 | } // namespace file 82 | } // namespace sink 83 | } // namespace v1 84 | } // namespace blackhole 85 | -------------------------------------------------------------------------------- /src/termcolor.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "blackhole/termcolor.hpp" 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | namespace { 8 | 9 | class scope_t { 10 | std::ostream& stream; 11 | 12 | public: 13 | scope_t(std::ostream& stream, const termcolor_t& color) : 14 | stream(stream) 15 | { 16 | stream << color; 17 | } 18 | 19 | ~scope_t() noexcept(false) { 20 | stream << termcolor_t::reset(); 21 | } 22 | }; 23 | 24 | } // namespace 25 | 26 | termcolor_t::termcolor_t() noexcept : 27 | attr(0), 28 | code(39) 29 | {} 30 | 31 | termcolor_t::termcolor_t(int attr, int code) noexcept : 32 | attr(attr), 33 | code(code) 34 | {} 35 | 36 | auto termcolor_t::gray() -> termcolor_t { 37 | return {0, 2}; 38 | } 39 | 40 | auto termcolor_t::blue() -> termcolor_t { 41 | return termcolor_t(0, 34); 42 | } 43 | 44 | auto termcolor_t::green() -> termcolor_t { 45 | return termcolor_t(0, 32); 46 | } 47 | 48 | auto termcolor_t::yellow() -> termcolor_t { 49 | return termcolor_t(0, 33); 50 | } 51 | 52 | auto termcolor_t::red() -> termcolor_t { 53 | return termcolor_t(0, 31); 54 | } 55 | 56 | auto termcolor_t::reset() -> termcolor_t { 57 | return termcolor_t(0, 0); 58 | } 59 | 60 | auto termcolor_t::operator==(const termcolor_t& other) const noexcept -> bool { 61 | return code == other.code && attr == other.attr; 62 | } 63 | 64 | auto termcolor_t::operator!=(const termcolor_t& other) const noexcept -> bool { 65 | return !operator==(other); 66 | } 67 | 68 | auto termcolor_t::write(std::ostream& stream, const char* data, std::size_t size) -> void { 69 | if (colored()) { 70 | scope_t scope(stream, *this); 71 | stream.write(data, static_cast(size)); 72 | } else { 73 | stream.write(data, static_cast(size)); 74 | } 75 | } 76 | 77 | auto termcolor_t::colored() const noexcept -> bool { 78 | return *this != termcolor_t(); 79 | } 80 | 81 | auto operator<<(std::ostream& stream, const termcolor_t& color) -> std::ostream& { 82 | return stream << "\033[" << color.code << "m"; 83 | } 84 | 85 | } // namespace v1 86 | } // namespace blackhole 87 | -------------------------------------------------------------------------------- /include/blackhole/config/option.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | namespace blackhole { 11 | inline namespace v1 { 12 | namespace config { 13 | 14 | class node_t; 15 | 16 | template 17 | class option; 18 | 19 | template<> 20 | class option { 21 | public: 22 | typedef std::function void> each_function; 23 | typedef std::function void> member_function; 24 | 25 | private: 26 | std::unique_ptr node; 27 | 28 | public: 29 | /// Constructs an option object that will contan nothing. 30 | option() noexcept; 31 | 32 | /// Constructs an option object that will contain the specified configuration node. 33 | explicit option(std::unique_ptr node) noexcept; 34 | 35 | explicit operator bool() const noexcept; 36 | 37 | /// Unwraps an option, yielding the content of an underlying config node object. 38 | auto unwrap() const -> boost::optional; 39 | 40 | auto to_bool() const -> boost::optional; 41 | auto to_sint64() const -> boost::optional; 42 | auto to_uint64() const -> boost::optional; 43 | auto to_double() const -> boost::optional; 44 | auto to_string() const -> boost::optional; 45 | 46 | auto each(const each_function& fn) const -> void; 47 | auto each_map(const member_function& fn) const -> void; 48 | 49 | auto operator[](const std::size_t& idx) const -> option; 50 | auto operator[](const std::string& key) const -> option; 51 | 52 | private: 53 | template 54 | auto to(F&& fn) const -> decltype(fn()); 55 | }; 56 | 57 | /// Constructs an option of the specified configuration node type using given arguments. 58 | template 59 | auto make_option(Args&&... args) -> option { 60 | return option(std::unique_ptr(new T(std::forward(args)...))); 61 | } 62 | 63 | } // namespace config 64 | } // namespace v1 65 | } // namespace blackhole 66 | -------------------------------------------------------------------------------- /src/formatter/string/parser.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "error.hpp" 10 | #include "token.hpp" 11 | 12 | namespace blackhole { 13 | inline namespace v1 { 14 | namespace formatter { 15 | namespace string { 16 | 17 | class factory_t; 18 | 19 | class parser_t { 20 | public: 21 | typedef char char_type; 22 | typedef std::basic_string string_type; 23 | 24 | private: 25 | typedef string_type::const_iterator const_iterator; 26 | 27 | enum class state_t { 28 | /// Undetermined state. 29 | unknown, 30 | /// Parsing literal. 31 | literal, 32 | /// Parsing placeholder. 33 | placeholder, 34 | /// Parser is broken. 35 | broken 36 | }; 37 | 38 | state_t state; 39 | 40 | const std::string pattern; 41 | const_iterator pos; 42 | 43 | std::unordered_map> factories; 44 | 45 | public: 46 | explicit parser_t(std::string pattern); 47 | 48 | auto next() -> boost::optional; 49 | 50 | private: 51 | auto parse_spec() -> std::string; 52 | auto parse_unknown() -> boost::optional; 53 | auto parse_literal() -> literal_t; 54 | auto parse_placeholder() -> token_t; 55 | 56 | /// Returns `true` on exact match with the given range from the current position. 57 | /// 58 | /// The given range may be larger than `std::distance(pos, std::end(pattern))`. 59 | template 60 | auto exact(const Range& range) const -> bool; 61 | 62 | /// Returns `true` on exact match with the given range and position. 63 | /// 64 | /// The given range may be larger than `std::distance(pos, std::end(pattern))`. 65 | /// 66 | /// \overload 67 | template 68 | auto exact(const_iterator pos, const Range& range) const -> bool; 69 | 70 | /// Marks the parser as broken and throws an exception 71 | template 72 | __attribute__((noreturn)) auto throw_(Args&&... args) -> void; 73 | }; 74 | 75 | } // namespace string 76 | } // namespace formatter 77 | } // namespace v1 78 | } // namespace blackhole 79 | -------------------------------------------------------------------------------- /src/sink/asynchronous.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include "blackhole/sink.hpp" 7 | 8 | #include "../recordbuf.hpp" 9 | 10 | namespace blackhole { 11 | inline namespace v1 { 12 | namespace sink { 13 | 14 | /// I can imagine: drop, sleep, wait. 15 | class overflow_policy_t { 16 | public: 17 | enum class action_t { 18 | retry, 19 | drop 20 | }; 21 | 22 | public: 23 | virtual ~overflow_policy_t() = default; 24 | 25 | /// Handles record queue overflow. 26 | /// 27 | /// This method is called when the queue is unable to enqueue more items. It's okay to throw 28 | /// exceptions from here, they will be propagated directly to the sink caller. 29 | virtual auto overflow() -> action_t = 0; 30 | 31 | virtual auto wakeup() -> void = 0; 32 | }; 33 | 34 | class overflow_policy_factory_t { 35 | public: 36 | auto create(const std::string& name) const -> std::unique_ptr; 37 | }; 38 | 39 | class asynchronous_t : public sink_t { 40 | struct value_type { 41 | recordbuf_t record; 42 | std::string message; 43 | }; 44 | 45 | typedef cds::container::VyukovMPSCCycleQueue queue_type; 46 | 47 | queue_type queue; 48 | std::atomic stopped; 49 | std::unique_ptr wrapped; 50 | 51 | std::unique_ptr overflow_policy; 52 | 53 | std::thread thread; 54 | 55 | public: 56 | asynchronous_t(std::unique_ptr wrapped, std::size_t factor = 10); 57 | 58 | // TODO: Full customization. 59 | asynchronous_t(std::unique_ptr sink, 60 | std::size_t factor, 61 | // std::unique_ptr filter, 62 | // std::unique_ptr underflow_policy, 63 | // std::unique_ptr exception_policy, 64 | std::unique_ptr overflow_policy); 65 | 66 | ~asynchronous_t(); 67 | 68 | /// Returns the message queue capacity in number of events. 69 | auto capacity() const -> std::size_t; 70 | 71 | auto emit(const record_t& record, const string_view& message) -> void; 72 | 73 | private: 74 | auto run() -> void; 75 | }; 76 | 77 | } // namespace sink 78 | } // namespace v1 79 | } // namespace blackhole 80 | -------------------------------------------------------------------------------- /examples/4.config_facade.cpp: -------------------------------------------------------------------------------- 1 | /// This example demonstrates how to initialize Blackhole from configuration file using JSON 2 | /// builder. 3 | /// In this case the entire logging pipeline is initialized from file including severity mapping. 4 | /// The logging facade is used to allow runtime formatting and attributes provisioning. 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | using namespace blackhole; 18 | 19 | /// As always specify severity enumeration. 20 | enum severity { 21 | debug = 0, 22 | info = 1, 23 | warning = 2, 24 | error = 3 25 | }; 26 | 27 | auto main(int argc, char** argv) -> int { 28 | if (argc != 2) { 29 | std::cerr << "Usage: 3.config PATH" << std::endl; 30 | return 1; 31 | } 32 | 33 | /// Here we are going to build the logger using registry. The registry's responsibility is to 34 | /// track registered handlers, formatter and sinks, but for now we're not going to register 35 | /// anything else, since there are predefined types. 36 | auto inner = blackhole::registry::configured() 37 | /// Specify the concrete builder type we want to use. It may be JSON, XML, YAML or whatever 38 | /// else. 39 | ->builder(std::ifstream(argv[1])) 40 | /// Build the logger named "root". 41 | .build("root"); 42 | 43 | /// Wrap the logger with facade to obtain an ability to format messages and provide attributes. 44 | auto log = blackhole::logger_facade(inner); 45 | 46 | log.log(severity::debug, "{} {} HTTP/1.1 {} {}", "GET", "/static/image.png", 404, 347); 47 | log.log(severity::info, "nginx/1.6 configured", { 48 | {"elapsed", 32.5} 49 | }); 50 | log.log(severity::warning, "client stopped connection before send body completed"); 51 | log.log(severity::error, "file does not exist: {}", "/var/www/favicon.ico", blackhole::attribute_list{ 52 | {"Cache", true}, 53 | {"Cache-Duration", 10}, 54 | {"User-Agent", "Mozilla Firefox"} 55 | }); 56 | 57 | return 0; 58 | } 59 | -------------------------------------------------------------------------------- /src/sink/syslog.cpp: -------------------------------------------------------------------------------- 1 | #include "blackhole/sink/syslog.hpp" 2 | 3 | #include 4 | 5 | #include "blackhole/config/node.hpp" 6 | #include "blackhole/config/option.hpp" 7 | #include "blackhole/stdext/string_view.hpp" 8 | #include "blackhole/record.hpp" 9 | 10 | #include "../procname.hpp" 11 | #include "syslog.hpp" 12 | 13 | namespace blackhole { 14 | inline namespace v1 { 15 | namespace sink { 16 | 17 | syslog_t::syslog_t() { 18 | data.option = LOG_PID; 19 | data.facility = LOG_USER; 20 | data.identity = procname().to_string(); 21 | 22 | ::openlog(identity().c_str(), option(), facility()); 23 | } 24 | 25 | syslog_t::~syslog_t() { 26 | ::closelog(); 27 | } 28 | 29 | auto syslog_t::option() const noexcept -> int { 30 | return data.option; 31 | } 32 | 33 | auto syslog_t::facility() const noexcept -> int { 34 | return data.facility; 35 | } 36 | 37 | auto syslog_t::identity() const noexcept -> const std::string& { 38 | return data.identity; 39 | } 40 | 41 | auto syslog_t::priorities() const -> std::vector { 42 | return data.priorities; 43 | } 44 | 45 | auto syslog_t::priorities(std::vector priorities) -> void { 46 | data.priorities = std::move(priorities); 47 | } 48 | 49 | auto syslog_t::emit(const record_t& record, const string_view& formatted) -> void { 50 | const auto severity = static_cast(record.severity()); 51 | 52 | int priority; 53 | if (severity < data.priorities.size()) { 54 | priority = data.priorities[severity]; 55 | } else { 56 | priority = LOG_ERR; 57 | } 58 | 59 | ::syslog(priority, "%.*s", static_cast(formatted.size()), formatted.data()); 60 | } 61 | 62 | } // namespace sink 63 | 64 | auto factory::type() const noexcept -> const char* { 65 | return "syslog"; 66 | } 67 | 68 | auto factory::from(const config::node_t& config) const -> std::unique_ptr { 69 | (void)registry; 70 | sink::syslog_t syslog; 71 | 72 | if (auto mapping = config["priorities"]) { 73 | std::vector priorities; 74 | mapping.each([&](const config::node_t& config) { 75 | priorities.emplace_back(config.to_sint64()); 76 | }); 77 | 78 | syslog.priorities(std::move(priorities)); 79 | } 80 | 81 | return std::unique_ptr(new sink::syslog_t(std::move(syslog))); 82 | } 83 | 84 | } // namespace v1 85 | } // namespace blackhole 86 | -------------------------------------------------------------------------------- /src/formatter/string/grammar.inl.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace blackhole { 8 | inline namespace v1 { 9 | namespace formatter { 10 | namespace string { 11 | 12 | /// Represents parser grammar result. 13 | /// Given: `{...:{{name}={value}:p}{, :s}>50s}`. 14 | /// Spec: `>50s`. 15 | /// Extension: 16 | /// Pattern: `{name}={value}`. 17 | /// Separator: `, `. 18 | struct grammar_result_t { 19 | struct extension_t { 20 | boost::optional pattern; 21 | boost::optional separator; 22 | }; 23 | 24 | extension_t extension; 25 | std::string spec; 26 | 27 | auto pattern() const -> boost::optional { 28 | return extension.pattern; 29 | } 30 | 31 | auto separator() const -> boost::optional { 32 | return extension.separator; 33 | } 34 | }; 35 | 36 | struct optional_result_t { 37 | struct extension_t { 38 | boost::optional> otherwise; 39 | }; 40 | 41 | extension_t extension; 42 | std::string spec; 43 | }; 44 | 45 | auto parse(std::string pattern) -> grammar_result_t; 46 | auto parse_pattern(std::string pattern) -> std::vector; 47 | 48 | } // namespace string 49 | } // namespace formatter 50 | } // namespace v1 51 | } // namespace blackhole 52 | 53 | #pragma clang diagnostic push 54 | #pragma clang diagnostic ignored "-Wdisabled-macro-expansion" 55 | 56 | typedef boost::optional> adapt1_type; 57 | 58 | BOOST_FUSION_ADAPT_STRUCT(blackhole::formatter::string::optional_result_t::extension_t, 59 | (adapt1_type, otherwise) 60 | ) 61 | 62 | BOOST_FUSION_ADAPT_STRUCT(blackhole::formatter::string::optional_result_t, 63 | (blackhole::formatter::string::optional_result_t::extension_t, extension) 64 | (std::string, spec) 65 | ) 66 | 67 | BOOST_FUSION_ADAPT_STRUCT(blackhole::formatter::string::grammar_result_t::extension_t, 68 | (boost::optional, pattern) 69 | (boost::optional, separator) 70 | ) 71 | 72 | BOOST_FUSION_ADAPT_STRUCT(blackhole::formatter::string::grammar_result_t, 73 | (blackhole::formatter::string::grammar_result_t::extension_t, extension) 74 | (std::string, spec) 75 | ) 76 | 77 | #pragma clang diagnostic pop 78 | -------------------------------------------------------------------------------- /tests/cmake/PrepareGoogleTesting.cmake: -------------------------------------------------------------------------------- 1 | include(Compiler) 2 | include(ExternalProject) 3 | 4 | # Builds at least on GCC 4.8 and Apple clang-703.0.31. 5 | function(download_google_testing) 6 | set(GOOGLETEST_GIT_REPO "https://github.com/abseil/googletest.git") 7 | 8 | if(${CMAKE_VERSION} VERSION_LESS "3.0.0") 9 | set(GOOGLETEST_GIT_TAG release-1.8.0) 10 | else() 11 | set(GOOGLETEST_GIT_TAG "") 12 | endif() 13 | 14 | # Using a specific git tag violates the Abseil/Google Test Live At Head philosophy. 15 | # https://abseil.io/about/philosophy 16 | # But in case the blackhole tests fail to build, uncommenting the GIT_TAG line will 17 | # use a Google Test version, which was working. 18 | 19 | set_directory_properties(properties EP_PREFIX "${CMAKE_BINARY_DIR}/foreign") 20 | ExternalProject_ADD(googlemock 21 | GIT_REPOSITORY ${GOOGLETEST_GIT_REPO} 22 | GIT_TAG ${GOOGLETEST_GIT_TAG} 23 | SOURCE_DIR "${CMAKE_BINARY_DIR}/foreign/googlemock" 24 | CMAKE_ARGS "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" "-DCMAKE_CXX_FLAGS=-fPIC" 25 | INSTALL_COMMAND "") 26 | ExternalProject_GET_PROPERTY(googlemock SOURCE_DIR) 27 | ExternalProject_GET_PROPERTY(googlemock BINARY_DIR) 28 | 29 | set(GOOGLETEST_INCLUDE_DIR ${SOURCE_DIR}/googletest/include PARENT_SCOPE) 30 | set(GOOGLEMOCK_INCLUDE_DIR ${SOURCE_DIR}/googlemock/include PARENT_SCOPE) 31 | 32 | if(${CMAKE_VERSION} VERSION_LESS "3.0.0") 33 | set(GOOGLETEST_BINARY_DIR ${BINARY_DIR}/googlemock/gtest PARENT_SCOPE) 34 | set(GOOGLEMOCK_BINARY_DIR ${BINARY_DIR}/googlemock PARENT_SCOPE) 35 | else() 36 | set(GOOGLETEST_BINARY_DIR "") 37 | set(GOOGLEMOCK_BINARY_DIR ${BINARY_DIR}/lib PARENT_SCOPE) 38 | endif() 39 | endfunction() 40 | 41 | function(prepare_google_testing) 42 | download_google_testing() 43 | 44 | include_directories(SYSTEM PARENT_SCOPE 45 | ${GOOGLETEST_INCLUDE_DIR} 46 | ${GOOGLEMOCK_INCLUDE_DIR}) 47 | link_directories(${GOOGLETEST_BINARY_DIR} ${GOOGLEMOCK_BINARY_DIR}) 48 | endfunction() 49 | -------------------------------------------------------------------------------- /tests/src/unit/sink/console.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #include "mocks/registry.hpp" 13 | 14 | namespace blackhole { 15 | inline namespace v1 { 16 | namespace sink { 17 | namespace { 18 | 19 | using ::testing::StrictMock; 20 | using ::testing::internal::CaptureStdout; 21 | using ::testing::internal::GetCapturedStdout; 22 | 23 | TEST(console_t, WriteIntoStandardOutputByDefault) { 24 | console_t sink; 25 | 26 | const string_view message(""); 27 | const attribute_pack pack; 28 | record_t record(42, message, pack); 29 | 30 | CaptureStdout(); 31 | sink.emit(record, "expected"); 32 | 33 | const std::string actual = GetCapturedStdout(); 34 | EXPECT_EQ("expected\n", actual); 35 | } 36 | 37 | class cout_redirector_t { 38 | std::streambuf* prev; 39 | 40 | public: 41 | cout_redirector_t(std::streambuf* buffer) : 42 | prev(std::cout.rdbuf(buffer)) 43 | {} 44 | 45 | ~cout_redirector_t() { 46 | std::cout.rdbuf(prev); 47 | } 48 | }; 49 | 50 | TEST(console_t, ColoredOutput) { 51 | console_t sink(std::cout, [](const record_t&) -> termcolor_t { 52 | return termcolor_t::red(); 53 | }); 54 | 55 | const string_view message(""); 56 | const attribute_pack pack; 57 | record_t record(42, message, pack); 58 | 59 | std::ostringstream stream; 60 | cout_redirector_t lock(stream.rdbuf()); 61 | sink.emit(record, "expected"); 62 | 63 | if (::isatty(1)) { 64 | EXPECT_EQ("\033[31mexpected\033[0m\n", stream.str()); 65 | } else { 66 | EXPECT_EQ("expected\n", stream.str()); 67 | } 68 | } 69 | 70 | TEST(console_t, NonColoredOutputToNonTTY) { 71 | std::stringstream stream; 72 | console_t sink(stream, [](const record_t&) -> termcolor_t { 73 | return termcolor_t::red(); 74 | }); 75 | 76 | const string_view message(""); 77 | const attribute_pack pack; 78 | record_t record(42, message, pack); 79 | 80 | sink.emit(record, "expected"); 81 | 82 | EXPECT_EQ("expected\n", stream.str()); 83 | } 84 | 85 | TEST(console_t, FactoryType) { 86 | EXPECT_EQ(std::string("console"), factory(mock_registry_t()).type()); 87 | } 88 | 89 | } // namespace 90 | } // namespace sink 91 | } // namespace v1 92 | } // namespace blackhole 93 | -------------------------------------------------------------------------------- /src/config/option.cpp: -------------------------------------------------------------------------------- 1 | #include "blackhole/config/option.hpp" 2 | 3 | #include 4 | 5 | #include "blackhole/config/node.hpp" 6 | 7 | namespace blackhole { 8 | inline namespace v1 { 9 | namespace config { 10 | 11 | option::option() noexcept = default; 12 | option::option(std::unique_ptr node) noexcept : 13 | node(std::move(node)) 14 | {} 15 | 16 | option::operator bool() const noexcept { 17 | return node != nullptr; 18 | } 19 | 20 | auto option::unwrap() const -> boost::optional { 21 | return to([&]() -> boost::optional { 22 | return *node; 23 | }); 24 | } 25 | 26 | auto option::to_bool() const -> boost::optional { 27 | return to([&]() -> boost::optional { 28 | return node->to_bool(); 29 | }); 30 | } 31 | 32 | auto option::to_sint64() const -> boost::optional { 33 | return to([&]() -> boost::optional { 34 | return node->to_sint64(); 35 | }); 36 | } 37 | 38 | auto option::to_uint64() const -> boost::optional { 39 | return to([&]() -> boost::optional { 40 | return node->to_uint64(); 41 | }); 42 | } 43 | 44 | auto option::to_double() const -> boost::optional { 45 | return to([&]() -> boost::optional { 46 | return node->to_double(); 47 | }); 48 | } 49 | 50 | auto option::to_string() const -> boost::optional { 51 | return to([&]() -> boost::optional { 52 | return node->to_string(); 53 | }); 54 | } 55 | 56 | auto option::each(const each_function& fn) const -> void { 57 | if (node) { 58 | node->each(fn); 59 | } 60 | } 61 | 62 | auto option::each_map(const member_function& fn) const -> void { 63 | if (node) { 64 | node->each_map(fn); 65 | } 66 | } 67 | 68 | auto option::operator[](const std::size_t& idx) const -> option { 69 | return to([&]() -> option { 70 | return (*node)[idx]; 71 | }); 72 | } 73 | 74 | auto option::operator[](const std::string& key) const -> option { 75 | return to([&]() -> option { 76 | return (*node)[key]; 77 | }); 78 | } 79 | 80 | template 81 | auto option::to(F&& fn) const -> decltype(fn()) { 82 | if (node) { 83 | return fn(); 84 | } else { 85 | return {}; 86 | } 87 | } 88 | 89 | } // namespace config 90 | } // namespace v1 91 | } // namespace blackhole 92 | -------------------------------------------------------------------------------- /include/blackhole/scope/watcher.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "blackhole/attributes.hpp" 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | 8 | class logger_t; 9 | 10 | } // namespace v1 11 | } // namespace blackhole 12 | 13 | namespace blackhole { 14 | inline namespace v1 { 15 | namespace scope { 16 | 17 | class manager_t; 18 | 19 | /// Represents scoped attributes watch interface. 20 | /// 21 | /// Scoped attributes is the mechanism allowing to attach thread-local attributes to any logger 22 | /// implementation until associated instance of this watch lives on the stack. 23 | /// 24 | /// Internally scoped attributes are organized in a thread-local linked list ordering by least 25 | /// living to most ones. This means, that the least attached attributes have more priority, but 26 | /// they don't override each other, i.e duplicates are allowed. 27 | /// 28 | /// \warning explicit moving instances of this class will probably invoke an undefined behavior, 29 | /// because it can violate construction/destruction order, which is strict. 30 | class watcher_t { 31 | std::reference_wrapper manager; 32 | watcher_t* prev; 33 | 34 | public: 35 | /// Constructs a scoped attributes watch which will be associated with the specified logger. 36 | explicit watcher_t(logger_t& logger); 37 | 38 | /// Both copy and move construction are deliberately prohibited. 39 | watcher_t(const watcher_t& other) = delete; 40 | watcher_t(watcher_t&& other) = delete; 41 | 42 | /// Destroys the current scoped watch with popping early attached attributes from the scoped 43 | /// attributes stack. 44 | /// 45 | /// \warning scoped watch objects **must** be destroyed in reversed order they were created, 46 | /// otherwise the behavior is undefined. 47 | virtual ~watcher_t(); 48 | 49 | /// Both copy and move assignment are deliberately prohibited. 50 | auto operator=(const watcher_t& other) -> watcher_t& = delete; 51 | auto operator=(watcher_t&& other) -> watcher_t& = delete; 52 | 53 | /// Recursively collects all scoped attributes into the given attributes pack. 54 | auto collect(attribute_pack& pack) const -> void; 55 | 56 | /// Recursively rebind all scoped attributes with the new logger manager. 57 | /// 58 | /// Usually called in the middle of the logger's move operation. 59 | auto rebind(manager_t& manager) -> void; 60 | 61 | /// Returns an immutable reference to the internal attribute list. 62 | virtual auto attributes() const -> const attribute_list& = 0; 63 | }; 64 | 65 | } // namespace scope 66 | } // namespace v1 67 | } // namespace blackhole 68 | -------------------------------------------------------------------------------- /src/datetime/generator.linux.cpp: -------------------------------------------------------------------------------- 1 | #ifdef __linux__ 2 | 3 | #include "generator.linux.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "blackhole/extensions/format.hpp" 10 | 11 | namespace blackhole { 12 | inline namespace v1 { 13 | namespace datetime { 14 | namespace { 15 | 16 | struct visitor_t : public boost::static_visitor<> { 17 | fmt::MemoryWriter& stream; 18 | std::tm tm; 19 | std::uint64_t usec; 20 | char buffer[1024]; 21 | 22 | visitor_t(fmt::MemoryWriter& stream, const std::tm& tm, std::uint64_t usec) : 23 | stream(stream), 24 | tm(tm), 25 | usec(usec) 26 | {} 27 | 28 | auto operator()(const literal_t& value) -> void { 29 | std::size_t ret = std::strftime(buffer, sizeof(buffer), value.value.c_str(), &tm); 30 | stream << fmt::StringRef(buffer, ret); 31 | } 32 | 33 | auto operator()(epoch_t) -> void { 34 | stream.write("{}", std::mktime(&tm)); 35 | } 36 | 37 | auto operator()(usecond_t) -> void { 38 | stream.write("{:06d}", usec); 39 | } 40 | }; 41 | 42 | } // namespace 43 | 44 | generator_t::generator_t(std::string pattern) { 45 | std::string literal; 46 | 47 | auto pos = std::begin(pattern); 48 | while (pos != std::end(pattern)) { 49 | if (boost::starts_with(boost::make_iterator_range(pos, std::end(pattern)), "%f")) { 50 | tokens.emplace_back(literal_t{std::move(literal)}); 51 | tokens.emplace_back(usecond_t{}); 52 | literal.clear(); 53 | ++pos; 54 | ++pos; 55 | } else if (boost::starts_with(boost::make_iterator_range(pos, std::end(pattern)), "%s")) { 56 | tokens.emplace_back(literal_t{std::move(literal)}); 57 | tokens.emplace_back(epoch_t{}); 58 | literal.clear(); 59 | ++pos; 60 | ++pos; 61 | } else { 62 | literal.push_back(*pos); 63 | ++pos; 64 | } 65 | } 66 | 67 | if (!literal.empty()) { 68 | tokens.emplace_back(literal_t{std::move(literal)}); 69 | } 70 | } 71 | 72 | auto generator_t::operator()(writer_type& stream, const std::tm& tm, std::uint64_t usec) const -> void { 73 | visitor_t visitor(stream, tm, usec); 74 | 75 | for (auto& token : tokens) { 76 | boost::apply_visitor(visitor, token); 77 | } 78 | } 79 | 80 | auto make_generator(const std::string& pattern) -> generator_t { 81 | return generator_t(pattern); 82 | } 83 | 84 | } // namespace datetime 85 | } // namespace v1 86 | } // namespace blackhole 87 | 88 | #endif 89 | -------------------------------------------------------------------------------- /include/blackhole/record.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "blackhole/attributes.hpp" 8 | #include "blackhole/stdext/string_view.hpp" 9 | #include "blackhole/severity.hpp" 10 | 11 | namespace blackhole { 12 | inline namespace v1 { 13 | 14 | using stdext::string_view; 15 | 16 | class record_t { 17 | public: 18 | struct inner_t; 19 | 20 | typedef std::chrono::system_clock clock_type; 21 | typedef clock_type::time_point time_point; 22 | 23 | private: 24 | typedef std::aligned_storage<64>::type storage_type; 25 | storage_type storage; 26 | 27 | public: 28 | /// Creates a log record with the given severity, possibly unformatted message and attributes. 29 | /// 30 | /// The created record contains almost all information about the logging event associated except 31 | /// the timestamp and formatted message. 32 | /// 33 | /// These missing attributes are set right after filtering pass with `activate` method. 34 | /// 35 | /// \warning constructing from rvalue references is explicitly forbidden, specified objects must 36 | /// outlive the record created. 37 | record_t(severity_t severity, 38 | std::reference_wrapper message, 39 | std::reference_wrapper attributes); 40 | 41 | /// Constructs a log record from its internal state. 42 | record_t(inner_t inner) noexcept; 43 | 44 | auto message() const noexcept -> const string_view&; 45 | auto severity() const noexcept -> severity_t; 46 | auto timestamp() const noexcept -> time_point; 47 | 48 | auto pid() const noexcept -> std::uint64_t; 49 | auto lwp() const noexcept -> std::uint64_t; 50 | auto tid() const noexcept -> std::thread::native_handle_type; 51 | 52 | auto formatted() const noexcept -> const string_view&; 53 | auto attributes() const noexcept -> const attribute_pack&; 54 | 55 | /// Check whether the record is active. 56 | /// 57 | /// Active record is considered as passed filtering stage and should be accepted by any logger 58 | /// implementors unconditionally. Note that an event can be anyway filtered out during 59 | /// filtering in handlers or sinks. 60 | auto is_active() const noexcept -> bool; 61 | 62 | /// Activate the record by setting the given formatted message accompanied by obtaining and 63 | /// setting the current time point. 64 | auto activate(const string_view& formatted = string_view()) noexcept -> void; 65 | 66 | private: 67 | auto inner() noexcept -> inner_t&; 68 | auto inner() const noexcept -> const inner_t&; 69 | }; 70 | 71 | } // namespace v1 72 | } // namespace blackhole 73 | -------------------------------------------------------------------------------- /src/record.cpp: -------------------------------------------------------------------------------- 1 | #include "blackhole/record.hpp" 2 | 3 | #if defined(__linux__) 4 | #include 5 | #include 6 | #include 7 | #endif 8 | 9 | #include 10 | 11 | #include "blackhole/attribute.hpp" 12 | 13 | #include "record.hpp" 14 | 15 | namespace blackhole { 16 | inline namespace v1 { 17 | 18 | record_t::record_t(severity_t severity, 19 | std::reference_wrapper message, 20 | std::reference_wrapper attributes) 21 | { 22 | static_assert(sizeof(inner_t) <= sizeof(record_t), "padding or alignment violation"); 23 | 24 | auto& inner = this->inner(); 25 | inner.message = message; 26 | inner.formatted = message; 27 | 28 | inner.severity = severity; 29 | inner.timestamp = time_point(); 30 | 31 | inner.tid = ::pthread_self(); 32 | 33 | inner.attributes = attributes; 34 | } 35 | 36 | record_t::record_t(inner_t inner) noexcept { 37 | this->inner() = std::move(inner); 38 | } 39 | 40 | auto record_t::message() const noexcept -> const string_view& { 41 | return inner().message.get(); 42 | } 43 | 44 | auto record_t::severity() const noexcept -> severity_t { 45 | return inner().severity; 46 | } 47 | 48 | auto record_t::timestamp() const noexcept -> time_point { 49 | return inner().timestamp; 50 | } 51 | 52 | auto record_t::pid() const noexcept -> std::uint64_t { 53 | return static_cast(::getpid()); 54 | } 55 | 56 | auto record_t::lwp() const noexcept -> std::uint64_t { 57 | #if defined(__linux__) 58 | return ::syscall(SYS_gettid); 59 | #else 60 | return 0; 61 | #endif 62 | } 63 | 64 | auto record_t::tid() const noexcept -> std::thread::native_handle_type { 65 | return inner().tid; 66 | } 67 | 68 | auto record_t::formatted() const noexcept -> const string_view& { 69 | return inner().formatted; 70 | } 71 | 72 | auto record_t::attributes() const noexcept -> const attribute_pack& { 73 | return inner().attributes.get(); 74 | } 75 | 76 | auto record_t::is_active() const noexcept -> bool { 77 | return inner().timestamp != time_point(); 78 | } 79 | 80 | auto record_t::activate(const string_view& formatted) noexcept -> void { 81 | if (formatted.data() != nullptr) { 82 | inner().formatted = formatted; 83 | } 84 | 85 | // TODO: I can use coarse clock in linux. 86 | inner().timestamp = clock_type::now(); 87 | } 88 | 89 | auto record_t::inner() noexcept -> inner_t& { 90 | return reinterpret_cast(storage); 91 | } 92 | 93 | auto record_t::inner() const noexcept -> const inner_t& { 94 | return reinterpret_cast(storage); 95 | } 96 | 97 | } // namespace v1 98 | } // namespace blackhole 99 | -------------------------------------------------------------------------------- /include/blackhole/logger.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "blackhole/attributes.hpp" 4 | #include "blackhole/message.hpp" 5 | #include "blackhole/severity.hpp" 6 | 7 | namespace blackhole { 8 | inline namespace v1 { 9 | namespace scope { 10 | 11 | class manager_t; 12 | 13 | } // namespace scope 14 | } // namespace v1 15 | } // namespace blackhole 16 | 17 | namespace blackhole { 18 | inline namespace v1 { 19 | 20 | /// Represents the common logging interface in the library. 21 | /// 22 | // TODO: Brief introduction why interface is so, when each method is used and when and how they 23 | // should be implemented. 24 | /// The library introduces severity levels as an integer number which real meaning depends on the 25 | /// context of your application. 26 | /// 27 | /// # Construction 28 | /// 29 | /// Blackhole provides two convenient ways to obtain a configured logger instance. 30 | /// 31 | /// If you want to configure a logger programmically, there are typed builder classes that provide 32 | /// chained interface for simplifying and making the code eye-candy. 33 | /// 34 | /// On the other hand sometimes you can't build a logger programmically, because its configuration 35 | /// is unknown at compile time and is obtained from some config file for example. Or if you have a 36 | /// configuration object (JSON, XML, folly dynamic) you may want to create a logger using it. For 37 | /// these cases there is a \sa registry_t class. 38 | /// 39 | /// Otherwise you can always build logger instances directly using its constructors. 40 | class logger_t { 41 | public: 42 | virtual ~logger_t() = 0; 43 | 44 | /// Logs the given message with the specified severity level. 45 | virtual auto log(severity_t severity, const message_t& message) -> void = 0; 46 | 47 | /// Logs the given message with the specified severity level and attributes pack attached. 48 | virtual auto log(severity_t severity, const message_t& message, attribute_pack& pack) -> void = 0; 49 | 50 | /// Logs a message which is only to be constructed if the result record passes filtering with 51 | /// the specified severity and including the attributes pack provided. 52 | virtual auto log(severity_t severity, const lazy_message_t& message, attribute_pack& pack) -> void = 0; 53 | 54 | /// Returns a scoped attributes manager reference. 55 | /// 56 | /// Returned manager allows the external tools to attach scoped attributes to the current logger 57 | /// instance, making every further log event to contain them until the registered scoped guard 58 | /// keeped alive. 59 | /// 60 | /// \returns a scoped attributes manager. 61 | virtual auto manager() -> scope::manager_t& = 0; 62 | }; 63 | 64 | } // namespace v1 65 | } // namespace blackhole 66 | -------------------------------------------------------------------------------- /examples/2.simple.cpp: -------------------------------------------------------------------------------- 1 | /// This example demonstrates the a bit extended the Blackhole logging library usage and its 2 | /// features, like: 3 | /// - Custom severity mapping. 4 | /// - Logger construction using builder pattern. 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | /// As always specify severity enumeration. 19 | enum severity { 20 | debug = 0, 21 | info = 1, 22 | warning = 2, 23 | error = 3 24 | }; 25 | 26 | /// Severity mapping function. 27 | static auto sevmap(std::size_t severity, const std::string& spec, blackhole::writer_t& writer) -> void { 28 | static const std::array mapping = {{"D", "I", "W", "E"}}; 29 | 30 | if (severity < mapping.size()) { 31 | writer.write(spec, mapping[severity]); 32 | } else { 33 | writer.write(spec, severity); 34 | } 35 | } 36 | 37 | auto main(int, char**) -> int { 38 | /// Here we are going to configure our string/console handler and to build the logger. 39 | auto log = blackhole::builder() 40 | /// Add the blocking handler. 41 | .add(blackhole::builder() 42 | /// Configure string formatter. 43 | /// 44 | /// Pattern syntax behaves like as usual substitution for placeholder. For example if 45 | /// the attribute named `severity` has value `2`, then pattern `{severity}` will invoke 46 | /// severity mapping function provided and the result will be `W`. 47 | .set(blackhole::builder("{severity}, [{timestamp}]: {message}") 48 | .mapping(&sevmap) 49 | .build()) 50 | /// Configure console sink to write into stdout (also stderr can be configured). 51 | .add(blackhole::builder() 52 | .build()) 53 | /// And build the handler. Multiple handlers can be added to a single logger, but right 54 | /// now we confine ourselves with a single handler. 55 | .build()) 56 | /// Build the logger. 57 | .build(); 58 | 59 | log->log(severity::debug, "GET /static/image.png HTTP/1.1 404 347"); 60 | log->log(severity::info, "nginx/1.6 configured"); 61 | log->log(severity::warning, "client stopped connection before send body completed"); 62 | log->log(severity::error, "file does not exist: /var/www/favicon.ico"); 63 | 64 | return 0; 65 | } 66 | -------------------------------------------------------------------------------- /tests/include/mocks/node.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace blackhole { 10 | inline namespace v1 { 11 | namespace config { 12 | namespace testing { 13 | namespace mock { 14 | 15 | class node_t : public ::blackhole::config::node_t { 16 | typedef ::blackhole::config::node_t super; 17 | 18 | public: 19 | auto is_bool() const noexcept -> bool { return false; } 20 | auto is_sint64() const noexcept -> bool { return false; } 21 | auto is_uint64() const noexcept -> bool { return is_uint64_(); } 22 | auto is_double() const noexcept -> bool { return false; } 23 | 24 | auto is_string() const noexcept -> bool { return is_string_(); } 25 | auto is_vector() const noexcept -> bool { return false; } 26 | auto is_object() const noexcept -> bool { return false; } 27 | 28 | MOCK_CONST_METHOD0(is_uint64_, bool()); 29 | MOCK_CONST_METHOD0(is_string_, bool()); 30 | 31 | MOCK_CONST_METHOD0(to_bool, bool()); 32 | MOCK_CONST_METHOD0(to_sint64, std::int64_t()); 33 | MOCK_CONST_METHOD0(to_uint64, std::uint64_t()); 34 | MOCK_CONST_METHOD0(to_double, double()); 35 | MOCK_CONST_METHOD0(to_string, std::string()); 36 | 37 | MOCK_CONST_METHOD1(each, void(const each_function&)); 38 | MOCK_CONST_METHOD1(each_map, void(const member_function&)); 39 | 40 | MOCK_CONST_METHOD1(subscript_idx, super*(const std::size_t&)); 41 | MOCK_CONST_METHOD1(subscript_key, super*(const std::string&)); 42 | 43 | auto operator[](const std::size_t& idx) const -> option { 44 | if (auto node = subscript_idx(idx)) { 45 | return option(std::unique_ptr(node)); 46 | } else { 47 | return {}; 48 | } 49 | } 50 | 51 | auto operator[](const std::string& key) const -> option { 52 | if (auto node = subscript_key(key)) { 53 | return option(std::unique_ptr(node)); 54 | } else { 55 | return {}; 56 | } 57 | } 58 | }; 59 | 60 | } // namespace mock 61 | } // namespace testing 62 | } // namespace config 63 | } // namespace v1 64 | } // namespace blackhole 65 | 66 | #include 67 | 68 | namespace blackhole { 69 | inline namespace v1 { 70 | namespace config { 71 | 72 | template<> 73 | class factory : public factory_t { 74 | public: 75 | MOCK_CONST_METHOD0(config, const node_t&()); 76 | }; 77 | 78 | template<> 79 | class factory_traits { 80 | public: 81 | static auto construct() -> std::unique_ptr { 82 | return blackhole::make_unique>(); 83 | } 84 | }; 85 | 86 | } // namespace config 87 | } // namespace v1 88 | } // namespace blackhole 89 | -------------------------------------------------------------------------------- /include/blackhole/root.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "blackhole/forward.hpp" 8 | #include "blackhole/logger.hpp" 9 | 10 | namespace blackhole { 11 | inline namespace v1 { 12 | 13 | class handler_t; 14 | class record_t; 15 | 16 | } // namespace v1 17 | } // namespace blackhole 18 | 19 | namespace blackhole { 20 | inline namespace v1 { 21 | 22 | class root_logger_t : public logger_t { 23 | public: 24 | typedef std::function bool> filter_t; 25 | 26 | private: 27 | struct sync_t; 28 | std::unique_ptr sync; 29 | 30 | struct inner_t; 31 | std::shared_ptr inner; 32 | 33 | public: 34 | /// Constructs a root level logger with the given handlers. 35 | /// 36 | /// \note you can create a logger with no handlers, it'll just drop all messages. 37 | root_logger_t(std::vector> handlers); 38 | 39 | /// Constructs a root level logger with the given filtering function and handlers. 40 | /// 41 | /// \overload 42 | /// \note you can create a logger with no handlers, it'll just drop all messages. 43 | root_logger_t(filter_t filter, std::vector> handlers); 44 | 45 | root_logger_t(const root_logger_t& other) = delete; 46 | 47 | /// Constructs a root level logger by consuming another existing logger. 48 | root_logger_t(root_logger_t&& other) noexcept; 49 | 50 | ~root_logger_t(); 51 | 52 | auto operator=(const root_logger_t& other) -> root_logger_t& = delete; 53 | auto operator=(root_logger_t&& other) noexcept -> root_logger_t&; 54 | 55 | /// Replaces the current logger filter function with the given one. 56 | /// 57 | /// Any logging event for which the filter function returns `false` is rejected. 58 | /// 59 | /// \warning the function must be thread-safe. 60 | auto filter(filter_t fn) -> void; 61 | 62 | auto log(severity_t severity, const message_t& message) -> void; 63 | auto log(severity_t severity, const message_t& message, attribute_pack& pack) -> void; 64 | auto log(severity_t severity, const lazy_message_t& message, attribute_pack& pack) -> void; 65 | 66 | auto manager() -> scope::manager_t&; 67 | 68 | private: 69 | template 70 | auto consume(severity_t severity, const string_view& pattern, attribute_pack& pack, const F& fn) -> void; 71 | }; 72 | 73 | template<> 74 | class builder { 75 | public: 76 | typedef root_logger_t result_type; 77 | 78 | private: 79 | class inner_t; 80 | std::unique_ptr d; 81 | 82 | public: 83 | builder(); 84 | 85 | auto add(std::unique_ptr handler) & -> builder&; 86 | auto add(std::unique_ptr handler) && -> builder&&; 87 | 88 | auto build() && -> std::unique_ptr; 89 | }; 90 | 91 | } // namespace v1 92 | } // namespace blackhole 93 | -------------------------------------------------------------------------------- /src/sink/asynchronous.cpp: -------------------------------------------------------------------------------- 1 | #include "blackhole/sink/asynchronous.hpp" 2 | 3 | #include 4 | 5 | #include "blackhole/config/node.hpp" 6 | #include "blackhole/config/option.hpp" 7 | #include "blackhole/registry.hpp" 8 | 9 | #include "../memory.hpp" 10 | #include "../util/deleter.hpp" 11 | #include "asynchronous.hpp" 12 | 13 | namespace blackhole { 14 | inline namespace v1 { 15 | 16 | class builder::inner_t { 17 | public: 18 | std::unique_ptr wrapped; 19 | std::unique_ptr overflow_policy; 20 | std::size_t factor; 21 | }; 22 | 23 | builder::builder(std::unique_ptr wrapped) : 24 | d(new inner_t{std::move(wrapped), sink::overflow_policy_factory_t().create("wait"), 10}) 25 | {} 26 | 27 | auto builder::factor(std::size_t value) & -> builder& { 28 | d->factor = value; 29 | return *this; 30 | } 31 | 32 | auto builder::factor(std::size_t value) && -> builder&& { 33 | return std::move(factor(value)); 34 | } 35 | 36 | auto builder::wait() & -> builder& { 37 | d->overflow_policy = sink::overflow_policy_factory_t().create("wait"); 38 | return *this; 39 | } 40 | 41 | auto builder::drop() && -> builder&& { 42 | return std::move(wait()); 43 | } 44 | 45 | auto builder::drop() & -> builder& { 46 | d->overflow_policy = sink::overflow_policy_factory_t().create("drop"); 47 | return *this; 48 | } 49 | 50 | auto builder::wait() && -> builder&& { 51 | return std::move(wait()); 52 | } 53 | 54 | auto builder::build() && -> std::unique_ptr { 55 | return blackhole::make_unique(std::move(d->wrapped), d->factor); 56 | } 57 | 58 | auto factory::type() const noexcept -> const char* { 59 | return "asynchronous"; 60 | } 61 | 62 | auto factory::from(const config::node_t& config) const -> 63 | std::unique_ptr 64 | { 65 | auto type = config["sink"]["type"].to_string(); 66 | 67 | if (!type) { 68 | throw std::invalid_argument("\"sink\" field with \"type\" is required"); 69 | } 70 | 71 | auto factory = registry.sink(type.get()); 72 | 73 | auto factor = config["factor"].to_uint64().get(); 74 | auto overflow = sink::overflow_policy_factory_t().create(config["overflow"].to_string().get()); 75 | 76 | // It's safe to unwrap here, because we've already checked that there is "sink" child and it's 77 | // an object. 78 | auto sink = factory(*config["sink"].unwrap()); 79 | 80 | return std::unique_ptr(new sink::asynchronous_t(std::move(sink), factor, std::move(overflow))); 81 | } 82 | 83 | template auto deleter_t::operator()(builder::inner_t* value) -> void; 84 | 85 | } // namespace v1 86 | } // namespace blackhole 87 | -------------------------------------------------------------------------------- /include/blackhole/sink/console.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "blackhole/factory.hpp" 6 | 7 | namespace blackhole { 8 | inline namespace v1 { 9 | namespace sink { 10 | 11 | /// Represents a console sink which is responsible for writing all incoming log events directly 12 | /// into one of the selected standard outputs with an ability to optionally colorize result strings. 13 | /// 14 | /// The sink automatically detects whether the destination stream is a TTY disabling colored output 15 | /// otherwise, which makes possible to redirect standard output to file without escaping codes 16 | /// garbage. 17 | /// 18 | /// Note, that despite of C++ `std::cout` and `std::cerr` thread-safety with no undefined behavior 19 | /// its guarantees is insufficiently for safe working with them from multiple threads, leading to 20 | /// result messages intermixing. 21 | /// To avoid this a global mutex is used internally, which is kinda hack. Any other stdout/stderr 22 | /// usage outside from logger will probably results in character mixing, but no undefined behavior 23 | /// will be invoked. 24 | class console_t; 25 | 26 | } // namespace sink 27 | 28 | template<> 29 | class builder { 30 | class inner_t; 31 | std::unique_ptr d; 32 | 33 | public: 34 | /// Constructs a defaultly configured console sink builder. 35 | /// 36 | /// By default the generated sink will write all incoming events to the standard output with no 37 | /// coloring. 38 | builder(); 39 | 40 | /// Sets the destination stream to the standard output pipe. 41 | auto stdout() & -> builder&; 42 | auto stdout() && -> builder&&; 43 | 44 | /// Sets the destination stream to the standard error pipe. 45 | auto stderr() & -> builder&; 46 | auto stderr() && -> builder&&; 47 | 48 | /// Sets terminal color mapping for a given severity making all log events to be colored with 49 | /// specified color. 50 | auto colorize(severity_t severity, termcolor_t color) & -> builder&; 51 | auto colorize(severity_t severity, termcolor_t color) && -> builder&&; 52 | 53 | /// Resets the terminal color mapping for this builder with the specified one. 54 | auto colorize(std::function fn) & -> builder&; 55 | auto colorize(std::function fn) && -> builder&&; 56 | 57 | /// Consumes this builder yielding a newly created console sink with the options configured. 58 | auto build() && -> std::unique_ptr; 59 | }; 60 | 61 | template<> 62 | class factory : public factory { 63 | const registry_t& registry; 64 | 65 | public: 66 | constexpr explicit factory(const registry_t& registry) noexcept : 67 | registry(registry) 68 | {} 69 | 70 | auto type() const noexcept -> const char* override; 71 | auto from(const config::node_t& config) const -> std::unique_ptr override; 72 | }; 73 | 74 | } // namespace v1 75 | } // namespace blackhole 76 | -------------------------------------------------------------------------------- /src/util/time.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | 8 | static auto gmtime(const time_t* t, struct tm* tp) noexcept -> struct tm* { 9 | int yday; 10 | uintptr_t n, sec, min, hour, mday, mon, year, wday, days, leap; 11 | 12 | // The calculation is valid for positive time_t only. 13 | n = static_cast(*t); 14 | days = n / 86400; 15 | 16 | // Jaunary 1, 1970 was Thursday 17 | wday = (4 + days) % 7; 18 | n %= 86400; 19 | hour = n / 3600; 20 | n %= 3600; 21 | min = n / 60; 22 | sec = n % 60; 23 | 24 | // The algorithm based on Gauss's formula, see src/http/ngx_http_parse_time.c. 25 | // Days since March 1, 1 BC. 26 | days = days - (31 + 28) + 719527; 27 | 28 | // The "days" should be adjusted to 1 only, however, some March 1st's go to previous year, so 29 | // we adjust them to 2. This causes also shift of the last Feburary days to next year, but we 30 | // catch the case when "yday" becomes negative. 31 | year = (days + 2) * 400 / (365 * 400 + 100 - 4 + 1); 32 | yday = static_cast(days - (365 * year + year / 4 - year / 100 + year / 400)); 33 | 34 | leap = (year % 4 == 0) && (year % 100 || (year % 400 == 0)); 35 | 36 | if (yday < 0) { 37 | yday = static_cast(365 + leap + static_cast(yday)); 38 | year--; 39 | } 40 | 41 | // The empirical formula that maps "yday" to month. There are at least 10 variants, some of 42 | // them are: 43 | // mon = (yday + 31) * 15 / 459 44 | // mon = (yday + 31) * 17 / 520 45 | // mon = (yday + 31) * 20 / 612 46 | mon = static_cast((yday + 31) * 10 / 306); 47 | 48 | // The Gauss's formula that evaluates days before the month. 49 | mday = static_cast(yday)- (367 * mon / 12 - 30) + 1; 50 | 51 | if (yday >= 306) { 52 | year++; 53 | mon -= 10; 54 | yday -= 306; 55 | } else { 56 | mon += 2; 57 | yday += 31 + 28 + static_cast(leap); 58 | } 59 | 60 | tp->tm_sec = static_cast(sec); 61 | tp->tm_min = static_cast(min); 62 | tp->tm_hour = static_cast(hour); 63 | tp->tm_mday = static_cast(mday); 64 | tp->tm_mon = static_cast(mon - 1); 65 | tp->tm_year = static_cast(year - 1900); 66 | tp->tm_yday = yday; 67 | tp->tm_wday = static_cast(wday); 68 | tp->tm_isdst = 0; 69 | 70 | return tp; 71 | } 72 | 73 | class tzinit_t { 74 | public: 75 | tzinit_t() { tzset(); } 76 | }; 77 | 78 | #pragma clang diagnostic push 79 | #pragma clang diagnostic ignored "-Wglobal-constructors" 80 | 81 | static const tzinit_t tz; 82 | 83 | #pragma clang diagnostic pop 84 | 85 | static auto localtime(const time_t* t, struct tm* tp) noexcept -> struct tm* { 86 | time_t time = *t - timezone; 87 | gmtime_r(&time, tp); 88 | tp->tm_gmtoff = timezone; 89 | tp->tm_zone = *tzname; 90 | 91 | return tp; 92 | } 93 | 94 | } // namespace v1 95 | } // namespace blackhole 96 | -------------------------------------------------------------------------------- /tests/registry.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "mocks/node.hpp" 7 | 8 | namespace blackhole { 9 | namespace testing { 10 | 11 | using ::testing::ByRef; 12 | using ::testing::Invoke; 13 | using ::testing::Return; 14 | using ::testing::ReturnRef; 15 | using ::testing::_; 16 | 17 | TEST(registry_t, ThrowsWhenTryingBuildOnHandlerWithNoType) { 18 | using config::testing::mock::node_t; 19 | 20 | auto registry = registry::empty(); 21 | 22 | node_t n0; 23 | auto n1 = new node_t; 24 | node_t n2; 25 | 26 | config::factory factory; 27 | 28 | try { 29 | auto builder = registry->builder(); 30 | 31 | auto& factory = dynamic_cast&>(builder.configurator()); 32 | EXPECT_CALL(factory, config()) 33 | .Times(1) 34 | .WillOnce(ReturnRef(n0)); 35 | 36 | EXPECT_CALL(n0, subscript_key("root")) 37 | .Times(1) 38 | .WillOnce(Return(n1)); 39 | 40 | EXPECT_CALL(*n1, each(_)) 41 | .Times(1) 42 | .WillOnce(Invoke([&](const node_t::each_function& fn) { 43 | fn(n2); 44 | })); 45 | 46 | EXPECT_CALL(n2, subscript_key("type")) 47 | .Times(1) 48 | .WillOnce(Return(nullptr)); 49 | 50 | builder.build("root"); 51 | FAIL(); 52 | } catch (const std::logic_error& err) { 53 | EXPECT_STREQ("handler with type \"blocking\" is not registered", err.what()); 54 | } 55 | } 56 | 57 | TEST(registry_t, ThrowsWhenTryingBuildOnUnknownHandler) { 58 | using config::testing::mock::node_t; 59 | 60 | auto registry = registry::configured(); 61 | 62 | node_t n0; 63 | auto n1 = new node_t; 64 | node_t n2; 65 | auto n3 = new node_t; 66 | 67 | config::factory factory; 68 | 69 | try { 70 | auto builder = registry->builder(); 71 | 72 | auto& factory = dynamic_cast&>(builder.configurator()); 73 | EXPECT_CALL(factory, config()) 74 | .Times(1) 75 | .WillOnce(ReturnRef(n0)); 76 | 77 | EXPECT_CALL(n0, subscript_key("root")) 78 | .Times(1) 79 | .WillOnce(Return(n1)); 80 | 81 | EXPECT_CALL(*n1, each(_)) 82 | .Times(1) 83 | .WillOnce(Invoke([&](const node_t::each_function& fn) { 84 | fn(n2); 85 | })); 86 | 87 | EXPECT_CALL(n2, subscript_key("type")) 88 | .Times(1) 89 | .WillOnce(Return(n3)); 90 | 91 | EXPECT_CALL(*n3, to_string()) 92 | .Times(1) 93 | .WillOnce(Return("unknown")); 94 | 95 | builder.build("root"); 96 | FAIL(); 97 | } catch (const std::logic_error& err) { 98 | EXPECT_STREQ("handler with type \"unknown\" is not registered", err.what()); 99 | } 100 | } 101 | 102 | } // namespace testing 103 | } // namespace blackhole 104 | -------------------------------------------------------------------------------- /include/blackhole/sink/asynchronous.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "blackhole/factory.hpp" 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | namespace sink { 8 | 9 | /// The asynchronous sink wraps the other sink and causes log events to be written to them on a 10 | /// separate thread. 11 | /// 12 | /// Depending on the underlying overflow policies and queues the implementation may preallocate the 13 | /// space required to keep all item structs in the memory. 14 | /// 15 | /// \note exceptions while writing to those sink will be hidden from the application. 16 | /// 17 | /// # Parameters 18 | /// 19 | /// The factor value maps directly into the queue capacity and equals exp2(factor). The value must 20 | /// fit in [0; 20] range (1048576 items). 21 | /// 22 | /// Overflow policy decides what action is taken when the queue is overflowed. There are currently 23 | /// only two available policies: drop and wait. The first one will silently (or not) drop all log 24 | /// events that weren't enqueued. The second one will block the caller thread until the queue is 25 | /// full. 26 | /// 27 | /// \throw std::invalid_argument on construction if the factor is greater than 20. 28 | /// \throw std::invalid_argument on construction if the overflow policy value differs from "drop" or 29 | /// "wait". 30 | class asynchronous_t; 31 | 32 | } // namespace sink 33 | 34 | template<> 35 | class builder { 36 | class inner_t; 37 | std::unique_ptr d; 38 | 39 | public: 40 | /// Constructs a sink builder from some other sink. 41 | /// 42 | /// The ownership of the specified sink is transferred to the builder. The default overflow 43 | /// policy will block the calling thread while the internal message queue is full. 44 | /// 45 | /// \param wrapped The target sink (usually the blocking one) that is need to make asynchronous. 46 | explicit builder(std::unique_ptr wrapped); 47 | 48 | /// Sets the queue size factor. 49 | auto factor(std::size_t value) & -> builder&; 50 | auto factor(std::size_t value) && -> builder&&; 51 | 52 | /// Sets the drop overflow policy. 53 | auto drop() & -> builder&; 54 | auto drop() && -> builder&&; 55 | 56 | /// Sets the wait overflow policy. 57 | auto wait() & -> builder&; 58 | auto wait() && -> builder&&; 59 | 60 | /// Consumes this builder yielding a newly created asynchronous sink with the options 61 | /// configured. 62 | auto build() && -> std::unique_ptr; 63 | }; 64 | 65 | /// Represents asynchronous sink factory. 66 | /// 67 | /// Register an instance of this class in the registry to be able to construct asynchronous sinks 68 | /// via config. 69 | template<> 70 | class factory : public factory { 71 | const registry_t& registry; 72 | 73 | public: 74 | constexpr explicit factory(const registry_t& registry) noexcept : 75 | registry(registry) 76 | {} 77 | 78 | auto type() const noexcept -> const char* override; 79 | auto from(const config::node_t& config) const -> std::unique_ptr override; 80 | }; 81 | 82 | } // namespace v1 83 | } // namespace blackhole 84 | -------------------------------------------------------------------------------- /src/sink/file.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include "blackhole/stdext/string_view.hpp" 11 | #include "blackhole/sink.hpp" 12 | #include "blackhole/sink/file.hpp" 13 | 14 | #include "../memory.hpp" 15 | #include "file/flusher.hpp" 16 | #include "file/rotate.hpp" 17 | #include "file/stream.hpp" 18 | 19 | namespace blackhole { 20 | inline namespace v1 { 21 | namespace sink { 22 | namespace file { 23 | 24 | class backend_t { 25 | std::string name; 26 | std::unique_ptr stream; 27 | std::unique_ptr rotate; 28 | std::unique_ptr flusher; 29 | 30 | public: 31 | backend_t(std::unique_ptr stream, std::unique_ptr rotate, std::unique_ptr flusher) : 32 | stream(std::move(stream)), 33 | rotate(std::move(rotate)), 34 | flusher(std::move(flusher)) 35 | {} 36 | 37 | auto should_rotate() const -> bool { 38 | return rotate->should_rotate(); 39 | } 40 | 41 | auto write(const string_view& message) -> void { 42 | stream->write(message.data(), static_cast(message.size())); 43 | stream->put('\n'); 44 | if (flusher->update(message.size() + 1) == flusher_t::flush) { 45 | stream->flush(); 46 | } 47 | } 48 | }; 49 | 50 | } // namespace file 51 | 52 | class file_t : public sink_t { 53 | std::unique_ptr stream_factory; 54 | std::unique_ptr rotate_factory; 55 | std::unique_ptr flusher_factory; 56 | 57 | struct { 58 | std::string path; 59 | std::map backends; 60 | } data; 61 | 62 | mutable std::mutex mutex; 63 | 64 | public: 65 | /// \param path a path with final destination file to open. All files are opened with append 66 | /// mode by default. 67 | file_t(const std::string& path, 68 | std::unique_ptr stream_factory, 69 | std::unique_ptr rotate_factory, 70 | std::unique_ptr flusher_factory); 71 | 72 | /// Returns a const lvalue reference to destination path pattern. 73 | /// 74 | /// The path can contain attribute placeholders, meaning that the real destination name will be 75 | /// deduced at runtime using provided log record. No real file will be opened at construction 76 | /// time. 77 | auto path() const -> const std::string&; 78 | 79 | auto filename(const record_t& record) const -> std::string; 80 | 81 | auto backend(const std::string& filename) -> file::backend_t&; 82 | 83 | auto create_backend(const std::string& filename) -> file::backend_t&; 84 | 85 | /// Outputs the formatted message with its associated record to the file. 86 | /// 87 | /// Depending on the filename pattern it is possible to write into multiple destinations. 88 | auto emit(const record_t& record, const string_view& formatted) -> void override; 89 | }; 90 | 91 | } // namespace sink 92 | } // namespace v1 93 | } // namespace blackhole 94 | -------------------------------------------------------------------------------- /src/formatter/string/token.cpp: -------------------------------------------------------------------------------- 1 | #include "token.hpp" 2 | 3 | namespace blackhole { 4 | inline namespace v1 { 5 | namespace formatter { 6 | namespace string { 7 | namespace placeholder { 8 | 9 | generic::generic(std::string name) : 10 | name(std::move(name)), 11 | spec("{}") 12 | {} 13 | 14 | generic::generic(std::string name, std::string spec) : 15 | name(std::move(name)), 16 | spec(std::move(spec)) 17 | {} 18 | 19 | generic::generic(std::string name) : 20 | generic(std::move(name)) 21 | {} 22 | 23 | generic::generic(std::string name, std::string spec) : 24 | generic(std::move(name), std::move(spec)) 25 | {} 26 | 27 | generic::generic(generic token, std::string prefix, std::string suffix) : 28 | generic(std::move(token)), 29 | prefix(std::move(prefix)), 30 | suffix(std::move(suffix)) 31 | {} 32 | 33 | message_t::message_t() : spec("{}") {} 34 | message_t::message_t(std::string spec) : spec(std::move(spec)) {} 35 | 36 | template 37 | severity::severity() : spec("{}") {} 38 | 39 | template 40 | severity::severity(std::string spec) : spec(std::move(spec)) {} 41 | 42 | timestamp::timestamp() : spec("{}") {} 43 | timestamp::timestamp(std::string spec) : spec(std::move(spec)) {} 44 | 45 | timestamp::timestamp() : 46 | pattern("%Y-%m-%d %H:%M:%S.%f"), 47 | spec("{}"), 48 | gmtime(true), 49 | generator(datetime::make_generator(pattern)) 50 | {} 51 | 52 | timestamp::timestamp(std::string pattern, std::string spec, bool gmtime) : 53 | pattern(pattern.empty() ? "%Y-%m-%d %H:%M:%S.%f" : std::move(pattern)), 54 | spec(std::move(spec)), 55 | gmtime(gmtime), 56 | generator(datetime::make_generator(this->pattern)) 57 | {} 58 | 59 | template 60 | process::process() : spec("{}") {} 61 | 62 | template 63 | process::process(std::string spec) : spec(std::move(spec)) {} 64 | 65 | template 66 | thread::thread() : spec("{}") {} 67 | 68 | template 69 | thread::thread(std::string spec) : spec(std::move(spec)) {} 70 | 71 | thread::thread() : spec("{:#x}") {} 72 | thread::thread(std::string spec) : spec(std::move(spec)) {} 73 | 74 | template 75 | attribute::attribute() : 76 | spec(""), 77 | format("{:}") 78 | {} 79 | 80 | template 81 | attribute::attribute(std::string spec) : 82 | spec(std::move(spec)), 83 | format("{:" + this->spec + "}") 84 | {} 85 | 86 | leftover_t::leftover_t() : 87 | spec("{:}"), 88 | separator(", "), 89 | tokens({ph::attribute(), literal_t(": "), ph::attribute()}) 90 | {} 91 | 92 | template struct severity; 93 | template struct severity; 94 | 95 | template struct process; 96 | template struct process; 97 | 98 | template struct thread; 99 | template struct thread; 100 | 101 | template struct attribute; 102 | template struct attribute; 103 | 104 | } // namespace placeholder 105 | } // namespace string 106 | } // namespace formatter 107 | } // namespace v1 108 | } // namespace blackhole 109 | -------------------------------------------------------------------------------- /src/handler/blocking.cpp: -------------------------------------------------------------------------------- 1 | #include "blackhole/handler/blocking.hpp" 2 | 3 | #include 4 | 5 | #include "blackhole/config/node.hpp" 6 | #include "blackhole/config/option.hpp" 7 | #include "blackhole/extensions/writer.hpp" 8 | #include "blackhole/formatter.hpp" 9 | #include "blackhole/registry.hpp" 10 | #include "blackhole/sink.hpp" 11 | 12 | #include "../memory.hpp" 13 | #include "../util/deleter.hpp" 14 | #include "blocking.hpp" 15 | 16 | namespace blackhole { 17 | inline namespace v1 { 18 | namespace handler { 19 | 20 | blocking_t::blocking_t(std::unique_ptr formatter, 21 | std::vector> sinks) : 22 | formatter(std::move(formatter)), 23 | sinks(std::move(sinks)) 24 | {} 25 | 26 | auto blocking_t::handle(const record_t& record) -> void { 27 | writer_t writer; 28 | 29 | formatter->format(record, writer); 30 | 31 | for (const auto& sink : sinks) { 32 | // TODO: Check for filter. 33 | sink->emit(record, writer.result()); 34 | } 35 | } 36 | 37 | } // namespace handler 38 | 39 | using handler::blocking_t; 40 | 41 | class builder::inner_t { 42 | public: 43 | std::unique_ptr formatter; 44 | std::vector> sinks; 45 | }; 46 | 47 | // TODO: TEST! 48 | builder::builder() : 49 | d(new inner_t) 50 | {} 51 | 52 | auto builder::set(std::unique_ptr formatter) & -> builder& { 53 | d->formatter = std::move(formatter); 54 | return *this; 55 | } 56 | 57 | auto builder::set(std::unique_ptr formatter) && -> builder&& { 58 | return std::move(set(std::move(formatter))); 59 | } 60 | 61 | auto builder::add(std::unique_ptr sink) & -> builder& { 62 | d->sinks.emplace_back(std::move(sink)); 63 | return *this; 64 | } 65 | 66 | auto builder::add(std::unique_ptr sink) && -> builder&& { 67 | return std::move(add(std::move(sink))); 68 | } 69 | 70 | auto builder::build() && -> std::unique_ptr { 71 | return blackhole::make_unique(std::move(d->formatter), std::move(d->sinks)); 72 | } 73 | 74 | auto factory::type() const noexcept -> const char* { 75 | return "blocking"; 76 | } 77 | 78 | auto factory::from(const config::node_t& config) const -> std::unique_ptr { 79 | builder builder; 80 | 81 | // TODO: Unsafe! Test and wrap with result. 82 | if (auto type = config["formatter"]["type"].to_string()) { 83 | builder.set(registry.formatter(type.get())(*config["formatter"].unwrap())); 84 | } else { 85 | throw std::invalid_argument("each handler must have a formatter with type"); 86 | } 87 | 88 | config["sinks"].each([&](const config::node_t& config) { 89 | if (auto type = config["type"].to_string()) { 90 | builder.add(registry.sink(type.get())(config)); 91 | } else { 92 | throw std::invalid_argument("each sink must have a type"); 93 | } 94 | }); 95 | 96 | return std::move(builder).build(); 97 | } 98 | 99 | template auto deleter_t::operator()(builder::inner_t*) -> void; 100 | 101 | } // namespace v1 102 | } // namespace blackhole 103 | -------------------------------------------------------------------------------- /tests/src/unit/sink/file/flusher/bytecount.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | namespace blackhole { 6 | inline namespace v1 { 7 | namespace sink { 8 | namespace file { 9 | namespace flusher { 10 | namespace { 11 | 12 | TEST(bytecount_t, Default) { 13 | bytecount_t flusher(1024); 14 | 15 | EXPECT_EQ(1024, flusher.threshold()); 16 | EXPECT_EQ(0, flusher.count()); 17 | } 18 | 19 | TEST(bytecount_t, Zero) { 20 | bytecount_t flusher(0); 21 | 22 | EXPECT_EQ(std::numeric_limits::max(), flusher.threshold()); 23 | } 24 | 25 | TEST(bytecount_t, Update) { 26 | bytecount_t flusher(1024); 27 | 28 | EXPECT_EQ(flusher_t::idle, flusher.update(10)); 29 | EXPECT_EQ(flusher_t::idle, flusher.update(1000)); 30 | EXPECT_EQ(flusher_t::flush, flusher.update(14)); 31 | EXPECT_EQ(flusher_t::idle, flusher.update(1023)); 32 | EXPECT_EQ(flusher_t::flush, flusher.update(1)); 33 | EXPECT_EQ(flusher_t::flush, flusher.update(100500)); // 100500 % 1024 = 148 34 | EXPECT_EQ(flusher_t::idle, flusher.update(2)); 35 | EXPECT_EQ(flusher_t::flush, flusher.update(875)); 36 | } 37 | 38 | TEST(bytecount_t, CounterOverflow) { 39 | bytecount_t flusher(1024); 40 | 41 | flusher.update(10); 42 | EXPECT_EQ(10, flusher.count()); 43 | 44 | flusher.update(1000); 45 | EXPECT_EQ(1010, flusher.count()); 46 | 47 | flusher.update(14); 48 | EXPECT_EQ(0, flusher.count()); 49 | 50 | flusher.update(1023); 51 | EXPECT_EQ(1023, flusher.count()); 52 | 53 | flusher.update(1); 54 | EXPECT_EQ(0, flusher.count()); 55 | 56 | flusher.update(100500); 57 | EXPECT_EQ(148, flusher.count()); 58 | 59 | flusher.update(2); 60 | EXPECT_EQ(150, flusher.count()); 61 | 62 | flusher.update(875); 63 | EXPECT_EQ(1, flusher.count()); 64 | } 65 | 66 | TEST(bytecount_t, Reset) { 67 | bytecount_t flusher(1024); 68 | 69 | flusher.update(10); 70 | EXPECT_EQ(10, flusher.count()); 71 | 72 | flusher.update(1000); 73 | EXPECT_EQ(1010, flusher.count()); 74 | 75 | flusher.reset(); 76 | EXPECT_EQ(0, flusher.count()); 77 | } 78 | 79 | TEST(bytecount_factory_t, Init) { 80 | EXPECT_EQ(1024, bytecount_factory_t(1024).threshold()); 81 | } 82 | 83 | TEST(bytecount_factory_t, CreatesByteCountFlusher) { 84 | auto factory = bytecount_factory_t(1024); 85 | auto flusher = factory.create(); 86 | auto repeat = dynamic_cast(*flusher); 87 | 88 | EXPECT_EQ(1024, repeat.threshold()); 89 | } 90 | 91 | TEST(parse_dunit, WithoutUnit) { 92 | EXPECT_EQ(1024, parse_dunit("1024")); 93 | } 94 | 95 | TEST(parse_dunit, KnownUnits) { 96 | EXPECT_EQ(1024, parse_dunit("1024B")); 97 | EXPECT_EQ(1024 * 1e3, parse_dunit("1024kB")); 98 | EXPECT_EQ(1024 * 1e6, parse_dunit("1024MB")); 99 | EXPECT_EQ(1024 * 1e9, parse_dunit("1024GB")); 100 | 101 | EXPECT_EQ(1024 * 1ULL << 10, parse_dunit("1024KiB")); 102 | EXPECT_EQ(1024 * 1ULL << 20, parse_dunit("1024MiB")); 103 | EXPECT_EQ(1024 * 1ULL << 30, parse_dunit("1024GiB")); 104 | } 105 | 106 | TEST(parse_dunit, ThrowsOnUnknownUnit) { 107 | EXPECT_THROW(parse_dunit("1024Hz"), std::invalid_argument); 108 | } 109 | 110 | } // namespace 111 | } // namespace flusher 112 | } // namespace file 113 | } // namespace sink 114 | } // namespace v1 115 | } // namespace blackhole 116 | -------------------------------------------------------------------------------- /include/blackhole/extensions/facade.inl.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "blackhole/attributes.hpp" 6 | #include "blackhole/stdext/string_view.hpp" 7 | #include "blackhole/extensions/writer.hpp" 8 | 9 | namespace blackhole { 10 | inline namespace v1 { 11 | 12 | namespace ph = std::placeholders; 13 | 14 | /// Internal details. Please move along, nothing to see here. 15 | namespace detail { 16 | namespace gcc { 17 | 18 | /// Workaround for GCC versions up to 4.9, which fails to expand variadic pack inside lambdas. 19 | template 20 | inline auto write_all(fmt::MemoryWriter& wr, const char* pattern, const Args&... args) -> string_view { 21 | wr.write(pattern, args...); 22 | return {wr.data(), wr.size()}; 23 | } 24 | 25 | } // namespace gcc 26 | 27 | /// Helper metafunction that deduces the last type from the given variadic pack. 28 | /// 29 | /// For example: 30 | /// last_of::type -> int. 31 | /// last_of::type -> double. 32 | template 33 | struct last_of; 34 | 35 | /// Forbid unpacking empty variadic pack. 36 | /// 37 | /// \note the same behavior can be achieved using another type parameter prepending variadic pack, 38 | /// but GCC fails to compile it with `sorry, unimplemented`. 39 | template<> 40 | struct last_of<>; 41 | 42 | template 43 | struct last_of { 44 | typedef T type; 45 | }; 46 | 47 | template 48 | struct last_of { 49 | typedef typename last_of::type type; 50 | }; 51 | 52 | /// Helper metafunction that determines whether the last parameter from given variadic pack is an 53 | /// attributes list. 54 | /// 55 | /// For example: 56 | /// with_attributes::type -> std::false_type. 57 | /// with_attributes::type -> std::true_type. 58 | template 59 | struct with_attributes : public std::is_same::type, attribute_list> {}; 60 | 61 | template 62 | struct dummy_t {}; 63 | 64 | template class F, typename T, typename... Args> 65 | struct internal_t; 66 | 67 | template class F, typename... Args, typename T, typename... Tail> 68 | struct internal_t, T, Tail...> { 69 | typedef typename internal_t, Tail...>::type type; 70 | }; 71 | 72 | template class F, typename... Args, typename Tail, typename Last> 73 | struct internal_t, Tail, Last> { 74 | typedef F type; 75 | }; 76 | 77 | template class F, typename... Args> 78 | struct without_tail { 79 | typedef typename detail::internal_t, Args...>::type type; 80 | }; 81 | 82 | template 83 | struct select_t { 84 | template 85 | static 86 | auto apply(Logger& log, int severity, const string_view& pattern, const Args&... args, 87 | const attribute_list& attributes) -> void 88 | { 89 | fmt::MemoryWriter wr; 90 | const auto fn = std::bind(&gcc::write_all, std::ref(wr), pattern.data(), std::cref(args)...); 91 | 92 | attribute_pack pack{attributes}; 93 | log.log(severity, {pattern, std::cref(fn)}, pack); 94 | } 95 | }; 96 | 97 | } // namespace detail 98 | } // namespace v1 99 | } // namespace blackhole 100 | -------------------------------------------------------------------------------- /include/blackhole/config/node.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace blackhole { 8 | inline namespace v1 { 9 | namespace config { 10 | 11 | template 12 | class option; 13 | 14 | /// Represents the configuration tree node. 15 | /// 16 | /// Blackhole operates with instances of this class while configuring the logging system from some 17 | /// generic source, from file for example. It assumes that the whole configuration can be described 18 | /// using tree data structure, like JSON, XML or YAML. 19 | /// To be able to initialize from your own data format you must create subclass and implement its 20 | /// converting methods as like as tree traversing using subscription operators. 21 | /// 22 | /// Implementations are free to throw exceptions inside conversion methods on either type mismatch 23 | /// or whatever else. 24 | class node_t { 25 | public: 26 | typedef std::function void> each_function; 27 | typedef std::function void> member_function; 28 | 29 | public: 30 | virtual ~node_t() = 0; 31 | 32 | virtual auto is_bool() const noexcept -> bool = 0; 33 | virtual auto is_sint64() const noexcept -> bool = 0; 34 | virtual auto is_uint64() const noexcept -> bool = 0; 35 | virtual auto is_double() const noexcept -> bool = 0; 36 | virtual auto is_string() const noexcept -> bool = 0; 37 | virtual auto is_vector() const noexcept -> bool = 0; 38 | virtual auto is_object() const noexcept -> bool = 0; 39 | 40 | /// Tries to convert the underlying object to bool. 41 | virtual auto to_bool() const -> bool = 0; 42 | 43 | /// Tries to convert the underlying object to signed integer. 44 | virtual auto to_sint64() const -> std::int64_t = 0; 45 | 46 | /// Tries to convert the underlying object to unsigned integer. 47 | virtual auto to_uint64() const -> std::uint64_t = 0; 48 | 49 | /// Tries to convert the underlying object to double. 50 | virtual auto to_double() const -> double = 0; 51 | 52 | /// Tries to convert the underlying object to string. 53 | virtual auto to_string() const -> std::string = 0; 54 | 55 | /// Assuming that the underlying object is an array, performs inner iteration over it by 56 | /// applying the given function to each element. 57 | /// 58 | /// Should do nothing if the underlying array is empty. 59 | virtual auto each(const each_function& fn) const -> void = 0; 60 | 61 | /// Assuming that the underlying object is a map, performs inner iteration over it by applying 62 | /// the given function to each key-value element. 63 | /// 64 | /// Should do nothing if the underlying map is empty. 65 | virtual auto each_map(const member_function& fn) const -> void = 0; 66 | 67 | /// Assuming that the underlying object is an array performs index operation returning the 68 | /// option object with some node at the given index on success, none otherwise. 69 | /// 70 | /// Depending on the concrete implementation it may or not throw exceptions on out of range 71 | /// access. 72 | virtual auto operator[](const std::size_t& idx) const -> option = 0; 73 | 74 | /// Assuming that the underlying object is a map performs tree traversing operation returning 75 | /// option object with some node at the given key on success, none otherwise. 76 | /// 77 | /// Depending on the concrete implementation it may or not throw exceptions on out of range 78 | /// access. 79 | virtual auto operator[](const std::string& key) const -> option = 0; 80 | }; 81 | 82 | } // namespace config 83 | } // namespace v1 84 | } // namespace blackhole 85 | -------------------------------------------------------------------------------- /include/blackhole/registry.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "forward.hpp" 8 | 9 | namespace blackhole { 10 | inline namespace v1 { 11 | 12 | class builder_t { 13 | const registry_t& registry; 14 | std::unique_ptr factory; 15 | 16 | public: 17 | builder_t(const registry_t& registry, std::unique_ptr factory); 18 | 19 | auto configurator() noexcept -> config::factory_t&; 20 | 21 | auto build(const std::string& name) -> root_logger_t; 22 | 23 | private: 24 | auto handler(const config::node_t& config) const -> std::unique_ptr; 25 | }; 26 | 27 | class registry_t { 28 | public: 29 | typedef std::function(const config::node_t&)> sink_factory; 30 | typedef std::function(const config::node_t&)> filter_factory; 31 | typedef std::function(const config::node_t&)> handler_factory; 32 | typedef std::function(const config::node_t&)> formatter_factory; 33 | 34 | public: 35 | virtual ~registry_t() = default; 36 | 37 | /// Returns the sink factory with the given type if registered, throws otherwise. 38 | virtual auto sink(const std::string& type) const -> sink_factory = 0; 39 | 40 | /// Returns the filter factory with the given type if registered, throws otherwise. 41 | virtual auto filter(const std::string& type) const -> filter_factory = 0; 42 | 43 | /// Returns the handler factory with the given type if registered, throws otherwise. 44 | virtual auto handler(const std::string& type) const -> handler_factory = 0; 45 | 46 | /// Returns the formatter factory with the given type if registered, throws otherwise. 47 | virtual auto formatter(const std::string& type) const -> formatter_factory = 0; 48 | 49 | /// Returns a logger builder by constructing its configuration factory using the given 50 | /// arguments. 51 | template 52 | auto builder(Args&&... args) const -> builder_t; 53 | 54 | /// Registers a new typed factory with this registry. 55 | /// 56 | /// After registering the new factory can be used for constructing formatters, sinks or handlers 57 | /// depending on type using generic configuration object. 58 | template 59 | auto add(Args&&... args) -> void; 60 | 61 | /// Registers a new sink factory with this registry. 62 | virtual auto add(std::shared_ptr> factory) -> void = 0; 63 | 64 | /// Registers a new filter factory with this registry. 65 | virtual auto add(std::shared_ptr> factory) -> void = 0; 66 | 67 | /// Registers a new handler factory with this registry. 68 | virtual auto add(std::shared_ptr> factory) -> void = 0; 69 | 70 | /// Registers a new formatter factory with this registry. 71 | virtual auto add(std::shared_ptr> factory) -> void = 0; 72 | }; 73 | 74 | template 75 | inline auto registry_t::add(Args&&... args) -> void { 76 | add(std::make_shared>(std::forward(args)...)); 77 | } 78 | 79 | template 80 | inline auto registry_t::builder(Args&&... args) const -> builder_t { 81 | return {*this, config::factory_traits::construct(std::forward(args)...)}; 82 | } 83 | 84 | namespace registry { 85 | 86 | /// Creates a new empty default registry. 87 | auto empty() -> std::unique_ptr; 88 | 89 | /// Creates a new configured default registry, which will contain all available components in the 90 | /// library. 91 | auto configured() -> std::unique_ptr; 92 | 93 | } // namespace registry 94 | } // namespace v1 95 | } // namespace blackhole 96 | -------------------------------------------------------------------------------- /tests/record.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #if defined(__linux__) 4 | #include 5 | #include 6 | #include 7 | #endif 8 | 9 | #include 10 | #include 11 | 12 | namespace blackhole { 13 | namespace testing { 14 | 15 | TEST(Record, Severity) { 16 | const string_view message("GET /porn.png HTTP/1.1"); 17 | const attribute_pack pack; 18 | 19 | record_t record(42, message, pack); 20 | 21 | EXPECT_EQ(42, record.severity()); 22 | } 23 | 24 | TEST(Record, Message) { 25 | const string_view message("GET /porn.png HTTP/1.1"); 26 | const attribute_pack pack; 27 | 28 | record_t record(42, message, pack); 29 | 30 | EXPECT_EQ("GET /porn.png HTTP/1.1", record.message().to_string()); 31 | } 32 | 33 | TEST(Record, Attributes) { 34 | const string_view message("GET /porn.png HTTP/1.1"); 35 | const view_of::type attributes{{"key#1", {42}}}; 36 | const attribute_pack pack{attributes}; 37 | 38 | record_t record(42, message, pack); 39 | 40 | ASSERT_EQ(1, record.attributes().size()); 41 | EXPECT_EQ(attributes, record.attributes().at(0).get()); 42 | } 43 | 44 | TEST(Record, Pid) { 45 | const string_view message("GET /porn.png HTTP/1.1"); 46 | const attribute_pack pack; 47 | 48 | record_t record(42, message, pack); 49 | 50 | EXPECT_EQ(::getpid(), record.pid()); 51 | } 52 | 53 | TEST(Record, Lwp) { 54 | const string_view message("GET /porn.png HTTP/1.1"); 55 | const attribute_pack pack; 56 | 57 | record_t record(42, message, pack); 58 | 59 | #if defined(__linux__) 60 | EXPECT_TRUE(record.lwp() > 0); 61 | EXPECT_EQ(syscall(SYS_gettid), record.lwp()); 62 | #else 63 | EXPECT_EQ(0, record.lwp()); 64 | #endif 65 | } 66 | 67 | TEST(Record, Tid) { 68 | const string_view message("GET /porn.png HTTP/1.1"); 69 | const attribute_pack pack; 70 | 71 | record_t record(42, message, pack); 72 | EXPECT_EQ(::pthread_self(), record.tid()); 73 | } 74 | 75 | TEST(Record, NullTimestampByDefault) { 76 | const string_view message("GET /porn.png HTTP/1.1"); 77 | const attribute_pack pack; 78 | 79 | record_t record(42, message, pack); 80 | 81 | EXPECT_EQ(record_t::time_point(), record.timestamp()); 82 | } 83 | 84 | TEST(Record, Timestamp) { 85 | const string_view message("GET /porn.png HTTP/1.1"); 86 | const attribute_pack pack; 87 | 88 | const auto min = record_t::clock_type::now(); 89 | record_t record(42, message, pack); 90 | record.activate(); 91 | const auto max = record_t::clock_type::now(); 92 | 93 | EXPECT_TRUE(min <= record.timestamp()); 94 | EXPECT_TRUE(max >= record.timestamp()); 95 | } 96 | 97 | TEST(Record, FormattedEqualsMessageByDefault) { 98 | const string_view message("GET /porn.png HTTP/1.1"); 99 | const attribute_pack pack; 100 | 101 | record_t record(42, message, pack); 102 | record.activate(); 103 | 104 | EXPECT_EQ("GET /porn.png HTTP/1.1", record.formatted().to_string()); 105 | } 106 | 107 | TEST(Record, Formatted) { 108 | const string_view message("GET /porn.png HTTP/1.1"); 109 | const string_view formatted("GET /porn.png HTTP/1.1 - SUCCESS"); 110 | const attribute_pack pack; 111 | 112 | record_t record(42, message, pack); 113 | record.activate(formatted); 114 | 115 | EXPECT_EQ("GET /porn.png HTTP/1.1 - SUCCESS", record.formatted().to_string()); 116 | } 117 | 118 | TEST(Record, IsActive) { 119 | const string_view message("GET /porn.png HTTP/1.1"); 120 | const attribute_pack pack; 121 | 122 | record_t record(42, message, pack); 123 | EXPECT_FALSE(record.is_active()); 124 | 125 | record.activate(); 126 | EXPECT_TRUE(record.is_active()); 127 | } 128 | 129 | } // namespace testing 130 | } // namespace blackhole 131 | -------------------------------------------------------------------------------- /tests/src/unit/sink/udp.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #include "mocks/node.hpp" 13 | #include "mocks/registry.hpp" 14 | 15 | namespace blackhole { 16 | inline namespace v1 { 17 | namespace sink { 18 | namespace socket { 19 | namespace { 20 | 21 | using ::testing::Return; 22 | using ::testing::StrictMock; 23 | 24 | TEST(udp_t, Endpoint) { 25 | udp_t sink("0.0.0.0", 20000); 26 | 27 | EXPECT_EQ(boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), 20000), sink.endpoint()); 28 | } 29 | 30 | TEST(udp_t, SendsData) { 31 | boost::asio::io_service io_service; 32 | boost::asio::ip::udp::socket socket(io_service, 33 | boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), 0)); 34 | const auto endpoint = socket.local_endpoint(); 35 | 36 | udp_t sink(endpoint.address().to_string(), endpoint.port()); 37 | 38 | const string_view message(""); 39 | const attribute_pack pack; 40 | const record_t record(0, message, pack); 41 | 42 | sink.emit(record, "{}"); 43 | 44 | boost::array buffer; 45 | boost::asio::ip::udp::endpoint remote; 46 | socket.receive_from(boost::asio::buffer(buffer), remote, 0); 47 | 48 | EXPECT_EQ('{', buffer[0]); 49 | EXPECT_EQ('}', buffer[1]); 50 | } 51 | 52 | TEST(udp_t, FactoryType) { 53 | EXPECT_EQ(std::string("udp"), factory(mock_registry_t()).type()); 54 | } 55 | 56 | TEST(udp_t, FactoryThrowsIfHostParameterIsMissing) { 57 | StrictMock config; 58 | 59 | EXPECT_CALL(config, subscript_key("host")) 60 | .Times(1) 61 | .WillOnce(Return(nullptr)); 62 | 63 | try { 64 | factory(mock_registry_t()).from(config); 65 | } catch (const std::invalid_argument& err) { 66 | EXPECT_STREQ(R"(parameter "host" is required)", err.what()); 67 | } 68 | } 69 | 70 | TEST(udp_t, FactoryThrowsIfPortParameterIsMissing) { 71 | using config::testing::mock::node_t; 72 | 73 | StrictMock config; 74 | 75 | auto n1 = new node_t; 76 | EXPECT_CALL(config, subscript_key("host")) 77 | .Times(1) 78 | .WillOnce(Return(n1)); 79 | 80 | EXPECT_CALL(*n1, to_string()) 81 | .Times(1) 82 | .WillOnce(Return("localhost")); 83 | 84 | EXPECT_CALL(config, subscript_key("port")) 85 | .Times(1) 86 | .WillOnce(Return(nullptr)); 87 | 88 | try { 89 | factory(mock_registry_t()).from(config); 90 | } catch (const std::invalid_argument& err) { 91 | EXPECT_STREQ(R"(parameter "port" is required)", err.what()); 92 | } 93 | } 94 | 95 | TEST(udp_t, FactoryConfig) { 96 | using config::testing::mock::node_t; 97 | 98 | StrictMock config; 99 | 100 | auto n1 = new node_t; 101 | EXPECT_CALL(config, subscript_key("host")) 102 | .Times(1) 103 | .WillOnce(Return(n1)); 104 | 105 | EXPECT_CALL(*n1, to_string()) 106 | .Times(1) 107 | .WillOnce(Return("0.0.0.0")); 108 | 109 | auto n2 = new node_t; 110 | EXPECT_CALL(config, subscript_key("port")) 111 | .Times(1) 112 | .WillOnce(Return(n2)); 113 | 114 | EXPECT_CALL(*n2, to_uint64()) 115 | .Times(1) 116 | .WillOnce(Return(20000)); 117 | 118 | const auto sink = factory(mock_registry_t()).from(config); 119 | const auto& cast = dynamic_cast(*sink); 120 | 121 | EXPECT_EQ(boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), 20000), cast.endpoint()); 122 | } 123 | 124 | } // namespace 125 | } // namespace socket 126 | } // namespace sink 127 | } // namespace v1 128 | } // namespace blackhole 129 | -------------------------------------------------------------------------------- /tests/src/unit/sink/asynchronous.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | // TODO: Rename directory to just "mock" to be consistent with namespace. 10 | #include "mocks/registry.hpp" 11 | #include "mocks/sink.hpp" 12 | 13 | namespace blackhole { 14 | inline namespace v1 { 15 | namespace sink { 16 | namespace { 17 | 18 | using ::testing::_; 19 | using ::testing::Invoke; 20 | 21 | namespace mock = testing::mock; 22 | 23 | TEST(asynchronous_t, DelegatesEmit) { 24 | std::unique_ptr wrapped(new mock::sink_t); 25 | 26 | EXPECT_CALL(*wrapped, emit(_, string_view("formatted message"))) 27 | .Times(1) 28 | .WillOnce(Invoke([](const record_t& record, string_view) { 29 | ASSERT_EQ(1, record.attributes().size()); 30 | EXPECT_EQ((attribute_list{{"key#1", {"value#1"}}}), record.attributes().at(0).get()); 31 | })); 32 | 33 | asynchronous_t sink(std::move(wrapped)); 34 | 35 | const string_view message("unformatted message"); 36 | const attribute_list attributes{{"key#1", {"value#1"}}}; 37 | const attribute_pack pack({attributes}); 38 | record_t record(42, message, pack); 39 | 40 | sink.emit(record, "formatted message"); 41 | } 42 | 43 | TEST(asynchronous_t, FactoryType) { 44 | mock_registry_t registry; 45 | factory factory(registry); 46 | 47 | EXPECT_EQ(std::string("asynchronous"), factory.type()); 48 | } 49 | 50 | TEST(overflow_policy_factory_t, CreatesRegisteredPolicies) { 51 | EXPECT_NO_THROW(overflow_policy_factory_t().create("drop")); 52 | EXPECT_NO_THROW(overflow_policy_factory_t().create("wait")); 53 | } 54 | 55 | TEST(overflow_policy_factory_t, ThrowsIfRequestedNonRegisteredPolicy) { 56 | EXPECT_THROW(overflow_policy_factory_t().create(""), std::invalid_argument); 57 | } 58 | 59 | TEST(asynchronous_t, Builder) { 60 | std::unique_ptr wrapped(new mock::sink_t); 61 | 62 | builder builder(std::move(wrapped)); 63 | auto sink = std::move(builder).build(); 64 | 65 | EXPECT_EQ(1024, dynamic_cast(*sink).capacity()); 66 | } 67 | 68 | TEST(asynchronous_t, BuilderSetFactor) { 69 | std::unique_ptr wrapped(new mock::sink_t); 70 | 71 | builder builder(std::move(wrapped)); 72 | builder.factor(5); 73 | auto sink = std::move(builder).build(); 74 | 75 | EXPECT_EQ(32, dynamic_cast(*sink).capacity()); 76 | } 77 | 78 | TEST(asynchronous_t, BuilderSetFactorFlow) { 79 | std::unique_ptr wrapped(new mock::sink_t); 80 | 81 | auto sink = builder(std::move(wrapped)) 82 | .factor(5) 83 | .build(); 84 | 85 | EXPECT_EQ(32, dynamic_cast(*sink).capacity()); 86 | } 87 | 88 | TEST(asynchronous_t, BuilderSetDropOverflowPolicy) { 89 | std::unique_ptr wrapped(new mock::sink_t); 90 | 91 | builder builder(std::move(wrapped)); 92 | builder.drop(); 93 | std::move(builder).build(); 94 | } 95 | 96 | TEST(asynchronous_t, BuilderSetDropOverflowPolicyFlow) { 97 | std::unique_ptr wrapped(new mock::sink_t); 98 | 99 | builder(std::move(wrapped)) 100 | .drop() 101 | .build(); 102 | } 103 | 104 | TEST(asynchronous_t, BuilderSetWaitOverflowPolicy) { 105 | std::unique_ptr wrapped(new mock::sink_t); 106 | 107 | builder builder(std::move(wrapped)); 108 | builder.wait(); 109 | std::move(builder).build(); 110 | } 111 | 112 | TEST(asynchronous_t, BuilderSetWaitOverflowPolicyFlow) { 113 | std::unique_ptr wrapped(new mock::sink_t); 114 | 115 | builder(std::move(wrapped)) 116 | .wait() 117 | .build(); 118 | } 119 | 120 | } // namespace 121 | } // namespace sink 122 | } // namespace v1 123 | } // namespace blackhole 124 | --------------------------------------------------------------------------------