blob: 71293af549f5a0ed458ff9249fa46e3d59c02504 [file] [log] [blame]
Brian Silverman9891b292020-06-23 16:34:22 -07001#include <algorithm>
James Kuszmaul38735e82019-12-07 16:42:06 -08002#include <iostream>
Brian Silverman9891b292020-06-23 16:34:22 -07003#include <memory>
4#include <optional>
5#include <string>
6#include <string_view>
7#include <vector>
James Kuszmaul38735e82019-12-07 16:42:06 -08008
Austin Schuh0e8db662021-07-06 10:43:47 -07009#include "absl/strings/escaping.h"
James Kuszmaul38735e82019-12-07 16:42:06 -080010#include "aos/configuration.h"
Austin Schuhb06f03b2021-02-17 22:00:37 -080011#include "aos/events/logging/log_reader.h"
James Kuszmaul38735e82019-12-07 16:42:06 -080012#include "aos/events/simulated_event_loop.h"
13#include "aos/init.h"
14#include "aos/json_to_flatbuffer.h"
15#include "gflags/gflags.h"
16
James Kuszmaul38735e82019-12-07 16:42:06 -080017DEFINE_string(
18 name, "",
19 "Name to match for printing out channels. Empty means no name filter.");
20DEFINE_string(type, "",
21 "Channel type to match for printing out channels. Empty means no "
22 "type filter.");
Austin Schuha81454b2020-05-12 19:58:36 -070023DEFINE_bool(fetch, false,
24 "If true, also print out the messages from before the start of the "
25 "log file");
Austin Schuh6f3babe2020-01-26 20:34:50 -080026DEFINE_bool(raw, false,
27 "If true, just print the data out unsorted and unparsed");
Brian Silverman8ff74aa2021-02-05 16:37:15 -080028DEFINE_string(raw_header, "",
29 "If set, the file to read the header from in raw mode");
Austin Schuha81454b2020-05-12 19:58:36 -070030DEFINE_bool(format_raw, true,
31 "If true and --raw is specified, print out raw data, but use the "
32 "schema to format the data.");
Austin Schuhae46f362020-04-11 19:52:56 -070033DEFINE_int32(max_vector_size, 100,
34 "If positive, vectors longer than this will not be printed");
Ravago Jones5cc9df52020-09-02 21:29:58 -070035DEFINE_bool(pretty, false,
36 "If true, pretty print the messages on multiple lines");
Austin Schuh569c7f92020-12-11 20:01:42 -080037DEFINE_bool(print, true,
38 "If true, actually print the messages. If false, discard them, "
39 "confirming they can be parsed.");
Tyler Chatowee0afa82021-08-01 22:00:36 -070040DEFINE_uint64(
41 count, 0,
42 "If >0, log_cat will exit after printing this many messages. This "
43 "includes messages from before the start of the log if --fetch is set.");
Austin Schuh7af06d52021-06-28 15:46:59 -070044DEFINE_bool(print_parts_only, false,
45 "If true, only print out the results of logfile sorting.");
Austin Schuh25b17652021-07-21 15:42:56 -070046DEFINE_bool(channels, false,
47 "If true, print out all the configured channels for this log.");
Austin Schuh6f3babe2020-01-26 20:34:50 -080048
Brian Silverman9891b292020-06-23 16:34:22 -070049// Print the flatbuffer out to stdout, both to remove the unnecessary cruft from
50// glog and to allow the user to readily redirect just the logged output
51// independent of any debugging information on stderr.
52void PrintMessage(const std::string_view node_name, const aos::Channel *channel,
Austin Schuh746690f2020-08-01 16:15:57 -070053 const aos::Context &context,
54 aos::FastStringBuilder *builder) {
55 builder->Reset();
Austin Schuha9df9ad2021-06-16 14:49:39 -070056 CHECK(flatbuffers::Verify(*channel->schema(),
57 *channel->schema()->root_table(),
58 static_cast<const uint8_t *>(context.data),
59 static_cast<size_t>(context.size)))
60 << ": Corrupted flatbuffer on " << channel->name()->c_str() << " "
61 << channel->type()->c_str();
62
Ravago Jones5cc9df52020-09-02 21:29:58 -070063 aos::FlatbufferToJson(
64 builder, channel->schema(), static_cast<const uint8_t *>(context.data),
65 {FLAGS_pretty, static_cast<size_t>(FLAGS_max_vector_size)});
Austin Schuh746690f2020-08-01 16:15:57 -070066
Austin Schuha81454b2020-05-12 19:58:36 -070067 if (context.monotonic_remote_time != context.monotonic_event_time) {
68 std::cout << node_name << context.realtime_event_time << " ("
69 << context.monotonic_event_time << ") sent "
70 << context.realtime_remote_time << " ("
71 << context.monotonic_remote_time << ") "
72 << channel->name()->c_str() << ' ' << channel->type()->c_str()
Austin Schuh746690f2020-08-01 16:15:57 -070073 << ": " << *builder << std::endl;
Austin Schuha81454b2020-05-12 19:58:36 -070074 } else {
75 std::cout << node_name << context.realtime_event_time << " ("
76 << context.monotonic_event_time << ") "
77 << channel->name()->c_str() << ' ' << channel->type()->c_str()
Austin Schuh746690f2020-08-01 16:15:57 -070078 << ": " << *builder << std::endl;
Austin Schuha81454b2020-05-12 19:58:36 -070079 }
80}
81
Austin Schuh58646e22021-08-23 23:51:46 -070082// Prints out raw log parts to stdout.
83int PrintRaw(int argc, char **argv) {
84 if (argc != 2) {
85 LOG(FATAL) << "Expected 1 logfile as an argument.";
86 }
87 aos::logger::SpanReader reader(argv[1]);
88 absl::Span<const uint8_t> raw_log_file_header_span = reader.ReadMessage();
89
90 if (raw_log_file_header_span == absl::Span<const uint8_t>()) {
91 LOG(WARNING) << "Empty log file on " << reader.filename();
92 return 0;
93 }
94
95 // Now, reproduce the log file header deduplication logic inline so we can
96 // print out all the headers we find.
97 aos::SizePrefixedFlatbufferVector<aos::logger::LogFileHeader> log_file_header(
98 raw_log_file_header_span);
99 if (!log_file_header.Verify()) {
100 LOG(ERROR) << "Header corrupted on " << reader.filename();
101 return 1;
102 }
103 while (true) {
104 absl::Span<const uint8_t> maybe_header_data = reader.PeekMessage();
105 if (maybe_header_data == absl::Span<const uint8_t>()) {
106 break;
107 }
108
109 aos::SizePrefixedFlatbufferSpan<aos::logger::LogFileHeader> maybe_header(
110 maybe_header_data);
111 if (maybe_header.Verify()) {
112 std::cout << aos::FlatbufferToJson(
113 log_file_header, {.multi_line = FLAGS_pretty,
114 .max_vector_size = static_cast<size_t>(
115 FLAGS_max_vector_size)})
116 << std::endl;
117 LOG(WARNING) << "Found duplicate LogFileHeader in " << reader.filename();
118 log_file_header =
119 aos::SizePrefixedFlatbufferVector<aos::logger::LogFileHeader>(
120 maybe_header_data);
121
122 reader.ConsumeMessage();
123 } else {
124 break;
125 }
126 }
127
128 // And now use the final sha256 to match the raw_header.
129 std::optional<aos::logger::MessageReader> raw_header_reader;
130 const aos::logger::LogFileHeader *full_header = &log_file_header.message();
131 if (!FLAGS_raw_header.empty()) {
132 raw_header_reader.emplace(FLAGS_raw_header);
133 std::cout << aos::FlatbufferToJson(full_header,
134 {.multi_line = FLAGS_pretty,
135 .max_vector_size = static_cast<size_t>(
136 FLAGS_max_vector_size)})
137 << std::endl;
138 CHECK_EQ(
139 full_header->configuration_sha256()->string_view(),
140 aos::logger::Sha256(raw_header_reader->raw_log_file_header().span()));
141 full_header = raw_header_reader->log_file_header();
142 }
143
144 if (!FLAGS_print) {
145 return 0;
146 }
147
148 std::cout << aos::FlatbufferToJson(full_header,
149 {.multi_line = FLAGS_pretty,
150 .max_vector_size = static_cast<size_t>(
151 FLAGS_max_vector_size)})
152 << std::endl;
153 CHECK(full_header->has_configuration())
154 << ": Missing configuration! You may want to provide the path to the "
155 "logged configuration file using the --raw_header flag.";
156
157 while (true) {
158 const aos::SizePrefixedFlatbufferSpan<aos::logger::MessageHeader> message(
159 reader.ReadMessage());
160 if (message.span() == absl::Span<const uint8_t>()) {
161 break;
162 }
163 CHECK(message.Verify());
164
165 const auto *const channels = full_header->configuration()->channels();
166 const size_t channel_index = message.message().channel_index();
167 CHECK_LT(channel_index, channels->size());
168 const aos::Channel *const channel = channels->Get(channel_index);
169
170 CHECK(message.Verify()) << absl::BytesToHexString(
171 std::string_view(reinterpret_cast<const char *>(message.span().data()),
172 message.span().size()));
173
174 if (message.message().data() != nullptr) {
175 CHECK(channel->has_schema());
176
177 CHECK(flatbuffers::Verify(
178 *channel->schema(), *channel->schema()->root_table(),
179 message.message().data()->data(), message.message().data()->size()))
180 << ": Corrupted flatbuffer on " << channel->name()->c_str() << " "
181 << channel->type()->c_str();
182 }
183
184 if (FLAGS_format_raw && message.message().data() != nullptr) {
185 std::cout << aos::configuration::StrippedChannelToString(channel) << " "
186 << aos::FlatbufferToJson(message, {.multi_line = FLAGS_pretty,
187 .max_vector_size = 4})
188 << ": "
189 << aos::FlatbufferToJson(
190 channel->schema(), message.message().data()->data(),
191 {FLAGS_pretty,
192 static_cast<size_t>(FLAGS_max_vector_size)})
193 << std::endl;
194 } else {
195 std::cout << aos::configuration::StrippedChannelToString(channel) << " "
196 << aos::FlatbufferToJson(
197 message, {FLAGS_pretty,
198 static_cast<size_t>(FLAGS_max_vector_size)})
199 << std::endl;
200 }
201 }
202 return 0;
203}
204
205// This class prints out all data from a node on a boot.
206class NodePrinter {
207 public:
208 NodePrinter(aos::EventLoop *event_loop, uint64_t *message_print_counter,
209 aos::SimulatedEventLoopFactory *factory,
210 aos::FastStringBuilder *builder)
211 : factory_(factory),
212 event_loop_(event_loop),
213 message_print_counter_(message_print_counter),
214 node_name_(
215 event_loop_->node() == nullptr
216 ? ""
217 : std::string(event_loop->node()->name()->string_view()) + " "),
218 builder_(builder) {
219 event_loop_->SkipTimingReport();
220 event_loop_->SkipAosLog();
221
222 const flatbuffers::Vector<flatbuffers::Offset<aos::Channel>> *channels =
223 event_loop_->configuration()->channels();
224
225 for (flatbuffers::uoffset_t i = 0; i < channels->size(); i++) {
226 const aos::Channel *channel = channels->Get(i);
227 const flatbuffers::string_view name = channel->name()->string_view();
228 const flatbuffers::string_view type = channel->type()->string_view();
229 if (name.find(FLAGS_name) != std::string::npos &&
230 type.find(FLAGS_type) != std::string::npos) {
231 if (!aos::configuration::ChannelIsReadableOnNode(channel,
232 event_loop_->node())) {
233 continue;
234 }
235 VLOG(1) << "Listening on " << name << " " << type;
236
237 CHECK_NOTNULL(channel->schema());
238 event_loop_->MakeRawWatcher(
239 channel, [this, channel](const aos::Context &context,
240 const void * /*message*/) {
241 if (!FLAGS_print) {
242 return;
243 }
244
245 if (!FLAGS_fetch && !started_) {
246 return;
247 }
248
249 PrintMessage(node_name_, channel, context, builder_);
250 ++(*message_print_counter_);
251 if (FLAGS_count > 0 && *message_print_counter_ >= FLAGS_count) {
252 factory_->Exit();
253 }
254 });
255 }
256 }
257 }
258
259 void SetStarted(bool started, aos::monotonic_clock::time_point monotonic_now,
260 aos::realtime_clock::time_point realtime_now) {
261 started_ = started;
262 if (started_) {
263 std::cout << std::endl;
264 std::cout << (event_loop_->node() != nullptr
265 ? (event_loop_->node()->name()->str() + " ")
266 : "")
267 << "Log starting at " << realtime_now << " (" << monotonic_now
268 << ")";
269 std::cout << std::endl << std::endl;
270 } else {
271 std::cout << std::endl;
272 std::cout << (event_loop_->node() != nullptr
273 ? (event_loop_->node()->name()->str() + " ")
274 : "")
275 << "Log shutting down at " << realtime_now << " ("
276 << monotonic_now << ")";
277 std::cout << std::endl << std::endl;
278 }
279 }
280
281 private:
282 struct MessageInfo {
283 std::string node_name;
284 std::unique_ptr<aos::RawFetcher> fetcher;
285 };
286
287 aos::SimulatedEventLoopFactory *factory_;
288 aos::EventLoop *event_loop_;
289
290 uint64_t *message_print_counter_ = nullptr;
291
292 std::string node_name_;
293
294 bool started_ = false;
295
296 aos::FastStringBuilder *builder_;
297};
298
James Kuszmaul38735e82019-12-07 16:42:06 -0800299int main(int argc, char **argv) {
300 gflags::SetUsageMessage(
Austin Schuh6f3babe2020-01-26 20:34:50 -0800301 "Usage:\n"
302 " log_cat [args] logfile1 logfile2 ...\n"
303 "\n"
James Kuszmaul38735e82019-12-07 16:42:06 -0800304 "This program provides a basic interface to dump data from a logfile to "
305 "stdout. Given a logfile, channel name filter, and type filter, it will "
306 "print all the messages in the logfile matching the filters. The message "
307 "filters work by taking the values of --name and --type and printing any "
308 "channel whose name contains --name as a substr and whose type contains "
309 "--type as a substr. Not specifying --name or --type leaves them free. "
310 "Calling this program without --name or --type specified prints out all "
311 "the logged data.");
312 aos::InitGoogle(&argc, &argv);
313
Austin Schuh6f3babe2020-01-26 20:34:50 -0800314 if (FLAGS_raw) {
Austin Schuh58646e22021-08-23 23:51:46 -0700315 return PrintRaw(argc, argv);
James Kuszmaul38735e82019-12-07 16:42:06 -0800316 }
317
Austin Schuh6f3babe2020-01-26 20:34:50 -0800318 if (argc < 2) {
319 LOG(FATAL) << "Expected at least 1 logfile as an argument.";
320 }
321
Austin Schuh11d43732020-09-21 17:28:30 -0700322 const std::vector<aos::logger::LogFile> logfiles =
Austin Schuh58646e22021-08-23 23:51:46 -0700323 aos::logger::SortParts(aos::logger::FindLogs(argc, argv));
Austin Schuh5212cad2020-09-09 23:12:09 -0700324
Austin Schuhfe3fb342021-01-16 18:50:37 -0800325 for (auto &it : logfiles) {
326 VLOG(1) << it;
Austin Schuh7af06d52021-06-28 15:46:59 -0700327 if (FLAGS_print_parts_only) {
328 std::cout << it << std::endl;
329 }
330 }
331 if (FLAGS_print_parts_only) {
332 return 0;
Austin Schuhfe3fb342021-01-16 18:50:37 -0800333 }
334
Austin Schuh6f3babe2020-01-26 20:34:50 -0800335 aos::logger::LogReader reader(logfiles);
Austin Schuha81454b2020-05-12 19:58:36 -0700336
Austin Schuh25b17652021-07-21 15:42:56 -0700337 if (FLAGS_channels) {
338 const aos::Configuration *config = reader.configuration();
339 for (const aos::Channel *channel : *config->channels()) {
340 std::cout << channel->name()->c_str() << " " << channel->type()->c_str()
341 << '\n';
342 }
343 return 0;
344 }
345
Austin Schuh58646e22021-08-23 23:51:46 -0700346 {
347 bool found_channel = false;
Austin Schuh6f3babe2020-01-26 20:34:50 -0800348 const flatbuffers::Vector<flatbuffers::Offset<aos::Channel>> *channels =
Austin Schuh58646e22021-08-23 23:51:46 -0700349 reader.configuration()->channels();
Brian Silverman9891b292020-06-23 16:34:22 -0700350
Austin Schuh6f3babe2020-01-26 20:34:50 -0800351 for (flatbuffers::uoffset_t i = 0; i < channels->size(); i++) {
352 const aos::Channel *channel = channels->Get(i);
353 const flatbuffers::string_view name = channel->name()->string_view();
354 const flatbuffers::string_view type = channel->type()->string_view();
355 if (name.find(FLAGS_name) != std::string::npos &&
356 type.find(FLAGS_type) != std::string::npos) {
Austin Schuh6f3babe2020-01-26 20:34:50 -0800357 found_channel = true;
358 }
359 }
Austin Schuh58646e22021-08-23 23:51:46 -0700360 if (!found_channel) {
361 LOG(FATAL) << "Could not find any channels";
Austin Schuha81454b2020-05-12 19:58:36 -0700362 }
James Kuszmaul38735e82019-12-07 16:42:06 -0800363 }
364
Austin Schuh58646e22021-08-23 23:51:46 -0700365 aos::FastStringBuilder builder;
James Kuszmaul912af072020-10-31 16:06:54 -0700366
Austin Schuh58646e22021-08-23 23:51:46 -0700367 uint64_t message_print_counter = 0;
368
369 std::vector<NodePrinter *> printers;
Sanjay Narayananbeb328c2021-09-01 16:24:20 -0700370 printers.resize(aos::configuration::NodesCount(reader.configuration()),
371 nullptr);
372
373 aos::SimulatedEventLoopFactory event_loop_factory(reader.configuration());
374
375 reader.RegisterWithoutStarting(&event_loop_factory);
Austin Schuh58646e22021-08-23 23:51:46 -0700376
377 for (const aos::Node *node :
378 aos::configuration::GetNodes(event_loop_factory.configuration())) {
379 size_t node_index = aos::configuration::GetNodeIndex(
380 event_loop_factory.configuration(), node);
381 // Spin up the printer, and hook up the SetStarted method so that it gets
382 // notified when the log starts and stops.
383 aos::NodeEventLoopFactory *node_factory =
384 event_loop_factory.GetNodeEventLoopFactory(node);
385 node_factory->OnStartup([&event_loop_factory, node_factory,
386 &message_print_counter, &builder, &printers,
387 node_index]() {
388 printers[node_index] = node_factory->AlwaysStart<NodePrinter>(
389 "printer", &message_print_counter, &event_loop_factory, &builder);
390 });
391 node_factory->OnShutdown(
392 [&printers, node_index]() { printers[node_index] = nullptr; });
393
394 reader.OnStart(node, [&printers, node_index, node_factory]() {
395 CHECK(printers[node_index]);
396 printers[node_index]->SetStarted(true, node_factory->monotonic_now(),
397 node_factory->realtime_now());
398 });
399 reader.OnEnd(node, [&printers, node_index, node_factory]() {
400 CHECK(printers[node_index]);
401 printers[node_index]->SetStarted(false, node_factory->monotonic_now(),
402 node_factory->realtime_now());
403 });
Austin Schuha81454b2020-05-12 19:58:36 -0700404 }
405
406 event_loop_factory.Run();
James Kuszmaul38735e82019-12-07 16:42:06 -0800407
Austin Schuh51a92592020-08-09 13:17:00 -0700408 reader.Deregister();
409
James Kuszmaul38735e82019-12-07 16:42:06 -0800410 return 0;
411}