log4cplus实现网络日志发送、存储(组播地址发送接收)

1. 概念解释

log4cplus​ 是一个仿照 log4j 用 C++ 实现的强大、灵活、可配置的日志库。其核心架构基于三大组件:

  • Logger​:应用程序获取并进行日志记录的入口。
  • Appender​:决定日志的输出目的地(如控制台、文件、网络等)。
  • Layout​:控制日志消息的输出格式。

要实现网络日志,核心就是使用或自定义一个 ​SocketAppender,它负责将日志事件序列化并通过网络发送到远端的日志服务器。

两种主要模式:​

  1. 发送模式​:应用程序直接将日志通过 Socket 发送到中央日志服务器(如 Logstash、Fluentd 或自定义服务)。
  2. 存储模式​:应用程序作为日志服务器,接收来自网络的日志并将其写入本地文件或数据库。

起因
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
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值