1 //===-- CommandObjectLog.cpp ----------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "CommandObjectLog.h"
10 #include "lldb/Core/Debugger.h"
11 #include "lldb/Host/OptionParser.h"
12 #include "lldb/Interpreter/CommandOptionArgumentTable.h"
13 #include "lldb/Interpreter/CommandReturnObject.h"
14 #include "lldb/Interpreter/OptionArgParser.h"
15 #include "lldb/Interpreter/OptionValueEnumeration.h"
16 #include "lldb/Interpreter/OptionValueUInt64.h"
17 #include "lldb/Interpreter/Options.h"
18 #include "lldb/Utility/Args.h"
19 #include "lldb/Utility/FileSpec.h"
20 #include "lldb/Utility/Log.h"
21 #include "lldb/Utility/Stream.h"
22 #include "lldb/Utility/Timer.h"
23 
24 using namespace lldb;
25 using namespace lldb_private;
26 
27 #define LLDB_OPTIONS_log_enable
28 #include "CommandOptions.inc"
29 
30 #define LLDB_OPTIONS_log_dump
31 #include "CommandOptions.inc"
32 
33 /// Common completion logic for log enable/disable.
CompleteEnableDisable(CompletionRequest & request)34 static void CompleteEnableDisable(CompletionRequest &request) {
35   size_t arg_index = request.GetCursorIndex();
36   if (arg_index == 0) { // We got: log enable/disable x[tab]
37     for (llvm::StringRef channel : Log::ListChannels())
38       request.TryCompleteCurrentArg(channel);
39   } else if (arg_index >= 1) { // We got: log enable/disable channel x[tab]
40     llvm::StringRef channel = request.GetParsedLine().GetArgumentAtIndex(0);
41     Log::ForEachChannelCategory(
42         channel, [&request](llvm::StringRef name, llvm::StringRef desc) {
43           request.TryCompleteCurrentArg(name, desc);
44         });
45   }
46 }
47 
48 class CommandObjectLogEnable : public CommandObjectParsed {
49 public:
50   // Constructors and Destructors
CommandObjectLogEnable(CommandInterpreter & interpreter)51   CommandObjectLogEnable(CommandInterpreter &interpreter)
52       : CommandObjectParsed(interpreter, "log enable",
53                             "Enable logging for a single log channel.",
54                             nullptr) {
55     CommandArgumentEntry arg1;
56     CommandArgumentEntry arg2;
57     CommandArgumentData channel_arg;
58     CommandArgumentData category_arg;
59 
60     // Define the first (and only) variant of this arg.
61     channel_arg.arg_type = eArgTypeLogChannel;
62     channel_arg.arg_repetition = eArgRepeatPlain;
63 
64     // There is only one variant this argument could be; put it into the
65     // argument entry.
66     arg1.push_back(channel_arg);
67 
68     category_arg.arg_type = eArgTypeLogCategory;
69     category_arg.arg_repetition = eArgRepeatPlus;
70 
71     arg2.push_back(category_arg);
72 
73     // Push the data for the first argument into the m_arguments vector.
74     m_arguments.push_back(arg1);
75     m_arguments.push_back(arg2);
76   }
77 
78   ~CommandObjectLogEnable() override = default;
79 
GetOptions()80   Options *GetOptions() override { return &m_options; }
81 
82   class CommandOptions : public Options {
83   public:
84     CommandOptions() = default;
85 
86     ~CommandOptions() override = default;
87 
SetOptionValue(uint32_t option_idx,llvm::StringRef option_arg,ExecutionContext * execution_context)88     Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
89                           ExecutionContext *execution_context) override {
90       Status error;
91       const int short_option = m_getopt_table[option_idx].val;
92 
93       switch (short_option) {
94       case 'f':
95         log_file.SetFile(option_arg, FileSpec::Style::native);
96         FileSystem::Instance().Resolve(log_file);
97         break;
98       case 'h':
99         handler = (LogHandlerKind)OptionArgParser::ToOptionEnum(
100             option_arg, GetDefinitions()[option_idx].enum_values, 0, error);
101         if (!error.Success())
102           error.SetErrorStringWithFormat(
103               "unrecognized value for log handler '%s'",
104               option_arg.str().c_str());
105         break;
106       case 'b':
107         error =
108             buffer_size.SetValueFromString(option_arg, eVarSetOperationAssign);
109         break;
110       case 'v':
111         log_options |= LLDB_LOG_OPTION_VERBOSE;
112         break;
113       case 's':
114         log_options |= LLDB_LOG_OPTION_PREPEND_SEQUENCE;
115         break;
116       case 'T':
117         log_options |= LLDB_LOG_OPTION_PREPEND_TIMESTAMP;
118         break;
119       case 'p':
120         log_options |= LLDB_LOG_OPTION_PREPEND_PROC_AND_THREAD;
121         break;
122       case 'n':
123         log_options |= LLDB_LOG_OPTION_PREPEND_THREAD_NAME;
124         break;
125       case 'S':
126         log_options |= LLDB_LOG_OPTION_BACKTRACE;
127         break;
128       case 'a':
129         log_options |= LLDB_LOG_OPTION_APPEND;
130         break;
131       case 'F':
132         log_options |= LLDB_LOG_OPTION_PREPEND_FILE_FUNCTION;
133         break;
134       default:
135         llvm_unreachable("Unimplemented option");
136       }
137 
138       return error;
139     }
140 
OptionParsingStarting(ExecutionContext * execution_context)141     void OptionParsingStarting(ExecutionContext *execution_context) override {
142       log_file.Clear();
143       buffer_size.Clear();
144       handler = eLogHandlerStream;
145       log_options = 0;
146     }
147 
GetDefinitions()148     llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
149       return llvm::ArrayRef(g_log_enable_options);
150     }
151 
152     FileSpec log_file;
153     OptionValueUInt64 buffer_size;
154     LogHandlerKind handler = eLogHandlerStream;
155     uint32_t log_options = 0;
156   };
157 
158   void
HandleArgumentCompletion(CompletionRequest & request,OptionElementVector & opt_element_vector)159   HandleArgumentCompletion(CompletionRequest &request,
160                            OptionElementVector &opt_element_vector) override {
161     CompleteEnableDisable(request);
162   }
163 
164 protected:
DoExecute(Args & args,CommandReturnObject & result)165   void DoExecute(Args &args, CommandReturnObject &result) override {
166     if (args.GetArgumentCount() < 2) {
167       result.AppendErrorWithFormat(
168           "%s takes a log channel and one or more log types.\n",
169           m_cmd_name.c_str());
170       return;
171     }
172 
173     if (m_options.handler == eLogHandlerCircular &&
174         m_options.buffer_size.GetCurrentValue() == 0) {
175       result.AppendError(
176           "the circular buffer handler requires a non-zero buffer size.\n");
177       return;
178     }
179 
180     if ((m_options.handler != eLogHandlerCircular &&
181          m_options.handler != eLogHandlerStream) &&
182         m_options.buffer_size.GetCurrentValue() != 0) {
183       result.AppendError("a buffer size can only be specified for the circular "
184                          "and stream buffer handler.\n");
185       return;
186     }
187 
188     if (m_options.handler != eLogHandlerStream && m_options.log_file) {
189       result.AppendError(
190           "a file name can only be specified for the stream handler.\n");
191       return;
192     }
193 
194     // Store into a std::string since we're about to shift the channel off.
195     const std::string channel = std::string(args[0].ref());
196     args.Shift(); // Shift off the channel
197     char log_file[PATH_MAX];
198     if (m_options.log_file)
199       m_options.log_file.GetPath(log_file, sizeof(log_file));
200     else
201       log_file[0] = '\0';
202 
203     std::string error;
204     llvm::raw_string_ostream error_stream(error);
205     bool success = GetDebugger().EnableLog(
206         channel, args.GetArgumentArrayRef(), log_file, m_options.log_options,
207         m_options.buffer_size.GetCurrentValue(), m_options.handler,
208         error_stream);
209     result.GetErrorStream() << error_stream.str();
210 
211     if (success)
212       result.SetStatus(eReturnStatusSuccessFinishNoResult);
213     else
214       result.SetStatus(eReturnStatusFailed);
215   }
216 
217   CommandOptions m_options;
218 };
219 
220 class CommandObjectLogDisable : public CommandObjectParsed {
221 public:
222   // Constructors and Destructors
CommandObjectLogDisable(CommandInterpreter & interpreter)223   CommandObjectLogDisable(CommandInterpreter &interpreter)
224       : CommandObjectParsed(interpreter, "log disable",
225                             "Disable one or more log channel categories.",
226                             nullptr) {
227     CommandArgumentEntry arg1;
228     CommandArgumentEntry arg2;
229     CommandArgumentData channel_arg;
230     CommandArgumentData category_arg;
231 
232     // Define the first (and only) variant of this arg.
233     channel_arg.arg_type = eArgTypeLogChannel;
234     channel_arg.arg_repetition = eArgRepeatPlain;
235 
236     // There is only one variant this argument could be; put it into the
237     // argument entry.
238     arg1.push_back(channel_arg);
239 
240     category_arg.arg_type = eArgTypeLogCategory;
241     category_arg.arg_repetition = eArgRepeatPlus;
242 
243     arg2.push_back(category_arg);
244 
245     // Push the data for the first argument into the m_arguments vector.
246     m_arguments.push_back(arg1);
247     m_arguments.push_back(arg2);
248   }
249 
250   ~CommandObjectLogDisable() override = default;
251 
252   void
HandleArgumentCompletion(CompletionRequest & request,OptionElementVector & opt_element_vector)253   HandleArgumentCompletion(CompletionRequest &request,
254                            OptionElementVector &opt_element_vector) override {
255     CompleteEnableDisable(request);
256   }
257 
258 protected:
DoExecute(Args & args,CommandReturnObject & result)259   void DoExecute(Args &args, CommandReturnObject &result) override {
260     if (args.empty()) {
261       result.AppendErrorWithFormat(
262           "%s takes a log channel and one or more log types.\n",
263           m_cmd_name.c_str());
264       return;
265     }
266 
267     const std::string channel = std::string(args[0].ref());
268     args.Shift(); // Shift off the channel
269     if (channel == "all") {
270       Log::DisableAllLogChannels();
271       result.SetStatus(eReturnStatusSuccessFinishNoResult);
272     } else {
273       std::string error;
274       llvm::raw_string_ostream error_stream(error);
275       if (Log::DisableLogChannel(channel, args.GetArgumentArrayRef(),
276                                  error_stream))
277         result.SetStatus(eReturnStatusSuccessFinishNoResult);
278       result.GetErrorStream() << error_stream.str();
279     }
280   }
281 };
282 
283 class CommandObjectLogList : public CommandObjectParsed {
284 public:
285   // Constructors and Destructors
CommandObjectLogList(CommandInterpreter & interpreter)286   CommandObjectLogList(CommandInterpreter &interpreter)
287       : CommandObjectParsed(interpreter, "log list",
288                             "List the log categories for one or more log "
289                             "channels.  If none specified, lists them all.",
290                             nullptr) {
291     CommandArgumentEntry arg;
292     CommandArgumentData channel_arg;
293 
294     // Define the first (and only) variant of this arg.
295     channel_arg.arg_type = eArgTypeLogChannel;
296     channel_arg.arg_repetition = eArgRepeatStar;
297 
298     // There is only one variant this argument could be; put it into the
299     // argument entry.
300     arg.push_back(channel_arg);
301 
302     // Push the data for the first argument into the m_arguments vector.
303     m_arguments.push_back(arg);
304   }
305 
306   ~CommandObjectLogList() override = default;
307 
308   void
HandleArgumentCompletion(CompletionRequest & request,OptionElementVector & opt_element_vector)309   HandleArgumentCompletion(CompletionRequest &request,
310                            OptionElementVector &opt_element_vector) override {
311     for (llvm::StringRef channel : Log::ListChannels())
312       request.TryCompleteCurrentArg(channel);
313   }
314 
315 protected:
DoExecute(Args & args,CommandReturnObject & result)316   void DoExecute(Args &args, CommandReturnObject &result) override {
317     std::string output;
318     llvm::raw_string_ostream output_stream(output);
319     if (args.empty()) {
320       Log::ListAllLogChannels(output_stream);
321       result.SetStatus(eReturnStatusSuccessFinishResult);
322     } else {
323       bool success = true;
324       for (const auto &entry : args.entries())
325         success =
326             success && Log::ListChannelCategories(entry.ref(), output_stream);
327       if (success)
328         result.SetStatus(eReturnStatusSuccessFinishResult);
329     }
330     result.GetOutputStream() << output_stream.str();
331   }
332 };
333 class CommandObjectLogDump : public CommandObjectParsed {
334 public:
CommandObjectLogDump(CommandInterpreter & interpreter)335   CommandObjectLogDump(CommandInterpreter &interpreter)
336       : CommandObjectParsed(interpreter, "log dump",
337                             "dump circular buffer logs", nullptr) {
338     CommandArgumentEntry arg1;
339     CommandArgumentData channel_arg;
340 
341     // Define the first (and only) variant of this arg.
342     channel_arg.arg_type = eArgTypeLogChannel;
343     channel_arg.arg_repetition = eArgRepeatPlain;
344 
345     // There is only one variant this argument could be; put it into the
346     // argument entry.
347     arg1.push_back(channel_arg);
348 
349     // Push the data for the first argument into the m_arguments vector.
350     m_arguments.push_back(arg1);
351   }
352 
353   ~CommandObjectLogDump() override = default;
354 
GetOptions()355   Options *GetOptions() override { return &m_options; }
356 
357   class CommandOptions : public Options {
358   public:
359     CommandOptions() = default;
360 
361     ~CommandOptions() override = default;
362 
SetOptionValue(uint32_t option_idx,llvm::StringRef option_arg,ExecutionContext * execution_context)363     Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
364                           ExecutionContext *execution_context) override {
365       Status error;
366       const int short_option = m_getopt_table[option_idx].val;
367 
368       switch (short_option) {
369       case 'f':
370         log_file.SetFile(option_arg, FileSpec::Style::native);
371         FileSystem::Instance().Resolve(log_file);
372         break;
373       default:
374         llvm_unreachable("Unimplemented option");
375       }
376 
377       return error;
378     }
379 
OptionParsingStarting(ExecutionContext * execution_context)380     void OptionParsingStarting(ExecutionContext *execution_context) override {
381       log_file.Clear();
382     }
383 
GetDefinitions()384     llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
385       return llvm::ArrayRef(g_log_dump_options);
386     }
387 
388     FileSpec log_file;
389   };
390 
391   void
HandleArgumentCompletion(CompletionRequest & request,OptionElementVector & opt_element_vector)392   HandleArgumentCompletion(CompletionRequest &request,
393                            OptionElementVector &opt_element_vector) override {
394     CompleteEnableDisable(request);
395   }
396 
397 protected:
DoExecute(Args & args,CommandReturnObject & result)398   void DoExecute(Args &args, CommandReturnObject &result) override {
399     if (args.empty()) {
400       result.AppendErrorWithFormat(
401           "%s takes a log channel and one or more log types.\n",
402           m_cmd_name.c_str());
403       return;
404     }
405 
406     std::unique_ptr<llvm::raw_ostream> stream_up;
407     if (m_options.log_file) {
408       const File::OpenOptions flags = File::eOpenOptionWriteOnly |
409                                       File::eOpenOptionCanCreate |
410                                       File::eOpenOptionTruncate;
411       llvm::Expected<FileUP> file = FileSystem::Instance().Open(
412           m_options.log_file, flags, lldb::eFilePermissionsFileDefault, false);
413       if (!file) {
414         result.AppendErrorWithFormat("Unable to open log file '%s': %s",
415                                      m_options.log_file.GetPath().c_str(),
416                                      llvm::toString(file.takeError()).c_str());
417         return;
418       }
419       stream_up = std::make_unique<llvm::raw_fd_ostream>(
420           (*file)->GetDescriptor(), /*shouldClose=*/true);
421     } else {
422       stream_up = std::make_unique<llvm::raw_fd_ostream>(
423           GetDebugger().GetOutputFile().GetDescriptor(), /*shouldClose=*/false);
424     }
425 
426     const std::string channel = std::string(args[0].ref());
427     std::string error;
428     llvm::raw_string_ostream error_stream(error);
429     if (Log::DumpLogChannel(channel, *stream_up, error_stream)) {
430       result.SetStatus(eReturnStatusSuccessFinishNoResult);
431     } else {
432       result.SetStatus(eReturnStatusFailed);
433       result.GetErrorStream() << error_stream.str();
434     }
435   }
436 
437   CommandOptions m_options;
438 };
439 
440 class CommandObjectLogTimerEnable : public CommandObjectParsed {
441 public:
442   // Constructors and Destructors
CommandObjectLogTimerEnable(CommandInterpreter & interpreter)443   CommandObjectLogTimerEnable(CommandInterpreter &interpreter)
444       : CommandObjectParsed(interpreter, "log timers enable",
445                             "enable LLDB internal performance timers",
446                             "log timers enable <depth>") {
447     CommandArgumentEntry arg;
448     CommandArgumentData depth_arg;
449 
450     // Define the first (and only) variant of this arg.
451     depth_arg.arg_type = eArgTypeCount;
452     depth_arg.arg_repetition = eArgRepeatOptional;
453 
454     // There is only one variant this argument could be; put it into the
455     // argument entry.
456     arg.push_back(depth_arg);
457 
458     // Push the data for the first argument into the m_arguments vector.
459     m_arguments.push_back(arg);
460   }
461 
462   ~CommandObjectLogTimerEnable() override = default;
463 
464 protected:
DoExecute(Args & args,CommandReturnObject & result)465   void DoExecute(Args &args, CommandReturnObject &result) override {
466     result.SetStatus(eReturnStatusFailed);
467 
468     if (args.GetArgumentCount() == 0) {
469       Timer::SetDisplayDepth(UINT32_MAX);
470       result.SetStatus(eReturnStatusSuccessFinishNoResult);
471     } else if (args.GetArgumentCount() == 1) {
472       uint32_t depth;
473       if (args[0].ref().consumeInteger(0, depth)) {
474         result.AppendError(
475             "Could not convert enable depth to an unsigned integer.");
476       } else {
477         Timer::SetDisplayDepth(depth);
478         result.SetStatus(eReturnStatusSuccessFinishNoResult);
479       }
480     }
481 
482     if (!result.Succeeded()) {
483       result.AppendError("Missing subcommand");
484       result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str());
485     }
486   }
487 };
488 
489 class CommandObjectLogTimerDisable : public CommandObjectParsed {
490 public:
491   // Constructors and Destructors
CommandObjectLogTimerDisable(CommandInterpreter & interpreter)492   CommandObjectLogTimerDisable(CommandInterpreter &interpreter)
493       : CommandObjectParsed(interpreter, "log timers disable",
494                             "disable LLDB internal performance timers",
495                             nullptr) {}
496 
497   ~CommandObjectLogTimerDisable() override = default;
498 
499 protected:
DoExecute(Args & args,CommandReturnObject & result)500   void DoExecute(Args &args, CommandReturnObject &result) override {
501     Timer::DumpCategoryTimes(result.GetOutputStream());
502     Timer::SetDisplayDepth(0);
503     result.SetStatus(eReturnStatusSuccessFinishResult);
504 
505     if (!result.Succeeded()) {
506       result.AppendError("Missing subcommand");
507       result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str());
508     }
509   }
510 };
511 
512 class CommandObjectLogTimerDump : public CommandObjectParsed {
513 public:
514   // Constructors and Destructors
CommandObjectLogTimerDump(CommandInterpreter & interpreter)515   CommandObjectLogTimerDump(CommandInterpreter &interpreter)
516       : CommandObjectParsed(interpreter, "log timers dump",
517                             "dump LLDB internal performance timers", nullptr) {}
518 
519   ~CommandObjectLogTimerDump() override = default;
520 
521 protected:
DoExecute(Args & args,CommandReturnObject & result)522   void DoExecute(Args &args, CommandReturnObject &result) override {
523     Timer::DumpCategoryTimes(result.GetOutputStream());
524     result.SetStatus(eReturnStatusSuccessFinishResult);
525 
526     if (!result.Succeeded()) {
527       result.AppendError("Missing subcommand");
528       result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str());
529     }
530   }
531 };
532 
533 class CommandObjectLogTimerReset : public CommandObjectParsed {
534 public:
535   // Constructors and Destructors
CommandObjectLogTimerReset(CommandInterpreter & interpreter)536   CommandObjectLogTimerReset(CommandInterpreter &interpreter)
537       : CommandObjectParsed(interpreter, "log timers reset",
538                             "reset LLDB internal performance timers", nullptr) {
539   }
540 
541   ~CommandObjectLogTimerReset() override = default;
542 
543 protected:
DoExecute(Args & args,CommandReturnObject & result)544   void DoExecute(Args &args, CommandReturnObject &result) override {
545     Timer::ResetCategoryTimes();
546     result.SetStatus(eReturnStatusSuccessFinishResult);
547 
548     if (!result.Succeeded()) {
549       result.AppendError("Missing subcommand");
550       result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str());
551     }
552   }
553 };
554 
555 class CommandObjectLogTimerIncrement : public CommandObjectParsed {
556 public:
557   // Constructors and Destructors
CommandObjectLogTimerIncrement(CommandInterpreter & interpreter)558   CommandObjectLogTimerIncrement(CommandInterpreter &interpreter)
559       : CommandObjectParsed(interpreter, "log timers increment",
560                             "increment LLDB internal performance timers",
561                             "log timers increment <bool>") {
562     CommandArgumentEntry arg;
563     CommandArgumentData bool_arg;
564 
565     // Define the first (and only) variant of this arg.
566     bool_arg.arg_type = eArgTypeBoolean;
567     bool_arg.arg_repetition = eArgRepeatPlain;
568 
569     // There is only one variant this argument could be; put it into the
570     // argument entry.
571     arg.push_back(bool_arg);
572 
573     // Push the data for the first argument into the m_arguments vector.
574     m_arguments.push_back(arg);
575   }
576 
577   ~CommandObjectLogTimerIncrement() override = default;
578 
579   void
HandleArgumentCompletion(CompletionRequest & request,OptionElementVector & opt_element_vector)580   HandleArgumentCompletion(CompletionRequest &request,
581                            OptionElementVector &opt_element_vector) override {
582     request.TryCompleteCurrentArg("true");
583     request.TryCompleteCurrentArg("false");
584   }
585 
586 protected:
DoExecute(Args & args,CommandReturnObject & result)587   void DoExecute(Args &args, CommandReturnObject &result) override {
588     result.SetStatus(eReturnStatusFailed);
589 
590     if (args.GetArgumentCount() == 1) {
591       bool success;
592       bool increment =
593           OptionArgParser::ToBoolean(args[0].ref(), false, &success);
594 
595       if (success) {
596         Timer::SetQuiet(!increment);
597         result.SetStatus(eReturnStatusSuccessFinishNoResult);
598       } else
599         result.AppendError("Could not convert increment value to boolean.");
600     }
601 
602     if (!result.Succeeded()) {
603       result.AppendError("Missing subcommand");
604       result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str());
605     }
606   }
607 };
608 
609 class CommandObjectLogTimer : public CommandObjectMultiword {
610 public:
CommandObjectLogTimer(CommandInterpreter & interpreter)611   CommandObjectLogTimer(CommandInterpreter &interpreter)
612       : CommandObjectMultiword(interpreter, "log timers",
613                                "Enable, disable, dump, and reset LLDB internal "
614                                "performance timers.",
615                                "log timers < enable <depth> | disable | dump | "
616                                "increment <bool> | reset >") {
617     LoadSubCommand("enable", CommandObjectSP(
618                                  new CommandObjectLogTimerEnable(interpreter)));
619     LoadSubCommand("disable", CommandObjectSP(new CommandObjectLogTimerDisable(
620                                   interpreter)));
621     LoadSubCommand("dump",
622                    CommandObjectSP(new CommandObjectLogTimerDump(interpreter)));
623     LoadSubCommand(
624         "reset", CommandObjectSP(new CommandObjectLogTimerReset(interpreter)));
625     LoadSubCommand(
626         "increment",
627         CommandObjectSP(new CommandObjectLogTimerIncrement(interpreter)));
628   }
629 
630   ~CommandObjectLogTimer() override = default;
631 };
632 
CommandObjectLog(CommandInterpreter & interpreter)633 CommandObjectLog::CommandObjectLog(CommandInterpreter &interpreter)
634     : CommandObjectMultiword(interpreter, "log",
635                              "Commands controlling LLDB internal logging.",
636                              "log <subcommand> [<command-options>]") {
637   LoadSubCommand("enable",
638                  CommandObjectSP(new CommandObjectLogEnable(interpreter)));
639   LoadSubCommand("disable",
640                  CommandObjectSP(new CommandObjectLogDisable(interpreter)));
641   LoadSubCommand("list",
642                  CommandObjectSP(new CommandObjectLogList(interpreter)));
643   LoadSubCommand("dump",
644                  CommandObjectSP(new CommandObjectLogDump(interpreter)));
645   LoadSubCommand("timers",
646                  CommandObjectSP(new CommandObjectLogTimer(interpreter)));
647 }
648 
649 CommandObjectLog::~CommandObjectLog() = default;
650