GELF infrastructure
This commit is contained in:
@@ -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\"],[])
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
// */
|
||||
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user