133 lines
3.7 KiB
C++
133 lines
3.7 KiB
C++
#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_;
|
|
};
|
|
|
|
}
|
|
}
|