GELF infrastructure

This commit is contained in:
2022-11-13 00:16:43 -05:00
parent f4af8e4a89
commit 883913b92d
14 changed files with 1053 additions and 4 deletions
+17 -2
View File
@@ -2,13 +2,25 @@ PHP_ARG_ENABLE(wrenchboard_api,
[Whether to enable the "wrenchboard_api" extension],
[ --enable-wrenchboard_api Enable "wrenchboard_api" extension support])
# *** Mac:
# brew install rapidjson boost
# c++ -DRAPIDJSON_HAS_STDSTRING=1 -I./include -I/usr/include -I/opt/homebrew/include -std=gnu++14 -o main.cpp.o -c main.cpp
# c++ main.cpp.o -o main -L/opt/homebrew/lib -lboost_system-mt -lboost_iostreams-mt -lpthread
# *** Linux:
# dnf install rapidjson-devel boost boost-devel # centos
# apt install rapidjson-dev libboost-all-dev # ubuntu
# c++ -DRAPIDJSON_HAS_STDSTRING=1 -I./include -I/usr/include -std=gnu++14 -o main.cpp.o -c main.cpp
# c++ main.cpp.o -o main -lboost_system -lboost_iostreams -lpthread
if test $PHP_WRENCHBOARD_API != "no"; then
PHP_REQUIRE_CXX()
PHP_SUBST(WRENCHBOARD_API_SHARED_LIBADD)
PHP_ADD_LIBRARY(stdc++, 1, WRENCHBOARD_API_SHARED_LIBADD)
PHP_ADD_INCLUDE(/usr/include/pgsql)
PHP_ADD_INCLUDE(/usr/include/postgresql)
PHP_ADD_LIBRARY_WITH_PATH(pq, /usr/lib64, WRENCHBOARD_API_SHARED_LIBADD)
PHP_ADD_LIBRARY(stdc++, 1, WRENCHBOARD_API_SHARED_LIBADD)
PHP_ADD_LIBRARY(curl, 1, WRENCHBOARD_API_SHARED_LIBADD)
PHP_ADD_LIBRARY(config++, 1, WRENCHBOARD_API_SHARED_LIBADD)
PHP_ADD_LIBRARY(json-c, 1, WRENCHBOARD_API_SHARED_LIBADD)
@@ -16,6 +28,9 @@ if test $PHP_WRENCHBOARD_API != "no"; then
PHP_ADD_LIBRARY(z, 1, WRENCHBOARD_API_SHARED_LIBADD)
PHP_ADD_LIBRARY(pcrecpp, 1, WRENCHBOARD_API_SHARED_LIBADD)
PHP_ADD_LIBRARY(gcc_s, 1, WRENCHBOARD_API_SHARED_LIBADD)
PHP_ADD_LIBRARY(boost_system, 1, WRENCHBOARD_API_SHARED_LIBADD)
PHP_ADD_LIBRARY(boost_iostreams, 1, WRENCHBOARD_API_SHARED_LIBADD)
PHP_ADD_LIBRARY(pthread, 1, WRENCHBOARD_API_SHARED_LIBADD)
#LIBCFG_DIR="`pwd`/libconfig-1.3.1"
@@ -25,7 +40,7 @@ if test $PHP_WRENCHBOARD_API != "no"; then
wrenchboard_api,
core/*.cc shared_tool/*.cc,
$ext_shared,
,-I@ext_srcdir@/libconfig-1.0.1)
,-I@ext_srcdir@/include/gelfcpp -std=gnu++14 -DRAPIDJSON_HAS_STDSTRING=1)
PHP_DEFINE([WRENCHBOARD_API_NS],[\"wrenchboard_api\"],[])
PHP_DEFINE([WRENCHBOARD_CONFIG],[\"`pwd|sed s/src//`etc/\"],[])
PHP_DEFINE([WRENCHBOARD_LOG],[\"`pwd|sed s/src//`logs/wrenchboard_api.log\"],[])
+48 -1
View File
@@ -1,7 +1,12 @@
#include "clog.h"
#include "php_wrenchboard_log.h"
//gelfcpp::output::GelfUDPOutput graylog("10.0.0.112", 12201);
void logfmt( TLogLevel level, const char * format, ... ) {
size_t n = 32678;
char buffer[n];
bzero(buffer, n);
try {
//FILELog::ReportingLevel() = level;
va_list args;
@@ -15,10 +20,19 @@ void logfmt( TLogLevel level, const char * format, ... ) {
FILELog::ToString(level).c_str(),
getpid(),
(level > logDEBUG ? level - logDEBUG : 0, '\t'));
vfprintf(f, format, args);
vsnprintf (buffer, n, format, args);
fprintf(f, buffer);
fprintf(f, "\n");
}
va_end (args);
// Graylog
/*if (strlen(buffer) > 0) {
GELF_MESSAGE(graylog)
(CurrentTimeStamp())
("level", FILELog::ToString(level).c_str())
("pid", getpid())
(buffer);
} // */
} catch(const std::exception& e) {
FILE_LOG(logERROR) << e.what();
}
@@ -40,3 +54,36 @@ void logfmt( TLogLevel level, const char * format, ... ) {
}
*/
/* void GraylogStream(std::string raw) {
std::string marker1 ("]: ");
std::string marker2 (" [");
std::string marker3 (" ");
std::size_t found = raw.find(marker1);
if (found != std::string::npos) {
std::string msg = raw.substr (found + 3);
std::string level ("FLOG_MAX");
found = raw.find(marker2);
if (found != std::string::npos) {
std::string str = raw.substr(0, found - 1);
boost::trim_right(str);
found = str.find(marker3);
while (found != std::string::npos) {
level = str.substr(found);
found = str.find(marker3);
}
}
if (!msg.empty()) {
GELF_MESSAGE(graylog)
(CurrentTimeStamp())
("pid", getpid())
("level", level.c_str())
(msg.c_str());
}
}
}
// */
+17 -1
View File
@@ -4,6 +4,12 @@
#include "php_wrenchboard_log.h"
#include "php_filelog_max_level.h"
#include <gelfcpp/GelfMessageStream.hpp>
#include <gelfcpp/output/GelfUDPOutput.hpp>
#include <gelfcpp/decorator/Timestamp.hpp>
#include <boost/algorithm/string.hpp>
#include <sstream>
#include <string>
#include <cstdio>
@@ -12,6 +18,8 @@
#include <unistd.h>
inline std::string NowTime();
inline gelfcpp::decorator::CurrentTimestamp CurrentTimeStamp();
void GraylogStream(std::string raw);
enum TLogLevel {logERROR, logWARNING, logINFO, logDEBUG, logDEBUG1, logDEBUG2, logDEBUG3, logDEBUG4, logSQL, FLOG_MAX};
@@ -45,7 +53,7 @@ std::ostringstream& Log<T>::Get(TLogLevel level)
{
os << "- " << NowTime();
os << " " << ToString(level);
os << " [" << getpid() << "]: ";
os << " [" << getpid() << "]: ";
os << std::string(level > logDEBUG ? level - logDEBUG : 0, '\t');
return os;
}
@@ -53,6 +61,9 @@ std::ostringstream& Log<T>::Get(TLogLevel level)
template <typename T>
Log<T>::~Log()
{
// Graylog
//GraylogStream(os.str());
os << std::endl;
T::Output(os.str());
}
@@ -182,6 +193,11 @@ inline std::string NowTime()
#endif //WIN32
inline gelfcpp::decorator::CurrentTimestamp CurrentTimeStamp()
{
gelfcpp::decorator::CurrentTimestamp timestamp;
return timestamp;
}
#endif //__LOG_H__
@@ -0,0 +1,176 @@
#pragma once
#include <rapidjson/document.h>
#include <unordered_set>
#include <string>
#include <chrono>
namespace gelfcpp
{
namespace detail
{
struct DocumentAccessor;
}
/**
* \brief A single GELF message, with write-only access to its fields.
*
* \note This class only supports GELF version 1.1 and treats deprecated standard fields as normal additional fields.
*
* Currently the following standard fields are handled: <code>version, host, short_message, full_message, timestamp, level</code>.
* Standard fields are included "as-is". All other fields are prefixed with "_".
*/
class GelfMessage
{
friend struct detail::DocumentAccessor;
struct FieldSetter
{
GelfMessage& owner;
std::string field;
template<typename T>
void operator=(T&& value)
{
owner.SetField(field, std::forward<T>(value));
}
};
public:
GelfMessage() :
doc_(rapidjson::kObjectType)
{
SetField("version", "1.1");
}
/**
* \brief Quick accessor for <code>short_message</code> field.
*
* Behaves like \code SetField("short_message", message). \endcode
*
* \param message message
*/
void SetMessage(const std::string& message)
{
SetField("short_message", message);
}
/**
* \brief Quick accessor for <code>full_message</code> field.
*
* Behaves like \code SetField("full_message", message). \endcode
*
* \param message message
*/
void SetFullMessage(const std::string& message)
{
SetField("full_message", message);
}
/**
* \brief Quick accessor for <code>host</code> field.
*
* Behaves like \code SetField("host", message). \endcode
*
* \param host hostname
*/
void SetHost(const std::string& host)
{
SetField("host", host);
}
/**
* \brief Quick accessor for <code>timestamp</code> field.
*
* Behaves like \code SetField("timestamp", message). \endcode
*
* \param timestamp timestamp
*/
void SetTimestamp(double timestamp)
{
SetField("timestamp", timestamp);
}
#ifndef GELFCPP_DOXYGEN_RUNNING
template<typename T>
auto SetField(const std::string& name, T value) -> std::enable_if_t<std::is_arithmetic<T>::value && !std::is_same<T, bool>::value>
{
rapidjson::Value json(std::forward<T>(value));
SetField(name, std::move(json));
}
void SetField(const std::string& name, bool value)
{
SetField(name, value ? 1 : 0);
}
void SetField(const std::string& name, const char* value)
{
rapidjson::Value json(value, doc_.GetAllocator());
SetField(name, std::move(json));
}
void SetField(const std::string& name, const std::string& value)
{
rapidjson::Value json(value, doc_.GetAllocator());
SetField(name, std::move(json));
}
#else
/**
* \brief Adds a field to the message.
*
* \note If a field was already assigned the old value is overwritten.
*
* \tparam T value type, supported: <code>bool, integral, floating point, strings</code>
* \param name field name, the "_" prefix is added automatically
* \param value field value of any supported type
*/
template<typename T> void SetField(const std::string& name, T value) {}
#endif
/**
* \brief Adds a field to the message.
*
* This behaves like SetField(const std::string&, T) but allows map/array like syntax.
*
* Example usage:
* \code{.cpp}
* GelfMessage message;
* message["field_name"] = field_value;
* \endcode
*
* \param field field name, the "_" prefix is added automatically
* \return wrapper to allow assignment, see the usage example.
*/
FieldSetter operator[](const std::string& field)
{
return { *this, field };
}
private:
void SetField(const std::string& field, rapidjson::Value&& value)
{
static const std::unordered_set<std::string> BUILDIN_FIELDS{ "version", "host", "short_message", "full_message", "timestamp", "level" };
rapidjson::Value key;
if (BUILDIN_FIELDS.find(field) != BUILDIN_FIELDS.end())
key.SetString(field.c_str(), static_cast<rapidjson::SizeType>(field.size()), doc_.GetAllocator());
else
key.SetString(("_" + field).c_str(), static_cast<rapidjson::SizeType>(field.size() + 1), doc_.GetAllocator());
auto find = doc_.FindMember(key);
if (find != doc_.MemberEnd())
find->value = std::move(value);
else
doc_.AddMember(std::move(key), std::move(value), doc_.GetAllocator());
}
private:
rapidjson::Document doc_;
};
}
@@ -0,0 +1,144 @@
#pragma once
#include <gelfcpp/GelfMessage.hpp>
#include <type_traits>
#include <functional>
namespace gelfcpp
{
/**
* \brief Builder class for GelfMessage instances.
*
* Allows convenient construction for GelfMessages with additional Decorator support.
* The builder is implicitly convertible to a GelfMessage and can be directly passed to an Output.
*
* \note The internal GelfMessage is not copy-constructible, thus \code GelfMessage msg = builder; \endcode is invalid!
* But move-construction from an temporary GelfMessageBuilder is allowed.
*
* Example usage:
* \code
* GelfMessageBuilder builder;
* builder("This is the short_message");
* builder("field_1", true)("field_2", "chains are possible too!");
*
* output->Write(builder);
* \endcode
*
* Decorators are also supported.
* A decorator is a functional having the following syntax:
* \code
* void operator()(GelfMessage& message)
* \endcode
* This allows to dynamically set common values via a globally defined decorator.
* Common use cases are e.g. adding the current timestamp, setting the "host" field.
*
* Example useage:
* \code
* auto add_common_fields = [](GelfMessage& message)
* {
* message.SetTimestamp(std::time(nullptr));
* message.SetHost("my-host");
* };
* builder(add_common_fields);
* \endcode
*/
struct GelfMessageBuilder
{
public:
#ifndef GELFCPP_DOXYGEN_RUNNING
GelfMessageBuilder&& operator()(const std::string& message) &&
{
message_.SetMessage(message);
return std::move(*this);
}
template<typename T>
GelfMessageBuilder&& operator()(const std::string& field, T&& value) &&
{
message_.SetField(field, std::forward<T>(value));
return std::move(*this);
}
GelfMessageBuilder& operator()(const std::string& message) &
{
message_.SetMessage(message);
return *this;
}
template<typename T>
GelfMessageBuilder& operator()(const std::string& field, T&& value) &
{
message_.SetField(field, std::forward<T>(value));
return *this;
}
template<typename Decorator, typename = decltype(std::declval<Decorator>()(std::declval<GelfMessage&>()))>
GelfMessageBuilder& operator()(Decorator&& decorator) &
{
std::forward<Decorator>(decorator)(message_);
return *this;
}
template<typename Decorator, typename = decltype(std::declval<Decorator>()(std::declval<GelfMessage&>()))>
GelfMessageBuilder&& operator()(Decorator&& decorator) &&
{
std::forward<Decorator>(decorator)(message_);
return std::move(*this);
}
operator const GelfMessage&() const&
{
return message_;
}
operator GelfMessage&&() &&
{
return std::move(message_);
}
#else
/**
* \brief Sets the short_message field
*
* Equivalent to \c gelf_message.SetMessage(message).
*
* \param message message
* \return \c *this , for chaining
* \see GelfMessage::SetMessage()
*/
GelfMessageBuilder& operator()(const std::string& message) {}
/**
* \brief Sets a field to the given value
*
* Equivalent to \c gelf_message.SetField(field, value).
*
* \tparam T any supported field type, \see GelfMessage::SetField()
* \param field field name
* \param value field value
* \return \c *this , for chaining
* \see GelfMessage::SetField()
*/
template<typename T> GelfMessageBuilder& operator()(const std::string& field, T value) {}
/**
* \brief Applies a decorator to the message
*
* Equivalent to \c decorator(gelf_message).
*
* \tparam Decorator decorator type
* \param decorator decorator function
* \return \c *this , for chaining
*/
template<typename Decorator> GelfMessageBuilder& operator()(Decorator decorator) {}
#endif
private:
GelfMessage message_;
};
}
@@ -0,0 +1,94 @@
#pragma once
#include <gelfcpp/GelfMessageBuilder.hpp>
#include <gelfcpp/detail/Sender.hpp>
#include <memory>
namespace gelfcpp
{
/**
* \brief Allows stream like sending of GelfMessage
*
* Usually used in conjunction with a convenience macro like GELF_MESSAGE to allow conditional sending.
*
* Allows contextual-conversion to bool to check wether the output is valid.
* An output is valid if it can accept messages, meaning:
* * output is a reference to an output type
* * output is a non-null pointer to an output type
* * output is a valid smart pointer (shared, unique) to an output type
*
* \note
* Send explicitly requires to provide the output again, since it is not stored inside the stream.
* This is due to the need to support arbitrary, non-virtual output types.
*
* Usage example:
* \code
* GelfMessageStream stream(output);
* if (stream)
* {
* stream.Send(output) = message;
* // or
* stream.Send(output) << message_1 << message_2;
* }
* \endcode
*/
struct GelfMessageStream
{
public:
/**
* \brief Creates a new stream
*
* Checks wether the output is valid and thus invalidates this stream.
*
* \tparam T output type
* \param output output
*/
template<typename T>
explicit GelfMessageStream(T&& output) :
valid_(detail::Sender<std::decay_t<T>>::IsValid(output)) {}
/**
* \brief Checks if this stream is associated to a valid output
*
* \return \c true if output is valid, \c false otherwise
*/
explicit operator bool()
{
return valid_;
}
/**
* \brief Opens a stream to an output
*
* \note After calling Send() once this stream is considered invalid.
*
* \tparam T output type
* \param output output
* \return Sink for GelfMessages, which are send to the output
*/
template<typename T>
detail::Sender<std::decay_t<T>> Send(T&& output)
{
valid_ = false;
return { std::forward<T>(output) };
}
private:
bool valid_;
};
}
/**
* \ingroup Marcos
* \brief Convenience macro for easy stream-like creation and sending of GelfMessages
*
* Usage:
* \code
* GELF_MESSAGE()("short_message")("field_1", value);
* \endcode
*/
#define GELF_MESSAGE(output) \
for (::gelfcpp::GelfMessageStream stream(output); stream ;) \
stream.Send(output) = ::gelfcpp::GelfMessageBuilder()
@@ -0,0 +1,56 @@
#pragma once
#include <gelfcpp/GelfMessage.hpp>
#include <boost/asio/ip/host_name.hpp>
namespace gelfcpp
{
namespace decorator
{
/**
* \brief Decorator which appends the "hostname"
*
* Defaults to the current hostname of the system.
*/
class Host
{
public:
/**
* \brief Uses the systems current hostname
*/
Host() : Host(boost::asio::ip::host_name()) {}
/**
* \brief Explicitly sets the hostname
* \param host the hostname
*/
Host(const std::string& host) : host_(host) {}
/**
* \brief Resets the hostname to the system current hostname
*/
void SetDefault() { SetHost(boost::asio::ip::host_name()); }
/**
* \brief Explicitly sets the hostname
* \param host the hostname
*/
void SetHost(const std::string& host) { host_ = host; }
/**
* \brief Decorator apply operator
* \param message the message
*/
void operator()(GelfMessage& message) const
{
message.SetField("host", host_);
}
private:
std::string host_;
};
}
}
@@ -0,0 +1,76 @@
#pragma once
#include <gelfcpp/GelfMessage.hpp>
#include <tuple>
namespace gelfcpp
{
namespace decorator
{
/**
* \brief Statically combines multiple decorators
*
* \note The decorators are applied in definition order.
*
* \tparam Decorators decorators that should be applied
*/
template<typename... Decorators>
class StaticDecoratorSet
{
using DecoratorSet = std::tuple<Decorators...>;
public:
/**
* \brief Decorator apply operator
* \param message the message
*/
void operator()(GelfMessage& message)
{
operator()(message, std::make_index_sequence<std::tuple_size<DecoratorSet>::value>{});
}
#ifndef GELFCPP_DOXYGEN_RUNNING
template<std::size_t I>
auto get() -> std::add_lvalue_reference_t<std::tuple_element_t<I, DecoratorSet>>
{
return std::get<I>(decorators_);
}
template<typename T>
auto get() -> std::add_lvalue_reference_t<T>
{
return std::get<T>(decorators_);
}
#else
/**
* \brief Accesses the decorator in the set by index
* \tparam I decorator index
* \return the decorator
*/
template<std::size_t I> Decorator& get();
/**
* \brief Accesses the decorator in the set by name
* \tparam T decorator type
* \return the decorator
*/
template<typename T> Decorator& get();
#endif
private:
template<std::size_t... I>
void operator()(GelfMessage& message, std::index_sequence<I...>)
{
int dummy[] = { (std::get<I>(decorators_)(message), 0)... };
(void) dummy;
}
private:
DecoratorSet decorators_;
};
}
}
@@ -0,0 +1,42 @@
#pragma once
#include <gelfcpp/GelfMessage.hpp>
namespace gelfcpp
{
namespace decorator
{
/**
* \brief Decorator which appends the current timestamp
*
* Uses UnixTime with sub-second resolution.
*/
class CurrentTimestamp
{
public:
/**
* \brief Decorator apply operator
* \param message the message
*/
void operator()(GelfMessage& message) const
{
message.SetField("timestamp", GetCurrentTime());
}
/**
* \brief Returns the current timestamp as UnixTime
*
* \return unixtime with sub-second resolution
*/
static double GetCurrentTime()
{
using clock = std::chrono::system_clock;
using unixtime = std::chrono::duration<double>;
return std::chrono::duration_cast<unixtime>(clock::now().time_since_epoch()).count();
}
};
}
}
@@ -0,0 +1,20 @@
#pragma once
#include <gelfcpp/GelfMessage.hpp>
namespace gelfcpp
{
namespace detail
{
struct DocumentAccessor
{
static const rapidjson::Document& get(const GelfMessage& message)
{
return message.doc_;
}
};
}
}
@@ -0,0 +1,132 @@
#pragma once
#include <gelfcpp/GelfMessage.hpp>
#include <gelfcpp/detail/DocumentAccessor.hpp>
#include <rapidjson/stringbuffer.h>
#include <rapidjson/writer.h>
#ifndef GELFCPP_NO_COMPRESSION
# include <boost/iostreams/device/array.hpp>
# include <boost/iostreams/filter/gzip.hpp>
# include <boost/iostreams/copy.hpp>
# include <boost/iostreams/filtering_streambuf.hpp>
#endif
#include <chrono>
#include <random>
#include <sstream>
namespace gelfcpp
{
namespace detail
{
/**
* \brief Handles chunking and compression
*
* \todo Merge this into GelfUDPOutput, for convenience and performance.
*
* \see GelfUDPOutput
*/
class GelfSerializer
{
static constexpr std::size_t DEFAULT_CHUNK_SIZE = 2096;
static constexpr std::size_t DEFAULT_COMPRESS_THRESHOLD = 0;
public:
GelfSerializer() :
random_(std::random_device{}()),
chunk_size_(DEFAULT_CHUNK_SIZE),
compress_threshold_(DEFAULT_COMPRESS_THRESHOLD) {}
/**
* \brief Serialize JSON message into chunks, maybe gzip compressed.
*
* \param message the message
* \return list of chunks to send
*/
std::vector<std::string> Serialize(const GelfMessage& message)
{
std::vector<std::string> out;
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
detail::DocumentAccessor::get(message).Accept(writer);
#ifndef GELFCPP_NO_COMPRESSION
if (buffer.GetSize() >= compress_threshold_)
{
std::string compressed = Compress(buffer.GetString(), buffer.GetSize());
if (compressed.size() > chunk_size_)
ToChunks(compressed.c_str(), compressed.size(), out);
else
out.emplace_back(std::move(compressed));
}
else
#endif
{
if (buffer.GetSize() > chunk_size_)
ToChunks(buffer.GetString(), buffer.GetSize(), out);
else
out.emplace_back(buffer.GetString(), buffer.GetSize());
}
return out;
}
private:
#ifndef GELFCPP_NO_COMPRESSION
std::string Compress(const char* message, std::size_t size)
{
boost::iostreams::array_source input(message, size);
boost::iostreams::filtering_istreambuf compressor;
compressor.push(boost::iostreams::gzip_compressor());
compressor.push(input);
std::ostringstream out;
boost::iostreams::copy(compressor, out);
return out.str();
}
#endif
uint64_t GenerateMessageID()
{
uint64_t random = random_();
uint64_t time = static_cast<uint64_t>(std::chrono::steady_clock::now().time_since_epoch().count());
return random ^ time;
}
void ToChunks(const char* message, size_t size, std::vector<std::string>& out)
{
static constexpr char CHUNKED_GELF_MAGIC[2] = { 0x1e, 0x0f };
static constexpr std::size_t HEADER_SIZE = 2 + 8 + 1 + 1;
std::size_t count = size / chunk_size_ + 1;
const char* message_ptr = message;
uint64_t message_id = GenerateMessageID();
for (std::size_t i = 0; i < count; ++i, message_ptr += chunk_size_)
{
std::string chunk;
chunk.reserve(count + HEADER_SIZE);
chunk.append(CHUNKED_GELF_MAGIC, 2);
chunk.append(reinterpret_cast<char*>(&message_id), 8);
chunk.push_back(static_cast<char>(i & 0x7F));
chunk.push_back(static_cast<char>(count & 0x7F));
chunk.append(message_ptr, chunk_size_);
out.emplace_back(std::move(chunk));
}
}
private:
std::mt19937_64 random_;
std::size_t chunk_size_;
std::size_t compress_threshold_;
};
}
}
@@ -0,0 +1,109 @@
#pragma once
#include <gelfcpp/GelfMessage.hpp>
#include <memory>
namespace gelfcpp
{
namespace detail
{
template<typename T>
class Sender
{
public:
Sender(const Sender&) = delete;
Sender& operator=(const Sender&) = delete;
static bool IsValid(const T&)
{
return true;
}
Sender(T& output) :
output_(output) {}
void operator=(const GelfMessage& message)
{
output_.Write(message);
}
private:
T& output_;
};
template<typename T>
class Sender<T*>
{
public:
Sender(const Sender&) = delete;
Sender& operator=(const Sender&) = delete;
static bool IsValid(const T* output)
{
return output != nullptr;
}
Sender(T* output) :
output_(output) {}
void operator=(const GelfMessage& message)
{
output_->Write(message);
}
private:
T* output_;
};
template<typename T>
class Sender<std::shared_ptr<T>>
{
public:
Sender(const Sender&) = delete;
Sender& operator=(const Sender&) = delete;
static bool IsValid(const std::shared_ptr<T>& output)
{
return output != nullptr;
}
Sender(const std::shared_ptr<T>& output) :
output_(output) {}
void operator=(const GelfMessage& message)
{
output_->Write(message);
}
private:
const std::shared_ptr<T>& output_;
};
template<typename T, typename Deleter>
class Sender<std::unique_ptr<T, Deleter>>
{
public:
Sender(const Sender&) = delete;
Sender& operator=(const Sender&) = delete;
static bool IsValid(const std::unique_ptr<T, Deleter>& output)
{
return output != nullptr;
}
Sender(const std::unique_ptr<T, Deleter>& output) :
output_(output) {}
void operator=(const GelfMessage& message)
{
output_->Write(message);
}
private:
const std::unique_ptr<T, Deleter>& output_;
};
}
}
@@ -0,0 +1,64 @@
#pragma once
#include <gelfcpp/GelfMessage.hpp>
#include <gelfcpp/detail/DocumentAccessor.hpp>
#include <rapidjson/stringbuffer.h>
#include <rapidjson/writer.h>
#include <ostream>
namespace gelfcpp
{
namespace output
{
/**
* \brief Writes GelfMessages as JSON to given std::ostream
*
* Uses short notation, which minimizes the space requirements
* No newline is appended, the output is not flushed.
*/
class GelfJSONOutput
{
public:
/**
* \brief Constructs a new output on the given stream
*
* \param output underlying stream
*/
GelfJSONOutput(std::ostream& output) :
output_(output) {}
/**
* \brief Serializes a message to the underlying stream
*
* \param message the message
*/
void Write(const GelfMessage& message)
{
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
detail::DocumentAccessor::get(message).Accept(writer);
output_.write(buffer.GetString(), buffer.GetSize());
}
private:
std::ostream& output_;
};
}
/**
* \brief Serializes a message to the given stream
*
* \param os the stream
* \param message the message
* \return the stream
* \see GelfJSONOutput
*/
inline std::ostream& operator<<(std::ostream& os, const GelfMessage& message)
{
output::GelfJSONOutput(os).Write(message);
return os;
}
}
@@ -0,0 +1,58 @@
#pragma once
#include <gelfcpp/GelfMessage.hpp>
#include <gelfcpp/detail/GelfSerializer.hpp>
#include <boost/asio/ip/udp.hpp>
#include <string>
#include <cstdint>
namespace gelfcpp
{
namespace output
{
/**
* \brief Writes GelfMessages to an UDP socket
*
* Serializes and sends messages to e.g. an Graylog Node, sending is non-blocking.
*
* Uses chunking for big messages due to size limitations of UDP.
* Additionally uses compression if enabled (by setting \c GELFCPP_WITH_COMPRESSION in CMake).
*/
class GelfUDPOutput
{
public:
/**
* \brief Creates an new output socket to given host and port
*
* \param host remote hostname
* \param port remote UDP port
*/
GelfUDPOutput(const std::string& host, uint16_t port)
{
boost::asio::ip::udp::resolver resolver(service_);
boost::asio::ip::udp::resolver::query query(host, std::to_string(port));
endpoint_ = *resolver.resolve(query);
socket_.reset(new boost::asio::ip::udp::socket(service_, endpoint_.protocol()));
}
/**
* \brief Serializes a message to the UDP stream
*
* \param message the message
*/
void Write(const GelfMessage& message)
{
for (const std::string& chunk : serializer_.Serialize(message))
socket_->async_send_to(boost::asio::buffer(chunk), endpoint_, [](const boost::system::error_code&, unsigned long int) {});
}
private:
detail::GelfSerializer serializer_;
boost::asio::io_context service_;
boost::asio::ip::udp::endpoint endpoint_;
std::unique_ptr<boost::asio::ip::udp::socket> socket_;
};
}
}