/proc/self/cwd/test/test_common/logging.h
Line | Count | Source (jump to first uncovered line) |
1 | | #pragma once |
2 | | |
3 | | #include <cstdint> |
4 | | #include <string> |
5 | | #include <vector> |
6 | | |
7 | | #include "source/common/common/logger.h" |
8 | | |
9 | | #include "absl/strings/str_join.h" |
10 | | #include "absl/strings/str_split.h" |
11 | | #include "absl/synchronization/mutex.h" |
12 | | #include "spdlog/spdlog.h" |
13 | | |
14 | | namespace Envoy { |
15 | | |
16 | | /** |
17 | | * Provides a mechanism to temporarily set the logging level on |
18 | | * construction, restoring its previous state on destruction. |
19 | | * |
20 | | * The log_level is the minimum log severity required to print messages. |
21 | | * Messages below this loglevel will be suppressed. |
22 | | * |
23 | | * Note that during the scope of this object, command-line overrides, e.g., |
24 | | * --log-level trace, will not take effect. |
25 | | * |
26 | | * Also note: instantiating this setter should only occur when the system is |
27 | | * in a quiescent state, e.g. at startup or between tests. |
28 | | * |
29 | | * This is intended for use in EXPECT_LOG_CONTAINS and similar macros. |
30 | | */ |
31 | | class LogLevelSetter { |
32 | | public: |
33 | | explicit LogLevelSetter(spdlog::level::level_enum log_level); |
34 | | ~LogLevelSetter(); |
35 | | |
36 | | private: |
37 | | std::vector<spdlog::level::level_enum> previous_levels_; |
38 | | FineGrainLogLevelMap previous_fine_grain_levels_; |
39 | | }; |
40 | | |
41 | | /** |
42 | | * Records log messages in a vector<string>, forwarding them to the previous |
43 | | * delegate. This is useful for unit-testing log messages while still being able |
44 | | * to see them on stderr. |
45 | | * |
46 | | * Also note: instantiating this sink should only occur when the system is |
47 | | * in a quiescent state, e.g. at startup or between tests |
48 | | * |
49 | | * This is intended for use in EXPECT_LOG_CONTAINS and similar macros. |
50 | | */ |
51 | | class LogRecordingSink : public Logger::SinkDelegate { |
52 | | public: |
53 | | explicit LogRecordingSink(Logger::DelegatingLogSinkSharedPtr log_sink); |
54 | | ~LogRecordingSink() override; |
55 | | |
56 | | // Logger::SinkDelegate |
57 | | void log(absl::string_view msg, const spdlog::details::log_msg& log_msg) override; |
58 | | void flush() override; |
59 | | |
60 | 0 | const std::vector<std::string> messages() const { |
61 | 0 | absl::MutexLock ml(&mtx_); |
62 | 0 | std::vector<std::string> copy(messages_); |
63 | 0 | return copy; |
64 | 0 | } |
65 | | |
66 | | private: |
67 | | mutable absl::Mutex mtx_; |
68 | | std::vector<std::string> messages_ ABSL_GUARDED_BY(mtx_); |
69 | | }; |
70 | | |
71 | | using StringPair = std::pair<std::string, std::string>; |
72 | | |
73 | | using ExpectedLogMessages = std::vector<StringPair>; |
74 | | |
75 | | // Below macros specify Envoy:: before class names so that the macro can be used outside of |
76 | | // namespace Envoy. |
77 | | |
78 | | // Alias for EXPECT_LOG_CONTAINS_ALL_OF_HELPER, with escaped=true |
79 | | #define EXPECT_LOG_CONTAINS_ALL_OF_ESCAPED(expected_messages, stmt) \ |
80 | | EXPECT_LOG_CONTAINS_ALL_OF_HELPER(expected_messages, stmt, true) |
81 | | |
82 | | // Alias for EXPECT_LOG_CONTAINS_ALL_OF_HELPER, with escaped=false |
83 | | #define EXPECT_LOG_CONTAINS_ALL_OF(expected_messages, stmt) \ |
84 | | EXPECT_LOG_CONTAINS_ALL_OF_HELPER(expected_messages, stmt, false) |
85 | | |
86 | | // Validates that when stmt is executed, log messages containing substr and loglevel will be |
87 | | // emitted. Escaped=true sets the behavior to function like the --log-format-escaped CLI flag. |
88 | | // Failure message e.g., |
89 | | // |
90 | | // Logs: |
91 | | // [2018-04-12 05:51:00.245][7290192][debug][upstream] grpc_mux_impl.cc:160] Received gRPC |
92 | | // [2018-04-12 05:51:00.246][7290192][warning][upstream] grpc_mux_impl.cc:63] Called bar |
93 | | // [2018-04-12 05:51:00.246][7290192][trace][upstream] grpc_mux_impl.cc:80] Sending foo |
94 | | // Does NOT contain: |
95 | | // 'warning', 'Too many sendDiscoveryRequest calls for baz’ |
96 | | // 'warning', 'Too man sendDiscoveryRequest calls for foo' |
97 | | #define EXPECT_LOG_CONTAINS_ALL_OF_HELPER(expected_messages, stmt, escaped) \ |
98 | | do { \ |
99 | | ASSERT_FALSE(expected_messages.empty()) << "Expected messages cannot be empty."; \ |
100 | | Envoy::LogLevelSetter save_levels(spdlog::level::trace); \ |
101 | | Envoy::Logger::DelegatingLogSinkSharedPtr sink_ptr = Envoy::Logger::Registry::getSink(); \ |
102 | | sink_ptr->setShouldEscape(escaped); \ |
103 | | Envoy::LogRecordingSink log_recorder(sink_ptr); \ |
104 | | stmt; \ |
105 | | auto messages = log_recorder.messages(); \ |
106 | | if (messages.empty()) { \ |
107 | | FAIL() << "Expected message(s), but NONE was recorded."; \ |
108 | | } \ |
109 | | Envoy::ExpectedLogMessages failed_expectations; \ |
110 | | for (const Envoy::StringPair& expected : expected_messages) { \ |
111 | | const auto log_message = \ |
112 | | std::find_if(messages.begin(), messages.end(), [&expected](const std::string& message) { \ |
113 | | return (message.find(expected.second) != std::string::npos) && \ |
114 | | (message.find(expected.first) != std::string::npos); \ |
115 | | }); \ |
116 | | if (log_message == messages.end()) { \ |
117 | | failed_expectations.push_back(expected); \ |
118 | | } \ |
119 | | } \ |
120 | | if (!failed_expectations.empty()) { \ |
121 | | std::string failed_message; \ |
122 | | absl::StrAppend(&failed_message, "\nLogs:\n ", absl::StrJoin(messages, " "), \ |
123 | | "\n Do NOT contain:\n"); \ |
124 | | for (const auto& expectation : failed_expectations) { \ |
125 | | absl::StrAppend(&failed_message, " '", expectation.first, "', '", expectation.second, \ |
126 | | "'\n"); \ |
127 | | } \ |
128 | | FAIL() << failed_message; \ |
129 | | } \ |
130 | | } while (false) |
131 | | |
132 | | // Validates that when stmt is executed, log message containing substr and loglevel will NOT be |
133 | | // emitted. Failure message e.g., |
134 | | // |
135 | | // Logs: |
136 | | // [2018-04-12 05:51:00.245][7290192][warning][upstream] grpc_mux_impl.cc:160] Received gRPC |
137 | | // [2018-04-12 05:51:00.246][7290192][trace][upstream] grpc_mux_impl.cc:63] Called bar |
138 | | // Should NOT contain: |
139 | | // 'warning', 'Received gRPC’ |
140 | | #define EXPECT_LOG_NOT_CONTAINS(loglevel, substr, stmt) \ |
141 | | do { \ |
142 | | Envoy::LogLevelSetter save_levels(spdlog::level::trace); \ |
143 | | Envoy::LogRecordingSink log_recorder(Envoy::Logger::Registry::getSink()); \ |
144 | | stmt; \ |
145 | | auto messages = log_recorder.messages(); \ |
146 | | for (const std::string& message : messages) { \ |
147 | | if ((message.find(substr) != std::string::npos) && \ |
148 | | (message.find(loglevel) != std::string::npos)) { \ |
149 | | FAIL() << "\nLogs:\n " << absl::StrJoin(messages, " ") << "\n Should NOT contain:\n '" \ |
150 | | << loglevel << "', '" << substr "'\n"; \ |
151 | | } \ |
152 | | } \ |
153 | | } while (false) |
154 | | |
155 | | // Validates that when stmt is executed, the supplied substring matches at least one log message. |
156 | | // Failure message e.g., |
157 | | // |
158 | | // Logs: |
159 | | // [2018-04-12 05:51:00.245][7290192][debug][upstream] grpc_mux_impl.cc:160] Received gRPC |
160 | | // [2018-04-12 05:51:00.246][7290192][trace][upstream] grpc_mux_impl.cc:80] Sending foo |
161 | | // Do NOT contain: |
162 | | // 'warning', 'Too many sendDiscoveryRequest calls for baz’ |
163 | | #define EXPECT_LOG_CONTAINS(loglevel, substr, stmt) \ |
164 | | do { \ |
165 | | const Envoy::ExpectedLogMessages message{{loglevel, substr}}; \ |
166 | | EXPECT_LOG_CONTAINS_ALL_OF(message, stmt); \ |
167 | | } while (false) |
168 | | |
169 | | // Validates that when stmt is executed, the supplied substring occurs exactly the specified |
170 | | // number of times. |
171 | | #define EXPECT_LOG_CONTAINS_N_TIMES(loglevel, substr, expected_occurrences, stmt) \ |
172 | | do { \ |
173 | | Envoy::LogLevelSetter save_levels(spdlog::level::trace); \ |
174 | | Envoy::LogRecordingSink log_recorder(Envoy::Logger::Registry::getSink()); \ |
175 | | stmt; \ |
176 | | auto messages = log_recorder.messages(); \ |
177 | | uint64_t actual_occurrences = 0; \ |
178 | | for (const std::string& message : messages) { \ |
179 | | if ((message.find(substr) != std::string::npos) && \ |
180 | | (message.find(loglevel) != std::string::npos)) { \ |
181 | | actual_occurrences++; \ |
182 | | } \ |
183 | | } \ |
184 | | EXPECT_EQ(expected_occurrences, actual_occurrences); \ |
185 | | } while (false) |
186 | | |
187 | | // Validates that when stmt is executed, no logs will be emitted. |
188 | | // Expected equality of these values: |
189 | | // 0 |
190 | | // logs.size() |
191 | | // Which is: 3 |
192 | | // Logs: |
193 | | // [2018-04-12 05:51:00.245][7290192][debug][upstream] grpc_mux_impl.cc:160] Received gRPC |
194 | | // [2018-04-12 05:51:00.246][7290192][trace][upstream] grpc_mux_impl.cc:80] Sending foo |
195 | | #define EXPECT_NO_LOGS(stmt) \ |
196 | | do { \ |
197 | | Envoy::LogLevelSetter save_levels(spdlog::level::trace); \ |
198 | | Envoy::LogRecordingSink log_recorder(Envoy::Logger::Registry::getSink()); \ |
199 | | stmt; \ |
200 | | const std::vector<std::string> logs = log_recorder.messages(); \ |
201 | | ASSERT_EQ(0, logs.size()) << " Logs:\n " << absl::StrJoin(logs, " "); \ |
202 | | } while (false) |
203 | | |
204 | | // Validates that when stmt is executed, the supplied substring is eventually logged. |
205 | | // This both waits infinitely (not ideal) and walks the full log list every time (not ideal). |
206 | | #define WAIT_FOR_LOG_CONTAINS(loglevel_raw, substr_raw, stmt) \ |
207 | | do { \ |
208 | | Envoy::LogLevelSetter save_levels(spdlog::level::trace); \ |
209 | | Envoy::Logger::DelegatingLogSinkSharedPtr sink_ptr = Envoy::Logger::Registry::getSink(); \ |
210 | | std::string loglevel = loglevel_raw; \ |
211 | | std::string substr = substr_raw; \ |
212 | | Envoy::LogRecordingSink log_recorder(sink_ptr); \ |
213 | | stmt; \ |
214 | | while (true) { \ |
215 | | auto messages = log_recorder.messages(); \ |
216 | | if (messages.empty()) { \ |
217 | | continue; \ |
218 | | } \ |
219 | | const auto log_message = std::find_if( \ |
220 | | messages.begin(), messages.end(), [&substr, loglevel](const std::string& message) { \ |
221 | | return (message.find(substr) != std::string::npos) && \ |
222 | | (message.find(loglevel) != std::string::npos); \ |
223 | | }); \ |
224 | | if (log_message != messages.end()) { \ |
225 | | break; \ |
226 | | } \ |
227 | | } \ |
228 | | } while (false) |
229 | | |
230 | | } // namespace Envoy |