blob: 31d12be1adb94ada0920de8f958945519b17a352 [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"
Philipp Schrader790cb542023-07-05 21:06:52 -070010#include "gflags/gflags.h"
11
Austin Schuh893d7f42022-09-16 15:01:35 -070012#include "aos/aos_cli_utils.h"
James Kuszmaul38735e82019-12-07 16:42:06 -080013#include "aos/configuration.h"
Austin Schuhb06f03b2021-02-17 22:00:37 -080014#include "aos/events/logging/log_reader.h"
James Kuszmaul38735e82019-12-07 16:42:06 -080015#include "aos/events/simulated_event_loop.h"
16#include "aos/init.h"
17#include "aos/json_to_flatbuffer.h"
Austin Schuhb0e439d2023-05-15 10:55:40 -070018#include "aos/sha256.h"
James Kuszmaul38735e82019-12-07 16:42:06 -080019
James Kuszmaul38735e82019-12-07 16:42:06 -080020DEFINE_string(
21 name, "",
22 "Name to match for printing out channels. Empty means no name filter.");
23DEFINE_string(type, "",
24 "Channel type to match for printing out channels. Empty means no "
25 "type filter.");
Austin Schuh041fe9f2021-10-16 23:01:15 -070026DEFINE_bool(json, false, "If true, print fully valid JSON");
Austin Schuha81454b2020-05-12 19:58:36 -070027DEFINE_bool(fetch, false,
28 "If true, also print out the messages from before the start of the "
29 "log file");
Austin Schuh6f3babe2020-01-26 20:34:50 -080030DEFINE_bool(raw, false,
31 "If true, just print the data out unsorted and unparsed");
Brian Silverman8ff74aa2021-02-05 16:37:15 -080032DEFINE_string(raw_header, "",
33 "If set, the file to read the header from in raw mode");
Austin Schuhff3bc902022-05-11 16:10:57 -070034DEFINE_bool(distributed_clock, false,
35 "If true, print out the distributed time");
Austin Schuha81454b2020-05-12 19:58:36 -070036DEFINE_bool(format_raw, true,
37 "If true and --raw is specified, print out raw data, but use the "
38 "schema to format the data.");
Austin Schuh893d7f42022-09-16 15:01:35 -070039DEFINE_int64(max_vector_size, 100,
Austin Schuhae46f362020-04-11 19:52:56 -070040 "If positive, vectors longer than this will not be printed");
Ravago Jones5cc9df52020-09-02 21:29:58 -070041DEFINE_bool(pretty, false,
42 "If true, pretty print the messages on multiple lines");
Austin Schuh893d7f42022-09-16 15:01:35 -070043DEFINE_bool(
44 pretty_max, false,
45 "If true, expand every field to its own line (expands more than -pretty)");
46DEFINE_bool(print_timestamps, true, "If true, timestamps are printed.");
Austin Schuh569c7f92020-12-11 20:01:42 -080047DEFINE_bool(print, true,
48 "If true, actually print the messages. If false, discard them, "
49 "confirming they can be parsed.");
Tyler Chatowee0afa82021-08-01 22:00:36 -070050DEFINE_uint64(
51 count, 0,
52 "If >0, log_cat will exit after printing this many messages. This "
53 "includes messages from before the start of the log if --fetch is set.");
Austin Schuh7af06d52021-06-28 15:46:59 -070054DEFINE_bool(print_parts_only, false,
55 "If true, only print out the results of logfile sorting.");
Austin Schuh25b17652021-07-21 15:42:56 -070056DEFINE_bool(channels, false,
57 "If true, print out all the configured channels for this log.");
Milind Upadhyay184dfda2022-03-26 15:54:38 -070058DEFINE_double(monotonic_start_time, 0.0,
59 "If set, only print messages sent at or after this many seconds "
60 "after epoch.");
61DEFINE_double(monotonic_end_time, 0.0,
62 "If set, only print messages sent at or before this many seconds "
63 "after epoch.");
Austin Schuhbe6d2962022-11-01 09:24:56 -070064DEFINE_bool(hex, false,
Naman Gupta54047212022-09-23 14:10:30 -070065 "Are integers in the messages printed in hex notation.");
Austin Schuh6f3babe2020-01-26 20:34:50 -080066
Austin Schuh041fe9f2021-10-16 23:01:15 -070067using aos::monotonic_clock;
68namespace chrono = std::chrono;
69
Austin Schuh58646e22021-08-23 23:51:46 -070070// Prints out raw log parts to stdout.
71int PrintRaw(int argc, char **argv) {
Austin Schuhdb605092022-09-26 15:16:53 -070072 if (argc == 1) {
73 CHECK(!FLAGS_raw_header.empty());
74 aos::logger::MessageReader raw_header_reader(FLAGS_raw_header);
75 std::cout << aos::FlatbufferToJson(raw_header_reader.raw_log_file_header(),
76 {.multi_line = FLAGS_pretty,
77 .max_vector_size = static_cast<size_t>(
78 FLAGS_max_vector_size)})
79 << std::endl;
80 return 0;
81 }
82 if (argc != 2 && argc != 1) {
Austin Schuh58646e22021-08-23 23:51:46 -070083 LOG(FATAL) << "Expected 1 logfile as an argument.";
84 }
85 aos::logger::SpanReader reader(argv[1]);
86 absl::Span<const uint8_t> raw_log_file_header_span = reader.ReadMessage();
87
James Kuszmaul9776b392023-01-14 14:08:08 -080088 if (raw_log_file_header_span.empty()) {
Austin Schuh58646e22021-08-23 23:51:46 -070089 LOG(WARNING) << "Empty log file on " << reader.filename();
90 return 0;
91 }
92
93 // Now, reproduce the log file header deduplication logic inline so we can
94 // print out all the headers we find.
95 aos::SizePrefixedFlatbufferVector<aos::logger::LogFileHeader> log_file_header(
96 raw_log_file_header_span);
97 if (!log_file_header.Verify()) {
98 LOG(ERROR) << "Header corrupted on " << reader.filename();
99 return 1;
100 }
101 while (true) {
102 absl::Span<const uint8_t> maybe_header_data = reader.PeekMessage();
James Kuszmaul9776b392023-01-14 14:08:08 -0800103 if (maybe_header_data.empty()) {
Austin Schuh58646e22021-08-23 23:51:46 -0700104 break;
105 }
106
107 aos::SizePrefixedFlatbufferSpan<aos::logger::LogFileHeader> maybe_header(
108 maybe_header_data);
109 if (maybe_header.Verify()) {
110 std::cout << aos::FlatbufferToJson(
111 log_file_header, {.multi_line = FLAGS_pretty,
112 .max_vector_size = static_cast<size_t>(
113 FLAGS_max_vector_size)})
114 << std::endl;
115 LOG(WARNING) << "Found duplicate LogFileHeader in " << reader.filename();
116 log_file_header =
117 aos::SizePrefixedFlatbufferVector<aos::logger::LogFileHeader>(
118 maybe_header_data);
119
120 reader.ConsumeMessage();
121 } else {
122 break;
123 }
124 }
125
126 // And now use the final sha256 to match the raw_header.
127 std::optional<aos::logger::MessageReader> raw_header_reader;
128 const aos::logger::LogFileHeader *full_header = &log_file_header.message();
129 if (!FLAGS_raw_header.empty()) {
130 raw_header_reader.emplace(FLAGS_raw_header);
131 std::cout << aos::FlatbufferToJson(full_header,
132 {.multi_line = FLAGS_pretty,
133 .max_vector_size = static_cast<size_t>(
134 FLAGS_max_vector_size)})
135 << std::endl;
Austin Schuhb0e439d2023-05-15 10:55:40 -0700136 CHECK_EQ(full_header->configuration_sha256()->string_view(),
137 aos::Sha256(raw_header_reader->raw_log_file_header().span()));
Austin Schuh58646e22021-08-23 23:51:46 -0700138 full_header = raw_header_reader->log_file_header();
139 }
140
141 if (!FLAGS_print) {
142 return 0;
143 }
144
145 std::cout << aos::FlatbufferToJson(full_header,
146 {.multi_line = FLAGS_pretty,
147 .max_vector_size = static_cast<size_t>(
148 FLAGS_max_vector_size)})
149 << std::endl;
150 CHECK(full_header->has_configuration())
151 << ": Missing configuration! You may want to provide the path to the "
152 "logged configuration file using the --raw_header flag.";
153
154 while (true) {
155 const aos::SizePrefixedFlatbufferSpan<aos::logger::MessageHeader> message(
156 reader.ReadMessage());
James Kuszmaul9776b392023-01-14 14:08:08 -0800157 if (message.span().empty()) {
Austin Schuh58646e22021-08-23 23:51:46 -0700158 break;
159 }
160 CHECK(message.Verify());
161
162 const auto *const channels = full_header->configuration()->channels();
163 const size_t channel_index = message.message().channel_index();
164 CHECK_LT(channel_index, channels->size());
165 const aos::Channel *const channel = channels->Get(channel_index);
166
167 CHECK(message.Verify()) << absl::BytesToHexString(
168 std::string_view(reinterpret_cast<const char *>(message.span().data()),
169 message.span().size()));
170
171 if (message.message().data() != nullptr) {
172 CHECK(channel->has_schema());
173
174 CHECK(flatbuffers::Verify(
175 *channel->schema(), *channel->schema()->root_table(),
176 message.message().data()->data(), message.message().data()->size()))
177 << ": Corrupted flatbuffer on " << channel->name()->c_str() << " "
178 << channel->type()->c_str();
179 }
180
181 if (FLAGS_format_raw && message.message().data() != nullptr) {
182 std::cout << aos::configuration::StrippedChannelToString(channel) << " "
183 << aos::FlatbufferToJson(message, {.multi_line = FLAGS_pretty,
184 .max_vector_size = 4})
185 << ": "
186 << aos::FlatbufferToJson(
187 channel->schema(), message.message().data()->data(),
188 {FLAGS_pretty,
189 static_cast<size_t>(FLAGS_max_vector_size)})
190 << std::endl;
191 } else {
192 std::cout << aos::configuration::StrippedChannelToString(channel) << " "
193 << aos::FlatbufferToJson(
194 message, {FLAGS_pretty,
195 static_cast<size_t>(FLAGS_max_vector_size)})
196 << std::endl;
197 }
198 }
199 return 0;
200}
201
202// This class prints out all data from a node on a boot.
203class NodePrinter {
204 public:
Naman Gupta54047212022-09-23 14:10:30 -0700205 NodePrinter(aos::EventLoop *event_loop,
206 aos::SimulatedEventLoopFactory *factory, aos::Printer *printer)
Austin Schuh58646e22021-08-23 23:51:46 -0700207 : factory_(factory),
Austin Schuhff3bc902022-05-11 16:10:57 -0700208 node_factory_(factory->GetNodeEventLoopFactory(event_loop->node())),
Austin Schuh58646e22021-08-23 23:51:46 -0700209 event_loop_(event_loop),
Austin Schuh58646e22021-08-23 23:51:46 -0700210 node_name_(
211 event_loop_->node() == nullptr
212 ? ""
Austin Schuh041fe9f2021-10-16 23:01:15 -0700213 : std::string(event_loop->node()->name()->string_view())),
Naman Gupta54047212022-09-23 14:10:30 -0700214 printer_(printer) {
Austin Schuh58646e22021-08-23 23:51:46 -0700215 event_loop_->SkipTimingReport();
216 event_loop_->SkipAosLog();
217
218 const flatbuffers::Vector<flatbuffers::Offset<aos::Channel>> *channels =
219 event_loop_->configuration()->channels();
220
Milind Upadhyay184dfda2022-03-26 15:54:38 -0700221 const monotonic_clock::time_point start_time =
222 (FLAGS_monotonic_start_time == 0.0
223 ? monotonic_clock::min_time
224 : monotonic_clock::time_point(
225 std::chrono::duration_cast<monotonic_clock::duration>(
226 std::chrono::duration<double>(
227 FLAGS_monotonic_start_time))));
228 const monotonic_clock::time_point end_time =
229 (FLAGS_monotonic_end_time == 0.0
230 ? monotonic_clock::max_time
231 : monotonic_clock::time_point(
232 std::chrono::duration_cast<monotonic_clock::duration>(
233 std::chrono::duration<double>(
234 FLAGS_monotonic_end_time))));
235
Austin Schuh58646e22021-08-23 23:51:46 -0700236 for (flatbuffers::uoffset_t i = 0; i < channels->size(); i++) {
237 const aos::Channel *channel = channels->Get(i);
238 const flatbuffers::string_view name = channel->name()->string_view();
239 const flatbuffers::string_view type = channel->type()->string_view();
240 if (name.find(FLAGS_name) != std::string::npos &&
241 type.find(FLAGS_type) != std::string::npos) {
242 if (!aos::configuration::ChannelIsReadableOnNode(channel,
243 event_loop_->node())) {
244 continue;
245 }
246 VLOG(1) << "Listening on " << name << " " << type;
247
248 CHECK_NOTNULL(channel->schema());
Austin Schuh60e77942022-05-16 17:48:24 -0700249 event_loop_->MakeRawWatcher(channel, [this, channel, start_time,
250 end_time](
251 const aos::Context &context,
252 const void * /*message*/) {
253 if (!FLAGS_print) {
254 return;
255 }
Naman Gupta54047212022-09-23 14:10:30 -0700256 if (FLAGS_count > 0 && printer_->message_count() >= FLAGS_count) {
257 return;
258 }
Austin Schuh58646e22021-08-23 23:51:46 -0700259
Austin Schuh60e77942022-05-16 17:48:24 -0700260 if (!FLAGS_fetch && !started_) {
261 return;
262 }
Austin Schuh58646e22021-08-23 23:51:46 -0700263
Austin Schuh60e77942022-05-16 17:48:24 -0700264 if (context.monotonic_event_time < start_time ||
265 context.monotonic_event_time > end_time) {
266 return;
267 }
Milind Upadhyay184dfda2022-03-26 15:54:38 -0700268
Naman Gupta54047212022-09-23 14:10:30 -0700269 printer_->PrintMessage(node_name_, node_factory_, channel, context);
270 if (FLAGS_count > 0 && printer_->message_count() >= FLAGS_count) {
Austin Schuh60e77942022-05-16 17:48:24 -0700271 factory_->Exit();
272 }
273 });
Austin Schuh58646e22021-08-23 23:51:46 -0700274 }
275 }
276 }
277
278 void SetStarted(bool started, aos::monotonic_clock::time_point monotonic_now,
279 aos::realtime_clock::time_point realtime_now) {
280 started_ = started;
Austin Schuh041fe9f2021-10-16 23:01:15 -0700281 if (FLAGS_json) {
282 return;
283 }
Austin Schuh58646e22021-08-23 23:51:46 -0700284 if (started_) {
285 std::cout << std::endl;
286 std::cout << (event_loop_->node() != nullptr
287 ? (event_loop_->node()->name()->str() + " ")
288 : "")
289 << "Log starting at " << realtime_now << " (" << monotonic_now
290 << ")";
291 std::cout << std::endl << std::endl;
292 } else {
293 std::cout << std::endl;
294 std::cout << (event_loop_->node() != nullptr
295 ? (event_loop_->node()->name()->str() + " ")
296 : "")
297 << "Log shutting down at " << realtime_now << " ("
298 << monotonic_now << ")";
299 std::cout << std::endl << std::endl;
300 }
301 }
302
303 private:
304 struct MessageInfo {
305 std::string node_name;
306 std::unique_ptr<aos::RawFetcher> fetcher;
307 };
308
309 aos::SimulatedEventLoopFactory *factory_;
Austin Schuhff3bc902022-05-11 16:10:57 -0700310 aos::NodeEventLoopFactory *node_factory_;
Austin Schuh58646e22021-08-23 23:51:46 -0700311 aos::EventLoop *event_loop_;
312
Austin Schuh58646e22021-08-23 23:51:46 -0700313 std::string node_name_;
314
315 bool started_ = false;
316
Naman Gupta54047212022-09-23 14:10:30 -0700317 aos::Printer *printer_ = nullptr;
Austin Schuh58646e22021-08-23 23:51:46 -0700318};
319
James Kuszmaul38735e82019-12-07 16:42:06 -0800320int main(int argc, char **argv) {
321 gflags::SetUsageMessage(
Austin Schuh6f3babe2020-01-26 20:34:50 -0800322 "Usage:\n"
323 " log_cat [args] logfile1 logfile2 ...\n"
324 "\n"
James Kuszmaul38735e82019-12-07 16:42:06 -0800325 "This program provides a basic interface to dump data from a logfile to "
326 "stdout. Given a logfile, channel name filter, and type filter, it will "
327 "print all the messages in the logfile matching the filters. The message "
328 "filters work by taking the values of --name and --type and printing any "
329 "channel whose name contains --name as a substr and whose type contains "
330 "--type as a substr. Not specifying --name or --type leaves them free. "
331 "Calling this program without --name or --type specified prints out all "
332 "the logged data.");
333 aos::InitGoogle(&argc, &argv);
334
Austin Schuh6f3babe2020-01-26 20:34:50 -0800335 if (FLAGS_raw) {
Austin Schuh58646e22021-08-23 23:51:46 -0700336 return PrintRaw(argc, argv);
James Kuszmaul38735e82019-12-07 16:42:06 -0800337 }
338
Austin Schuh6f3babe2020-01-26 20:34:50 -0800339 if (argc < 2) {
340 LOG(FATAL) << "Expected at least 1 logfile as an argument.";
341 }
342
Austin Schuh11d43732020-09-21 17:28:30 -0700343 const std::vector<aos::logger::LogFile> logfiles =
Austin Schuh58646e22021-08-23 23:51:46 -0700344 aos::logger::SortParts(aos::logger::FindLogs(argc, argv));
Austin Schuh5212cad2020-09-09 23:12:09 -0700345
Austin Schuhfe3fb342021-01-16 18:50:37 -0800346 for (auto &it : logfiles) {
347 VLOG(1) << it;
Austin Schuh7af06d52021-06-28 15:46:59 -0700348 if (FLAGS_print_parts_only) {
349 std::cout << it << std::endl;
350 }
351 }
352 if (FLAGS_print_parts_only) {
353 return 0;
Austin Schuhfe3fb342021-01-16 18:50:37 -0800354 }
355
Austin Schuh6f3babe2020-01-26 20:34:50 -0800356 aos::logger::LogReader reader(logfiles);
Austin Schuha81454b2020-05-12 19:58:36 -0700357
Austin Schuh25b17652021-07-21 15:42:56 -0700358 if (FLAGS_channels) {
359 const aos::Configuration *config = reader.configuration();
360 for (const aos::Channel *channel : *config->channels()) {
361 std::cout << channel->name()->c_str() << " " << channel->type()->c_str()
362 << '\n';
363 }
364 return 0;
365 }
366
Austin Schuh58646e22021-08-23 23:51:46 -0700367 {
368 bool found_channel = false;
Austin Schuh6f3babe2020-01-26 20:34:50 -0800369 const flatbuffers::Vector<flatbuffers::Offset<aos::Channel>> *channels =
Austin Schuh58646e22021-08-23 23:51:46 -0700370 reader.configuration()->channels();
Brian Silverman9891b292020-06-23 16:34:22 -0700371
Austin Schuh6f3babe2020-01-26 20:34:50 -0800372 for (flatbuffers::uoffset_t i = 0; i < channels->size(); i++) {
373 const aos::Channel *channel = channels->Get(i);
374 const flatbuffers::string_view name = channel->name()->string_view();
375 const flatbuffers::string_view type = channel->type()->string_view();
376 if (name.find(FLAGS_name) != std::string::npos &&
377 type.find(FLAGS_type) != std::string::npos) {
Austin Schuh6f3babe2020-01-26 20:34:50 -0800378 found_channel = true;
379 }
380 }
Austin Schuh58646e22021-08-23 23:51:46 -0700381 if (!found_channel) {
382 LOG(FATAL) << "Could not find any channels";
Austin Schuha81454b2020-05-12 19:58:36 -0700383 }
James Kuszmaul38735e82019-12-07 16:42:06 -0800384 }
385
Naman Gupta54047212022-09-23 14:10:30 -0700386 aos::Printer printer(
387 {
388 .pretty = FLAGS_pretty,
389 .max_vector_size = static_cast<size_t>(FLAGS_max_vector_size),
390 .pretty_max = FLAGS_pretty_max,
391 .print_timestamps = FLAGS_print_timestamps,
392 .json = FLAGS_json,
393 .distributed_clock = FLAGS_distributed_clock,
Austin Schuhbe6d2962022-11-01 09:24:56 -0700394 .hex = FLAGS_hex,
Naman Gupta54047212022-09-23 14:10:30 -0700395 },
396 false);
Austin Schuh58646e22021-08-23 23:51:46 -0700397
398 std::vector<NodePrinter *> printers;
Sanjay Narayananbeb328c2021-09-01 16:24:20 -0700399 printers.resize(aos::configuration::NodesCount(reader.configuration()),
400 nullptr);
401
402 aos::SimulatedEventLoopFactory event_loop_factory(reader.configuration());
403
404 reader.RegisterWithoutStarting(&event_loop_factory);
Austin Schuh58646e22021-08-23 23:51:46 -0700405
406 for (const aos::Node *node :
407 aos::configuration::GetNodes(event_loop_factory.configuration())) {
408 size_t node_index = aos::configuration::GetNodeIndex(
409 event_loop_factory.configuration(), node);
410 // Spin up the printer, and hook up the SetStarted method so that it gets
411 // notified when the log starts and stops.
412 aos::NodeEventLoopFactory *node_factory =
413 event_loop_factory.GetNodeEventLoopFactory(node);
Naman Gupta54047212022-09-23 14:10:30 -0700414 node_factory->OnStartup(
415 [&event_loop_factory, node_factory, &printer, &printers, node_index]() {
416 printers[node_index] = node_factory->AlwaysStart<NodePrinter>(
417 "printer", &event_loop_factory, &printer);
418 });
Austin Schuh58646e22021-08-23 23:51:46 -0700419 node_factory->OnShutdown(
420 [&printers, node_index]() { printers[node_index] = nullptr; });
421
422 reader.OnStart(node, [&printers, node_index, node_factory]() {
423 CHECK(printers[node_index]);
424 printers[node_index]->SetStarted(true, node_factory->monotonic_now(),
425 node_factory->realtime_now());
426 });
427 reader.OnEnd(node, [&printers, node_index, node_factory]() {
428 CHECK(printers[node_index]);
429 printers[node_index]->SetStarted(false, node_factory->monotonic_now(),
430 node_factory->realtime_now());
431 });
Austin Schuha81454b2020-05-12 19:58:36 -0700432 }
433
434 event_loop_factory.Run();
James Kuszmaul38735e82019-12-07 16:42:06 -0800435
Austin Schuh51a92592020-08-09 13:17:00 -0700436 reader.Deregister();
437
James Kuszmaul38735e82019-12-07 16:42:06 -0800438 return 0;
439}