Add log appender that sends logs in GrayLog Extended Log Format
This commit is contained in:
parent
0366b2e6b3
commit
f3065b367e
5 changed files with 176 additions and 0 deletions
|
|
@ -137,6 +137,7 @@ set( fc_sources
|
||||||
src/log/appender.cpp
|
src/log/appender.cpp
|
||||||
src/log/console_appender.cpp
|
src/log/console_appender.cpp
|
||||||
src/log/file_appender.cpp
|
src/log/file_appender.cpp
|
||||||
|
src/log/gelf_appender.cpp
|
||||||
src/log/logger_config.cpp
|
src/log/logger_config.cpp
|
||||||
src/crypto/openssl.cpp
|
src/crypto/openssl.cpp
|
||||||
src/crypto/aes.cpp
|
src/crypto/aes.cpp
|
||||||
|
|
|
||||||
32
include/fc/log/gelf_appender.hpp
Normal file
32
include/fc/log/gelf_appender.hpp
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <fc/log/appender.hpp>
|
||||||
|
#include <fc/log/logger.hpp>
|
||||||
|
#include <fc/time.hpp>
|
||||||
|
|
||||||
|
namespace fc
|
||||||
|
{
|
||||||
|
// Log appender that sends log messages in JSON format over UDP
|
||||||
|
// https://www.graylog2.org/resources/gelf/specification
|
||||||
|
class gelf_appender : public appender
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct config
|
||||||
|
{
|
||||||
|
string endpoint = "127.0.0.1:12201";
|
||||||
|
string host = "fc"; // the name of the host, source or application that sent this message (just passed through to GELF server)
|
||||||
|
};
|
||||||
|
|
||||||
|
gelf_appender(const variant& args);
|
||||||
|
~gelf_appender();
|
||||||
|
virtual void log(const log_message& m) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
class impl;
|
||||||
|
fc::shared_ptr<impl> my;
|
||||||
|
};
|
||||||
|
} // namespace fc
|
||||||
|
|
||||||
|
#include <fc/reflect/reflect.hpp>
|
||||||
|
FC_REFLECT(fc::gelf_appender::config,
|
||||||
|
(endpoint)(host))
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
#include <fc/thread/scoped_lock.hpp>
|
#include <fc/thread/scoped_lock.hpp>
|
||||||
#include <fc/log/console_appender.hpp>
|
#include <fc/log/console_appender.hpp>
|
||||||
#include <fc/log/file_appender.hpp>
|
#include <fc/log/file_appender.hpp>
|
||||||
|
#include <fc/log/gelf_appender.hpp>
|
||||||
#include <fc/variant.hpp>
|
#include <fc/variant.hpp>
|
||||||
#include "console_defines.h"
|
#include "console_defines.h"
|
||||||
|
|
||||||
|
|
@ -45,4 +46,6 @@ namespace fc {
|
||||||
|
|
||||||
static bool reg_console_appender = appender::register_appender<console_appender>( "console" );
|
static bool reg_console_appender = appender::register_appender<console_appender>( "console" );
|
||||||
static bool reg_file_appender = appender::register_appender<file_appender>( "file" );
|
static bool reg_file_appender = appender::register_appender<file_appender>( "file" );
|
||||||
|
static bool reg_gelf_appender = appender::register_appender<gelf_appender>( "gelf" );
|
||||||
|
|
||||||
} // namespace fc
|
} // namespace fc
|
||||||
|
|
|
||||||
139
src/log/gelf_appender.cpp
Normal file
139
src/log/gelf_appender.cpp
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
#include <fc/network/udp_socket.hpp>
|
||||||
|
#include <fc/network/ip.hpp>
|
||||||
|
#include <fc/network/resolve.hpp>
|
||||||
|
#include <fc/exception/exception.hpp>
|
||||||
|
#include <fc/log/gelf_appender.hpp>
|
||||||
|
#include <fc/reflect/variant.hpp>
|
||||||
|
#include <fc/thread/scoped_lock.hpp>
|
||||||
|
#include <fc/thread/thread.hpp>
|
||||||
|
#include <fc/variant.hpp>
|
||||||
|
#include <fc/io/json.hpp>
|
||||||
|
|
||||||
|
#include <boost/thread/mutex.hpp>
|
||||||
|
#include <boost/lexical_cast.hpp>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <queue>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace fc
|
||||||
|
{
|
||||||
|
|
||||||
|
class gelf_appender::impl : public retainable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
config cfg;
|
||||||
|
optional<ip::endpoint> gelf_endpoint;
|
||||||
|
udp_socket gelf_socket;
|
||||||
|
boost::mutex socket_mutex;
|
||||||
|
|
||||||
|
impl(const config& c) :
|
||||||
|
cfg(c)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
~impl()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
gelf_appender::gelf_appender(const variant& args) :
|
||||||
|
my(new impl(args.as<config>()))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// if it's a numeric address:port, this will parse it
|
||||||
|
my->gelf_endpoint = ip::endpoint::from_string(my->cfg.endpoint);
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
if (!my->gelf_endpoint)
|
||||||
|
{
|
||||||
|
// couldn't parse as a numeric ip address, try resolving as a DNS name.
|
||||||
|
// This can yield, so don't do it in the catch block above
|
||||||
|
string::size_type colon_pos = my->cfg.endpoint.find(':');
|
||||||
|
try
|
||||||
|
{
|
||||||
|
uint16_t port = boost::lexical_cast<uint16_t>(my->cfg.endpoint.substr(colon_pos + 1, my->cfg.endpoint.size()));
|
||||||
|
|
||||||
|
string hostname = my->cfg.endpoint.substr( 0, colon_pos );
|
||||||
|
std::vector<ip::endpoint> endpoints = resolve(hostname, port);
|
||||||
|
if (endpoints.empty())
|
||||||
|
FC_THROW_EXCEPTION(unknown_host_exception, "The host name can not be resolved: ${hostname}",
|
||||||
|
("hostname", hostname));
|
||||||
|
my->gelf_endpoint = endpoints.back();
|
||||||
|
}
|
||||||
|
catch (const boost::bad_lexical_cast&)
|
||||||
|
{
|
||||||
|
FC_THROW("Bad port: ${port}", ("port", my->cfg.endpoint.substr(colon_pos + 1, my->cfg.endpoint.size())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (my->gelf_endpoint)
|
||||||
|
my->gelf_socket.open();
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
std::cerr << "error opening GELF socket to endpoint ${endpoint}" << my->cfg.endpoint << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gelf_appender::~gelf_appender()
|
||||||
|
{}
|
||||||
|
|
||||||
|
void gelf_appender::log(const log_message& message)
|
||||||
|
{
|
||||||
|
if (!my->gelf_endpoint)
|
||||||
|
return;
|
||||||
|
|
||||||
|
log_context context = message.get_context();
|
||||||
|
|
||||||
|
mutable_variant_object gelf_message;
|
||||||
|
gelf_message["version"] = "1.1";
|
||||||
|
gelf_message["host"] = my->cfg.host;
|
||||||
|
gelf_message["short_message"] = format_string(message.get_format(), message.get_data());
|
||||||
|
|
||||||
|
gelf_message["timestamp"] = context.get_timestamp().time_since_epoch().count() / 1000000.;
|
||||||
|
|
||||||
|
switch (context.get_log_level())
|
||||||
|
{
|
||||||
|
case log_level::debug:
|
||||||
|
gelf_message["level"] = 7; // debug
|
||||||
|
break;
|
||||||
|
case log_level::info:
|
||||||
|
gelf_message["level"] = 6; // info
|
||||||
|
break;
|
||||||
|
case log_level::warn:
|
||||||
|
gelf_message["level"] = 4; // warning
|
||||||
|
break;
|
||||||
|
case log_level::error:
|
||||||
|
gelf_message["level"] = 3; // error
|
||||||
|
break;
|
||||||
|
case log_level::all:
|
||||||
|
case log_level::off:
|
||||||
|
// these shouldn't be used in log messages, but do something deterministic just in case
|
||||||
|
gelf_message["level"] = 6; // info
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!context.get_context().empty())
|
||||||
|
gelf_message["context"] = context.get_context();
|
||||||
|
gelf_message["_line"] = context.get_line_number();
|
||||||
|
gelf_message["_file"] = context.get_file();
|
||||||
|
gelf_message["_method_name"] = context.get_method();
|
||||||
|
gelf_message["_thread_name"] = context.get_thread_name();
|
||||||
|
if (!context.get_task_name().empty())
|
||||||
|
gelf_message["_task_name"] = context.get_task_name();
|
||||||
|
|
||||||
|
string gelf_message_as_string = json::to_string(gelf_message);
|
||||||
|
std::shared_ptr<char> send_buffer(new char[gelf_message_as_string.size()], [](char* p){ delete[] p; });
|
||||||
|
memcpy(send_buffer.get(), gelf_message_as_string.c_str(), gelf_message_as_string.size());
|
||||||
|
|
||||||
|
{
|
||||||
|
scoped_lock<boost::mutex> lock(my->socket_mutex);
|
||||||
|
my->gelf_socket.send_to(send_buffer, gelf_message_as_string.size(), *my->gelf_endpoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // fc
|
||||||
|
|
@ -158,6 +158,7 @@ namespace fc
|
||||||
string log_context::get_host_name()const { return my->hostname; }
|
string log_context::get_host_name()const { return my->hostname; }
|
||||||
time_point log_context::get_timestamp()const { return my->timestamp; }
|
time_point log_context::get_timestamp()const { return my->timestamp; }
|
||||||
log_level log_context::get_log_level()const{ return my->level; }
|
log_level log_context::get_log_level()const{ return my->level; }
|
||||||
|
string log_context::get_context()const { return my->context; }
|
||||||
|
|
||||||
|
|
||||||
variant log_context::to_variant()const
|
variant log_context::to_variant()const
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue