├── docker-supervisord.conf
├── Dockerfile
├── .clang-format
├── .github
└── workflows
│ └── build.yml
├── docker-compose.yml
├── CMakeLists.txt
├── src
├── ThreadPool.h
├── Logger.h
├── ThreadPool.cpp
├── MQTT_Callbacks.h
├── MQTT_Callbacks.cpp
├── Logger.cpp
└── main.cpp
├── docker-rsyslog.conf
├── .gitignore
├── README.md
└── LICENSE
/docker-supervisord.conf:
--------------------------------------------------------------------------------
1 | [supervisord]
2 | nodaemon=true
3 |
4 | [program:rsyslog]
5 | command=/usr/sbin/rsyslogd -n
6 | autostart=true
7 | autorestart=true
8 |
9 | [program:app]
10 | command=/usr/bin/remys_fast_mqtt_logger
11 | autostart=true
12 | autorestart=true
13 | stdout_logfile=/dev/stdout
14 | stdout_logfile_maxbytes=0
15 | redirect_stderr=true
16 |
17 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM gcc:14
2 | WORKDIR /app
3 | RUN apt-get update && apt-get install -y \
4 | build-essential \
5 | cmake \
6 | bash \
7 | libssl-dev \
8 | libpaho-mqtt-dev \
9 | libpaho-mqttpp-dev \
10 | rsyslog \
11 | supervisor \
12 | mosquitto-clients \
13 | && rm -rf /var/lib/apt/lists/*
14 |
15 | COPY docker-rsyslog.conf /etc/rsyslog.conf
16 | COPY docker-supervisord.conf /etc/supervisor/supervisord.conf
17 |
18 | COPY CMakeLists.txt /app/
19 | COPY src /app/src
20 |
21 | RUN mkdir build && cd build \
22 | && cmake -DCMAKE_BUILD_TYPE=Release .. \
23 | && make \
24 | && make install
25 |
26 | CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]
27 |
--------------------------------------------------------------------------------
/.clang-format:
--------------------------------------------------------------------------------
1 | BasedOnStyle: WebKit
2 | AccessModifierOffset: '-4'
3 | AllowShortFunctionsOnASingleLine: Inline
4 | AlwaysBreakTemplateDeclarations: 'true'
5 | BreakBeforeBraces: Custom
6 | BraceWrapping:
7 | AfterCaseLabel: true
8 | AfterClass: true
9 | AfterEnum: true
10 | AfterUnion: true
11 | AfterFunction: true
12 | AfterNamespace: true
13 | AfterStruct: true
14 | SplitEmptyFunction: false
15 | AfterControlStatement: Always
16 | BeforeCatch: true
17 | BeforeElse: true
18 | ColumnLimit: '0'
19 | Cpp11BracedListStyle: 'true'
20 | PointerAlignment: Right
21 | BreakConstructorInitializers: 'AfterColon'
22 | UseTab: 'Never'
23 | AlignAfterOpenBracket: 'AlwaysBreak'
24 | AllowShortBlocksOnASingleLine: 'true'
25 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: CI Workflow
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 | branches:
9 | - master
10 |
11 | jobs:
12 | build:
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - name: Checkout code
17 | uses: actions/checkout@v2
18 |
19 | - name: Build MQTT logger using CMake
20 | run: |
21 | sudo apt-get update
22 | sudo apt-get install -y \
23 | build-essential \
24 | cmake \
25 | libpaho-mqtt-dev \
26 | libpaho-mqttpp-dev \
27 | mosquitto-clients
28 | mkdir build
29 | cd build
30 | cmake -DCMAKE_BUILD_TYPE=Release ..
31 | make
32 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | remys-fast-mqtt-logger:
3 | image: raymii/remys_fast_mqtt_logger:latest
4 | # or to build locally:
5 | # context: .
6 | # dockerfile: Dockerfile
7 | # set when using certificates:
8 | # volumes:
9 | # - /path/to/client.crt:/ssl/client.crt
10 | # - /path/to/client.key:/ssl/client.key
11 | # - /path/to/ca.crt:/ssl/ca.crt
12 | environment:
13 | - BROKER=tcp://test.mosquitto.org:1883
14 | #- USERNAME=user
15 | #- PASSWORD=pass
16 | #- TOPIC=home/temperature
17 | #- FACILITY=LOG_LOCAL6
18 | #- NO_LOG_TO_STDOUT=1
19 | #- CA_FILE=/ssl/ca.crt
20 | #- CLIENT_CERT=/ssl/client.crt
21 | #- CLIENT_KEY=/ssl/client.key
22 | #- CLIENT_KEY_PASSWORD=1234
23 | restart: unless-stopped
24 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.11)
2 |
3 | project(remys_fast_mqtt_logger)
4 |
5 | set(CMAKE_CXX_STANDARD 17)
6 | set(CMAKE_CXX_STANDARD_REQUIRED True)
7 | set(CMAKE_CXX_FLAGS_RELEASE "-O2")
8 | set(CMAKE_CXX_FLAGS_DEBUG "-g -O0")
9 |
10 | find_package(Threads REQUIRED)
11 |
12 | include(CheckIncludeFile)
13 | check_include_file(syslog.h HAVE_SYSLOG)
14 |
15 | if(HAVE_SYSLOG)
16 | add_definitions(-DHAVE_SYSLOG)
17 | endif()
18 |
19 | add_executable(remys_fast_mqtt_logger src/main.cpp
20 | src/ThreadPool.cpp
21 | src/ThreadPool.h
22 | src/Logger.cpp
23 | src/Logger.h
24 | src/MQTT_Callbacks.cpp
25 | src/MQTT_Callbacks.h)
26 |
27 | target_link_libraries(remys_fast_mqtt_logger
28 | PRIVATE
29 | ${PAHO_MQTT_C_LIBRARIES}
30 | ${PAHO_MQTT_CPP_LIBRARIES}
31 | Threads::Threads
32 | paho-mqttpp3
33 | paho-mqtt3as
34 | )
35 |
36 | install(TARGETS remys_fast_mqtt_logger
37 | RUNTIME DESTINATION /usr/bin
38 | )
--------------------------------------------------------------------------------
/src/ThreadPool.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2025 Remy van Elst
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU Afferro General Public License as published by
6 | * the Free Software Foundation, version 3.
7 | *
8 | * This program is distributed in the hope that it will be useful, but
9 | * WITHOUT ANY WARRANTY; without even the implied warranty of
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 | * General Public License for more details.
12 | *
13 | * You should have received a copy of the GNU General Public License
14 | * along with this program. If not, see .
15 | *
16 | */
17 |
18 | #pragma once
19 |
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 |
28 | class ThreadPool {
29 |
30 | public:
31 | explicit ThreadPool(size_t thread_count);
32 | ~ThreadPool();
33 |
34 | void enqueue(std::function task);
35 |
36 | private:
37 | std::vector _workers {};
38 | std::queue> _task_queue;
39 | std::mutex _queue_mutex;
40 | std::condition_variable _cv;
41 | std::atomic _stop;
42 |
43 | void worker_thread();
44 | };
45 |
46 |
--------------------------------------------------------------------------------
/src/Logger.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2025 Remy van Elst
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU Afferro General Public License as published by
6 | * the Free Software Foundation, version 3.
7 | *
8 | * This program is distributed in the hope that it will be useful, but
9 | * WITHOUT ANY WARRANTY; without even the implied warranty of
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 | * General Public License for more details.
12 | *
13 | * You should have received a copy of the GNU General Public License
14 | * along with this program. If not, see .
15 | *
16 | */
17 |
18 | #pragma once
19 |
20 | #include
21 |
22 | #ifdef HAVE_SYSLOG
23 | #include
24 | #else
25 | #define LOG_EMERG 0 /* system is unusable */
26 | #define LOG_ALERT 1 /* action must be taken immediately */
27 | #define LOG_CRIT 2 /* critical conditions */
28 | #define LOG_ERR 3 /* error conditions */
29 | #define LOG_WARNING 4 /* warning conditions */
30 | #define LOG_NOTICE 5 /* normal but significant condition */
31 | #define LOG_INFO 6 /* informational */
32 | #define LOG_DEBUG 7 /* debug-level messages */
33 | #endif
34 |
35 |
36 | class Logger {
37 | public:
38 | Logger(const char *ident, int facility, bool no_log_to_stderr);
39 | ~Logger();
40 |
41 | void log(int priority, const char *format, ...) const __attribute__((format(printf, 3, 4)));
42 | static int getFacilityFromString(const std::string &facility);
43 |
44 | };
45 |
46 |
--------------------------------------------------------------------------------
/docker-rsyslog.conf:
--------------------------------------------------------------------------------
1 | # /etc/rsyslog.conf configuration file for rsyslog
2 | #
3 | # For more information install rsyslog-doc and see
4 | # /usr/share/doc/rsyslog-doc/html/configuration/index.html
5 |
6 |
7 | #################
8 | #### MODULES ####
9 | #################
10 |
11 | module(load="imuxsock") # provides support for local system logging
12 | module(load="imklog") # provides kernel logging support
13 | #module(load="immark") # provides --MARK-- message capability
14 |
15 | # provides UDP syslog reception
16 | module(load="imudp")
17 | input(type="imudp" port="514")
18 |
19 | # provides TCP syslog reception
20 | module(load="imtcp")
21 | input(type="imtcp" port="514")
22 |
23 |
24 | ###########################
25 | #### GLOBAL DIRECTIVES ####
26 | ###########################
27 |
28 | #
29 | # Set the default permissions for all log files.
30 | #
31 | $FileOwner root
32 | $FileGroup adm
33 | $FileCreateMode 0640
34 | $DirCreateMode 0755
35 | $Umask 0022
36 |
37 | #
38 | # Where to place spool and state files
39 | #
40 | $WorkDirectory /var/spool/rsyslog
41 |
42 | #
43 | # Include all config files in /etc/rsyslog.d/
44 | #
45 | $IncludeConfig /etc/rsyslog.d/*.conf
46 |
47 |
48 | ###############
49 | #### RULES ####
50 | ###############
51 |
52 | #
53 | # Log anything besides private authentication messages to a single log file
54 | #
55 | *.*;auth,authpriv.none -/var/log/syslog
56 |
57 | #
58 | # Log commonly used facilities to their own log file
59 | #
60 | auth,authpriv.* /var/log/auth.log
61 | cron.* -/var/log/cron.log
62 | kern.* -/var/log/kern.log
63 | mail.* -/var/log/mail.log
64 | user.* -/var/log/user.log
65 |
66 | #
67 | # Emergencies are sent to everybody logged in.
68 | #
69 | *.emerg :omusrmsg:*
70 |
71 |
72 | # Forward logs to Logstash
73 | *.* @logstash:514
74 |
--------------------------------------------------------------------------------
/src/ThreadPool.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2025 Remy van Elst
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU Afferro General Public License as published by
6 | * the Free Software Foundation, version 3.
7 | *
8 | * This program is distributed in the hope that it will be useful, but
9 | * WITHOUT ANY WARRANTY; without even the implied warranty of
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 | * General Public License for more details.
12 | *
13 | * You should have received a copy of the GNU General Public License
14 | * along with this program. If not, see .
15 | *
16 | */
17 |
18 | #include "ThreadPool.h"
19 |
20 |
21 | ThreadPool::ThreadPool(size_t thread_count) :
22 | _stop(false)
23 | {
24 | for (size_t i = 0; i < thread_count; ++i)
25 | {
26 | _workers.emplace_back(&ThreadPool::worker_thread, this);
27 | }
28 | }
29 |
30 | ThreadPool::~ThreadPool()
31 | {
32 | _stop = true;
33 | _cv.notify_all();
34 | for (auto &thread: _workers)
35 | {
36 | if (thread.joinable())
37 | {
38 | thread.join();
39 | }
40 | }
41 | }
42 |
43 | void ThreadPool::enqueue(std::function task)
44 | {
45 | {
46 | std::lock_guard lock(_queue_mutex);
47 | _task_queue.push(std::move(task));
48 | }
49 | _cv.notify_one();
50 | }
51 |
52 | void ThreadPool::worker_thread()
53 | {
54 | while (!_stop) {
55 | std::function task;
56 | {
57 | std::unique_lock lock(_queue_mutex);
58 | _cv.wait(lock, [this] {
59 | return !_task_queue.empty() || _stop;
60 | });
61 | if (_stop)
62 | return;
63 |
64 | task = std::move(_task_queue.front());
65 | _task_queue.pop();
66 | }
67 |
68 | task();
69 | }
70 | }
--------------------------------------------------------------------------------
/src/MQTT_Callbacks.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2025 Remy van Elst
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU Afferro General Public License as published by
6 | * the Free Software Foundation, version 3.
7 | *
8 | * This program is distributed in the hope that it will be useful, but
9 | * WITHOUT ANY WARRANTY; without even the implied warranty of
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 | * General Public License for more details.
12 | *
13 | * You should have received a copy of the GNU General Public License
14 | * along with this program. If not, see .
15 | *
16 | */
17 |
18 | #pragma once
19 | #include
20 |
21 | class ThreadPool;
22 | class Logger;
23 |
24 |
25 | class MQTT_Success_Failure_Logger : public virtual mqtt::iaction_listener
26 | {
27 | public:
28 | MQTT_Success_Failure_Logger(const std::string& name, Logger& logger) :
29 | _name(name), _logger(logger)
30 | { }
31 |
32 | void on_failure(const mqtt::token &tok) override;
33 | void on_success(const mqtt::token &tok) override;
34 |
35 | private:
36 | std::string _name;
37 | Logger& _logger;
38 | };
39 |
40 |
41 | class MQTT_Callbacks : public virtual mqtt::callback, public virtual mqtt::iaction_listener {
42 | public:
43 | MQTT_Callbacks(mqtt::async_client& client,
44 | const std::string& topic,
45 | Logger& logger,
46 | ThreadPool& threadpool) :
47 | _client(client), _topic(topic),
48 | _logger(logger), _thread_pool(threadpool),
49 | _subLogger("Subscribe", _logger)
50 | { }
51 |
52 | void connected(const std::string &cause) override;
53 | void connection_lost(const std::string &cause) override;
54 | void on_failure(const mqtt::token &tok) override;
55 | void on_success(const mqtt::token&) override {};
56 | void message_arrived(mqtt::const_message_ptr msg) override;
57 |
58 | private:
59 | mqtt::async_client& _client;
60 | std::string _topic;
61 | Logger& _logger;
62 | ThreadPool &_thread_pool;
63 | MQTT_Success_Failure_Logger _subLogger;
64 | };
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # ---> C++
2 | # Prerequisites
3 | *.d
4 |
5 | # Compiled Object files
6 | *.slo
7 | *.lo
8 | *.o
9 | *.obj
10 |
11 | # Precompiled Headers
12 | *.gch
13 | *.pch
14 |
15 | # Compiled Dynamic libraries
16 | *.so
17 | *.dylib
18 | *.dll
19 |
20 | # Fortran module files
21 | *.mod
22 | *.smod
23 |
24 | # Compiled Static libraries
25 | *.lai
26 | *.la
27 | *.a
28 | *.lib
29 |
30 | # Executables
31 | *.exe
32 | *.out
33 | *.app
34 |
35 | # ---> JetBrains
36 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
37 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
38 | .idea/
39 | # User-specific stuff
40 | .idea/**/workspace.xml
41 | .idea/**/tasks.xml
42 | .idea/**/usage.statistics.xml
43 | .idea/**/dictionaries
44 | .idea/**/shelf
45 |
46 | # AWS User-specific
47 | .idea/**/aws.xml
48 |
49 | # Generated files
50 | .idea/**/contentModel.xml
51 |
52 | # Sensitive or high-churn files
53 | .idea/**/dataSources/
54 | .idea/**/dataSources.ids
55 | .idea/**/dataSources.local.xml
56 | .idea/**/sqlDataSources.xml
57 | .idea/**/dynamic.xml
58 | .idea/**/uiDesigner.xml
59 | .idea/**/dbnavigator.xml
60 |
61 | # Gradle
62 | .idea/**/gradle.xml
63 | .idea/**/libraries
64 |
65 | # Gradle and Maven with auto-import
66 | # When using Gradle or Maven with auto-import, you should exclude module files,
67 | # since they will be recreated, and may cause churn. Uncomment if using
68 | # auto-import.
69 | # .idea/artifacts
70 | # .idea/compiler.xml
71 | # .idea/jarRepositories.xml
72 | # .idea/modules.xml
73 | # .idea/*.iml
74 | # .idea/modules
75 | # *.iml
76 | # *.ipr
77 |
78 | # CMake
79 | cmake-build-*/
80 |
81 | # Mongo Explorer plugin
82 | .idea/**/mongoSettings.xml
83 |
84 | # File-based project format
85 | *.iws
86 |
87 | # IntelliJ
88 | out/
89 |
90 | # mpeltonen/sbt-idea plugin
91 | .idea_modules/
92 |
93 | # JIRA plugin
94 | atlassian-ide-plugin.xml
95 |
96 | # Cursive Clojure plugin
97 | .idea/replstate.xml
98 |
99 | # SonarLint plugin
100 | .idea/sonarlint/
101 |
102 | # Crashlytics plugin (for Android Studio and IntelliJ)
103 | com_crashlytics_export_strings.xml
104 | crashlytics.properties
105 | crashlytics-build.properties
106 | fabric.properties
107 |
108 | # Editor-based Rest Client
109 | .idea/httpRequests
110 |
111 | # Android studio 3.1+ serialized cache file
112 | .idea/caches/build_file_checksums.ser
113 |
114 |
--------------------------------------------------------------------------------
/src/MQTT_Callbacks.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2025 Remy van Elst
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU Afferro General Public License as published by
6 | * the Free Software Foundation, version 3.
7 | *
8 | * This program is distributed in the hope that it will be useful, but
9 | * WITHOUT ANY WARRANTY; without even the implied warranty of
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 | * General Public License for more details.
12 | *
13 | * You should have received a copy of the GNU General Public License
14 | * along with this program. If not, see .
15 | *
16 | */
17 |
18 |
19 | #include "MQTT_Callbacks.h"
20 | #include "Logger.h"
21 | #include "ThreadPool.h"
22 |
23 | void MQTT_Success_Failure_Logger::on_failure(const mqtt::token& tok)
24 | {
25 | if (tok.get_message_id() != 0)
26 | _logger.log(LOG_ERR, "%s failure for token: [%i]\n", _name.c_str(), tok.get_message_id());
27 | else
28 | _logger.log(LOG_ERR, "%s failure\n", _name.c_str());
29 | }
30 |
31 | void MQTT_Success_Failure_Logger::on_success(const mqtt::token& tok)
32 | {
33 | if (tok.get_message_id() != 0)
34 | _logger.log(LOG_INFO, "%s success for token: [%i]\n", _name.c_str(), tok.get_message_id());
35 |
36 |
37 | auto top = tok.get_topics();
38 | if (top && !top->empty()) {
39 | _logger.log(LOG_INFO, "%s success for topic: [%s]\n", _name.c_str(), (*top)[0].c_str());
40 | }
41 | }
42 |
43 | void MQTT_Callbacks::connected(const std::string& cause)
44 | {
45 | _logger.log(LOG_INFO, "Connected to MQTT broker '%s'\n", cause.c_str());
46 | try {
47 | _logger.log(LOG_INFO, "Subscribing to topic '%s'\n", _topic.c_str());
48 | _client.subscribe(_topic, 0, nullptr, _subLogger);
49 | } catch (const mqtt::exception &e) {
50 | _logger.log(LOG_ERR, "MQTT subscribe failed: %s\n", e.what());
51 | }
52 | }
53 |
54 | void MQTT_Callbacks::connection_lost(const std::string& cause)
55 | {
56 | _logger.log(LOG_ERR, "MQTT connection lost: %s\n", cause.c_str());
57 | }
58 |
59 |
60 | void MQTT_Callbacks::on_failure(const mqtt::token& )
61 | {
62 | _logger.log(LOG_ERR, "MQTT connection attempt failed\n");
63 | }
64 |
65 |
66 | void MQTT_Callbacks::message_arrived(mqtt::const_message_ptr msg)
67 | {
68 | if (msg != nullptr) {
69 | _thread_pool.enqueue([this, msg] {
70 | _logger.log(LOG_INFO, "topic='%s', qos='%i', retained='%s', msg='%s'",
71 | msg->get_topic().c_str(), msg->get_qos(),
72 | msg->is_retained() ? "true" : "false",
73 | msg->get_payload_ref().c_str());
74 | });
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/Logger.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2025 Remy van Elst
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU Afferro General Public License as published by
6 | * the Free Software Foundation, version 3.
7 | *
8 | * This program is distributed in the hope that it will be useful, but
9 | * WITHOUT ANY WARRANTY; without even the implied warranty of
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 | * General Public License for more details.
12 | *
13 | * You should have received a copy of the GNU General Public License
14 | * along with this program. If not, see .
15 | *
16 | */
17 |
18 | #include "Logger.h"
19 |
20 | #include
21 | #include