NVIDIA DeepStream SDK API Reference

9.2 Release
sources/gst-plugins/gst-nvdsvideotemplate/ds-sparse4d/utils/log_utils.h
Go to the documentation of this file.
1 /*
2  * SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3  * SPDX-License-Identifier: Apache-2.0
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 #ifndef LOG_UTILS_H
19 #define LOG_UTILS_H
20 
21 #include <string>
22 #include <iostream>
23 #include <sstream>
24 #include <iomanip>
25 #include <vector>
26 #include <chrono>
27 #include <unordered_map>
28 #include <map>
29 #include <memory>
30 #include <algorithm>
31 #include <stack>
32 
33 // ANSI color codes
34 #define ANSI_COLOR_ERROR "\x1b[31m" // Bright red - standard for errors
35 #define ANSI_COLOR_SUCCESS "\x1b[38;2;118;185;0m" // NVIDIA green (76B900 hex)
36 #define ANSI_COLOR_WARNING "\x1b[33m" // Yellow/amber - standard for warnings
37 #define ANSI_COLOR_INFO "\x1b[38;2;0;175;255m" // Cyan - better for info than blue
38 #define ANSI_COLOR_INFO2 "\x1b[38;2;147;161;161m" // Soft gray
39 #define ANSI_COLOR_FRAME "\x1b[38;2;255;165;0m" // Orange for frame
40 #define ANSI_COLOR_RESET "\x1b[0m"
41 
51 enum class LogLevel {
52  INFO, // Display all: info, warn, error
53  WARN, // Display: warn, error
54  ERROR // Display: error only
55 };
56 
57 class PrettyLogger {
58 private:
59  using high_res_clock = std::chrono::high_resolution_clock;
60  using time_point = std::chrono::time_point<high_res_clock>;
61 
62  // Special message types that need special formatting
63  enum class SpecialMessageType {
64  NONE,
65  INPUT_TENSORS,
66  DYNAMIC_TENSOR
67  };
68 
69  // Process node for building a tree structure
70  struct ProcessNode {
71  std::string name;
72  int frame_num;
73  time_point start_time;
74  std::vector<std::string> info_messages;
75  bool is_main_process;
76  std::vector<std::shared_ptr<ProcessNode>> children;
77  ProcessNode* parent;
78 
79  ProcessNode(const std::string& n, int frame, bool main, ProcessNode* p)
80  : name(n), frame_num(frame), is_main_process(main), parent(p) {}
81  };
82 
83  // Member variables
84  LogLevel log_level;
85  int current_frame;
86  std::shared_ptr<ProcessNode> root_process;
87  std::stack<ProcessNode*> process_stack;
88  std::unordered_map<std::string, ProcessNode*> process_map;
89 
90  // Track whether we need to add spacing before the next process
91  bool needsSpacingForNextProcess;
92 
93  // Track if the last message was a root-level info
94  bool lastMessageWasRootLevelInfo;
95 
96  // Helper to get unique process key
97  std::string getProcessKey(const std::string& process, int frameNum) const {
98  return process + "_" + std::to_string(frameNum);
99  }
100 
101  // Helper to format time with appropriate units
102  std::string formatTime(double ms_duration) const {
103  std::stringstream ss;
104  ss << std::fixed << std::setprecision(2) << std::setw(7) << ms_duration << "ms";
105  return ss.str();
106  }
107 
108  // Detect special message type
109  SpecialMessageType detectSpecialMessageType(const std::string& message) const {
110  // Case-sensitive check for exact "Input tensors:" string at the beginning
111  if (message.compare(0, 14, "Input tensors:") == 0) {
112  return SpecialMessageType::INPUT_TENSORS;
113  }
114  // Case-sensitive check for Dynamic tensor lines
115  if (message.compare(0, 15, "Dynamic tensor:") == 0) {
116  return SpecialMessageType::DYNAMIC_TENSOR;
117  }
118  // Treat info messages that should be at root level as special
119  if (needsSpacingForNextProcess || process_stack.empty()) {
120  return SpecialMessageType::INPUT_TENSORS; // Use same formatting as input tensors
121  }
122  return SpecialMessageType::NONE;
123  }
124 
125  // Get indentation based on context and message type
126  std::string getIndentationForMessage(const std::string& message, bool isInfoLine = true) const {
127  SpecialMessageType messageType = detectSpecialMessageType(message);
128 
129  // Default indentation for regular messages
130  if (messageType == SpecialMessageType::NONE) {
131  if (!process_stack.empty()) {
132  return getTreeIndentation(process_stack.top(), isInfoLine);
133  } else {
134  return " │ └─ "; // Default when no context
135  }
136  }
137 
138  // Special handling for INPUT_TENSORS messages
139  if (messageType == SpecialMessageType::INPUT_TENSORS) {
140  // Input tensors should appear at the root level with specific indentation
141  return " └─ "; // Special indentation for Input tensors
142  }
143 
144 
145  // Special handling for DYNAMIC_TENSOR messages
146  if (messageType == SpecialMessageType::DYNAMIC_TENSOR) {
147  // Dynamic tensors should be indented consistently with other info messages
148  // They should appear at same level as info messages, but after a special message
149  if (!process_stack.empty()) {
150  return getTreeIndentation(process_stack.top(), true);
151  } else {
152  return " │ └─ ";
153  }
154  }
155 
156  // Default fallback
157  return " │ └─ ";
158  }
159 
160  // Get tree-style indentation for any process
161  std::string getTreeIndentation(ProcessNode* node, bool isInfoLine = true) const {
162  if (!node) return " │";
163 
164  std::vector<ProcessNode*> path;
165  ProcessNode* current = node;
166 
167  // Build path from root to node
168  while (current && !current->is_main_process) {
169  path.push_back(current);
170  current = current->parent;
171  }
172 
173  // Reverse to get root-to-node order
174  std::reverse(path.begin(), path.end());
175 
176  // Base indentation
177  std::string indent = " │";
178 
179  // Add appropriate tree branches based on path
180  if (path.size() > 0) {
181  for (size_t i = 0; i < path.size() - 1; i++) {
182  indent += " │";
183  }
184 
185  // Last segment depends on whether this is an info line or a process line
186  if (isInfoLine) {
187  indent += " └─ ";
188  } else {
189  indent += " ├─► ";
190  }
191  } else {
192  // For top-level processes (direct children of main process)
193  indent += isInfoLine ? " └─ " : " ├─► ";
194  }
195 
196  return indent;
197  }
198 
199  // Get indentation for completed process message
200  std::string getCompletionIndentation(ProcessNode* node) const {
201  if (!node) return " │";
202  if (node->is_main_process) return "";
203 
204  return getTreeIndentation(node, false);
205  }
206 
207  // Get spacing indentation for separator lines
208  std::string getSpacingIndentation(ProcessNode* node) const {
209  if (!node) return " │";
210 
211  std::vector<ProcessNode*> path;
212  ProcessNode* current = node;
213 
214  // Build path from root to node
215  while (current && !current->is_main_process) {
216  path.push_back(current);
217  current = current->parent;
218  }
219 
220  // Reverse to get root-to-node order
221  std::reverse(path.begin(), path.end());
222 
223  // Base indentation
224  std::string indent = " │";
225 
226  // Add appropriate tree branches based on path
227  for (size_t i = 0; i < path.size(); i++) {
228  indent += " │";
229  }
230 
231  return indent;
232  }
233 
240  LogLevel stringToLogLevel(const std::string& level_str) const {
241  std::string lower_str = level_str;
242  std::transform(lower_str.begin(), lower_str.end(), lower_str.begin(), ::tolower);
243 
244  if (lower_str == "info" || lower_str == "information") {
245  return LogLevel::INFO;
246  } else if (lower_str == "warn" || lower_str == "warning") {
247  return LogLevel::WARN;
248  } else {
249  return LogLevel::ERROR; // Default to ERROR
250  }
251  }
252 
253 public:
255  : log_level(level),
256  current_frame(-1),
257  root_process(nullptr),
258  needsSpacingForNextProcess(false),
259  lastMessageWasRootLevelInfo(false)
260  {}
261 
267  void setLogLevel(const std::string& level_str) {
268  log_level = stringToLogLevel(level_str);
269  }
270 
276  void setLogLevel(LogLevel level) {
277  log_level = level;
278  }
279 
280  void reset() {
281  root_process = nullptr;
282  process_stack = std::stack<ProcessNode*>();
283  process_map.clear();
284  current_frame = -1;
285  needsSpacingForNextProcess = false;
286  lastMessageWasRootLevelInfo = false;
287  }
288 
296  void warning(const std::string& message) {
297  // Display warnings only if log level is WARN or INFO
298  if (log_level == LogLevel::ERROR) {
299  return;
300  }
301 
302  // Indentation only used in INFO mode
303  if (log_level == LogLevel::INFO) {
304  std::string indent;
305  if (!process_stack.empty()) {
306  indent = getTreeIndentation(process_stack.top(), true);
307  } else {
308  indent = " │ └─ ";
309  }
310  std::cout << indent;
311  }
312 
313  std::cout << ANSI_COLOR_WARNING << "⚠️ " << message << ANSI_COLOR_RESET << "\n";
314  }
315 
323  void error(const std::string& message) {
324  // Indentation only used in INFO mode
325  if (log_level == LogLevel::INFO) {
326  // Get indentation based on current process context
327  std::string indent;
328  if (!process_stack.empty()) {
329  indent = getTreeIndentation(process_stack.top(), true);
330  } else {
331  indent = " │ └─ ";
332  }
333  std::cout << indent;
334  }
335 
336  std::cout << ANSI_COLOR_ERROR << "❌ " << message << ANSI_COLOR_RESET << "\n";
337  }
338 
346  void info(const std::string& message) {
347  // Display info only if log level is INFO
348  if (log_level != LogLevel::INFO) {
349  return;
350  }
351 
352  // Get appropriate indentation based on message type and context
353  SpecialMessageType messageType = detectSpecialMessageType(message);
354  std::string indent = getIndentationForMessage(message, true);
355 
356  // Store info message in current process if available
357  if (!process_stack.empty() && messageType == SpecialMessageType::NONE) {
358  process_stack.top()->info_messages.push_back(message);
359  }
360 
361  // Print the message with appropriate formatting
362  std::cout << indent
363  << ANSI_COLOR_INFO2 << message << ANSI_COLOR_RESET << "\n";
364 
365  // Reset spacing flag if this was a top-level message
366  if (messageType == SpecialMessageType::INPUT_TENSORS) {
367  needsSpacingForNextProcess = false;
368  }
369 
370  // Set lastMessageWasRootLevelInfo to true if this was a root-level info message
371  lastMessageWasRootLevelInfo = messageType == SpecialMessageType::INPUT_TENSORS;
372  }
373 
383  void persistent_info(const std::string& message) {
384  if (log_level != LogLevel::INFO) {
385  // In ERROR or WARN mode, print directly to console without formatting
386  std::cout << message << "\n";
387  return;
388  }
389 
390  // If log level is not INFO, use the regular info method to maintain consistent formatting
391  info(message);
392  }
393 
404  void processStart(const std::string& process, int frameNum,
405  bool isMainProcess = false,
406  const std::string& additionalInfo = "") {
407  // Process timing is only displayed in INFO mode
408  if (log_level != LogLevel::INFO) {
409  return;
410  }
411 
412  // Check for frame change and reset state if needed
413  if (frameNum != current_frame) {
414  current_frame = frameNum;
415  root_process = nullptr;
416  process_stack = std::stack<ProcessNode*>();
417  process_map.clear();
418  needsSpacingForNextProcess = false;
419  lastMessageWasRootLevelInfo = false;
420  }
421 
422  std::string process_key = getProcessKey(process, frameNum);
423 
424  // Create new process node
425  ProcessNode* parent = nullptr;
426  if (!isMainProcess && !process_stack.empty()) {
427  parent = process_stack.top();
428  }
429 
430  auto node = std::make_shared<ProcessNode>(process, frameNum, isMainProcess, parent);
431  node->start_time = high_res_clock::now();
432 
433  // Add to parent's children if not main process
434  if (parent) {
435  parent->children.push_back(node);
436  } else if (isMainProcess) {
437  // Store main process as root
438  root_process = node;
439  }
440 
441  // Add to process map and stack
442  ProcessNode* node_ptr = node.get();
443  process_map[process_key] = node_ptr;
444  process_stack.push(node_ptr);
445 
446  // Format and print start message
447  if (isMainProcess) {
448  // Main process uses fixed formatting
449  if (frameNum >= 0) {
450  std::cout << "\n" << process << " Frame-" << frameNum
451  << " │ ⚡ Starting" << "\n"
452  << " │ │\n";
453  } else {
454  std::cout << "\n" << process
455  << " │ ⚡ Starting" << "\n"
456  << " │ │\n";
457  }
458  // Reset spacing flag since main process adds its own spacing
459  needsSpacingForNextProcess = false;
460  lastMessageWasRootLevelInfo = false;
461  } else {
462  // If not main process and spacing is needed, add it before the process line
463  if (needsSpacingForNextProcess && parent) {
464  std::string spacing = getSpacingIndentation(parent);
465  std::cout << spacing << " │\n";
466  }
467 
468  // Always add a vertical spacing line before a new process starts
469  // This guarantees visual separation between info messages and process blocks
470  if (!isMainProcess && parent && process_stack.size() > 1) {
471  std::string spacing = getSpacingIndentation(parent);
472  std::cout << spacing << " │\n";
473  }
474 
475  // If the last message was a root-level info and this is a first-level process,
476  // add a line break for visual separation
477  if (lastMessageWasRootLevelInfo && process_stack.size() == 1) {
478  std::cout << " │ │\n";
479  }
480 
481  // Get tree-style indentation for child process
482  std::string indentation = getTreeIndentation(node_ptr, false);
483  std::cout << indentation << process << " │ ⚡ Starting" << "\n";
484 
485  // Reset flags
486  needsSpacingForNextProcess = false;
487  lastMessageWasRootLevelInfo = false;
488  }
489 
490  // Handle additional info
491  if (!additionalInfo.empty()) {
492  node->info_messages.push_back(additionalInfo);
493  std::string info_indent = getTreeIndentation(node_ptr, true);
494  std::cout << info_indent
495  << ANSI_COLOR_INFO2 << additionalInfo << ANSI_COLOR_RESET << "\n";
496  }
497  }
498 
510  void processComplete(const std::string& process, int frameNum,
511  bool success = true,
512  bool isMainProcess = false,
513  const std::string& additionalInfo = "") {
514  // Process completion is only displayed in INFO mode
515  if (log_level != LogLevel::INFO) {
516  return;
517  }
518 
519  std::string process_key = getProcessKey(process, frameNum);
520  auto it = process_map.find(process_key);
521 
522  if (it == process_map.end()) {
523  // Process not found
524  return;
525  }
526 
527  ProcessNode* node = it->second;
528 
529  // Calculate timing
530  auto end_time = high_res_clock::now();
531  auto duration = std::chrono::duration_cast<std::chrono::microseconds>(
532  end_time - node->start_time);
533  double ms_duration = duration.count() / 1000.0;
534 
535  // Format timing info
536  std::string timing_info = formatTime(ms_duration);
537 
538  // Create success and failure symbols with appropriate colors
539  // Using more prominent characters for better visibility
540  std::string successSymbol = std::string(ANSI_COLOR_SUCCESS) + "✔" + std::string(ANSI_COLOR_RESET);
541  std::string failureSymbol = std::string(ANSI_COLOR_ERROR) + "✘" + std::string(ANSI_COLOR_RESET);
542 
543  // Format and print completion message
544  if (isMainProcess) {
545  // Main process uses fixed formatting
546  if (frameNum >= 0) {
547  std::cout << process << " Frame-" << frameNum
548  << " │ ⏱ " << std::right << timing_info
549  << " │ " << (success ? successSymbol : failureSymbol) << " "
550  << (success ? "Completed" : "Failed") << "\n";
551  } else {
552  std::cout << process
553  << " │ ⏱ " << std::right << timing_info
554  << " │ " << (success ? successSymbol : failureSymbol) << " "
555  << (success ? "Completed" : "Failed") << "\n";
556  }
557  } else {
558  // Get indentation for this process in the tree
559  std::string indentation = getCompletionIndentation(node);
560 
561  // Output completion message
562  std::cout << indentation << process
563  << " │ ⏱ " << std::right << timing_info
564  << " │ " << (success ? successSymbol : failureSymbol) << " "
565  << (success ? "Completed" : "Failed") << "\n";
566 
567  // Set spacing flag for next process that starts after this completion
568  needsSpacingForNextProcess = true;
569 
570  // Add a newline with proper indentation after completion for better visual separation
571  if (node->parent) {
572  // Use the parent's spacing indentation to get the right tree structure
573  std::string spacing = getSpacingIndentation(node->parent);
574  std::cout << spacing << " │\n";
575  }
576  }
577 
578  // Handle additional info
579  if (!additionalInfo.empty()) {
580  std::string info_indent = getTreeIndentation(node, true);
581  std::cout << info_indent
582  << ANSI_COLOR_INFO2 << additionalInfo << ANSI_COLOR_RESET << "\n";
583  }
584 
585  // Pop from process stack if it's the current process
586  if (!process_stack.empty() && process_stack.top() == node) {
587  process_stack.pop();
588  }
589 
590  // Remove from process map
591  process_map.erase(process_key);
592  }
593 
594 };
595 
596 #endif /* LOG_UTILS_H */
PrettyLogger::processComplete
void processComplete(const std::string &process, int frameNum, bool success=true, bool isMainProcess=false, const std::string &additionalInfo="")
Mark the completion of a process and display timing information.
Definition: sources/gst-plugins/gst-nvdsvideotemplate/ds-sparse4d/utils/log_utils.h:510
PrettyLogger::PrettyLogger
PrettyLogger(LogLevel level=LogLevel::ERROR)
Definition: sources/gst-plugins/gst-nvdsvideotemplate/ds-sparse4d/utils/log_utils.h:254
LogLevel::ERROR
@ ERROR
PrettyLogger::persistent_info
void persistent_info(const std::string &message)
Prints a persistent information message that is always displayed.
Definition: sources/gst-plugins/gst-nvdsvideotemplate/ds-sparse4d/utils/log_utils.h:383
PrettyLogger
Definition: sources/gst-plugins/gst-nvdsvideotemplate/ds-sparse4d/utils/log_utils.h:57
LogLevel::WARN
@ WARN
ANSI_COLOR_RESET
#define ANSI_COLOR_RESET
Definition: sources/gst-plugins/gst-nvdsvideotemplate/ds-sparse4d/utils/log_utils.h:40
ANSI_COLOR_WARNING
#define ANSI_COLOR_WARNING
Definition: sources/gst-plugins/gst-nvdsvideotemplate/ds-sparse4d/utils/log_utils.h:36
PrettyLogger::processStart
void processStart(const std::string &process, int frameNum, bool isMainProcess=false, const std::string &additionalInfo="")
Mark the start of a process for timing and logging.
Definition: sources/gst-plugins/gst-nvdsvideotemplate/ds-sparse4d/utils/log_utils.h:404
ANSI_COLOR_ERROR
#define ANSI_COLOR_ERROR
Definition: sources/gst-plugins/gst-nvdsvideotemplate/ds-sparse4d/utils/log_utils.h:34
NONE
@ NONE
Indicates that no message is available.
Definition: sources/libs/nvmultiobjecttracker/include/modules/MultiViewAssociator/ICommunicator.hpp:38
PrettyLogger::reset
void reset()
Definition: sources/gst-plugins/gst-nvdsvideotemplate/ds-sparse4d/utils/log_utils.h:280
PrettyLogger::setLogLevel
void setLogLevel(LogLevel level)
Set the logging level using enum.
Definition: sources/gst-plugins/gst-nvdsvideotemplate/ds-sparse4d/utils/log_utils.h:276
PrettyLogger::warning
void warning(const std::string &message)
Display a warning message.
Definition: sources/gst-plugins/gst-nvdsvideotemplate/ds-sparse4d/utils/log_utils.h:296
ANSI_COLOR_INFO2
#define ANSI_COLOR_INFO2
Definition: sources/gst-plugins/gst-nvdsvideotemplate/ds-sparse4d/utils/log_utils.h:38
ANSI_COLOR_SUCCESS
#define ANSI_COLOR_SUCCESS
Definition: sources/gst-plugins/gst-nvdsvideotemplate/ds-sparse4d/utils/log_utils.h:35
PrettyLogger::setLogLevel
void setLogLevel(const std::string &level_str)
Set the logging level.
Definition: sources/gst-plugins/gst-nvdsvideotemplate/ds-sparse4d/utils/log_utils.h:267
PrettyLogger::error
void error(const std::string &message)
Display an error message.
Definition: sources/gst-plugins/gst-nvdsvideotemplate/ds-sparse4d/utils/log_utils.h:323
PrettyLogger::info
void info(const std::string &message)
Display an informational message.
Definition: sources/gst-plugins/gst-nvdsvideotemplate/ds-sparse4d/utils/log_utils.h:346
LogLevel
LogLevel
Enumeration for logging levels.
Definition: sources/gst-plugins/gst-nvdsvideotemplate/ds-sparse4d/utils/log_utils.h:51
LogLevel::INFO
@ INFO