1 //
2 // Copyright 2016 Pixar
3 //
4 // Licensed under the Apache License, Version 2.0 (the "Apache License")
5 // with the following modification; you may not use this file except in
6 // compliance with the Apache License and the following modification to it:
7 // Section 6. Trademarks. is deleted and replaced with:
8 //
9 // 6. Trademarks. This License does not grant permission to use the trade
10 //    names, trademarks, service marks, or product names of the Licensor
11 //    and its affiliates, except as required to comply with Section 4(c) of
12 //    the License and to reproduce the content of the NOTICE file.
13 //
14 // You may obtain a copy of the Apache License at
15 //
16 //     http://www.apache.org/licenses/LICENSE-2.0
17 //
18 // Unless required by applicable law or agreed to in writing, software
19 // distributed under the Apache License with the above modification is
20 // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21 // KIND, either express or implied. See the Apache License for the specific
22 // language governing permissions and limitations under the Apache License.
23 //
24 #ifndef PXR_BASE_TF_DIAGNOSTIC_MGR_H
25 #define PXR_BASE_TF_DIAGNOSTIC_MGR_H
26 
27 /// \file tf/diagnosticMgr.h
28 
29 #include "pxr/pxr.h"
30 #include "pxr/base/tf/callContext.h"
31 #include "pxr/base/tf/debug.h"
32 #include "pxr/base/tf/diagnosticLite.h"
33 #include "pxr/base/tf/error.h"
34 #include "pxr/base/tf/singleton.h"
35 #include "pxr/base/tf/status.h"
36 #include "pxr/base/tf/stringUtils.h"
37 #include "pxr/base/tf/warning.h"
38 #include "pxr/base/tf/weakPtr.h"
39 #include "pxr/base/tf/enum.h"
40 #include "pxr/base/tf/api.h"
41 
42 #include "pxr/base/arch/inttypes.h"
43 #include "pxr/base/arch/attributes.h"
44 #include "pxr/base/arch/functionLite.h"
45 
46 #include <tbb/enumerable_thread_specific.h>
47 #include <tbb/spin_rw_mutex.h>
48 
49 #include <atomic>
50 #include <cstdarg>
51 #include <list>
52 #include <string>
53 #include <vector>
54 
55 PXR_NAMESPACE_OPEN_SCOPE
56 
57 TF_DEBUG_CODES(
58     TF_LOG_STACK_TRACE_ON_ERROR,
59     TF_LOG_STACK_TRACE_ON_WARNING,
60     TF_ERROR_MARK_TRACKING,
61     TF_PRINT_ALL_POSTED_ERRORS_TO_STDERR
62     );
63 
64 class TfError;
65 class TfErrorMark;
66 
67 /// \class TfDiagnosticMgr
68 /// \ingroup group_tf_Diagnostic
69 ///
70 /// Singleton class through which all errors and diagnostics pass.
71 class TfDiagnosticMgr : public TfWeakBase {
72 public:
73 
74     typedef TfDiagnosticMgr This;
75 
76     typedef std::list<TfError> ErrorList;
77 
78     /// Synonym for standard STL iterator to traverse the error list.
79     ///
80     /// The error list for a thread is an STL list.  The \c ErrorIterator type
81     /// is an STL iterator and can be used without restriction in any way that
82     /// it is legal to use an STL iterator.
83     ///
84     /// Given an iterator, one accesses the error in the standard STL fashion:
85     /// \code
86     ///     TfErrorMark m;
87     ///
88     ///     ... ;
89     ///     if (!m.IsClean()) {
90     ///         TfErrorMark::Iterator i;
91     ///         for (i = m.GetBegin(); i != m.GetEnd(); ++i) {
92     ///            cout << "file = " << i->GetSourceFileName()
93     ///                 << "line = " << i->GetSourceLineNumber() << "\n";
94     ///         }
95     /// \endcode
96     typedef ErrorList::iterator ErrorIterator;
97 
98     /// Returns the name of the given diagnostic code.
99     TF_API
100     static std::string GetCodeName(const TfEnum &code);
101 
102     /// Return a human-readable diagnostic message. The TfDiagnosticMgr uses
103     /// this function to print diagnostics when no diagnostic delegates are
104     /// installed. Diagnostic delegate implementations can call this to produce
105     /// messages in the same format, if desired.
106     TF_API
107     static std::string FormatDiagnostic(const TfEnum &code,
108             const TfCallContext &context, const std::string &msg,
109             const TfDiagnosticInfo &info);
110 
111     /// \class Delegate
112     /// One may set a delegate with the \c TfDiagnosticMgr which will be
113     /// called to respond to errors and diagnostics.
114     ///
115     /// \note None of the methods in \c TfDiagnosticMgr::Delegate can be
116     /// reentrant.
117     ///
118     /// Practically speaking, this means they cannot invoke:
119     ///
120     /// - TF_ERROR
121     /// - TF_RUNTIME_ERROR
122     /// - TF_CODING_ERROR
123     /// - TF_WARN
124     /// - TF_STATUS
125     ///
126     /// For a more complete list, see diagnostic.h
127     ///
128     class Delegate {
129       public:
130         TF_API
131         virtual ~Delegate() = 0;
132 
133         /// Called when a \c TfError is posted.
134         virtual void IssueError(TfError const &err) = 0;
135 
136         /// Called when a \c TF_FATAL_ERROR is issued (or a failed
137         /// \c TF_AXIOM).
138         virtual void IssueFatalError(TfCallContext const &context,
139                                      std::string const &msg) = 0;
140 
141         /// Called when a \c TF_STATUS() is issued.
142         virtual void IssueStatus(TfStatus const &status) = 0;
143 
144         /// Called when a \c TF_WARNING() is issued.
145         virtual void IssueWarning(TfWarning const &warning) = 0;
146 
147     protected:
148         /// Abort the program, but avoid the session logging mechanism. This
149         /// is intended to be used for fatal error cases where any information
150         /// has already been logged.
151         TF_API
152         void _UnhandledAbort() const;
153     };
154 
155     /// Return the singleton instance.
GetInstance()156     TF_API static This &GetInstance() {
157         return TfSingleton<This>::GetInstance();
158     }
159 
160     /// Add the delegate \p delegate to the list of current delegates.
161     ///
162     /// This will add the delegate even if it already exists in the list.
163     ///
164     /// Each delegate will be called when diagnostics and errors are invoked
165     ///
166     /// This function is thread safe.
167     TF_API
168     void AddDelegate(Delegate* delegate);
169 
170     /// Removes all delegates equal to \p delegate from the current delegates.
171     ///
172     /// This function is thread safe.
173     TF_API
174     void RemoveDelegate(Delegate* delegate);
175 
176     /// Set whether errors, warnings and status messages should be printed out
177     /// to the terminal.
178     TF_API
SetQuiet(bool quiet)179     void SetQuiet(bool quiet) { _quiet = quiet; }
180 
181     /// Return an iterator to the beginning of this thread's error list.
GetErrorBegin()182     ErrorIterator GetErrorBegin() { return _errorList.local().begin(); }
183 
184     /// Return an iterator to the end of this thread's error list.
GetErrorEnd()185     ErrorIterator GetErrorEnd() { return _errorList.local().end(); }
186 
187     /// Remove error specified by iterator \p i.
188     /// \deprecated Use TfErrorMark instead.
189     TF_API
190     ErrorIterator EraseError(ErrorIterator i);
191 
192     /// Remove all the errors in [first, last) from this thread's error
193     /// stream. This should generally not be invoked directly. Use TfErrorMark
194     /// instead.
195     TF_API
196     ErrorIterator EraseRange(ErrorIterator first, ErrorIterator last);
197 
198     /// Append an error to the list of active errors.  This is generally not
199     /// meant to be called by user code.  It is public so that the system
200     /// which translates tf errors to and from python exceptions can manage
201     /// errors.
202     TF_API
203     void AppendError(TfError const &e);
204 
205     /// This method will create a TfError, append it to the error list, and
206     /// pass it to all delegates.
207     ///
208     /// If no delegates have been registered and no error mark is active, this
209     /// method will print the error to stderr.
210     TF_API
211     void PostError(TfEnum errorCode, const char* errorCodeString,
212         TfCallContext const &context,
213         const std::string& commentary, TfDiagnosticInfo info,
214         bool quiet);
215 
216     /// This method will create a TfError, append it to the error list, and
217     /// pass it to all delegates.
218     ///
219     /// If no delegates have been registered and no error mark is active, this
220     /// method will print the error to stderr.
221     TF_API
222     void PostError(const TfDiagnosticBase& diagnostic);
223 
224     /// This method will create a TfWarning and pass it to all delegates.
225     ///
226     /// If no delegates have been registered, this method will print the
227     /// warning msg to stderr.
228     TF_API
229     void PostWarning(TfEnum warningCode, const char *warningCodeString,
230         TfCallContext const &context, std::string const &commentary,
231         TfDiagnosticInfo info, bool quiet) const;
232 
233     /// This method will create a TfWarning and pass it to all delegates.
234     ///
235     /// If no delegates have been registered, this method will print the
236     /// warning msg to stderr.
237     TF_API
238     void PostWarning(const TfDiagnosticBase& diagnostic) const;
239 
240     /// This method will create a TfStatus and pass it to all delegates.
241     ///
242     /// If no delegates have been registered, this method will print the
243     /// status msg to stderr.
244     TF_API
245     void PostStatus(TfEnum statusCode, const char *statusCodeString,
246         TfCallContext const &context, std::string const &commentary,
247         TfDiagnosticInfo info, bool quiet) const;
248 
249     /// This method will create a TfStatus and pass it to all delegates.
250     ///
251     /// If no delegates have been registered, this method will print the
252     /// status msg to stderr.
253     TF_API
254     void PostStatus(const TfDiagnosticBase& diagnostic) const;
255 
256     /// This method will issue a fatal error to all delegates.
257     ///
258     /// If no delegates have been registered, this method will print the error
259     /// msg and abort the process.
260     TF_API
261     void PostFatal(TfCallContext const &context, TfEnum statusCode,
262                    std::string const &msg) const;
263 
264     /// Return true if an instance of TfErrorMark exists in the current thread
265     /// of execution, false otherwise.
HasActiveErrorMark()266     bool HasActiveErrorMark() { return _errorMarkCounts.local() > 0; }
267 
268 #if !defined(doxygen)
269     //
270     // Public, but *only* meant to be used by the TF_ERROR() macro.
271     //
272     /// \private
273     class ErrorHelper {
274       public:
ErrorHelper(TfCallContext const & context,TfEnum errorCode,const char * errorCodeString)275         ErrorHelper(TfCallContext const &context, TfEnum errorCode,
276                     const char* errorCodeString)
277             : _context(context), _errorCode(errorCode),
278               _errorCodeString(errorCodeString)
279         {
280         }
281 
282         TF_API
283         void Post(const char* fmt, ...) const
284             ARCH_PRINTF_FUNCTION(2,3);
285 
286         TF_API
287         void PostQuietly(const char* fmt, ...) const
288             ARCH_PRINTF_FUNCTION(2,3);
289 
290         TF_API
291         void Post(const std::string& msg) const;
292 
293         TF_API
294         void PostWithInfo(
295                 const std::string& msg,
296                 TfDiagnosticInfo info = TfDiagnosticInfo()) const;
297 
298         TF_API
299         void PostQuietly(const std::string& msg,
300                          TfDiagnosticInfo info = TfDiagnosticInfo()) const;
301 
302       private:
303         TfCallContext _context;
304         TfEnum _errorCode;
305         const char *_errorCodeString;
306     };
307 
308     struct WarningHelper {
WarningHelperWarningHelper309         WarningHelper(TfCallContext const &context, TfEnum warningCode,
310                       const char *warningCodeString)
311             : _context(context), _warningCode(warningCode),
312               _warningCodeString(warningCodeString)
313         {
314         }
315 
316         TF_API
317         void Post(const char* fmt, ...) const
318             ARCH_PRINTF_FUNCTION(2,3);
319 
320         TF_API
321         void PostQuietly(const char* fmt, ...) const
322             ARCH_PRINTF_FUNCTION(2,3);
323 
324         TF_API
325         void Post(const std::string &str) const;
326 
327         TF_API
328         void PostWithInfo(
329                 const std::string& msg,
330                 TfDiagnosticInfo info = TfDiagnosticInfo()) const;
331 
332         TF_API
333         void PostQuietly(const std::string& msg) const;
334 
335       private:
336         TfCallContext _context;
337         TfEnum _warningCode;
338         const char *_warningCodeString;
339     };
340 
341     struct StatusHelper {
StatusHelperStatusHelper342         StatusHelper(TfCallContext const &context, TfEnum statusCode,
343                      const char *statusCodeString)
344             : _context(context), _statusCode(statusCode),
345               _statusCodeString(statusCodeString)
346         {
347         }
348 
349         TF_API
350         void Post(const char* fmt, ...) const
351             ARCH_PRINTF_FUNCTION(2,3);
352 
353         TF_API
354         void PostQuietly(const char* fmt, ...) const
355             ARCH_PRINTF_FUNCTION(2,3);
356 
357         TF_API
358         void Post(const std::string &str) const;
359 
360         TF_API
361         void PostWithInfo(
362                 const std::string& msg,
363                 TfDiagnosticInfo info = TfDiagnosticInfo()) const;
364 
365         TF_API
366         void PostQuietly(const std::string& msg) const;
367 
368       private:
369         TfCallContext _context;
370         TfEnum _statusCode;
371         const char *_statusCodeString;
372     };
373 
374     struct FatalHelper {
FatalHelperFatalHelper375         FatalHelper(TfCallContext const &context, TfEnum statusCode)
376             : _context(context),
377               _statusCode(statusCode)
378         {
379         }
380 
PostFatalHelper381         void Post(const std::string &str) const {
382             This::GetInstance().PostFatal(_context, _statusCode, str);
383         }
384       private:
385         TfCallContext _context;
386         TfEnum _statusCode;
387     };
388 
389 #endif
390 
391 private:
392 
393     TfDiagnosticMgr();
394     virtual ~TfDiagnosticMgr();
395     friend class TfSingleton<This>;
396 
397     // Return an iterator to the first error with serial number >= mark, or the
398     // past-the-end iterator, if no such errors exist.
399     TF_API
400     ErrorIterator _GetErrorMarkBegin(size_t mark, size_t *nErrors);
401 
402     // Invoked by ErrorMark ctor.
_CreateErrorMark()403     inline void _CreateErrorMark() { ++_errorMarkCounts.local(); }
404 
405     // Invoked by ErrorMark dtor.
_DestroyErrorMark()406     inline bool _DestroyErrorMark() { return --_errorMarkCounts.local() == 0; }
407 
408     // Report an error, either via delegate or print to stderr, and issue a
409     // notice if this thread of execution is the main thread.
410     void _ReportError(const TfError &err);
411 
412     // Splice the errors in src into this thread's local list.  Also reassign
413     // serial numbers to all the spliced errors to ensure they work correctly
414     // with local error marks.
415     void _SpliceErrors(ErrorList &src);
416 
417     // Helper to append pending error messages to the crash log.
418     void _AppendErrorsToLogText(ErrorIterator i);
419 
420     // Helper to fully rebuild the crash log error text when errors are erased
421     // from the middle.
422     void _RebuildErrorLogText();
423 
424     // Helper to actually publish log text into the Arch crash handler.
425     void _SetLogInfoForErrors(std::vector<std::string> const &logText) const;
426 
427     // A guard used to protect reentrency when adding/removing
428     // delegates as well as posting errors/warnings/statuses
429     mutable tbb::enumerable_thread_specific<bool> _reentrantGuard;
430 
431     // The registered delegates.
432     std::vector<Delegate*> _delegates;
433 
434     mutable tbb::spin_rw_mutex _delegatesMutex;
435 
436     // Global serial number for sorting.
437     std::atomic<size_t> _nextSerial;
438 
439     // Thread-specific error list.
440     tbb::enumerable_thread_specific<ErrorList> _errorList;
441 
442     // Thread-specific diagnostic log text for pending errors.
443     struct _LogText {
444         void AppendAndPublish(ErrorIterator i, ErrorIterator end);
445         void RebuildAndPublish(ErrorIterator i, ErrorIterator end);
446 
447         std::pair<std::vector<std::string>,
448                   std::vector<std::string>> texts;
449         bool parity = false;
450     private:
451         void _AppendAndPublishImpl(bool clear,
452                                    ErrorIterator i, ErrorIterator end);
453     };
454     tbb::enumerable_thread_specific<_LogText> _logText;
455 
456     // Thread-specific error mark counts.  Use a native key for best performance
457     // here.
458     tbb::enumerable_thread_specific<
459         size_t, tbb::cache_aligned_allocator<size_t>,
460         tbb::ets_key_per_instance> _errorMarkCounts;
461 
462     bool _quiet;
463 
464     friend class TfError;
465     friend class TfErrorTransport;
466     friend class TfErrorMark;
467 };
468 
469 TF_API_TEMPLATE_CLASS(TfSingleton<TfDiagnosticMgr>);
470 
471 PXR_NAMESPACE_CLOSE_SCOPE
472 
473 #endif // PXR_BASE_TF_DIAGNOSTIC_MGR_H
474