blob: a26eb49a6b9419444238e76b40de1345c43a81f6 [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
James Kuszmaul38735e82019-12-07 16:42:06 -080082int main(int argc, char **argv) {
83 gflags::SetUsageMessage(
Austin Schuh6f3babe2020-01-26 20:34:50 -080084 "Usage:\n"
85 " log_cat [args] logfile1 logfile2 ...\n"
86 "\n"
James Kuszmaul38735e82019-12-07 16:42:06 -080087 "This program provides a basic interface to dump data from a logfile to "
88 "stdout. Given a logfile, channel name filter, and type filter, it will "
89 "print all the messages in the logfile matching the filters. The message "
90 "filters work by taking the values of --name and --type and printing any "
91 "channel whose name contains --name as a substr and whose type contains "
92 "--type as a substr. Not specifying --name or --type leaves them free. "
93 "Calling this program without --name or --type specified prints out all "
94 "the logged data.");
95 aos::InitGoogle(&argc, &argv);
96
Austin Schuh6f3babe2020-01-26 20:34:50 -080097 if (FLAGS_raw) {
98 if (argc != 2) {
99 LOG(FATAL) << "Expected 1 logfile as an argument.";
James Kuszmaul38735e82019-12-07 16:42:06 -0800100 }
Austin Schuh0e8db662021-07-06 10:43:47 -0700101 aos::logger::SpanReader reader(argv[1]);
102 absl::Span<const uint8_t> raw_log_file_header_span = reader.ReadMessage();
Brian Silverman8ff74aa2021-02-05 16:37:15 -0800103
Austin Schuh0e8db662021-07-06 10:43:47 -0700104 if (raw_log_file_header_span == absl::Span<const uint8_t>()) {
105 LOG(WARNING) << "Empty log file on " << reader.filename();
106 return 0;
107 }
108
109 // Now, reproduce the log file header deduplication logic inline so we can
110 // print out all the headers we find.
111 aos::SizePrefixedFlatbufferVector<aos::logger::LogFileHeader>
112 log_file_header(raw_log_file_header_span);
113 if (!log_file_header.Verify()) {
114 LOG(ERROR) << "Header corrupted on " << reader.filename();
115 return 1;
116 }
117 while (true) {
118 absl::Span<const uint8_t> maybe_header_data = reader.PeekMessage();
119 if (maybe_header_data == absl::Span<const uint8_t>()) {
120 break;
121 }
122
123 aos::SizePrefixedFlatbufferSpan<aos::logger::LogFileHeader> maybe_header(
124 maybe_header_data);
125 if (maybe_header.Verify()) {
126 std::cout << aos::FlatbufferToJson(
127 log_file_header,
128 {.multi_line = FLAGS_pretty,
129 .max_vector_size =
130 static_cast<size_t>(FLAGS_max_vector_size)})
131 << std::endl;
132 LOG(WARNING) << "Found duplicate LogFileHeader in "
133 << reader.filename();
134 log_file_header =
135 aos::SizePrefixedFlatbufferVector<aos::logger::LogFileHeader>(
136 maybe_header_data);
137
138 reader.ConsumeMessage();
139 } else {
140 break;
141 }
142 }
143
144 // And now use the final sha256 to match the raw_header.
Brian Silverman8ff74aa2021-02-05 16:37:15 -0800145 std::optional<aos::logger::MessageReader> raw_header_reader;
Austin Schuh0e8db662021-07-06 10:43:47 -0700146 const aos::logger::LogFileHeader *full_header = &log_file_header.message();
Brian Silverman8ff74aa2021-02-05 16:37:15 -0800147 if (!FLAGS_raw_header.empty()) {
148 raw_header_reader.emplace(FLAGS_raw_header);
Austin Schuh881530d2021-06-21 17:40:04 -0700149 std::cout << aos::FlatbufferToJson(
Austin Schuh0e8db662021-07-06 10:43:47 -0700150 full_header, {.multi_line = FLAGS_pretty,
151 .max_vector_size = static_cast<size_t>(
152 FLAGS_max_vector_size)})
Austin Schuh881530d2021-06-21 17:40:04 -0700153 << std::endl;
Brian Silverman8ff74aa2021-02-05 16:37:15 -0800154 CHECK_EQ(
Austin Schuh0e8db662021-07-06 10:43:47 -0700155 full_header->configuration_sha256()->string_view(),
Brian Silverman8ff74aa2021-02-05 16:37:15 -0800156 aos::logger::Sha256(raw_header_reader->raw_log_file_header().span()));
157 full_header = raw_header_reader->log_file_header();
158 }
159 std::cout << aos::FlatbufferToJson(full_header,
Austin Schuh2f8fd752020-09-01 22:38:28 -0700160 {.multi_line = FLAGS_pretty,
161 .max_vector_size = static_cast<size_t>(
162 FLAGS_max_vector_size)})
163 << std::endl;
Sanjay Narayanan1a798db2021-07-15 18:32:24 -0700164 CHECK(full_header->has_configuration())
165 << ": Missing configuration! You may want to provide the path to the "
166 "logged configuration file using the --raw_header flag.";
Austin Schuh6f3babe2020-01-26 20:34:50 -0800167
168 while (true) {
Austin Schuh0e8db662021-07-06 10:43:47 -0700169 const aos::SizePrefixedFlatbufferSpan<aos::logger::MessageHeader> message(
170 reader.ReadMessage());
171 if (message.span() == absl::Span<const uint8_t>()) {
Austin Schuh6f3babe2020-01-26 20:34:50 -0800172 break;
173 }
Brian Silverman8ff74aa2021-02-05 16:37:15 -0800174 const auto *const channels = full_header->configuration()->channels();
Austin Schuh0e8db662021-07-06 10:43:47 -0700175 const size_t channel_index = message.message().channel_index();
Brian Silverman8ff74aa2021-02-05 16:37:15 -0800176 CHECK_LT(channel_index, channels->size());
177 const aos::Channel *const channel = channels->Get(channel_index);
Austin Schuh6f3babe2020-01-26 20:34:50 -0800178
Austin Schuh0e8db662021-07-06 10:43:47 -0700179 CHECK(message.Verify()) << absl::BytesToHexString(std::string_view(
180 reinterpret_cast<const char *>(message.span().data()),
181 message.span().size()));
182
183 if (message.message().data() != nullptr) {
Austin Schuha9df9ad2021-06-16 14:49:39 -0700184 CHECK(channel->has_schema());
185
Austin Schuh0e8db662021-07-06 10:43:47 -0700186 CHECK(flatbuffers::Verify(
187 *channel->schema(), *channel->schema()->root_table(),
188 message.message().data()->data(), message.message().data()->size()))
Austin Schuha9df9ad2021-06-16 14:49:39 -0700189 << ": Corrupted flatbuffer on " << channel->name()->c_str() << " "
190 << channel->type()->c_str();
191 }
192
Austin Schuh0e8db662021-07-06 10:43:47 -0700193 if (FLAGS_format_raw && message.message().data() != nullptr) {
Austin Schuha81454b2020-05-12 19:58:36 -0700194 std::cout << aos::configuration::StrippedChannelToString(channel) << " "
Austin Schuh0e8db662021-07-06 10:43:47 -0700195 << aos::FlatbufferToJson(message, {.multi_line = FLAGS_pretty,
196 .max_vector_size = 4})
Ravago Jonescf453ab2020-05-06 21:14:53 -0700197 << ": "
Austin Schuha81454b2020-05-12 19:58:36 -0700198 << aos::FlatbufferToJson(
Austin Schuh0e8db662021-07-06 10:43:47 -0700199 channel->schema(), message.message().data()->data(),
Ravago Jones5cc9df52020-09-02 21:29:58 -0700200 {FLAGS_pretty,
201 static_cast<size_t>(FLAGS_max_vector_size)})
Austin Schuha81454b2020-05-12 19:58:36 -0700202 << std::endl;
203 } else {
204 std::cout << aos::configuration::StrippedChannelToString(channel) << " "
Ravago Jonescf453ab2020-05-06 21:14:53 -0700205 << aos::FlatbufferToJson(
Austin Schuh0e8db662021-07-06 10:43:47 -0700206 message, {FLAGS_pretty,
207 static_cast<size_t>(FLAGS_max_vector_size)})
Austin Schuha81454b2020-05-12 19:58:36 -0700208 << std::endl;
209 }
Austin Schuh6f3babe2020-01-26 20:34:50 -0800210 }
211 return 0;
James Kuszmaul38735e82019-12-07 16:42:06 -0800212 }
213
Austin Schuh6f3babe2020-01-26 20:34:50 -0800214 if (argc < 2) {
215 LOG(FATAL) << "Expected at least 1 logfile as an argument.";
216 }
217
Ravago Jones6b23e172020-12-05 17:39:01 -0800218 const std::vector<std::string> unsorted_logfiles =
219 aos::logger::FindLogs(argc, argv);
Austin Schuh6f3babe2020-01-26 20:34:50 -0800220
Austin Schuh11d43732020-09-21 17:28:30 -0700221 const std::vector<aos::logger::LogFile> logfiles =
Austin Schuh5212cad2020-09-09 23:12:09 -0700222 aos::logger::SortParts(unsorted_logfiles);
223
Austin Schuhfe3fb342021-01-16 18:50:37 -0800224 for (auto &it : logfiles) {
225 VLOG(1) << it;
Austin Schuh7af06d52021-06-28 15:46:59 -0700226 if (FLAGS_print_parts_only) {
227 std::cout << it << std::endl;
228 }
229 }
230 if (FLAGS_print_parts_only) {
231 return 0;
Austin Schuhfe3fb342021-01-16 18:50:37 -0800232 }
233
Austin Schuh6f3babe2020-01-26 20:34:50 -0800234 aos::logger::LogReader reader(logfiles);
Austin Schuha81454b2020-05-12 19:58:36 -0700235
Austin Schuh25b17652021-07-21 15:42:56 -0700236 if (FLAGS_channels) {
237 const aos::Configuration *config = reader.configuration();
238 for (const aos::Channel *channel : *config->channels()) {
239 std::cout << channel->name()->c_str() << " " << channel->type()->c_str()
240 << '\n';
241 }
242 return 0;
243 }
244
Austin Schuh746690f2020-08-01 16:15:57 -0700245 aos::FastStringBuilder builder;
246
Austin Schuha81454b2020-05-12 19:58:36 -0700247 aos::SimulatedEventLoopFactory event_loop_factory(reader.configuration());
248 reader.Register(&event_loop_factory);
Austin Schuh6f3babe2020-01-26 20:34:50 -0800249
250 std::vector<std::unique_ptr<aos::EventLoop>> printer_event_loops;
251
James Kuszmaul912af072020-10-31 16:06:54 -0700252 bool found_channel = false;
253
Tyler Chatowee0afa82021-08-01 22:00:36 -0700254 uint64_t message_print_counter = 0;
255
Austin Schuh07676622021-01-21 18:59:17 -0800256 for (const aos::Node *node :
257 aos::configuration::GetNodes(event_loop_factory.configuration())) {
Austin Schuh6f3babe2020-01-26 20:34:50 -0800258 std::unique_ptr<aos::EventLoop> printer_event_loop =
Austin Schuha81454b2020-05-12 19:58:36 -0700259 event_loop_factory.MakeEventLoop("printer", node);
Austin Schuh6f3babe2020-01-26 20:34:50 -0800260 printer_event_loop->SkipTimingReport();
261 printer_event_loop->SkipAosLog();
262
Brian Silverman9891b292020-06-23 16:34:22 -0700263 struct MessageInfo {
264 std::string node_name;
265 std::unique_ptr<aos::RawFetcher> fetcher;
266 };
267 std::vector<MessageInfo> messages_before_start;
Austin Schuha81454b2020-05-12 19:58:36 -0700268
Austin Schuh6f3babe2020-01-26 20:34:50 -0800269 const flatbuffers::Vector<flatbuffers::Offset<aos::Channel>> *channels =
Brian Silverman9891b292020-06-23 16:34:22 -0700270 printer_event_loop->configuration()->channels();
271
Austin Schuh6f3babe2020-01-26 20:34:50 -0800272 for (flatbuffers::uoffset_t i = 0; i < channels->size(); i++) {
273 const aos::Channel *channel = channels->Get(i);
274 const flatbuffers::string_view name = channel->name()->string_view();
275 const flatbuffers::string_view type = channel->type()->string_view();
276 if (name.find(FLAGS_name) != std::string::npos &&
277 type.find(FLAGS_type) != std::string::npos) {
278 if (!aos::configuration::ChannelIsReadableOnNode(
279 channel, printer_event_loop->node())) {
280 continue;
281 }
282 VLOG(1) << "Listening on " << name << " " << type;
283
284 std::string node_name =
285 node == nullptr ? ""
286 : std::string(node->name()->string_view()) + " ";
287
288 CHECK_NOTNULL(channel->schema());
Austin Schuha81454b2020-05-12 19:58:36 -0700289
Brian Silverman9891b292020-06-23 16:34:22 -0700290 // Fetch the last message on this channel from before the log start
291 // time.
Austin Schuha81454b2020-05-12 19:58:36 -0700292 if (FLAGS_fetch) {
Austin Schuha81454b2020-05-12 19:58:36 -0700293 std::unique_ptr<aos::RawFetcher> fetcher =
294 printer_event_loop->MakeRawFetcher(channel);
295 if (fetcher->Fetch()) {
Brian Silverman9891b292020-06-23 16:34:22 -0700296 MessageInfo message{.node_name = node_name,
297 .fetcher = std::move(fetcher)};
Austin Schuha81454b2020-05-12 19:58:36 -0700298 // Insert it sorted into the vector so we can print in time order
299 // instead of channel order at the start.
300 auto it = std::lower_bound(
Brian Silverman9891b292020-06-23 16:34:22 -0700301 messages_before_start.begin(), messages_before_start.end(),
302 message, [](const MessageInfo &lhs, const MessageInfo &rhs) {
303 if (lhs.fetcher->context().monotonic_event_time <
304 rhs.fetcher->context().monotonic_event_time) {
Austin Schuha81454b2020-05-12 19:58:36 -0700305 return true;
306 }
Brian Silverman9891b292020-06-23 16:34:22 -0700307 if (lhs.fetcher->context().monotonic_event_time >
308 rhs.fetcher->context().monotonic_event_time) {
Austin Schuha81454b2020-05-12 19:58:36 -0700309 return false;
310 }
Brian Silverman9891b292020-06-23 16:34:22 -0700311 return lhs.fetcher->channel() < rhs.fetcher->channel();
Austin Schuha81454b2020-05-12 19:58:36 -0700312 });
Brian Silverman9891b292020-06-23 16:34:22 -0700313 messages_before_start.insert(it, std::move(message));
Austin Schuha81454b2020-05-12 19:58:36 -0700314 }
315 }
316
Austin Schuh6f3babe2020-01-26 20:34:50 -0800317 printer_event_loop->MakeRawWatcher(
Tyler Chatowee0afa82021-08-01 22:00:36 -0700318 channel, [channel, node_name, &builder, &event_loop_factory,
319 &message_print_counter](const aos::Context &context,
320 const void * /*message*/) {
Austin Schuh569c7f92020-12-11 20:01:42 -0800321 if (FLAGS_print) {
322 PrintMessage(node_name, channel, context, &builder);
Tyler Chatowee0afa82021-08-01 22:00:36 -0700323 ++message_print_counter;
324 if (FLAGS_count > 0 && message_print_counter >= FLAGS_count) {
325 event_loop_factory.Exit();
326 }
Austin Schuh569c7f92020-12-11 20:01:42 -0800327 }
Austin Schuh6f3babe2020-01-26 20:34:50 -0800328 });
329 found_channel = true;
330 }
331 }
332
Brian Silverman9891b292020-06-23 16:34:22 -0700333 // Print the messages from before the log start time.
Austin Schuha81454b2020-05-12 19:58:36 -0700334 // TODO(austin): Sort between nodes too when it becomes annoying enough.
Brian Silverman9891b292020-06-23 16:34:22 -0700335 for (const MessageInfo &message : messages_before_start) {
Austin Schuh569c7f92020-12-11 20:01:42 -0800336 if (FLAGS_print) {
337 PrintMessage(message.node_name, message.fetcher->channel(),
338 message.fetcher->context(), &builder);
Tyler Chatowee0afa82021-08-01 22:00:36 -0700339 ++message_print_counter;
340 if (FLAGS_count > 0 && message_print_counter >= FLAGS_count) {
341 // We are done. Clean up and exit.
342 reader.Deregister();
343 return 0;
344 }
Austin Schuh569c7f92020-12-11 20:01:42 -0800345 }
Austin Schuha81454b2020-05-12 19:58:36 -0700346 }
Austin Schuh6f3babe2020-01-26 20:34:50 -0800347 printer_event_loops.emplace_back(std::move(printer_event_loop));
Brian Silverman9891b292020-06-23 16:34:22 -0700348
349 std::cout << std::endl;
Austin Schuhee711052020-08-24 16:06:09 -0700350 std::cout << (node != nullptr ? (node->name()->str() + " ") : "")
351 << "Log starting at " << reader.realtime_start_time(node) << " ("
352 << reader.monotonic_start_time(node) << ")";
Brian Silverman9891b292020-06-23 16:34:22 -0700353 std::cout << std::endl << std::endl;
James Kuszmaul38735e82019-12-07 16:42:06 -0800354 }
355
James Kuszmaul912af072020-10-31 16:06:54 -0700356 if (!found_channel) {
357 LOG(FATAL) << "Could not find any channels";
358 }
359
Austin Schuha81454b2020-05-12 19:58:36 -0700360 if (FLAGS_fetch) {
361 // New line to separate fetched messages from non-fetched messages.
362 std::cout << std::endl;
363 }
364
365 event_loop_factory.Run();
James Kuszmaul38735e82019-12-07 16:42:06 -0800366
Austin Schuh51a92592020-08-09 13:17:00 -0700367 reader.Deregister();
368
James Kuszmaul38735e82019-12-07 16:42:06 -0800369 return 0;
370}