blob: 60ad0e23d9045602ccf816f798b8489593ae033c [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 Schuh041fe9f2021-10-16 23:01:15 -070023DEFINE_bool(json, false, "If true, print fully valid JSON");
Austin Schuha81454b2020-05-12 19:58:36 -070024DEFINE_bool(fetch, false,
25 "If true, also print out the messages from before the start of the "
26 "log file");
Austin Schuh6f3babe2020-01-26 20:34:50 -080027DEFINE_bool(raw, false,
28 "If true, just print the data out unsorted and unparsed");
Brian Silverman8ff74aa2021-02-05 16:37:15 -080029DEFINE_string(raw_header, "",
30 "If set, the file to read the header from in raw mode");
Austin Schuha81454b2020-05-12 19:58:36 -070031DEFINE_bool(format_raw, true,
32 "If true and --raw is specified, print out raw data, but use the "
33 "schema to format the data.");
Austin Schuhae46f362020-04-11 19:52:56 -070034DEFINE_int32(max_vector_size, 100,
35 "If positive, vectors longer than this will not be printed");
Ravago Jones5cc9df52020-09-02 21:29:58 -070036DEFINE_bool(pretty, false,
37 "If true, pretty print the messages on multiple lines");
Austin Schuh569c7f92020-12-11 20:01:42 -080038DEFINE_bool(print, true,
39 "If true, actually print the messages. If false, discard them, "
40 "confirming they can be parsed.");
Tyler Chatowee0afa82021-08-01 22:00:36 -070041DEFINE_uint64(
42 count, 0,
43 "If >0, log_cat will exit after printing this many messages. This "
44 "includes messages from before the start of the log if --fetch is set.");
Austin Schuh7af06d52021-06-28 15:46:59 -070045DEFINE_bool(print_parts_only, false,
46 "If true, only print out the results of logfile sorting.");
Austin Schuh25b17652021-07-21 15:42:56 -070047DEFINE_bool(channels, false,
48 "If true, print out all the configured channels for this log.");
Austin Schuh6f3babe2020-01-26 20:34:50 -080049
Austin Schuh041fe9f2021-10-16 23:01:15 -070050using aos::monotonic_clock;
51namespace chrono = std::chrono;
52
53void StreamSeconds(std::ostream &stream,
54 const aos::monotonic_clock::time_point now) {
55 if (now < monotonic_clock::epoch()) {
56 chrono::seconds seconds =
57 chrono::duration_cast<chrono::seconds>(now.time_since_epoch());
58
59 stream << "-" << -seconds.count() << "." << std::setfill('0')
60 << std::setw(9)
61 << chrono::duration_cast<chrono::nanoseconds>(seconds -
62 now.time_since_epoch())
63 .count();
64 } else {
65 chrono::seconds seconds =
66 chrono::duration_cast<chrono::seconds>(now.time_since_epoch());
67 stream << seconds.count() << "." << std::setfill('0') << std::setw(9)
68 << chrono::duration_cast<chrono::nanoseconds>(
69 now.time_since_epoch() - seconds)
70 .count();
71 }
72}
73
Brian Silverman9891b292020-06-23 16:34:22 -070074// Print the flatbuffer out to stdout, both to remove the unnecessary cruft from
75// glog and to allow the user to readily redirect just the logged output
76// independent of any debugging information on stderr.
77void PrintMessage(const std::string_view node_name, const aos::Channel *channel,
Austin Schuh746690f2020-08-01 16:15:57 -070078 const aos::Context &context,
79 aos::FastStringBuilder *builder) {
80 builder->Reset();
Austin Schuha9df9ad2021-06-16 14:49:39 -070081 CHECK(flatbuffers::Verify(*channel->schema(),
82 *channel->schema()->root_table(),
83 static_cast<const uint8_t *>(context.data),
84 static_cast<size_t>(context.size)))
85 << ": Corrupted flatbuffer on " << channel->name()->c_str() << " "
86 << channel->type()->c_str();
87
Ravago Jones5cc9df52020-09-02 21:29:58 -070088 aos::FlatbufferToJson(
89 builder, channel->schema(), static_cast<const uint8_t *>(context.data),
90 {FLAGS_pretty, static_cast<size_t>(FLAGS_max_vector_size)});
Austin Schuh746690f2020-08-01 16:15:57 -070091
Austin Schuh041fe9f2021-10-16 23:01:15 -070092 if (FLAGS_json) {
93 std::cout << "{";
94 if (!node_name.empty()) {
95 std::cout << "\"node\": \"" << node_name << "\", ";
96 }
97 std::cout << "\"monotonic_event_time\": ";
98 StreamSeconds(std::cout, context.monotonic_event_time);
99 std::cout << ", \"realtime_event_time\": \"" << context.realtime_event_time
100 << "\", ";
101
102 if (context.monotonic_remote_time != context.monotonic_event_time) {
103 std::cout << "\"monotonic_remote_time\": ";
104 StreamSeconds(std::cout, context.monotonic_remote_time);
105 std::cout << ", \"realtime_remote_time\": \""
106 << context.realtime_remote_time << "\", ";
107 }
108
109 std::cout << "\"channel\": "
110 << aos::configuration::StrippedChannelToString(channel)
111 << ", \"data\": " << *builder << "}" << std::endl;
Austin Schuha81454b2020-05-12 19:58:36 -0700112 } else {
Austin Schuh041fe9f2021-10-16 23:01:15 -0700113 if (!node_name.empty()) {
114 std::cout << node_name << " ";
115 }
116 if (context.monotonic_remote_time != context.monotonic_event_time) {
117 std::cout << context.realtime_event_time << " ("
118 << context.monotonic_event_time << ") sent "
119 << context.realtime_remote_time << " ("
120 << context.monotonic_remote_time << ") "
121 << channel->name()->c_str() << ' ' << channel->type()->c_str()
122 << ": " << *builder << std::endl;
123 } else {
124 std::cout << context.realtime_event_time << " ("
125 << context.monotonic_event_time << ") "
126 << channel->name()->c_str() << ' ' << channel->type()->c_str()
127 << ": " << *builder << std::endl;
128 }
Austin Schuha81454b2020-05-12 19:58:36 -0700129 }
130}
131
Austin Schuh58646e22021-08-23 23:51:46 -0700132// Prints out raw log parts to stdout.
133int PrintRaw(int argc, char **argv) {
134 if (argc != 2) {
135 LOG(FATAL) << "Expected 1 logfile as an argument.";
136 }
137 aos::logger::SpanReader reader(argv[1]);
138 absl::Span<const uint8_t> raw_log_file_header_span = reader.ReadMessage();
139
140 if (raw_log_file_header_span == absl::Span<const uint8_t>()) {
141 LOG(WARNING) << "Empty log file on " << reader.filename();
142 return 0;
143 }
144
145 // Now, reproduce the log file header deduplication logic inline so we can
146 // print out all the headers we find.
147 aos::SizePrefixedFlatbufferVector<aos::logger::LogFileHeader> log_file_header(
148 raw_log_file_header_span);
149 if (!log_file_header.Verify()) {
150 LOG(ERROR) << "Header corrupted on " << reader.filename();
151 return 1;
152 }
153 while (true) {
154 absl::Span<const uint8_t> maybe_header_data = reader.PeekMessage();
155 if (maybe_header_data == absl::Span<const uint8_t>()) {
156 break;
157 }
158
159 aos::SizePrefixedFlatbufferSpan<aos::logger::LogFileHeader> maybe_header(
160 maybe_header_data);
161 if (maybe_header.Verify()) {
162 std::cout << aos::FlatbufferToJson(
163 log_file_header, {.multi_line = FLAGS_pretty,
164 .max_vector_size = static_cast<size_t>(
165 FLAGS_max_vector_size)})
166 << std::endl;
167 LOG(WARNING) << "Found duplicate LogFileHeader in " << reader.filename();
168 log_file_header =
169 aos::SizePrefixedFlatbufferVector<aos::logger::LogFileHeader>(
170 maybe_header_data);
171
172 reader.ConsumeMessage();
173 } else {
174 break;
175 }
176 }
177
178 // And now use the final sha256 to match the raw_header.
179 std::optional<aos::logger::MessageReader> raw_header_reader;
180 const aos::logger::LogFileHeader *full_header = &log_file_header.message();
181 if (!FLAGS_raw_header.empty()) {
182 raw_header_reader.emplace(FLAGS_raw_header);
183 std::cout << aos::FlatbufferToJson(full_header,
184 {.multi_line = FLAGS_pretty,
185 .max_vector_size = static_cast<size_t>(
186 FLAGS_max_vector_size)})
187 << std::endl;
188 CHECK_EQ(
189 full_header->configuration_sha256()->string_view(),
190 aos::logger::Sha256(raw_header_reader->raw_log_file_header().span()));
191 full_header = raw_header_reader->log_file_header();
192 }
193
194 if (!FLAGS_print) {
195 return 0;
196 }
197
198 std::cout << aos::FlatbufferToJson(full_header,
199 {.multi_line = FLAGS_pretty,
200 .max_vector_size = static_cast<size_t>(
201 FLAGS_max_vector_size)})
202 << std::endl;
203 CHECK(full_header->has_configuration())
204 << ": Missing configuration! You may want to provide the path to the "
205 "logged configuration file using the --raw_header flag.";
206
207 while (true) {
208 const aos::SizePrefixedFlatbufferSpan<aos::logger::MessageHeader> message(
209 reader.ReadMessage());
210 if (message.span() == absl::Span<const uint8_t>()) {
211 break;
212 }
213 CHECK(message.Verify());
214
215 const auto *const channels = full_header->configuration()->channels();
216 const size_t channel_index = message.message().channel_index();
217 CHECK_LT(channel_index, channels->size());
218 const aos::Channel *const channel = channels->Get(channel_index);
219
220 CHECK(message.Verify()) << absl::BytesToHexString(
221 std::string_view(reinterpret_cast<const char *>(message.span().data()),
222 message.span().size()));
223
224 if (message.message().data() != nullptr) {
225 CHECK(channel->has_schema());
226
227 CHECK(flatbuffers::Verify(
228 *channel->schema(), *channel->schema()->root_table(),
229 message.message().data()->data(), message.message().data()->size()))
230 << ": Corrupted flatbuffer on " << channel->name()->c_str() << " "
231 << channel->type()->c_str();
232 }
233
234 if (FLAGS_format_raw && message.message().data() != nullptr) {
235 std::cout << aos::configuration::StrippedChannelToString(channel) << " "
236 << aos::FlatbufferToJson(message, {.multi_line = FLAGS_pretty,
237 .max_vector_size = 4})
238 << ": "
239 << aos::FlatbufferToJson(
240 channel->schema(), message.message().data()->data(),
241 {FLAGS_pretty,
242 static_cast<size_t>(FLAGS_max_vector_size)})
243 << std::endl;
244 } else {
245 std::cout << aos::configuration::StrippedChannelToString(channel) << " "
246 << aos::FlatbufferToJson(
247 message, {FLAGS_pretty,
248 static_cast<size_t>(FLAGS_max_vector_size)})
249 << std::endl;
250 }
251 }
252 return 0;
253}
254
255// This class prints out all data from a node on a boot.
256class NodePrinter {
257 public:
258 NodePrinter(aos::EventLoop *event_loop, uint64_t *message_print_counter,
259 aos::SimulatedEventLoopFactory *factory,
260 aos::FastStringBuilder *builder)
261 : factory_(factory),
262 event_loop_(event_loop),
263 message_print_counter_(message_print_counter),
264 node_name_(
265 event_loop_->node() == nullptr
266 ? ""
Austin Schuh041fe9f2021-10-16 23:01:15 -0700267 : std::string(event_loop->node()->name()->string_view())),
Austin Schuh58646e22021-08-23 23:51:46 -0700268 builder_(builder) {
269 event_loop_->SkipTimingReport();
270 event_loop_->SkipAosLog();
271
272 const flatbuffers::Vector<flatbuffers::Offset<aos::Channel>> *channels =
273 event_loop_->configuration()->channels();
274
275 for (flatbuffers::uoffset_t i = 0; i < channels->size(); i++) {
276 const aos::Channel *channel = channels->Get(i);
277 const flatbuffers::string_view name = channel->name()->string_view();
278 const flatbuffers::string_view type = channel->type()->string_view();
279 if (name.find(FLAGS_name) != std::string::npos &&
280 type.find(FLAGS_type) != std::string::npos) {
281 if (!aos::configuration::ChannelIsReadableOnNode(channel,
282 event_loop_->node())) {
283 continue;
284 }
285 VLOG(1) << "Listening on " << name << " " << type;
286
287 CHECK_NOTNULL(channel->schema());
288 event_loop_->MakeRawWatcher(
289 channel, [this, channel](const aos::Context &context,
290 const void * /*message*/) {
291 if (!FLAGS_print) {
292 return;
293 }
294
295 if (!FLAGS_fetch && !started_) {
296 return;
297 }
298
299 PrintMessage(node_name_, channel, context, builder_);
300 ++(*message_print_counter_);
301 if (FLAGS_count > 0 && *message_print_counter_ >= FLAGS_count) {
302 factory_->Exit();
303 }
304 });
305 }
306 }
307 }
308
309 void SetStarted(bool started, aos::monotonic_clock::time_point monotonic_now,
310 aos::realtime_clock::time_point realtime_now) {
311 started_ = started;
Austin Schuh041fe9f2021-10-16 23:01:15 -0700312 if (FLAGS_json) {
313 return;
314 }
Austin Schuh58646e22021-08-23 23:51:46 -0700315 if (started_) {
316 std::cout << std::endl;
317 std::cout << (event_loop_->node() != nullptr
318 ? (event_loop_->node()->name()->str() + " ")
319 : "")
320 << "Log starting at " << realtime_now << " (" << monotonic_now
321 << ")";
322 std::cout << std::endl << std::endl;
323 } else {
324 std::cout << std::endl;
325 std::cout << (event_loop_->node() != nullptr
326 ? (event_loop_->node()->name()->str() + " ")
327 : "")
328 << "Log shutting down at " << realtime_now << " ("
329 << monotonic_now << ")";
330 std::cout << std::endl << std::endl;
331 }
332 }
333
334 private:
335 struct MessageInfo {
336 std::string node_name;
337 std::unique_ptr<aos::RawFetcher> fetcher;
338 };
339
340 aos::SimulatedEventLoopFactory *factory_;
341 aos::EventLoop *event_loop_;
342
343 uint64_t *message_print_counter_ = nullptr;
344
345 std::string node_name_;
346
347 bool started_ = false;
348
349 aos::FastStringBuilder *builder_;
350};
351
James Kuszmaul38735e82019-12-07 16:42:06 -0800352int main(int argc, char **argv) {
353 gflags::SetUsageMessage(
Austin Schuh6f3babe2020-01-26 20:34:50 -0800354 "Usage:\n"
355 " log_cat [args] logfile1 logfile2 ...\n"
356 "\n"
James Kuszmaul38735e82019-12-07 16:42:06 -0800357 "This program provides a basic interface to dump data from a logfile to "
358 "stdout. Given a logfile, channel name filter, and type filter, it will "
359 "print all the messages in the logfile matching the filters. The message "
360 "filters work by taking the values of --name and --type and printing any "
361 "channel whose name contains --name as a substr and whose type contains "
362 "--type as a substr. Not specifying --name or --type leaves them free. "
363 "Calling this program without --name or --type specified prints out all "
364 "the logged data.");
365 aos::InitGoogle(&argc, &argv);
366
Austin Schuh6f3babe2020-01-26 20:34:50 -0800367 if (FLAGS_raw) {
Austin Schuh58646e22021-08-23 23:51:46 -0700368 return PrintRaw(argc, argv);
James Kuszmaul38735e82019-12-07 16:42:06 -0800369 }
370
Austin Schuh6f3babe2020-01-26 20:34:50 -0800371 if (argc < 2) {
372 LOG(FATAL) << "Expected at least 1 logfile as an argument.";
373 }
374
Austin Schuh11d43732020-09-21 17:28:30 -0700375 const std::vector<aos::logger::LogFile> logfiles =
Austin Schuh58646e22021-08-23 23:51:46 -0700376 aos::logger::SortParts(aos::logger::FindLogs(argc, argv));
Austin Schuh5212cad2020-09-09 23:12:09 -0700377
Austin Schuhfe3fb342021-01-16 18:50:37 -0800378 for (auto &it : logfiles) {
379 VLOG(1) << it;
Austin Schuh7af06d52021-06-28 15:46:59 -0700380 if (FLAGS_print_parts_only) {
381 std::cout << it << std::endl;
382 }
383 }
384 if (FLAGS_print_parts_only) {
385 return 0;
Austin Schuhfe3fb342021-01-16 18:50:37 -0800386 }
387
Austin Schuh6f3babe2020-01-26 20:34:50 -0800388 aos::logger::LogReader reader(logfiles);
Austin Schuha81454b2020-05-12 19:58:36 -0700389
Austin Schuh25b17652021-07-21 15:42:56 -0700390 if (FLAGS_channels) {
391 const aos::Configuration *config = reader.configuration();
392 for (const aos::Channel *channel : *config->channels()) {
393 std::cout << channel->name()->c_str() << " " << channel->type()->c_str()
394 << '\n';
395 }
396 return 0;
397 }
398
Austin Schuh58646e22021-08-23 23:51:46 -0700399 {
400 bool found_channel = false;
Austin Schuh6f3babe2020-01-26 20:34:50 -0800401 const flatbuffers::Vector<flatbuffers::Offset<aos::Channel>> *channels =
Austin Schuh58646e22021-08-23 23:51:46 -0700402 reader.configuration()->channels();
Brian Silverman9891b292020-06-23 16:34:22 -0700403
Austin Schuh6f3babe2020-01-26 20:34:50 -0800404 for (flatbuffers::uoffset_t i = 0; i < channels->size(); i++) {
405 const aos::Channel *channel = channels->Get(i);
406 const flatbuffers::string_view name = channel->name()->string_view();
407 const flatbuffers::string_view type = channel->type()->string_view();
408 if (name.find(FLAGS_name) != std::string::npos &&
409 type.find(FLAGS_type) != std::string::npos) {
Austin Schuh6f3babe2020-01-26 20:34:50 -0800410 found_channel = true;
411 }
412 }
Austin Schuh58646e22021-08-23 23:51:46 -0700413 if (!found_channel) {
414 LOG(FATAL) << "Could not find any channels";
Austin Schuha81454b2020-05-12 19:58:36 -0700415 }
James Kuszmaul38735e82019-12-07 16:42:06 -0800416 }
417
Austin Schuh58646e22021-08-23 23:51:46 -0700418 aos::FastStringBuilder builder;
James Kuszmaul912af072020-10-31 16:06:54 -0700419
Austin Schuh58646e22021-08-23 23:51:46 -0700420 uint64_t message_print_counter = 0;
421
422 std::vector<NodePrinter *> printers;
Sanjay Narayananbeb328c2021-09-01 16:24:20 -0700423 printers.resize(aos::configuration::NodesCount(reader.configuration()),
424 nullptr);
425
426 aos::SimulatedEventLoopFactory event_loop_factory(reader.configuration());
427
428 reader.RegisterWithoutStarting(&event_loop_factory);
Austin Schuh58646e22021-08-23 23:51:46 -0700429
430 for (const aos::Node *node :
431 aos::configuration::GetNodes(event_loop_factory.configuration())) {
432 size_t node_index = aos::configuration::GetNodeIndex(
433 event_loop_factory.configuration(), node);
434 // Spin up the printer, and hook up the SetStarted method so that it gets
435 // notified when the log starts and stops.
436 aos::NodeEventLoopFactory *node_factory =
437 event_loop_factory.GetNodeEventLoopFactory(node);
438 node_factory->OnStartup([&event_loop_factory, node_factory,
439 &message_print_counter, &builder, &printers,
440 node_index]() {
441 printers[node_index] = node_factory->AlwaysStart<NodePrinter>(
442 "printer", &message_print_counter, &event_loop_factory, &builder);
443 });
444 node_factory->OnShutdown(
445 [&printers, node_index]() { printers[node_index] = nullptr; });
446
447 reader.OnStart(node, [&printers, node_index, node_factory]() {
448 CHECK(printers[node_index]);
449 printers[node_index]->SetStarted(true, node_factory->monotonic_now(),
450 node_factory->realtime_now());
451 });
452 reader.OnEnd(node, [&printers, node_index, node_factory]() {
453 CHECK(printers[node_index]);
454 printers[node_index]->SetStarted(false, node_factory->monotonic_now(),
455 node_factory->realtime_now());
456 });
Austin Schuha81454b2020-05-12 19:58:36 -0700457 }
458
459 event_loop_factory.Run();
James Kuszmaul38735e82019-12-07 16:42:06 -0800460
Austin Schuh51a92592020-08-09 13:17:00 -0700461 reader.Deregister();
462
James Kuszmaul38735e82019-12-07 16:42:06 -0800463 return 0;
464}