#pragma once #include #include #include #include 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: version, host, short_message, full_message, timestamp, level. * 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 void operator=(T&& value) { owner.SetField(field, std::forward(value)); } }; public: GelfMessage() : doc_(rapidjson::kObjectType) { SetField("version", "1.1"); } /** * \brief Quick accessor for short_message 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 full_message 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 host field. * * Behaves like \code SetField("host", message). \endcode * * \param host hostname */ void SetHost(const std::string& host) { SetField("host", host); } /** * \brief Quick accessor for timestamp field. * * Behaves like \code SetField("timestamp", message). \endcode * * \param timestamp timestamp */ void SetTimestamp(double timestamp) { SetField("timestamp", timestamp); } #ifndef GELFCPP_DOXYGEN_RUNNING template auto SetField(const std::string& name, T value) -> std::enable_if_t::value && !std::is_same::value> { rapidjson::Value json(std::forward(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: bool, integral, floating point, strings * \param name field name, the "_" prefix is added automatically * \param value field value of any supported type */ template 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 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(field.size()), doc_.GetAllocator()); else key.SetString(("_" + field).c_str(), static_cast(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_; }; }