1 // Copyright 2024 The Pigweed Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 // use this file except in compliance with the License. You may obtain a copy of 5 // the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 // License for the specific language governing permissions and limitations under 13 // the License. 14 15 #pragma once 16 17 #include "pw_chrono/system_clock.h" 18 #include "pw_log/log.h" 19 20 // rate_limited adds a wrapper around a normal PW_LOG call to provide a rate 21 // limitor parameter to suppress chatty logs and provide info on how many logs 22 // were suppressed 23 // PW_LOG_EVERY_N_DURATION(level, min_interval_between_logs, msg, ...) 24 // - Required. 25 // level - An integer level as defined by pw_log/levels.h 26 // min_interval_between_logs - A std::chrono::duration of the minimum 27 // interval between 28 // two of the same logs. 29 // msg - Formattable message, same as you would use for PW_LOG or variants 30 // 31 // Does not check that input parameters have changed to un-suppress logs. 32 33 namespace pw::log::internal { 34 35 class RateLimiter { 36 public: 37 struct PollResult { 38 uint16_t count; 39 uint16_t logs_per_s; 40 }; 41 RateLimiter()42 explicit RateLimiter() {} 43 ~RateLimiter() = default; 44 45 PollResult Poll(chrono::SystemClock::duration min_interval_between_logs); 46 47 private: 48 uint32_t count_ = 0; 49 chrono::SystemClock::time_point last_timestamp_; 50 }; 51 52 } // namespace pw::log::internal 53 54 // PW_LOG_EVERY_N_DURATION(level, min_interval_between_logs, msg, ...) 55 // 56 // Logs a message at the given level, only it hasn't been logged within 57 // `min_interval_between_logs`. 58 // 59 // Inputs: 60 // level - An integer level as devifen by pw_log/levels.h 61 // min_interval_between_logs - A pw::chrono::SystemClock::duration that 62 // defines the minimum time interval between unsuppressed logs 63 // msg - Formattable message, same as you would use for PW_LOG or variants 64 // 65 // Includes a summary of how many logs were skipped, and a rough rate in 66 // integer seconds. 67 // 68 // NOTE: This macro is NOT threadsafe. The underlying object being modified by 69 // multiple threads calling the macro context may result in undefined behavior. 70 // 71 // Intended to supplement and replace widespread use of EVERY_N for logging. The 72 // main benefit this provides is responsiveness for bursty logs. 73 // LOG_RATE_LIMITED will log as soon as a burst starts - provided the 74 // `min_interval_between_logs` has elapsed - while EVERY_N may sit idle for a 75 // full period depending on the count state. 76 // 77 // Note that this will not log until called again, so the summary may include 78 // skipped logs from a prior burst. 79 #define PW_LOG_EVERY_N_DURATION(level, min_interval_between_logs, msg, ...) \ 80 do { \ 81 static pw::log::internal::RateLimiter rate_limiter; \ 82 \ 83 if (auto result = rate_limiter.Poll(min_interval_between_logs); \ 84 result.count == std::numeric_limits<uint16_t>::max()) { \ 85 PW_LOG(level, \ 86 PW_LOG_LEVEL, \ 87 PW_LOG_MODULE_NAME, \ 88 PW_LOG_FLAGS, \ 89 msg " (skipped %d or more, %d/s)", \ 90 ##__VA_ARGS__, \ 91 static_cast<unsigned>(result.count), \ 92 static_cast<unsigned>(result.logs_per_s)); \ 93 } else if (result.count != 0) { \ 94 PW_LOG(level, \ 95 PW_LOG_LEVEL, \ 96 PW_LOG_MODULE_NAME, \ 97 PW_LOG_FLAGS, \ 98 msg " (skipped %d, %d/s)", \ 99 ##__VA_ARGS__, \ 100 static_cast<unsigned>(result.count - 1), \ 101 static_cast<unsigned>(result.logs_per_s)); \ 102 } \ 103 } while (0) 104