From e0244787d9f5e55929d80d1da40abec67c38bbf0 Mon Sep 17 00:00:00 2001 From: "Steven Baigal (sbaigal)" Date: Thu, 24 Jul 2025 22:14:33 +0000 Subject: [PATCH] Pull request #4680: logger: add batched logger to improve performance Merge in SNORT/snort3 from ~SBAIGAL/snort3:newlog to master Squashed commit of the following: commit 3234f22b1c8c442884e594566d8973b2df532733 Author: Steven Baigal Date: Wed Jul 23 14:26:25 2025 -0400 logger: add cpu affinity for log writer thread commit 109903ad2a7b428e4f99a8b035dad085d8c9e785 Author: Steven Baigal Date: Wed Mar 26 14:56:13 2025 -0400 logger: add batched logger to improve packet_tracer output performace --- src/control/control_mgmt.cc | 82 +++++- src/control/control_mgmt.h | 3 + src/log/CMakeLists.txt | 2 + src/log/batched_logger.cc | 351 ++++++++++++++++++++++++++ src/log/batched_logger.h | 108 ++++++++ src/log/log.cc | 2 + src/main/snort.cc | 4 +- src/main/test/distill_verdict_stubs.h | 1 - src/packet_io/packet_tracer.cc | 19 +- src/packet_io/packet_tracer.h | 2 + src/packet_io/packet_tracer_module.cc | 9 + 11 files changed, 572 insertions(+), 11 deletions(-) create mode 100644 src/log/batched_logger.cc create mode 100644 src/log/batched_logger.h diff --git a/src/control/control_mgmt.cc b/src/control/control_mgmt.cc index 2d0638788..d59ae19dd 100644 --- a/src/control/control_mgmt.cc +++ b/src/control/control_mgmt.cc @@ -31,9 +31,11 @@ #include #include +#include #include #include "log/messages.h" +#include "main.h" #include "main/shell.h" #include "main/snort_config.h" #include "main/thread.h" @@ -53,7 +55,7 @@ static constexpr unsigned MAX_CONTROL_IDLE_TIME = 5; #endif static int listener = -1; -static socklen_t sock_addr_size = 0; +static socklen_t sock_addr_size = 0, my_sock_addr_size = 0; static struct sockaddr* sock_addr = nullptr; static struct sockaddr_in in_addr; static struct sockaddr_un unix_addr; @@ -486,7 +488,7 @@ int ControlMgmt::socket_init(const SnortConfig* sc) // FIXIT-M want to disable time wait int on = 1; setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); - + my_sock_addr_size = sock_addr_size; if (bind(listener, sock_addr, sock_addr_size) < 0) FatalError("Failed to bind control listener: %s\n", get_error(errno)); @@ -499,6 +501,82 @@ int ControlMgmt::socket_init(const SnortConfig* sc) return 0; } +// Helper function to wait for the prompt with a timeout +static bool wait_for_prompt(int fd, const std::string& prompt, int timeout_ms) +{ + char buffer[1024]; + std::string response; + auto start_time = std::chrono::steady_clock::now(); + + while (true) + { + ssize_t bytes = read(fd, buffer, sizeof(buffer) - 1); + if (bytes < 0) + { + if (errno == EAGAIN || errno == EWOULDBLOCK) + { + // Check if the timeout has been exceeded + auto elapsed = std::chrono::duration_cast( + std::chrono::steady_clock::now() - start_time); + if (elapsed.count() >= timeout_ms) + { + return false; // Timeout + } + continue; // Retry + } + else + { + ErrorMessage("Failed to read response: %s\n", get_error(errno)); + return false; + } + } + else if (bytes == 0) + { + return false; // Connection closed + } + + buffer[bytes] = '\0'; + response += buffer; + + // Check if the prompt is found in the response + if (response.find(prompt) != std::string::npos) + { + return true; + } + } +} + +bool ControlMgmt::send_command_to_socket(const std::string& command) +{ + std::string prompt = get_prompt(); + if (listener < 0) + { + ErrorMessage("Listener is not initialized.\n"); + return false; + } + + int fd = socket(sock_addr->sa_family, SOCK_STREAM, 0); + if (fd < 0) + { + ErrorMessage("Failed to create socket: %s\n", get_error(errno)); + return false; + } + + if (connect(fd, sock_addr, my_sock_addr_size) >= 0 && wait_for_prompt(fd, prompt, 30) && + write(fd, command.c_str(), command.size()) >= 0) + { + // Wait for the prompt again to ensure the command was processed + if (wait_for_prompt(fd, prompt, 90)) + { + close(fd); + return true; + } + } + close(fd); + return false; +} + + void ControlMgmt::socket_term() { clear_controls(); diff --git a/src/control/control_mgmt.h b/src/control/control_mgmt.h index e6982379f..4fab57498 100644 --- a/src/control/control_mgmt.h +++ b/src/control/control_mgmt.h @@ -24,6 +24,8 @@ #ifndef CONTROL_MGMT_H #define CONTROL_MGMT_H +#include + class ControlConn; struct lua_State; @@ -42,6 +44,7 @@ public: static void socket_term(); static ControlConn* find_control(const lua_State*); + static bool send_command_to_socket(const std::string& command); static bool service_users(); }; diff --git a/src/log/CMakeLists.txt b/src/log/CMakeLists.txt index 99de682d2..b1e708731 100644 --- a/src/log/CMakeLists.txt +++ b/src/log/CMakeLists.txt @@ -11,6 +11,8 @@ set (LOG_INCLUDES add_library ( log OBJECT ${LOG_INCLUDES} + batched_logger.cc + batched_logger.h log.cc log.h log_errors.h diff --git a/src/log/batched_logger.cc b/src/log/batched_logger.cc new file mode 100644 index 000000000..661c77d1e --- /dev/null +++ b/src/log/batched_logger.cc @@ -0,0 +1,351 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2025-2025 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- +// batched_logger.cc author Steven Baigal + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "batched_logger.h" + +#include +#include +#include +#include +#include + +#include "control/control_mgmt.h" +#include "main/snort_config.h" +#include "main/thread_config.h" +#include "utils/snort_pcre.h" +#include "utils/util.h" + +namespace BatchedLogger +{ + +thread_local LogBuffer BatchedLogManager::buffer; +BatchQueue BatchedLogManager::queue; +std::thread BatchedLogManager::writer_thread; +std::atomic BatchedLogManager::running(false); +std::atomic BatchQueue::overwrite_count(0); + +void LogBuffer::append(FILE* fh, bool use_syslog, const char* msg, size_t len) +{ + if (size + len >= LOG_BUFFER_THRESHOLD) + flush(); + std::memcpy(buffer + size, msg, len); + size += len; + this->fh = fh; + this->use_syslog = use_syslog; +} + +void LogBuffer::flush() +{ + if (size == 0) + return; + + LogBatch batch; + batch.data.assign(buffer, buffer + size); + batch.size = size; + batch.fh = fh; + batch.use_syslog = use_syslog; + batch.is_control_message = false; + BatchedLogManager::push_batch(std::move(batch)); + size = 0; + last_flush_time = std::chrono::steady_clock::now(); +} + +void LogBuffer::send_control_message(const char* msg, size_t len) +{ + if (len > LOG_BUFFER_THRESHOLD) + return; + + LogBatch batch; + batch.data.assign(msg, msg + len); + batch.size = len; + batch.fh = nullptr; + batch.use_syslog = false; + batch.is_control_message = true; + BatchedLogManager::push_batch(std::move(batch)); +} + +void BatchQueue::push(LogBatch&& batch) +{ + std::lock_guard lock(mtx); + size_t next_tail = tail + 1; + + if (next_tail >= LOG_QUEUE_SIZE) + next_tail = 0; + if (next_tail == head) + { + head++; + if (head >= LOG_QUEUE_SIZE) + head = 0; + overwrite_count++; + } + + buffer[tail] = std::move(batch); + tail = next_tail; + cv.notify_one(); +} + +bool BatchQueue::pop(LogBatch& batch) +{ + std::unique_lock lock(mtx); + + if (head == tail) + return false; + + batch = std::move(buffer[head]); + head++; + if (head >= LOG_QUEUE_SIZE) + head = 0; + return true; +} + +void BatchQueue::wait() +{ + std::unique_lock lock(mtx); + cv.wait(lock, [this] { return head != tail; }); +} + +bool BatchQueue::empty() const +{ + std::lock_guard lock(mtx); + return head == tail; +} + +struct FilterData +{ + std::string filter; + pcre2_code* re = nullptr; + pcre2_match_data* match_data = nullptr; + bool stop_trace = false; + uint64_t revision = 0; + + void clear() + { + if (match_data) pcre2_match_data_free(match_data); + if (re) pcre2_code_free(re); + filter.clear(); + match_data = nullptr; + re = nullptr; + stop_trace = false; + revision++; + } +}; + + +static FilterData s_filter; + +static void update_filter(const std::string& pattern_s) +{ + s_filter.clear(); + if (pattern_s.empty()) + return; + + if (pattern_s[0] == 'Y') + s_filter.stop_trace = true; + + std::string pattern = pattern_s.substr(1); + int error_code; + PCRE2_SIZE error_offset; + + pcre2_code* re = pcre2_compile((PCRE2_SPTR)pattern.c_str(), PCRE2_ZERO_TERMINATED, + PCRE2_MULTILINE, &error_code, &error_offset, nullptr); + + if (!re) + return; + + pcre2_match_data* match_data = pcre2_match_data_create_from_pattern(re, nullptr); + if (!match_data) + { + pcre2_code_free(re); + return; + } + + s_filter.filter = std::move(pattern); + s_filter.re = re; + s_filter.match_data = match_data; +} + +void BatchedLogManager::set_filter(const std::string& pattern) +{ + LogBuffer::send_control_message(pattern.c_str(), pattern.size()); +} + +void BatchedLogManager::shutdown() +{ + if (!running) + return; + flush_thread_buffers(); + running = false; + queue.push({}); + + if (writer_thread.joinable()) + writer_thread.join(); + + if (BatchQueue::get_overwrite_count() > 0) + fprintf(stderr, "BatchedLogManager Stats: Ring buffer overwrites = %lu\n", + static_cast(BatchQueue::get_overwrite_count())); + + if (!s_filter.filter.empty()) + s_filter.clear(); +} + +void BatchedLogManager::log(FILE* fh, bool use_syslog, const char* msg, size_t len) +{ + buffer.append(fh, use_syslog, msg, len); + auto now = std::chrono::steady_clock::now(); + auto elapsed = std::chrono::duration_cast(now - buffer.last_flush_time); + + if (buffer.size >= LOG_BUFFER_THRESHOLD || elapsed >= LOG_TIME_THRESHOLD) + buffer.flush(); +#ifdef REG_TEST + flush_thread_buffers(); // Force flush for regression tests +#endif +} + +#if 0 +void BatchedLogManager::log(FILE* fh, bool use_syslog, const char* format, va_list& ap) +{ + static char temp[1024]; + int len = vsnprintf(temp, sizeof(temp), format, ap); + + buffer.append(fh, use_syslog, temp, len); + auto now = std::chrono::steady_clock::now(); + auto elapsed = std::chrono::duration_cast(now - buffer.last_flush_time); + + if (buffer.size >= LOG_BUFFER_THRESHOLD || elapsed >= LOG_TIME_THRESHOLD) + buffer.flush(); + +#ifdef REG_TEST + flush_thread_buffers(); // Force flush for regression tests +#endif + +} +#endif +void BatchedLogManager::flush_thread_buffers() +{ + buffer.flush(); +} + +void BatchedLogManager::push_batch(LogBatch&& batch) +{ + queue.push(std::move(batch)); +} + +void BatchedLogManager::print_batch(const LogBatch& batch) +{ + static bool stop_trace = false; + static uint64_t revision = 0; + + // reset condition if filter was changed by user + if (s_filter.revision != revision) + { + revision = s_filter.revision; + stop_trace = false; + } + if (stop_trace) + return; + + if (!s_filter.filter.empty()) + { + int rc = pcre2_match( + s_filter.re, reinterpret_cast(batch.data.data()), + batch.size, 0, 0, s_filter.match_data, nullptr); + + if (rc < 0) + return; // Skip printing if no match + stop_trace = s_filter.stop_trace; + revision = s_filter.revision; + } + + if (!batch.use_syslog && batch.fh) + { + if ( snort::SnortConfig::log_quiet() and batch.fh == stdout ) + return; + + fprintf(batch.fh, "%.*s", static_cast(batch.size), batch.data.data()); + fflush(batch.fh); + } + else + { + const char* data = batch.data.data(); + size_t len = batch.size; + const char* start = data; + const char* end = data + len; + + while (start < end) + { + const char* line_end = start; + + while (line_end < end && *line_end != '\n') + ++line_end; + + size_t line_len = line_end - start; + + if (line_len > 0) + syslog(LOG_DAEMON | LOG_NOTICE, "%.*s", static_cast(line_len), start); + + start = line_end + 1; + } + } +#ifdef SHELL + if (stop_trace) + if (!ControlMgmt::send_command_to_socket("packet_tracer.disable()\n")) + fprintf(stderr, "Batched_logger: Failed to send command to control socket\n"); +#endif +} + +void BatchedLogManager::writer_thread_func() +{ + while (running || !queue.empty()) + { + LogBatch batch; + + if (queue.pop(batch)) + { + if (batch.is_control_message) + update_filter(std::string(batch.data.data(), batch.size)); + else + print_batch(batch); + } + else + queue.wait(); + } +} + +void BatchedLogManager::init() +{ + running = true; + + snort::ThreadConfig* thread_config = snort::SnortConfig::get_conf()->thread_config; + thread_config->implement_named_thread_affinity("BatchedLoggerWriter"); + writer_thread = std::thread(writer_thread_func); + SET_THREAD_NAME(writer_thread.native_handle(), "snort.logger"); + thread_config->implement_thread_affinity(STHREAD_TYPE_MAIN, snort::ThreadConfig::DEFAULT_THREAD_ID); + + std::atexit(BatchedLogManager::shutdown); + + sched_param sch_params; + sch_params.sched_priority = 1; + pthread_setschedparam(writer_thread.native_handle(), SCHED_OTHER, &sch_params); +} + +} // namespace BatchedLogger diff --git a/src/log/batched_logger.h b/src/log/batched_logger.h new file mode 100644 index 000000000..a68fd8aae --- /dev/null +++ b/src/log/batched_logger.h @@ -0,0 +1,108 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2025-2025 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- +// batched_logger.h author Steven Baigal + +#ifndef BATCHED_LOGGER_H +#define BATCHED_LOGGER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace BatchedLogger +{ + +const size_t LOG_BUFFER_THRESHOLD = 8192; +const size_t LOG_QUEUE_SIZE = 8192; +const std::chrono::milliseconds LOG_TIME_THRESHOLD(10); + +struct LogBatch +{ + std::vector data; + size_t size = 0; + FILE* fh = nullptr; + bool use_syslog = false; + bool is_control_message = false; +}; + +class LogBuffer +{ +public: + char buffer[LOG_BUFFER_THRESHOLD]; + size_t size = 0; + std::chrono::steady_clock::time_point last_flush_time = std::chrono::steady_clock::now(); + + void append(FILE* fh, bool use_syslog, const char* msg, size_t len); + void flush(); + static void send_control_message(const char* msg, size_t len); +private: + FILE* fh = nullptr; + bool use_syslog = true; +}; + +class BatchQueue +{ +private: + std::vector buffer; + size_t head = 0; + size_t tail = 0; + mutable std::mutex mtx; + std::condition_variable cv; + static std::atomic overwrite_count; + +public: + BatchQueue() : buffer(LOG_QUEUE_SIZE) {} + + void push(LogBatch&& batch); + bool pop(LogBatch& batch); + void wait(); + bool empty() const; + static uint64_t get_overwrite_count() { return overwrite_count.load(); } +}; + +class BatchedLogManager +{ +public: + static void init(); + static void shutdown(); + static void log(FILE* fh, bool use_syslog, const char* msg, size_t len); + //static void log(FILE* fh, bool use_syslog, const char* format, va_list& ap); + static void flush_thread_buffers(); + static void push_batch(LogBatch&& batch); + static void set_filter(const std::string& filter); + +private: + static thread_local LogBuffer buffer; + static BatchQueue queue; + static std::thread writer_thread; + static std::atomic running; + + static void writer_thread_func(); + static void print_batch(const LogBatch& batch); +}; + +} // namespace BatchedLogger + +#endif // BATCHED_LOGGER_H diff --git a/src/log/log.cc b/src/log/log.cc index 56ef50476..635e738ad 100644 --- a/src/log/log.cc +++ b/src/log/log.cc @@ -32,6 +32,7 @@ #include "utils/util.h" #include "utils/util_cstring.h" +#include "batched_logger.h" #include "log_text.h" #include "messages.h" @@ -122,6 +123,7 @@ void OpenLogger() void CloseLogger() { + BatchedLogger::BatchedLogManager::shutdown(); TextLog_Term(text_log); } diff --git a/src/main/snort.cc b/src/main/snort.cc index ad15f0c00..d092f5a73 100644 --- a/src/main/snort.cc +++ b/src/main/snort.cc @@ -93,6 +93,7 @@ #include "catch/snort_catch.h" #endif +#include "log/batched_logger.h" #include "snort_config.h" #include "thread_config.h" @@ -434,6 +435,7 @@ void Snort::setup(int argc, char* argv[]) init(argc, argv); const SnortConfig* sc = SnortConfig::get_conf(); + BatchedLogger::BatchedLogManager::init(); if ( sc->daemon_mode() ) daemonize(); @@ -472,9 +474,9 @@ void Snort::cleanup() if ( !SnortConfig::get_conf()->test_mode() ) // FIXIT-M ideally the check is in one place PrintStatistics(); - CloseLogger(); ThreadConfig::term(); clean_exit(0); + CloseLogger(); } void Snort::reload_failure_cleanup(SnortConfig* sc) diff --git a/src/main/test/distill_verdict_stubs.h b/src/main/test/distill_verdict_stubs.h index 3bcd6ffa2..b3e736ec3 100644 --- a/src/main/test/distill_verdict_stubs.h +++ b/src/main/test/distill_verdict_stubs.h @@ -130,7 +130,6 @@ eth_t* eth_open(const char*) { return nullptr; } eth_t* eth_close(eth_t*) { return nullptr; } ssize_t eth_send(eth_t*, const void*, size_t) { return -1; } void HostAttributesManager::initialize() { } - void select_default_policy(const _daq_pkt_hdr&, const snort::SnortConfig*) { } void select_default_policy(const _daq_flow_stats&, const snort::SnortConfig*) { } diff --git a/src/packet_io/packet_tracer.cc b/src/packet_io/packet_tracer.cc index d9a4fe907..acd6d2b07 100644 --- a/src/packet_io/packet_tracer.cc +++ b/src/packet_io/packet_tracer.cc @@ -30,7 +30,9 @@ #include #include "detection/ips_context.h" +#include "log/batched_logger.h" #include "log/messages.h" +#include "main/snort_config.h" #include "main/thread.h" #include "protocols/eth.h" #include "protocols/icmp4.h" @@ -157,7 +159,10 @@ void PacketTracer::dump(Packet* p) const char* drop_reason = p->active->get_drop_reason(); if (drop_reason) PacketTracer::log("Verdict Reason: %s, %s\n", drop_reason, p->active->get_action_string() ); - LogMessage(s_pkt_trace->log_fh, "%s\n", s_pkt_trace->buffer); + if (s_pkt_trace->buff_len < max_buff_size - 1) + s_pkt_trace->buffer[s_pkt_trace->buff_len++] = '\n'; + BatchedLogger::BatchedLogManager::log(s_pkt_trace->log_fh, SnortConfig::log_syslog(), + s_pkt_trace->buffer, s_pkt_trace->buff_len); } s_pkt_trace->reset(false); @@ -321,11 +326,13 @@ void PacketTracer::restart_timer() // ----------------------------------------------------------------------------- PacketTracer::PacketTracer() + : pt_timer(new Stopwatch), + buffer(new char[max_buff_size]()), + daq_buffer(new char[max_buff_size]()), + debug_session(new char[PT_DEBUG_SESSION_ID_SIZE]) { - pt_timer = new Stopwatch; - buffer = new char[max_buff_size] { }; - daq_buffer = new char[max_buff_size] { }; - debug_session = new char[PT_DEBUG_SESSION_ID_SIZE]; + dbg_str.reserve(256); + debugstr.reserve(256); mutes.resize(global_mutes.val, false); open_file(); @@ -359,7 +366,6 @@ void PacketTracer::populate_buf(const char* format, va_list ap, char* buffer, ui void PacketTracer::log_va(const char* format, va_list ap, bool daq_log, bool msg_only) { // FIXIT-L Need to find way to add 'PktTracerDbg' string as part of format string. - std::string dbg_str; if (shell_enabled and !daq_log) // only add debug string during shell execution { dbg_str = "PktTracerDbg "; @@ -395,7 +401,6 @@ void PacketTracer::add_ip_header_info(const Packet& p) if (shell_enabled) { PacketTracer::log("\n"); - oss << sipstr << " " << sport << " -> " << dipstr << " " << dport << " " << std::to_string(to_utype(proto)) diff --git a/src/packet_io/packet_tracer.h b/src/packet_io/packet_tracer.h index a589393a0..eb1422aa4 100644 --- a/src/packet_io/packet_tracer.h +++ b/src/packet_io/packet_tracer.h @@ -122,6 +122,8 @@ protected: void open_file(); virtual void dump_to_daq(Packet*); void reset(bool); +private: + std::string dbg_str; }; struct PacketTracerSuspend diff --git a/src/packet_io/packet_tracer_module.cc b/src/packet_io/packet_tracer_module.cc index 338509658..164fb8c5b 100644 --- a/src/packet_io/packet_tracer_module.cc +++ b/src/packet_io/packet_tracer_module.cc @@ -27,6 +27,7 @@ #include #include "control/control.h" +#include "log/batched_logger.h" #include "log/messages.h" #include "main/analyzer_command.h" #include "main/snort_config.h" @@ -60,6 +61,8 @@ static const Parameter enable_packet_tracer_params[] = {"dst_ip", Parameter::PT_STRING, nullptr, nullptr, "destination IP address filter"}, {"dst_port", Parameter::PT_INT, "0:65535", nullptr, "destination port filter"}, {"tenants", Parameter::PT_STRING, nullptr, nullptr, "tenants filter"}, + {"regex", Parameter::PT_STRING, nullptr, nullptr, "regex filter"}, + {"stop_after_match", Parameter::PT_BOOL, nullptr, nullptr, "stop trace after match is found"}, {nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr} }; @@ -110,6 +113,8 @@ static int enable(lua_State* L) int dport = luaL_optint(L, 5, 0); const char *tenantsstr = luaL_optstring(L, 6, nullptr); + const char *regexstr = luaL_optstring(L, 7, nullptr); + bool stop_after_match = luaL_opt(L,lua_toboolean, 8, false); SfIp sip, dip; sip.clear(); @@ -159,6 +164,10 @@ static int enable(lua_State* L) constraints.set_bits |= PacketConstraints::SetBits::SRC_PORT; if ( dport ) constraints.set_bits |= PacketConstraints::SetBits::DST_PORT; + + std::string filter_str = regexstr ? regexstr : ""; + if (!filter_str.empty()) filter_str = (stop_after_match ? "Y" : "N") + filter_str; + BatchedLogger::BatchedLogManager::set_filter(filter_str); main_broadcast_command(new PacketTracerDebug(&constraints), ControlConn::query_from_lua(L)); return 0; } -- 2.47.3