1. 概念解释
log4cplus 是一个仿照 log4j 用 C++ 实现的强大、灵活、可配置的日志库。其核心架构基于三大组件:
- Logger:应用程序获取并进行日志记录的入口。
- Appender:决定日志的输出目的地(如控制台、文件、网络等)。
- Layout:控制日志消息的输出格式。
要实现网络日志,核心就是使用或自定义一个 SocketAppender,它负责将日志事件序列化并通过网络发送到远端的日志服务器。
两种主要模式:
- 发送模式:应用程序直接将日志通过 Socket 发送到中央日志服务器(如 Logstash、Fluentd 或自定义服务)。
- 存储模式:应用程序作为日志服务器,接收来自网络的日志并将其写入本地文件或数据库。
起因
log4cplus自2.0.0版本以及支持了udp模式,但是在我的嵌入式设备无法交叉编译,因此在1.2.1版本加入udp支持。
2. 代码示例
发送端
#include <iostream>
#include <string>
#include <thread>
#include <chrono>
#include <cstring>
#include <stdexcept>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <log4cplus/configurator.h>
#include <log4cplus/logger.h>
#include <log4cplus/socketappender.h>
#include <log4cplus/asyncappender.h>
#include <log4cplus/layout.h>
#include <log4cplus/loggingmacros.h>
class UdpAppender : public log4cplus::Appender {
public:
UdpAppender(const log4cplus::tstring &host, unsigned short port)
: fd_(-1)
{
fd_ = ::socket(AF_INET, SOCK_DGRAM, 0);
if (fd_ < 0) {
throw std::runtime_error("socket() failed");
}
memset(&srv_, 0, sizeof(srv_));
srv_.sin_family = AF_INET;
srv_.sin_port = htons(port);
// 假设 host 是 IPv4 点分十进制
std::string host_s(host.begin(), host.end());
if (inet_pton(AF_INET, host_s.c_str(), &srv_.sin_addr) <= 0) {
::close(fd_);
fd_ = -1;
throw std::runtime_error("inet_pton() failed");
}
}
~UdpAppender() {
close();
}
void close() override {
if (fd_ != -1) {
::close(fd_);
fd_ = -1;
}
this->closed = true;
}
void append(const log4cplus::spi::InternalLoggingEvent &ev) override {
if (fd_ == -1) return;
// 直接使用事件里的消息文本(避免依赖缺失的 Layout API)
log4cplus::tstring ts = ev.getMessage();
// 若需要包含级别、logger 等,可在此拼接
std::string msg(ts.begin(), ts.end());
if (msg.empty() || msg.back() != '\n') {
msg.push_back('\n');
}
::sendto(fd_, msg.data(), static_cast<socklen_t>(msg.size()), 0,
reinterpret_cast<struct sockaddr*>(&srv_), sizeof(srv_));
}
private:
int fd_;
struct sockaddr_in srv_;
};
int main(int argc, char **argv)
{
if (argc < 3) {
std::cout << "Usage: loggingclient <host> <port> [async]\n"
<< "Example: loggingclient 127.0.0.1 5000 async\n";
return 1;
}
const std::string host = argv[1];
unsigned short port = static_cast<unsigned short>(std::stoi(argv[2]));
bool use_async = (argc >= 4 && std::string(argv[3]) == "async");
log4cplus::initialize();
// root console appender so local messages show too
log4cplus::Logger root = log4cplus::Logger::getRoot();
// Create socket appender to remote logging server
log4cplus::tstring thost = LOG4CPLUS_C_STR_TO_TSTRING(host.c_str());
//log4cplus::helpers::SharedObjectPtr<log4cplus::Appender> sa(new log4cplus::SocketAppender(thost, port));
log4cplus::helpers::SharedObjectPtr<log4cplus::Appender> sa(new UdpAppender(thost, port));
sa->setName(LOG4CPLUS_TEXT("RemoteSocketAppender"));
sa->setLayout(std::auto_ptr<log4cplus::Layout>(
new log4cplus::PatternLayout(LOG4CPLUS_TEXT("%D{%Y-%m-%d %H:%M:%S} %-5p %c - %m"))));
root.addAppender(sa);
log4cplus::Logger logger = log4cplus::Logger::getInstance(LOG4CPLUS_TEXT("client"));
// Send some example messages
for (int i = 0; i < 50; ++i) {
LOG4CPLUS_INFO(logger, "Test log message #" << i << " to " << host << ":" << port);
std::this_thread::sleep_for(std::chrono::milliseconds(200));
}
// Give async appender time to flush (if used)
std::this_thread::sleep_for(std::chrono::seconds(1));
log4cplus::Logger::shutdown();
return 0;
}
执行
./log4c_socket_client 239.192.0.11 10050
接收端
// Module: LOG4CPLUS
// File: loggingserver.cxx
// Created: 5/2003
// Author: Tad E. Smith
//
//
// Copyright 2003-2015 Tad E. Smith
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <cstdlib>
#include <list>
#include <iostream>
#include <log4cplus/configurator.h>
#include <log4cplus/socketappender.h>
#include <log4cplus/helpers/socket.h>
#include <log4cplus/thread/threads.h>
#include <log4cplus/spi/loggingevent.h>
#include <log4cplus/logger.h>
#include <log4cplus/thread/syncprims.h>
#include <log4cplus/loggingmacros.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <unistd.h>
#include <csignal>
namespace loggingserver
{
typedef std::list<log4cplus::thread::AbstractThreadPtr> ThreadQueueType;
class ReaperThread
: public log4cplus::thread::AbstractThread
{
public:
ReaperThread (log4cplus::thread::Mutex & mtx_,
log4cplus::thread::ManualResetEvent & ev_,
ThreadQueueType & queue_)
: mtx (mtx_)
, ev (ev_)
, queue (queue_)
, stop (false)
{ }
virtual
~ReaperThread ()
{ }
virtual void run ();
void signal_exit ();
private:
log4cplus::thread::Mutex & mtx;
log4cplus::thread::ManualResetEvent & ev;
ThreadQueueType & queue;
bool stop;
};
typedef log4cplus::helpers::SharedObjectPtr<ReaperThread> ReaperThreadPtr;
void
ReaperThread::signal_exit ()
{
log4cplus::thread::MutexGuard guard (mtx);
stop = true;
ev.signal ();
}
void
ReaperThread::run ()
{
ThreadQueueType q;
while (true)
{
ev.timed_wait (30 * 1000);
{
log4cplus::thread::MutexGuard guard (mtx);
// Check exit condition as the very first thing.
if (stop)
{
std::cout << "Reaper thread is stopping..." << std::endl;
return;
}
ev.reset ();
q.swap (queue);
}
if (! q.empty ())
{
std::cout << "Reaper thread is reaping " << q.size () << " threads."
<< std::endl;
for (ThreadQueueType::iterator it = q.begin (), end_it = q.end ();
it != end_it; ++it)
{
AbstractThread & t = **it;
t.join ();
}
q.clear ();
}
}
}
/**
This class wraps ReaperThread thread and its queue.
*/
class Reaper
{
public:
Reaper ()
{
reaper_thread = ReaperThreadPtr (new ReaperThread (mtx, ev, queue));
reaper_thread->start ();
}
~Reaper ()
{
reaper_thread->signal_exit ();
reaper_thread->join ();
}
void visit (log4cplus::thread::AbstractThreadPtr const & thread_ptr);
private:
log4cplus::thread::Mutex mtx;
log4cplus::thread::ManualResetEvent ev;
ThreadQueueType queue;
ReaperThreadPtr reaper_thread;
};
void
Reaper::visit (log4cplus::thread::AbstractThreadPtr const & thread_ptr)
{
log4cplus::thread::MutexGuard guard (mtx);
queue.push_back (thread_ptr);
ev.signal ();
}
class ClientThread
: public log4cplus::thread::AbstractThread
{
public:
ClientThread(log4cplus::helpers::Socket clientsock_, Reaper & reaper_)
: self_reference (log4cplus::thread::AbstractThreadPtr (this))
, clientsock(clientsock_)
, reaper (reaper_)
{
std::cout << "Received a client connection!!!!" << std::endl;
}
~ClientThread()
{
std::cout << "Client connection closed." << std::endl;
}
virtual void run();
private:
log4cplus::thread::AbstractThreadPtr self_reference;
log4cplus::helpers::Socket clientsock;
Reaper & reaper;
};
void
loggingserver::ClientThread::run()
{
try
{
while (1)
{
if (!clientsock.isOpen())
break;
log4cplus::helpers::SocketBuffer msgSizeBuffer(sizeof(unsigned int));
if (!clientsock.read(msgSizeBuffer))
break;
unsigned int msgSize = msgSizeBuffer.readInt();
log4cplus::helpers::SocketBuffer buffer(msgSize);
if (!clientsock.read(buffer))
break;
log4cplus::spi::InternalLoggingEvent event
= log4cplus::helpers::readFromBuffer(buffer);
log4cplus::Logger logger
= log4cplus::Logger::getInstance(event.getLoggerName());
logger.callAppenders(event);
}
}
catch (...)
{
reaper.visit (self_reference);
self_reference = log4cplus::thread::AbstractThreadPtr ();
throw;
}
reaper.visit (self_reference);
self_reference = log4cplus::thread::AbstractThreadPtr ();
}
} // namespace loggingserver
int
main(int argc, char** argv)
{
log4cplus::initialize ();
if(argc < 3) {
std::cout << "Usage: port config_file" << std::endl;
return 1;
}
int port = std::atoi(argv[2]);
const log4cplus::tstring configFile = LOG4CPLUS_C_STR_TO_TSTRING(argv[3]);
log4cplus::PropertyConfigurator config(configFile);
config.configure();
// Create UDP socket (Linux/BSD)
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
std::cerr << "Could not create UDP socket." << std::endl;
return 2;
}
// 设置地址重用选项(重要:允许多个进程绑定相同组播地址)
int reuse = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) {
std::cerr << "setsockopt SO_REUSEADDR failed" << std::endl;
::close(sockfd);
return false;
}
struct sockaddr_in servaddr;
std::memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(static_cast<uint16_t>(port));
if (::bind(sockfd, reinterpret_cast<struct sockaddr*>(&servaddr),
sizeof(servaddr)) < 0) {
std::cerr << "Could not bind UDP socket to port " << port
<< ". Maybe port is already in use." << std::endl;
::close(sockfd);
return 3;
}
// 加入组播组
std::string multicastGroup = argv[1];
struct ip_mreq mreq;
mreq.imr_multiaddr.s_addr = inet_addr(multicastGroup.c_str()); // 组播组地址
mreq.imr_interface.s_addr = htonl(INADDR_ANY); // 使用默认网络接口
if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
&mreq, sizeof(mreq)) < 0) {
std::cerr << "Could not join multicast group: " << multicastGroup << ",errno " << errno << std::endl;
::close(sockfd);
return false;
}
std::cout << "UDP Multicast server listening on port " << port
<< ", joined group: " << multicastGroup << std::endl;
std::cout << "Listening UDP on port " << port << "..." << std::endl;
loggingserver::Reaper reaper;
// handle Ctrl-C to exit cleanly
static volatile sig_atomic_t keep_running = 1;
auto sigint_handler = [](int){ keep_running = 0; };
std::signal(SIGINT, sigint_handler);
const size_t MAX_DATAGRAM = 1 * 1024;
log4cplus::helpers::SocketBuffer buffer(MAX_DATAGRAM);
while (keep_running)
{
struct sockaddr_in cliaddr;
socklen_t clilen = sizeof(cliaddr);
char* pos = buffer.getBuffer();
ssize_t n = ::recvfrom(sockfd, pos, MAX_DATAGRAM, 0,
reinterpret_cast<struct sockaddr*>(&cliaddr), &clilen);
if (n < 0) {
// interrupted by signal?
if (errno == EINTR) continue;
std::perror("recvfrom");
break;
}
if (n == 0) continue;
char clientIP[INET_ADDRSTRLEN];
const char* ipStr = inet_ntop(AF_INET, &cliaddr.sin_addr,
clientIP, sizeof(clientIP));
std::string hostname = ipStr;
log4cplus::Logger logger = log4cplus::Logger::getInstance(hostname.c_str());
LOG4CPLUS_INFO(logger, pos);
memset(pos, 0, MAX_DATAGRAM);
}
::close(sockfd);
std::cout << "Shutting down logging server..." << std::endl;
log4cplus::Logger::shutdown();
return 0;
}
配置文件
# root logger
log4cplus.rootLogger=DEBUG, stdout, file
# console appender
log4cplus.appender.stdout=log4cplus::ConsoleAppender
log4cplus.appender.stdout.layout=log4cplus::PatternLayout
log4cplus.appender.stdout.layout.ConversionPattern=[%D{%Y-%m-%d %H:%M:%S}] [%c] - %m
# file appender
log4cplus.appender.file=log4cplus::FileAppender
log4cplus.appender.file.File=logs/server.log
log4cplus.appender.file.layout=log4cplus::PatternLayout
log4cplus.appender.file.layout.ConversionPattern=[%D{%Y-%m-%d %H:%M:%S}] [%c] - %m
执行
./log4c_socket_server 239.192.0.11 10050 log.properties
242

被折叠的 条评论
为什么被折叠?



