NCBI C++ ToolKit
ncbidiag.cpp
Go to the documentation of this file.

Go to the SVN repository for this file.

1 /* $Id: ncbidiag.cpp 101898 2024-02-29 14:05:58Z gouriano $
2  * ===========================================================================
3  *
4  * PUBLIC DOMAIN NOTICE
5  * National Center for Biotechnology Information
6  *
7  * This software/database is a "United States Government Work" under the
8  * terms of the United States Copyright Act. It was written as part of
9  * the author's official duties as a United States Government employee and
10  * thus cannot be copyrighted. This software/database is freely available
11  * to the public for use. The National Library of Medicine and the U.S.
12  * Government have not placed any restriction on its use or reproduction.
13  *
14  * Although all reasonable efforts have been taken to ensure the accuracy
15  * and reliability of the software and data, the NLM and the U.S.
16  * Government do not and cannot warrant the performance or results that
17  * may be obtained by using this software or data. The NLM and the U.S.
18  * Government disclaim all warranties, express or implied, including
19  * warranties of performance, merchantability or fitness for any particular
20  * purpose.
21  *
22  * Please cite the author in any work or product based on this material.
23  *
24  * ===========================================================================
25  *
26  * Authors: Denis Vakatov et al.
27  *
28  * File Description:
29  * NCBI C++ diagnostic API
30  *
31  */
32 
33 
34 #include <ncbi_pch.hpp>
35 #include <common/ncbi_source_ver.h>
37 #include <common/ncbi_sanitizers.h>
38 #include <corelib/ncbiexpt.hpp>
39 #include <corelib/version.hpp>
40 #include <corelib/ncbi_process.hpp>
41 #include <corelib/ncbifile.hpp>
42 #include <corelib/syslog.hpp>
43 #include <corelib/error_codes.hpp>
44 #include <corelib/request_ctx.hpp>
46 #include <corelib/ncbi_strings.h>
48 #include <corelib/ncbiapp_api.hpp>
49 #include "ncbidiag_p.hpp"
50 #include "ncbisys.hpp"
51 #include <fcntl.h>
52 #include <stdlib.h>
53 #include <stack>
54 #include <atomic>
55 #include <thread>
56 #include <unordered_set>
57 #include <sstream>
58 
59 #if defined(NCBI_OS_UNIX)
60 # include <unistd.h>
61 # include <sys/utsname.h>
62 #endif
63 
64 #if defined(NCBI_OS_MSWIN)
65 # include <io.h>
66 #elif defined (NCBI_OS_DARWIN)
67 # include <crt_externs.h>
68 # define environ (*_NSGetEnviron())
69 #else
70 extern char** environ;
71 #endif
72 
73 
74 #define NCBI_USE_ERRCODE_X Corelib_Diag
75 
76 
78 
79 static bool s_DiagUseRWLock = true;
80 DEFINE_STATIC_MUTEX(s_DiagMutex);
81 DEFINE_STATIC_MUTEX(s_DiagPostMutex);
84 
85 DEFINE_STATIC_MUTEX(s_ApproveMutex);
86 
87 
88 void g_Diag_Use_RWLock(bool enable)
89 {
90  if (s_DiagUseRWLock == enable) return; // already switched
91  // Try to detect at least some dangerous situations.
92  // This does not guarantee safety since some thread may
93  // be waiting for lock while we switch to the new method.
94  if ( enable ) {
95  bool diag_unlocked = s_DiagMutex.TryLock();
96  // Mutex is locked - fail
97  _ASSERT(diag_unlocked);
98  if (!diag_unlocked) {
99  _TROUBLE;
100  NCBI_THROW(CCoreException, eCore,
101  "Cannot switch diagnostic to RW-lock - mutex is locked.");
102  }
103  s_DiagMutex.Unlock();
104  }
105  else {
106  // Switch from RW-lock to mutex
107  bool diag_unlocked = s_DiagRWLock->TryWriteLock();
108  // RW-lock is locked - fail
109  _ASSERT(diag_unlocked);
110  if (!diag_unlocked) {
111  _TROUBLE;
112  NCBI_THROW(CCoreException, eCore,
113  "Cannot switch diagnostic to mutex - RW-lock is locked.");
114  }
115  s_DiagRWLock->Unlock();
116  }
117  s_DiagUseRWLock = enable;
118 }
119 
120 
122 {
123 public:
124  enum ELockType {
125  eRead, // read lock
126  eWrite, // write lock (modifying flags etc.)
127  ePost // lock used by diag handlers to lock stream in Post()
128  };
129 
131  : m_UsedRWLock(false), m_LockType(locktype)
132  {
133  if (s_DiagUseRWLock) {
134  if (m_LockType == eRead) {
135  m_UsedRWLock = true;
136  s_DiagRWLock->ReadLock();
137  return;
138  }
139  if (m_LockType == eWrite) {
140  m_UsedRWLock = true;
141  s_DiagRWLock->WriteLock();
142  return;
143  }
144  // For ePost use normal mutex below.
145  }
146  if (m_LockType == ePost) {
147  s_DiagPostMutex.Lock();
148  }
149  else {
150  s_DiagMutex.Lock();
151  }
152  }
153 
155  {
156  if (m_UsedRWLock) {
157  s_DiagRWLock->Unlock();
158  }
159  else {
160  if (m_LockType == ePost) {
161  s_DiagPostMutex.Unlock();
162  }
163  else {
164  s_DiagMutex.Unlock();
165  }
166  }
167  }
168 
169 private:
172 };
173 
174 
175 static const char* kLogName_None = "NONE";
176 static const char* kLogName_Unknown = "UNKNOWN";
177 static const char* kLogName_Stdout = "STDOUT";
178 static const char* kLogName_Stderr = "STDERR";
179 static const char* kLogName_Stream = "STREAM";
180 static const char* kLogName_Memory = "MEMORY";
181 static const char* kLogName_Tee = "STDERR-TEE";
182 
183 
185 {
186 public:
188  virtual ~CDiagFileHandleHolder(void);
189 
190  int GetHandle(void) { return m_Handle; }
191 
192 private:
193  int m_Handle;
194 };
195 
196 
197 // Special diag handler for duplicating error log messages on stderr.
199 {
200 public:
201  CTeeDiagHandler(CDiagHandler* orig, bool own_orig);
202  virtual void Post(const SDiagMessage& mess);
203 
204  virtual void PostToConsole(const SDiagMessage& mess)
205  {
206  // Console manipulator ignores severity, so we have to always print
207  // the message to console and set NoTee flag to avoid duplicates.
209  const_cast<SDiagMessage&>(mess).m_NoTee = true;
210  }
211 
212  virtual string GetLogName(void)
213  {
214  return m_OrigHandler.get() ?
216  }
217  virtual void Reopen(TReopenFlags flags)
218  {
219  if ( m_OrigHandler.get() ) {
221  }
222  }
223 
225  {
226  return m_OrigHandler.get();
227  }
228 
229 private:
232 };
233 
234 
235 #if defined(NCBI_POSIX_THREADS) && defined(HAVE_PTHREAD_ATFORK)
236 
237 #include <unistd.h> // for pthread_atfork()
238 
239 extern "C" {
240  static void s_NcbiDiagPreFork(void)
241  {
242  s_DiagMutex.Lock();
243  }
244  static void s_NcbiDiagPostFork(void)
245  {
246  s_DiagMutex.Unlock();
247  }
248 }
249 
250 #endif
251 
252 
253 ///////////////////////////////////////////////////////
254 // Output format parameters
255 
256 // Use old output format if the flag is set
257 NCBI_PARAM_DECL(bool, Diag, Old_Post_Format);
258 NCBI_PARAM_DEF_EX(bool, Diag, Old_Post_Format, true, eParam_NoThread,
259  DIAG_OLD_POST_FORMAT);
260 static CSafeStatic<NCBI_PARAM_TYPE(Diag, Old_Post_Format)> s_OldPostFormat(
262 
263 // Auto-print context properties on set/change.
264 NCBI_PARAM_DECL(bool, Diag, AutoWrite_Context);
265 NCBI_PARAM_DEF_EX(bool, Diag, AutoWrite_Context, false, eParam_NoThread,
266  DIAG_AUTOWRITE_CONTEXT);
267 static CSafeStatic<NCBI_PARAM_TYPE(Diag, AutoWrite_Context)> s_AutoWrite_Context;
268 
269 // Print system TID rather than CThread::GetSelf()
270 NCBI_PARAM_DECL(bool, Diag, Print_System_TID);
271 NCBI_PARAM_DEF_EX(bool, Diag, Print_System_TID, false, eParam_NoThread,
272  DIAG_PRINT_SYSTEM_TID);
273 static CSafeStatic<NCBI_PARAM_TYPE(Diag, Print_System_TID)> s_PrintSystemTID;
274 
275 // Use assert() instead of abort() when printing fatal errors
276 // to show the assertion dialog and allow to choose the action
277 // (stop/debug/continue).
278 NCBI_PARAM_DECL(bool, Diag, Assert_On_Abort);
279 NCBI_PARAM_DEF_EX(bool, Diag, Assert_On_Abort, false, eParam_NoThread,
280  DIAG_ASSERT_ON_ABORT);
281 
282 // Limit log file size, rotate log when it reaches the limit.
283 NCBI_PARAM_DECL(long, Diag, Log_Size_Limit);
284 NCBI_PARAM_DEF_EX(long, Diag, Log_Size_Limit, 0, eParam_NoThread,
285  DIAG_LOG_SIZE_LIMIT);
286 static CSafeStatic<NCBI_PARAM_TYPE(Diag, Log_Size_Limit)> s_LogSizeLimit;
287 
288 // Limit max line length, zero = no limit (default).
289 NCBI_PARAM_DECL(size_t, Diag, Max_Line_Length);
290 NCBI_PARAM_DEF_EX(size_t, Diag, Max_Line_Length, 0, eParam_NoThread,
291  DIAG_MAX_LINE_LENGTH);
292 static CSafeStatic<NCBI_PARAM_TYPE(Diag, Max_Line_Length)> s_MaxLineLength;
293 
294 
295 ///////////////////////////////////////////////////////
296 // Output rate control parameters
297 
299 {
300 public:
301  typedef unsigned int TValue;
304 
305  operator TValue(void) const
306  {
307  return m_Value;
308  }
309 
310  void Set(TValue val)
311  {
312  m_Value = val;
313  }
314 
315 private:
317 };
318 
319 
321 {
322  lim.Set(kMax_UInt);
323  string s;
324  getline(in, s);
325  if ( !NStr::EqualNocase(s, "off") ) {
326  lim.Set(NStr::StringToNumeric<CLogRateLimit::TValue>(s));
327  }
328  return in;
329 }
330 
331 
332 // Need CSafeStatic_Proxy<> instance for CLogRateLimit to allow static
333 // initialization of default values with unsigned int.
335 
336 
337 // AppLog limit per period
338 NCBI_PARAM_DECL(CLogRateLimit, Diag, AppLog_Rate_Limit);
339 NCBI_PARAM_DEF_EX(CLogRateLimit, Diag, AppLog_Rate_Limit, 50000,
340  eParam_NoThread, DIAG_APPLOG_RATE_LIMIT);
341 static CSafeStatic<NCBI_PARAM_TYPE(Diag, AppLog_Rate_Limit)> s_AppLogRateLimit;
342 
343 // AppLog period, sec
344 NCBI_PARAM_DECL(unsigned int, Diag, AppLog_Rate_Period);
345 NCBI_PARAM_DEF_EX(unsigned int, Diag, AppLog_Rate_Period, 10, eParam_NoThread,
346  DIAG_APPLOG_RATE_PERIOD);
347 static CSafeStatic<NCBI_PARAM_TYPE(Diag, AppLog_Rate_Period)> s_AppLogRatePeriod;
348 
349 // ErrLog limit per period
350 NCBI_PARAM_DECL(CLogRateLimit, Diag, ErrLog_Rate_Limit);
351 NCBI_PARAM_DEF_EX(CLogRateLimit, Diag, ErrLog_Rate_Limit, 5000,
352  eParam_NoThread, DIAG_ERRLOG_RATE_LIMIT);
353 static CSafeStatic<NCBI_PARAM_TYPE(Diag, ErrLog_Rate_Limit)> s_ErrLogRateLimit;
354 
355 // ErrLog period, sec
356 NCBI_PARAM_DECL(unsigned int, Diag, ErrLog_Rate_Period);
357 NCBI_PARAM_DEF_EX(unsigned int, Diag, ErrLog_Rate_Period, 1, eParam_NoThread,
358  DIAG_ERRLOG_RATE_PERIOD);
359 static CSafeStatic<NCBI_PARAM_TYPE(Diag, ErrLog_Rate_Period)> s_ErrLogRatePeriod;
360 
361 // TraceLog limit per period
362 NCBI_PARAM_DECL(CLogRateLimit, Diag, TraceLog_Rate_Limit);
363 NCBI_PARAM_DEF_EX(CLogRateLimit, Diag, TraceLog_Rate_Limit, 5000,
364  eParam_NoThread, DIAG_TRACELOG_RATE_LIMIT);
365 static CSafeStatic<NCBI_PARAM_TYPE(Diag, TraceLog_Rate_Limit)> s_TraceLogRateLimit;
366 
367 // TraceLog period, sec
368 NCBI_PARAM_DECL(unsigned int, Diag, TraceLog_Rate_Period);
369 NCBI_PARAM_DEF_EX(unsigned int, Diag, TraceLog_Rate_Period, 1, eParam_NoThread,
370  DIAG_TRACELOG_RATE_PERIOD);
371 static CSafeStatic<NCBI_PARAM_TYPE(Diag, TraceLog_Rate_Period)> s_TraceLogRatePeriod;
372 
373 // Duplicate messages to STDERR
374 NCBI_PARAM_DECL(bool, Diag, Tee_To_Stderr);
375 NCBI_PARAM_DEF_EX(bool, Diag, Tee_To_Stderr, false, eParam_NoThread,
376  DIAG_TEE_TO_STDERR);
377 typedef NCBI_PARAM_TYPE(Diag, Tee_To_Stderr) TTeeToStderr;
378 
379 // Minimum severity of the messages duplicated to STDERR
380 NCBI_PARAM_ENUM_DECL(EDiagSev, Diag, Tee_Min_Severity);
381 NCBI_PARAM_ENUM_ARRAY(EDiagSev, Diag, Tee_Min_Severity)
382 {
383  {"Info", eDiag_Info},
384  {"Warning", eDiag_Warning},
385  {"Error", eDiag_Error},
386  {"Critical", eDiag_Critical},
387  {"Fatal", eDiag_Fatal},
388  {"Trace", eDiag_Trace}
389 };
390 
392 #if defined(NDEBUG)
393  eDiag_Error;
394 #else
396 #endif
397 
398 NCBI_PARAM_ENUM_DEF_EX(EDiagSev, Diag, Tee_Min_Severity,
400  eParam_NoThread, DIAG_TEE_MIN_SEVERITY);
401 
402 
403 NCBI_PARAM_DECL(size_t, Diag, Collect_Limit);
404 NCBI_PARAM_DEF_EX(size_t, Diag, Collect_Limit, 1000, eParam_NoThread,
405  DIAG_COLLECT_LIMIT);
406 
407 
409 NCBI_PARAM_DEF_EX(bool, Log, Truncate, false, eParam_NoThread, LOG_TRUNCATE);
410 
411 
412 NCBI_PARAM_DECL(bool, Log, NoCreate);
413 NCBI_PARAM_DEF_EX(bool, Log, NoCreate, false, eParam_NoThread, LOG_NOCREATE);
414 
415 
416 // Logging of environment variables: space separated list of names which
417 // should be logged after app start and after each request start.
418 NCBI_PARAM_DECL(string, Log, LogEnvironment);
419 NCBI_PARAM_DEF_EX(string, Log, LogEnvironment, "",
421  DIAG_LOG_ENVIRONMENT);
422 
423 
424 // Logging of registry values: space separated list of 'section:name' strings.
425 NCBI_PARAM_DECL(string, Log, LogRegistry);
426 NCBI_PARAM_DEF_EX(string, Log, LogRegistry, "",
428  DIAG_LOG_REGISTRY);
429 
430 
431 // Turn off all applog messages (start/stop, request start/stop, extra).
432 NCBI_PARAM_DECL(bool, Diag, Disable_AppLog_Messages);
433 NCBI_PARAM_DEF_EX(bool, Diag, Disable_AppLog_Messages, false, eParam_NoThread,
434  DIAG_DISABLE_APPLOG_MESSAGES);
435 static CSafeStatic<NCBI_PARAM_TYPE(Diag, Disable_AppLog_Messages)> s_DisableAppLog;
436 
437 
438 static bool s_FinishedSetupDiag = false;
439 
440 
441 ///////////////////////////////////////////////////////
442 // CDiagContextThreadData::
443 
444 
445 /// Thread local context data stored in TLS
447 {
448 public:
451 
452  /// Get current request context.
454  /// Set request context. If NULL, switches the current thread
455  /// to its default request context.
457 
459 
460  /// Request id
463  NCBI_DEPRECATED void IncRequestId(void);
464 
465  /// Get request timer, create if not exist yet
467  /// Get request timer or null
469  /// Delete request timer
471 
472  /// Diag buffer
473  CDiagBuffer& GetDiagBuffer(void) { return *m_DiagBuffer; }
474 
475  /// Get diag context data for the current thread
477 
478  /// Thread ID
479  typedef Uint8 TTID;
480 
481  /// Get cached thread ID
482  TTID GetTID(void) const { return m_TID; }
483 
484  /// Get thread post number
486 
487  void AddCollectGuard(CDiagCollectGuard* guard);
490 
491  void CollectDiagMessage(const SDiagMessage& mess);
492 
493 private:
496 
497  // Guards override the global post level and define severity
498  // for collecting messages.
499  typedef list<CDiagCollectGuard*> TCollectGuards;
500 
501  // Collected diag messages
502  typedef list<SDiagMessage> TDiagCollection;
503 
504  unique_ptr<CDiagBuffer> m_DiagBuffer; // Thread's diag buffer
505  TTID m_TID; // Cached thread ID
506  TCount m_ThreadPostNumber; // Number of posted messages
509  size_t m_DiagCollectionSize; // cached size of m_DiagCollection
511  CRef<CRequestContext> m_DefaultRequestCtx; // Default request context
512 };
513 
514 
516 {
517  // the severities will be adjusted by x_Init()
519 }
520 
521 
523 {
524  // the severities will be adjusted by x_Init()
525  x_Init(eDiag_Critical, print_severity, eDiscard);
526 }
527 
528 
530  EDiagSev collect_severity,
531  EAction action)
532 {
533  // the severities will be adjusted by x_Init()
534  x_Init(print_severity, collect_severity, action);
535 }
536 
537 
539  EDiagSev collect_severity,
540  EAction action)
541 {
542  // Get current print severity
543  EDiagSev psev, csev;
544  CDiagContextThreadData& thr_data =
546  if ( thr_data.GetCollectGuard() ) {
547  psev = thr_data.GetCollectGuard()->GetPrintSeverity();
548  csev = thr_data.GetCollectGuard()->GetCollectSeverity();
549  }
550  else {
551  psev = CDiagBuffer::sm_PostSeverity;
552  csev = psev;
553  }
554  psev = CompareDiagPostLevel(psev, print_severity) > 0
555  ? psev : print_severity;
556  csev = CompareDiagPostLevel(csev, collect_severity) < 0
557  ? csev : collect_severity;
558 
561  m_PrintSev = psev;
562  m_CollectSev = csev;
563  m_SeverityCap = csev;
564  m_Action = action;
565  thr_data.AddCollectGuard(this);
566 }
567 
568 
570 {
571  Release();
572 }
573 
574 
576 {
578  thr_data.RemoveCollectGuard(this);
579 }
580 
581 
583 {
584  SetAction(action);
585  Release();
586 }
587 
588 
590 {
591  if ( CompareDiagPostLevel(m_PrintSev, sev) < 0 ) {
592  m_PrintSev = sev;
593  }
594 }
595 
596 
598 {
599  if ( CompareDiagPostLevel(m_CollectSev, sev) < 0 ) {
600  m_CollectSev = sev;
601  }
602 }
603 
604 
605 ///////////////////////////////////////////////////////
606 // Static variables for Trace and Post filters
607 
610 
611 
612 // Analogue to strstr.
613 // Returns a pointer to the last occurrence of a search string in a string
614 const char*
615 str_rev_str(const char* begin_str, const char* end_str, const char* str_search)
616 {
617  if (begin_str == NULL)
618  return NULL;
619  if (end_str == NULL)
620  return NULL;
621  if (str_search == NULL)
622  return NULL;
623 
624  const char* search_char = str_search + strlen(str_search);
625  const char* cur_char = end_str;
626 
627  do {
628  --search_char;
629  do {
630  --cur_char;
631  } while(*cur_char != *search_char && cur_char != begin_str);
632  if (*cur_char != *search_char)
633  return NULL;
634  }
635  while (search_char != str_search);
636 
637  return cur_char;
638 }
639 
640 
641 
642 /////////////////////////////////////////////////////////////////////////////
643 /// CDiagCompileInfo::
644 
646  : m_File(""),
647  m_Module(""),
648  m_Line(0),
649  m_CurrFunctName(0),
650  m_Parsed(false),
651  m_ClassSet(false)
652 {
653 }
654 
656  int line,
657  const char* curr_funct,
658  const char* module)
659  : m_File(file),
660  m_Module(""),
661  m_Line(line),
662  m_CurrFunctName(curr_funct),
663  m_Parsed(false),
664  m_ClassSet(false)
665 {
666  if (!file) {
667  m_File = "";
668  return;
669  }
670  if (!module)
671  return;
672  if ( 0 != strcmp(module, "NCBI_MODULE") && x_NeedModule() ) {
673  m_Module = module;
674  }
675 }
676 
677 
679  int line,
680  const string& curr_funct,
681  const string& module)
682  : m_File(""),
683  m_Module(""),
684  m_Line(line),
685  m_CurrFunctName(""),
686  m_Parsed(false),
687  m_ClassSet(false)
688 {
689  SetFile(file);
690  if ( m_File && !module.empty() && x_NeedModule() ) {
691  SetModule(module);
692  }
693  SetFunction(curr_funct);
694 }
695 
696 
698 {
699  // Check for a file extension without creating of temporary string objects
700  const char* cur_extension = strrchr(m_File, '.');
701  if (cur_extension == NULL)
702  return false;
703 
704  if (*(cur_extension + 1) != '\0') {
705  ++cur_extension;
706  } else {
707  return false;
708  }
709 
710  return strcmp(cur_extension, "cpp") == 0 ||
711  strcmp(cur_extension, "C") == 0 ||
712  strcmp(cur_extension, "c") == 0 ||
713  strcmp(cur_extension, "cxx") == 0;
714 }
715 
716 
718 {
719 }
720 
721 
722 void CDiagCompileInfo::SetFile(const string& file)
723 {
724  m_StrFile = file;
725  m_File = m_StrFile.c_str();
726 }
727 
728 
729 void CDiagCompileInfo::SetModule(const string& module)
730 {
731  m_StrModule = module;
732  m_Module = m_StrModule.c_str();
733 }
734 
735 
737 {
738  m_Line = line;
739 }
740 
741 
742 void CDiagCompileInfo::SetFunction(const string& func)
743 {
744  m_Parsed = false;
745  m_StrCurrFunctName = func;
746  if (m_StrCurrFunctName.find(')') == NPOS) {
747  m_StrCurrFunctName += "()";
748  }
750  m_FunctName.clear();
751  if ( !m_ClassSet ) {
752  m_ClassName.clear();
753  }
754 }
755 
756 
757 void CDiagCompileInfo::SetClass(const string& cls)
758 {
759  m_ClassName = cls;
760  m_ClassSet = true;
761 }
762 
763 
764 // Skip matching l/r separators, return the last char before them
765 // or null if the separators are unbalanced.
766 const char* find_match(char lsep,
767  char rsep,
768  const char* start,
769  const char* stop)
770 {
771  if (*(stop - 1) != rsep) return stop;
772  int balance = 1;
773  const char* pos = stop - 2;
774  for (; pos > start; pos--) {
775  if (*pos == rsep) {
776  balance++;
777  }
778  else if (*pos == lsep) {
779  if (--balance == 0) break;
780  }
781  }
782  return (pos <= start) ? NULL : pos;
783 }
784 
785 
786 void
788 {
789  m_Parsed = true;
790  if (!m_CurrFunctName || !(*m_CurrFunctName)) {
791  return;
792  }
793  // Parse curr_funct
794 
795  // Skip function arguments
796  size_t len = strlen(m_CurrFunctName);
797  const char* end_str = find_match('(', ')',
799  m_CurrFunctName + len);
800  if (end_str == m_CurrFunctName + len) {
801  // Missing '('
802  return;
803  }
804  if ( end_str ) {
805  // Skip template arguments
806  end_str = find_match('<', '>', m_CurrFunctName, end_str);
807  }
808  if ( !end_str ) {
809  return;
810  }
811  // Get a function/method name
812  const char* start_str = NULL;
813 
814  // Get a function start position.
815  const char* start_str_tmp =
816  str_rev_str(m_CurrFunctName, end_str, "::");
817  bool has_class = start_str_tmp != NULL;
818  if (start_str_tmp != NULL) {
819  start_str = start_str_tmp + 2;
820  } else {
821  start_str_tmp = str_rev_str(m_CurrFunctName, end_str, " ");
822  if (start_str_tmp != NULL) {
823  start_str = start_str_tmp + 1;
824  }
825  }
826 
827  const char* cur_funct_name =
828  (start_str == NULL ? m_CurrFunctName : start_str);
829  while (cur_funct_name && *cur_funct_name &&
830  (*cur_funct_name == '*' || *cur_funct_name == '&')) {
831  ++cur_funct_name;
832  }
833  size_t cur_funct_name_len = end_str - cur_funct_name;
834  m_FunctName = string(cur_funct_name, cur_funct_name_len);
835 
836  // Get a class name
837  if (has_class && !m_ClassSet) {
838  end_str = find_match('<', '>', m_CurrFunctName, start_str - 2);
839  start_str = str_rev_str(m_CurrFunctName, end_str, " ");
840  const char* cur_class_name =
841  (start_str == NULL ? m_CurrFunctName : start_str + 1);
842  while (cur_class_name && *cur_class_name &&
843  (*cur_class_name == '*' || *cur_class_name == '&')) {
844  ++cur_class_name;
845  }
846  size_t cur_class_name_len = end_str - cur_class_name;
847  m_ClassName = string(cur_class_name, cur_class_name_len);
848  }
849 }
850 
851 
852 
853 ///////////////////////////////////////////////////////
854 // CDiagRecycler::
855 
857 public:
859  {
860 #if defined(NCBI_POSIX_THREADS) && defined(HAVE_PTHREAD_ATFORK)
861  pthread_atfork(s_NcbiDiagPreFork, // before
862  s_NcbiDiagPostFork, // after in parent
863  s_NcbiDiagPostFork); // after in child
864 #endif
865  }
867  {
868  SetDiagHandler(0, false);
869  SetDiagErrCodeInfo(0, false);
870  }
871 };
872 
874 
875 
876 // Helper function to check if applog severity lock is set and
877 // return the proper printable severity.
878 
880 {
881  if ( !CDiagContext::IsApplogSeverityLocked() ) return sev;
882  return CompareDiagPostLevel(sev, eDiag_Warning) < 0
883  ? sev : eDiag_Warning;
884 }
885 
886 
887 inline Uint8 s_GetThreadId(void)
888 {
889  if (s_PrintSystemTID->Get()) {
890  return (Uint8)GetCurrentThreadSystemID(); // GCC 3.4.6 gives warning - ignore it.
891  } else {
892  return CThread::GetSelf();
893  }
894 }
895 
896 
903 };
904 
906 #define USE_TLS_DATA_CACHE
907 #ifdef USE_TLS_DATA_CACHE
909 #endif
910 
911 
913 {
915 }
916 
917 
919  : m_DiagBuffer(new CDiagBuffer),
920  m_TID(s_GetThreadId()),
921  m_ThreadPostNumber(0),
922  m_DiagCollectionSize(0)
923 {
924  // Default context should auto-reset on request start.
927 }
928 
929 
931 {
932 #ifdef USE_TLS_DATA_CACHE
933  if ( s_ThreadDataCache == this ) {
934  s_ThreadDataCache = 0;
935  }
936 #endif
937 }
938 
939 
941  void* /*cleanup_data*/)
942 {
943  if ( CThread::IsMain() ) {
944  // Copy properties from the main thread's TLS to the global properties.
946  // Print stop message.
949  }
950  }
951  s_ThreadDataState = eDeinitialized; // re-enable protection
952  delete value;
953 }
954 
955 
957 {
958  // If any of this method's direct or indirect callees attempted to
959  // report a (typically fatal) error, the result would ordinarily
960  // be infinite recursion resulting in an undignified crash. The
961  // business with s_ThreadDataState allows the program to exit
962  // (relatively) gracefully in such cases.
963  //
964  // In principle, such an event could happen at any stage; in
965  // practice, however, the first call involves a superset of most
966  // following calls' actions, at least until deep program
967  // finalization. Moreover, attempting to catch bad calls
968  // mid-execution would both add overhead and open up uncatchable
969  // opportunities for inappropriate recursion.
970 
971  #ifdef USE_TLS_DATA_CACHE
973  return *data;
974  }
975 #endif
976 
977 
979  // Avoid false positives, while also taking care not to call
980  // anything that might itself produce diagnostics.
981  EThreadDataState thread_data_state = s_ThreadDataState; // for Clang10
982  switch (thread_data_state) {
983  case eInitialized:
984  break;
985 
986  case eUninitialized:
988  break;
989 
990  case eInitializing:
991  cerr << "FATAL ERROR: inappropriate recursion initializing NCBI"
992  " diagnostic framework." << endl;
993  Abort();
994  break;
995 
996  case eDeinitialized:
998  break;
999 
1000  case eReinitializing:
1001  cerr << "FATAL ERROR: NCBI diagnostic framework no longer"
1002  " initialized." << endl;
1003  Abort();
1004  break;
1005  }
1006  }
1007 
1009  s_ThreadData(nullptr, CSafeStaticLifeSpan(CSafeStaticLifeSpan::eLifeSpan_Long, 1));
1010 
1011  // Deliberate leak for 'data'-- for both cases:
1012  // GetValue() and new CDiagContextThreadData.
1014 
1015  CDiagContextThreadData* data = s_ThreadData.GetValue();
1016  if ( !data ) {
1017  // Cleanup data set to null for any thread except the main one.
1018  // This value is used as a flag to copy threads' properties to global
1019  // upon TLS cleanup.
1022  !CThread::IsMain() ? 0 : (void*)(1),
1025  }
1026 
1027 #ifdef USE_TLS_DATA_CACHE
1029 #endif
1030  return *data;
1031 }
1032 
1033 
1035 {
1037  return *m_RequestCtx;
1038 }
1039 
1040 
1041 static const Uint8 kOwnerTID_None = Uint8(-1);
1042 
1044 {
1045  if (m_RequestCtx) {
1046  // If pointers are the same (e.g. consecutive calls with the same ctx)
1047  if (ctx == m_RequestCtx.GetPointer()) {
1048  return;
1049  }
1050 
1051  // Reset TID in the context.
1053  }
1054 
1055  if (!ctx) {
1057  return;
1058  }
1059 
1060  m_RequestCtx = ctx;
1061  if (!m_RequestCtx->GetReadOnly()) {
1063  // Save current TID in the context.
1065  }
1066  else if (m_RequestCtx->m_OwnerTID != m_TID) {
1067  ERR_POST_X_ONCE(29,
1068  "Using the same CRequestContext in multiple threads is unsafe!"
1069  << CStackTrace());
1070  _TROUBLE;
1071  }
1072  }
1073  else {
1074  // Read-only contexts should not remember owner thread.
1076  }
1077 }
1078 
1079 
1082 {
1083  return inc == ePostNumber_Increment ?
1085 }
1086 
1087 
1089 {
1090  m_CollectGuards.push_front(guard);
1091 }
1092 
1093 
1095 {
1096  TCollectGuards::iterator itg = find(
1097  m_CollectGuards.begin(), m_CollectGuards.end(), guard);
1098  if (itg == m_CollectGuards.end()) {
1099  return; // The guard has been already released
1100  }
1101  m_CollectGuards.erase(itg);
1102  auto action = guard->GetAction();
1103  unique_ptr<CDiagLock> lock;
1104  if (action == CDiagCollectGuard::ePrintCapped) {
1105  lock.reset(new CDiagLock(CDiagLock::eRead));
1106  EDiagSev cap = guard->GetSeverityCap();
1107  auto start = guard->GetStartingPoint();
1109  if (itc->m_ThrPost >= start
1110  && CompareDiagPostLevel(itc->m_Severity, cap) > 0) {
1111  itc->m_Severity = cap;
1112  }
1113  }
1114  action = CDiagCollectGuard::ePrint;
1115  }
1116  if ( !m_CollectGuards.empty() ) {
1117  return;
1118  // Previously printing was done for each guard, discarding - only for
1119  // the last guard.
1120  }
1121  // If this is the last guard, perform its action
1122  if (lock.get() == nullptr) {
1123  lock.reset(new CDiagLock(CDiagLock::eRead));
1124  }
1125  if (action == CDiagCollectGuard::ePrint) {
1127  if ( handler ) {
1129  if ((itc->m_Flags & eDPF_IsConsole) != 0) {
1130  // Print all messages to console
1131  handler->PostToConsole(*itc);
1132  }
1133  // Make sure only messages with the severity above allowed
1134  // are printed to normal log.
1136  guard->GetCollectSeverity());
1137  bool allow_trace = post_sev == eDiag_Trace;
1138  if (itc->m_Severity == eDiag_Trace && !allow_trace) {
1139  continue; // trace is disabled
1140  }
1141  if (itc->m_Severity < post_sev) {
1142  continue;
1143  }
1144  handler->Post(*itc);
1145  }
1146  size_t discarded = m_DiagCollectionSize - m_DiagCollection.size();
1147  if (discarded > 0) {
1148  ERR_POST_X(18, Warning << "Discarded " << discarded <<
1149  " messages due to collection limit. Set "
1150  "DIAG_COLLECT_LIMIT to increase the limit.");
1151  }
1152  }
1153  }
1154  m_DiagCollection.clear();
1156 }
1157 
1158 
1160 {
1161  return m_CollectGuards.empty() ? NULL : m_CollectGuards.front();
1162 }
1163 
1164 
1166 {
1167  static CSafeStatic<NCBI_PARAM_TYPE(Diag, Collect_Limit)> s_DiagCollectLimit;
1168  if (m_DiagCollectionSize >= s_DiagCollectLimit->Get()) {
1169  m_DiagCollection.erase(m_DiagCollection.begin());
1170  }
1171  m_DiagCollection.push_back(mess);
1173 }
1174 
1175 
1178 {
1179  return GetRequestContext().GetRequestID();
1180 }
1181 
1182 
1184 {
1186 }
1187 
1188 
1190 {
1192 }
1193 
1194 
1196 {
1198 }
1199 
1200 
1201 extern void SetDiagRequestId(Uint8 id)
1202 {
1204 }
1205 
1206 
1207 ///////////////////////////////////////////////////////
1208 // CDiagContext::
1209 
1210 
1211 // AppState formatting/parsing
1212 static const char* s_AppStateStr[] = {
1213  "NS", "PB", "P", "PE", "RB", "R", "RE"
1214 };
1215 
1216 static const char* s_LegacyAppStateStr[] = {
1217  "AB", "A", "AE"
1218 };
1219 
1221 {
1222  return s_AppStateStr[state];
1223 }
1224 
1226 {
1227  for (int st = (int)eDiagAppState_AppBegin;
1228  st <= eDiagAppState_RequestEnd; st++) {
1229  if (state == s_AppStateStr[st]) {
1230  return (EDiagAppState)st;
1231  }
1232  }
1233  // Backward compatibility - allow to use 'A' instead of 'P'
1234  for (int st = (int)eDiagAppState_AppBegin;
1235  st <= eDiagAppState_AppEnd; st++) {
1236  if (state == s_LegacyAppStateStr[st - 1]) {
1237  return (EDiagAppState)st;
1238  }
1239  }
1240 
1241  // Throw to notify caller about invalid app state.
1242  NCBI_THROW(CException, eUnknown, "Invalid EDiagAppState value");
1243  /*NOTREACHED*/
1244  return eDiagAppState_NotSet;
1245 }
1246 
1247 
1248 NCBI_PARAM_DECL(bool, Diag, UTC_Timestamp);
1249 NCBI_PARAM_DEF_EX(bool, Diag, UTC_Timestamp, false,
1250  eParam_NoThread, DIAG_UTC_TIMESTAMP);
1251 
1252 
1253 static CTime s_GetFastTime(void)
1254 {
1255  static CSafeStatic<NCBI_PARAM_TYPE(Diag, UTC_Timestamp)> s_UtcTimestamp;
1256  return (s_UtcTimestamp->Get() && !CDiagContext::IsApplogSeverityLocked()) ?
1258 }
1259 
1260 
1262 {
1263  SDiagMessageData(void);
1265 
1266  string m_Message;
1267  string m_File;
1268  string m_Module;
1269  string m_Class;
1270  string m_Function;
1271  string m_Prefix;
1272  string m_ErrText;
1273 
1276 
1277  // If the following properties are not set, take them from DiagContext.
1278  string m_Host;
1279  string m_Client;
1280  string m_Session;
1281  string m_AppName;
1283 };
1284 
1285 
1287  : m_UID(0),
1288  m_Time(s_GetFastTime()),
1289  m_AppState(eDiagAppState_NotSet)
1290 {
1291 }
1292 
1293 
1296 
1297 
1299  : m_UID(0),
1300  m_Host(new CEncodedString),
1301  m_Username(new CEncodedString),
1302  m_AppName(new CEncodedString),
1303  m_AppNameSet(false),
1304  m_LoggedHitId(false),
1305  m_ExitCode(0),
1306  m_ExitCodeSet(false),
1307  m_ExitSig(0),
1308  m_AppState(eDiagAppState_AppBegin),
1309  m_StopWatch(new CStopWatch(CStopWatch::eStart)),
1310  m_MaxMessages(100), // limit number of collected messages to 100
1311  m_AppLogRC(new CRequestRateControl(
1312  GetLogRate_Limit(eLogRate_App),
1313  CTimeSpan((long)GetLogRate_Period(eLogRate_App)),
1314  CTimeSpan((long)0),
1315  CRequestRateControl::eErrCode,
1316  CRequestRateControl::eDiscrete)),
1317  m_ErrLogRC(new CRequestRateControl(
1318  GetLogRate_Limit(eLogRate_Err),
1319  CTimeSpan((long)GetLogRate_Period(eLogRate_Err)),
1320  CTimeSpan((long)0),
1321  CRequestRateControl::eErrCode,
1322  CRequestRateControl::eDiscrete)),
1323  m_TraceLogRC(new CRequestRateControl(
1324  GetLogRate_Limit(eLogRate_Trace),
1325  CTimeSpan((long)GetLogRate_Period(eLogRate_Trace)),
1326  CTimeSpan((long)0),
1327  CRequestRateControl::eErrCode,
1328  CRequestRateControl::eDiscrete)),
1329  m_AppLogSuspended(false),
1330  m_ErrLogSuspended(false),
1331  m_TraceLogSuspended(false)
1332 {
1333  sm_Instance = this;
1334 }
1335 
1336 
1338 {
1339  sm_Instance = NULL;
1340 }
1341 
1342 
1344 {
1345  CMutexGuard lock(s_ApproveMutex);
1348  CTimeSpan((long)0),
1353  CTimeSpan((long)0),
1358  CTimeSpan((long)0),
1361  m_AppLogSuspended = false;
1362  m_ErrLogSuspended = false;
1363  m_TraceLogSuspended = false;
1364 }
1365 
1366 
1368 {
1369  switch ( type ) {
1370  case eLogRate_App:
1371  return s_AppLogRateLimit->Get();
1372  case eLogRate_Err:
1373  return s_ErrLogRateLimit->Get();
1374  case eLogRate_Trace:
1375  default:
1376  return s_TraceLogRateLimit->Get();
1377  }
1378 }
1379 
1381 {
1382  CMutexGuard lock(s_ApproveMutex);
1383  switch ( type ) {
1384  case eLogRate_App:
1385  s_AppLogRateLimit->Set(limit);
1386  if ( m_AppLogRC.get() ) {
1387  m_AppLogRC->Reset(limit,
1389  CTimeSpan((long)0),
1392  }
1393  m_AppLogSuspended = false;
1394  break;
1395  case eLogRate_Err:
1396  s_ErrLogRateLimit->Set(limit);
1397  if ( m_ErrLogRC.get() ) {
1398  m_ErrLogRC->Reset(limit,
1400  CTimeSpan((long)0),
1403  }
1404  m_ErrLogSuspended = false;
1405  break;
1406  case eLogRate_Trace:
1407  default:
1408  s_TraceLogRateLimit->Set(limit);
1409  if ( m_TraceLogRC.get() ) {
1410  m_TraceLogRC->Reset(limit,
1412  CTimeSpan((long)0),
1415  }
1416  m_TraceLogSuspended = false;
1417  break;
1418  }
1419 }
1420 
1422 {
1423  switch ( type ) {
1424  case eLogRate_App:
1425  return s_AppLogRatePeriod->Get();
1426  case eLogRate_Err:
1427  return s_ErrLogRatePeriod->Get();
1428  case eLogRate_Trace:
1429  default:
1430  return s_TraceLogRatePeriod->Get();
1431  }
1432 }
1433 
1435 {
1436  CMutexGuard lock(s_ApproveMutex);
1437  switch ( type ) {
1438  case eLogRate_App:
1439  s_AppLogRatePeriod->Set(period);
1440  if ( m_AppLogRC.get() ) {
1442  CTimeSpan((long)period),
1443  CTimeSpan((long)0),
1446  }
1447  m_AppLogSuspended = false;
1448  break;
1449  case eLogRate_Err:
1450  s_ErrLogRatePeriod->Set(period);
1451  if ( m_ErrLogRC.get() ) {
1453  CTimeSpan((long)period),
1454  CTimeSpan((long)0),
1457  }
1458  m_ErrLogSuspended = false;
1459  break;
1460  case eLogRate_Trace:
1461  default:
1462  s_TraceLogRatePeriod->Set(period);
1463  if ( m_TraceLogRC.get() ) {
1465  CTimeSpan((long)period),
1466  CTimeSpan((long)0),
1469  }
1470  m_TraceLogSuspended = false;
1471  break;
1472  }
1473 }
1474 
1475 
1477  bool* show_warning)
1478 {
1479  bool approved = true;
1480  if ( IsSetDiagPostFlag(eDPF_AppLog, msg.m_Flags) ) {
1481  if ( m_AppLogRC->IsEnabled() ) {
1482  CMutexGuard lock(s_ApproveMutex);
1483  approved = m_AppLogRC->Approve();
1484  }
1485  if ( approved ) {
1486  m_AppLogSuspended = false;
1487  }
1488  else {
1489  *show_warning = !m_AppLogSuspended.exchange(true);
1490  }
1491  }
1492  else {
1493  switch ( msg.m_Severity ) {
1494  case eDiag_Info:
1495  case eDiag_Trace:
1496  if ( m_TraceLogRC->IsEnabled() ) {
1497  CMutexGuard lock(s_ApproveMutex);
1498  approved = m_TraceLogRC->Approve();
1499  }
1500  if ( approved ) {
1501  m_TraceLogSuspended = false;
1502  }
1503  else {
1504  *show_warning = !m_TraceLogSuspended.exchange(true);
1505  }
1506  break;
1507  default:
1508  if ( m_ErrLogRC->IsEnabled() ) {
1509  CMutexGuard lock(s_ApproveMutex);
1510  approved = m_ErrLogRC->Approve();
1511  }
1512  if ( approved ) {
1513  m_ErrLogSuspended = false;
1514  }
1515  else {
1516  *show_warning = !m_ErrLogSuspended.exchange(true);
1517  }
1518  }
1519  }
1520  return approved;
1521 }
1522 
1523 
1525 
1527 {
1528  if ( !sm_PID ) {
1530  }
1531  return sm_PID;
1532 }
1533 
1534 
1535 bool CDiagContext::UpdatePID(void)
1536 {
1537  TPID old_pid = sm_PID;
1538  TPID new_pid = CCurrentProcess::GetPid();
1539  if (sm_PID == new_pid) {
1540  return false;
1541  }
1542  sm_PID = new_pid;
1544  TUID old_uid = ctx.GetUID();
1545  // Update GUID to match the new PID
1546  ctx.x_CreateUID();
1547  ctx.Extra().
1548  Print("action", "fork").
1549  Print("parent_guid", ctx.GetStringUID(old_uid)).
1550  Print("parent_pid", NStr::NumericToString(old_pid));
1551  return true;
1552 }
1553 
1554 
1556 {
1557  TPID new_pid = CCurrentProcess::GetPid();
1558  if (sm_PID == new_pid) {
1559  return false;
1560  }
1561  sm_PID = new_pid;
1562  // Update GUID to match the new PID.
1563  // GetDiagContext() call is not async safe in common, but should be safe
1564  // after Diag API initialization, it's just doing a pointer type cast.
1566  return true;
1567 }
1568 
1569 
1570 void CDiagContext::UpdateOnFork(TOnForkFlags flags)
1571 {
1572  if (flags & fOnFork_AsyncSafe) {
1574  return;
1575  }
1576  // not async safe:
1577 
1578  // Do not perform any actions in the parent process
1579  if ( !UpdatePID() ) return;
1580 
1581  if (flags & fOnFork_ResetTimer) {
1582  GetDiagContext().m_StopWatch->Restart();
1583  }
1584  if (flags & fOnFork_PrintStart) {
1586  }
1587 }
1588 
1589 
1592 
1594 {
1595  // This method expected to be async-signal-safe
1596  // https://man7.org/linux/man-pages/man7/signal-safety.7.html
1597  // All non-async-safe code should go to x_CreateUID().
1598 
1599  typedef CDiagContext::TPID TPID;
1600  typedef CDiagContext::TUID TUID;
1601 
1602  TPID pid = CDiagContext::GetPID();
1603  time_t t = time(0);
1604 
1605  base &= 0xFFFF;
1606 
1607  // The low 4 bits are reserved for GUID generator version number.
1608  // As of October 2017 the following versions are used:
1609  // 0 - very old C++ Toolkit
1610  // 1 - contemporary C++ Toolkit
1611  // 2 - ?
1612  // 3 - "C" client (CLOG API); "Python" module 'python-applog'
1613  // 4 - ?
1614  // 5 - Java
1615  // 6 - Scala
1616  // 13 - Perl (NcbiApplog.pm)
1617 
1618  return (TUID(base) << 48) |
1619  ((TUID(pid) & 0xFFFF) << 32) |
1620  ((TUID(t) & 0xFFFFFFF) << 4) |
1621  1; // version #1 - fixed type conversion bug
1622 }
1623 
1625 {
1626  TUID base = kUID_InitBase;
1627  const string& host = GetHost();
1628  for (auto c : host) {
1629  base = base * kUID_Mult + c;
1630  }
1631  m_UID = s_CreateUID(base);
1632 }
1633 
1635 {
1636  // This method expected to be async-signal-safe
1637  // https://man7.org/linux/man-pages/man7/signal-safety.7.html
1638  // Implemented for Unix only, on other platforms works the same
1639  // as a regular x_CreateUID().
1640 
1641 #if defined(NCBI_OS_UNIX)
1642  TUID base = kUID_InitBase;
1643  struct utsname buf;
1644  if (uname(&buf) >= 0) {
1645  const char* s = buf.nodename;
1646  while (*s) {
1647  base = base * kUID_Mult + *s;
1648  s++;
1649  }
1650  }
1651  else {
1652  // Rough workaround in case of uname() error
1653  base = base * kUID_Mult + (TUID)GetPID();
1654  }
1655  m_UID = s_CreateUID(base);
1656 #else
1657  x_CreateUID();
1658 #endif
1659 }
1660 
1661 
1662 DEFINE_STATIC_MUTEX(s_CreateGUIDMutex);
1663 
1665 {
1666  if ( !m_UID ) {
1667  CMutexGuard guard(s_CreateGUIDMutex);
1668  if ( !m_UID ) {
1669  x_CreateUID();
1670  }
1671  }
1672  return m_UID;
1673 }
1674 
1675 
1676 void CDiagContext::GetStringUID(TUID uid, char* buf, size_t buflen) const
1677 {
1678  _ASSERT(buflen > 16);
1679  int hi = int((uid >> 32) & 0xFFFFFFFF);
1680  int lo = int(uid & 0xFFFFFFFF);
1681  snprintf(buf, buflen, "%08X%08X", hi, lo);
1682 }
1683 
1684 
1685 void CDiagContext::GetStringUID(TUID uid, char* buf) const
1686 {
1687  GetStringUID(uid, buf, 17);
1688 }
1689 
1690 
1692 {
1693  char buf[17];
1694  if (uid == 0) {
1695  uid = GetUID();
1696  }
1697  GetStringUID(uid, buf, 17);
1698  return string(buf);
1699 }
1700 
1701 
1703 {
1704  if (uid == 0) {
1705  uid = GetUID();
1706  }
1707  time_t t = time(0);
1708  // Clear old timestamp
1709  uid &= ~((TUID)0xFFFFFFF << 4);
1710  // Add current timestamp
1711  return uid | ((TUID(t) & 0xFFFFFFF) << 4);
1712 }
1713 
1714 
1715 string CDiagContext::GetNextHitID(void) const
1716 {
1717  return x_GetNextHitID(false);
1718 }
1719 
1720 
1721 string CDiagContext::x_GetNextHitID(bool is_default) const
1722 {
1723  static CAtomicCounter s_HitIdCounter;
1724 
1725  Uint8 hi = GetUID();
1726  Uint4 b3 = Uint4((hi >> 32) & 0xFFFFFFFF);
1727  Uint4 b2 = Uint4(hi & 0xFFFFFFFF);
1728 
1730  Uint8 tid = (thr_data.GetTID() & 0xFFFFFF) << 40;
1731  Uint8 rid;
1732  if ( !is_default ) {
1733  rid = ((Uint8)thr_data.GetRequestContext().GetRequestID() & 0xFFFFFF) << 16;
1734  }
1735  else {
1736  rid = ((Uint8)0xFFFFFF) << 16;
1737  }
1738  Uint8 us = (Uint8)(s_HitIdCounter.Add(1) & 0xFFFF);
1739  Uint8 lo = tid | rid | us;
1740  Uint4 b1 = Uint4((lo >> 32) & 0xFFFFFFFF);
1741  Uint4 b0 = Uint4(lo & 0xFFFFFFFF);
1742  char buf[33];
1743  snprintf(buf, 33, "%08X%08X%08X%08X", b3, b2, b1, b0);
1744  return string(buf);
1745 }
1746 
1747 
1749 {
1751 }
1752 
1753 
1754 const string& CDiagContext::GetUsername(void) const
1755 {
1756  return m_Username->GetOriginalString();
1757 }
1758 
1759 
1760 void CDiagContext::SetUsername(const string& username)
1761 {
1762  m_Username->SetString(username);
1763 }
1764 
1765 
1766 const string& CDiagContext::GetHost(void) const
1767 {
1768  // Check context properties
1769  if ( !m_Host->IsEmpty() ) {
1770  return m_Host->GetOriginalString();
1771  }
1772  if ( !m_HostIP.empty() ) {
1773  return m_HostIP;
1774  }
1775 
1776  // All platforms - check NCBI_HOST first.
1777  const TXChar* ncbi_host = NcbiSys_getenv(_TX("NCBI_HOST"));
1778  if (ncbi_host && *ncbi_host) {
1779  m_Host->SetString(_T_STDSTRING(ncbi_host));
1780  return m_Host->GetOriginalString();
1781  }
1782 
1783 #if defined(NCBI_OS_UNIX)
1784  // UNIX - use uname()
1785  {{
1786  struct utsname buf;
1787  if (uname(&buf) >= 0) {
1788  m_Host->SetString(buf.nodename);
1789  return m_Host->GetOriginalString();
1790  }
1791  }}
1792 #endif
1793 
1794 #if defined(NCBI_OS_MSWIN)
1795  // MSWIN - use COMPUTERNAME
1796  const TXChar* compname = NcbiSys_getenv(_TX("COMPUTERNAME"));
1797  if ( compname && *compname ) {
1798  m_Host->SetString(_T_STDSTRING(compname));
1799  return m_Host->GetOriginalString();
1800  }
1801 #endif
1802 
1803  // Server env. - use SERVER_ADDR
1804  const TXChar* servaddr = NcbiSys_getenv(_TX("SERVER_ADDR"));
1805  if ( servaddr && *servaddr ) {
1806  m_Host->SetString(_T_STDSTRING(servaddr));
1807  }
1808  return m_Host->GetOriginalString();
1809 }
1810 
1811 
1812 const string& CDiagContext::GetEncodedHost(void) const
1813 {
1814  if ( !m_Host->IsEmpty() ) {
1815  return m_Host->GetEncodedString();
1816  }
1817  if ( !m_HostIP.empty() ) {
1818  return m_HostIP;
1819  }
1820  // Initialize m_Host, this does not change m_HostIP
1821  GetHost();
1822  return m_Host->GetEncodedString();
1823 }
1824 
1825 
1826 const string& CDiagContext::GetHostname(void) const
1827 {
1828  return m_Host->GetOriginalString();
1829 }
1830 
1831 
1832 const string& CDiagContext::GetEncodedHostname(void) const
1833 {
1834  return m_Host->GetEncodedString();
1835 }
1837 
1838 void CDiagContext::SetHostname(const string& hostname)
1840  m_Host->SetString(hostname);
1844 void CDiagContext::SetHostIP(const string& ip)
1847  m_HostIP.clear();
1848  ERR_POST("Bad host IP value: " << ip);
1849  return;
1850  }
1855 DEFINE_STATIC_MUTEX(s_AppNameMutex);
1856 
1857 const string& CDiagContext::GetAppName(void) const
1858 {
1859  if ( !m_AppNameSet ) {
1860  CMutexGuard guard(s_AppNameMutex);
1861  if ( !m_AppNameSet ) {
1863  // Cache appname if CNcbiApplication instance exists only,
1864  // the Diag API can be used before instance initialization and appname
1865  // could be set to a binary name, and changes later (CXX-5076).
1866  if (CNcbiApplication::Instance() && !m_AppName->IsEmpty()) {
1867  m_AppNameSet = true;
1868  }
1869  }
1870  }
1871  return m_AppName->GetOriginalString();
1872 }
1873 
1874 
1875 const string& CDiagContext::GetEncodedAppName(void) const
1876 {
1877  if ( !m_AppNameSet ) {
1878  GetAppName(); // Initialize app name
1879  }
1880  return m_AppName->GetEncodedString();
1881 }
1882 
1883 
1884 void CDiagContext::SetAppName(const string& app_name)
1885 {
1886  if ( m_AppNameSet ) {
1887  // AppName can be set only once
1888  ERR_POST("Application name cannot be changed.");
1889  return;
1890  }
1891  CMutexGuard guard(s_AppNameMutex);
1892  m_AppName->SetString(app_name);
1893  m_AppNameSet = true;
1894  if ( m_AppName->IsEncoded() ) {
1895  ERR_POST("Illegal characters in application name: '" << app_name <<
1896  "', using URL-encode.");
1897  }
1898 }
1899 
1900 
1902 {
1904 }
1905 
1906 
1908 {
1910 }
1911 
1912 
1914 {
1915  s_AutoWrite_Context->Set(value);
1916 }
1917 
1918 
1919 inline bool IsGlobalProperty(const string& name)
1920 {
1921  return
1928 }
1929 
1930 
1931 void CDiagContext::SetProperty(const string& name,
1932  const string& value,
1934 {
1935  // Global properties
1936  if (name == kProperty_UserName) {
1937  SetUsername(value);
1938  return;
1939  }
1940  if (name == kProperty_HostName) {
1941  SetHostname(value);
1942  return;
1943  }
1944  if (name == kProperty_HostIP) {
1945  SetHostIP(value);
1946  return;
1947  }
1948  if (name == kProperty_AppName) {
1949  SetAppName(value);
1950  return;
1951  }
1952  if (name == kProperty_ExitCode) {
1954  return;
1955  }
1956  if (name == kProperty_ExitSig) {
1958  return;
1959  }
1960 
1961  // Request properties
1962  if (name == kProperty_AppState) {
1963  try {
1965  }
1966  catch (const CException&) {
1967  }
1968  return;
1969  }
1970  if (name == kProperty_ClientIP) {
1972  return;
1973  }
1974  if (name == kProperty_SessionID) {
1976  return;
1977  }
1978  if (name == kProperty_ReqStatus) {
1979  if ( !value.empty() ) {
1982  }
1983  else {
1985  }
1986  return;
1987  }
1988  if (name == kProperty_BytesRd) {
1991  return;
1992  }
1993  if (name == kProperty_BytesWr) {
1996  return;
1997  }
1998  if (name == kProperty_ReqTime) {
1999  // Cannot set this property
2000  return;
2001  }
2002 
2003  if ( mode == eProp_Default ) {
2005  }
2006 
2007  if ( mode == eProp_Global ) {
2009  m_Properties[name] = value;
2010  }
2011  if ( sm_Instance && s_AutoWrite_Context->Get() ) {
2014  }
2015 }
2016 
2017 
2018 string CDiagContext::GetProperty(const string& name,
2019  EPropertyMode mode) const
2020 {
2021  // Global properties
2022  if (name == kProperty_UserName) {
2023  return GetUsername();
2024  }
2025  if (name == kProperty_HostName) {
2026  return GetHostname();
2027  }
2028  if (name == kProperty_HostIP) {
2029  return GetHostIP();
2030  }
2031  if (name == kProperty_AppName) {
2032  return GetAppName();
2033  }
2034  if (name == kProperty_ExitCode) {
2035  return NStr::IntToString(m_ExitCode);
2036  }
2037  if (name == kProperty_ExitSig) {
2038  return NStr::IntToString(m_ExitSig);
2039  }
2040 
2041  // Request properties
2042  if (name == kProperty_AppState) {
2043  return s_AppStateToStr(GetAppState());
2044  }
2045  if (name == kProperty_ClientIP) {
2046  return GetRequestContext().GetClientIP();
2047  }
2048  if (name == kProperty_SessionID) {
2049  return GetSessionID();
2050  }
2051  if (name == kProperty_ReqStatus) {
2053  NStr::IntToString(GetRequestContext().GetRequestStatus())
2054  : kEmptyStr;
2055  }
2056  if (name == kProperty_BytesRd) {
2057  return NStr::Int8ToString(GetRequestContext().GetBytesRd());
2058  }
2059  if (name == kProperty_BytesWr) {
2060  return NStr::Int8ToString(GetRequestContext().GetBytesWr());
2061  }
2062  if (name == kProperty_ReqTime) {
2064  }
2065 
2066  // Check global properties
2069  return gprop != m_Properties.end() ? gprop->second : kEmptyStr;
2070 }
2071 
2072 
2073 void CDiagContext::DeleteProperty(const string& name,
2075 {
2076  // Check global properties
2078  TProperties::iterator gprop = m_Properties.find(name);
2079  if (gprop != m_Properties.end()) {
2080  m_Properties.erase(gprop);
2081  }
2082 }
2083 
2084 
2086 {
2088  ITERATE(TProperties, gprop, m_Properties) {
2090  gprop->first + "=" + gprop->second);
2091  }
2092 }
2093 
2094 
2095 void CDiagContext::PrintStart(const string& message)
2096 {
2098  // Print extra message. If ncbi_role and/or ncbi_location are set,
2099  // they will be logged by this extra. Otherwise nothing will be printed.
2101 
2102  static const char* kCloudIdFile = "/etc/ncbi/cloudid";
2103  CFile cloudid(kCloudIdFile);
2104  if ( cloudid.Exists() ) {
2105  CDiagContext_Extra ex = Extra();
2106  CNcbiIfstream in(kCloudIdFile);
2107  while (!in.eof() && in.good()) {
2108  string s;
2109  getline(in, s);
2110  size_t sep = s.find('\t');
2111  if (sep == NPOS) continue;
2112  string name = NStr::TruncateSpaces(s.substr(0, sep));
2113  string value = s.substr(sep + 1);
2114  ex.Print(name, value);
2115  }
2116  ex.Flush();
2117  }
2118 
2119  x_LogEnvironment();
2120 
2121  // Log NCBI_LOG_FIELDS
2122  map<string, string> env_map;
2123  for (char **env = environ; *env; ++env) {
2124  string n, v;
2126  NStr::ToLower(n);
2127  NStr::ReplaceInPlace(n, "_", "-");
2128  env_map[n] = v;
2129  }
2130 
2131  CNcbiLogFields f("env");
2132  f.LogFields(env_map);
2133 
2134  // Log hit id if already available.
2136 }
2137 
2138 
2140 {
2141  // If no hit id has been logged until app-stop,
2142  // try to force logging now.
2143  if (x_IsSetDefaultHitID()) {
2145  }
2146  else {
2148  if (ctx.IsSetHitID(CRequestContext::eHitID_Request)) {
2149  ctx.x_LogHitID(true);
2150  }
2151  }
2153 }
2154 
2155 
2156 void CDiagContext::PrintExtra(const string& message)
2157 {
2159 }
2160 
2161 
2163 {
2165 }
2166 
2167 
2169  : m_EventType(event_type),
2170  m_Args(0),
2171  m_Counter(new int(1)),
2172  m_Typed(false),
2173  m_PerfStatus(0),
2174  m_PerfTime(0),
2175  m_Flushed(false),
2176  m_AllowBadNames(false)
2177 {
2178 }
2179 
2180 
2182  double timespan,
2183  TExtraArgs& args)
2184  : m_EventType(SDiagMessage::eEvent_PerfLog),
2185  m_Args(0),
2186  m_Counter(new int(1)),
2187  m_Typed(false),
2188  m_PerfStatus(status),
2189  m_PerfTime(timespan),
2190  m_Flushed(false),
2191  m_AllowBadNames(false)
2192 {
2193  if (args.empty()) return;
2194  m_Args = new TExtraArgs;
2195  m_Args->splice(m_Args->end(), args);
2196 }
2197 
2198 
2200  : m_EventType(const_cast<CDiagContext_Extra&>(args).m_EventType),
2201  m_Args(const_cast<CDiagContext_Extra&>(args).m_Args),
2202  m_Counter(const_cast<CDiagContext_Extra&>(args).m_Counter),
2203  m_Typed(args.m_Typed),
2204  m_PerfStatus(args.m_PerfStatus),
2205  m_PerfTime(args.m_PerfTime),
2206  m_Flushed(args.m_Flushed),
2207  m_AllowBadNames(args.m_AllowBadNames)
2208 {
2209  (*m_Counter)++;
2210 }
2211 
2212 
2214 {
2215  m_AllowBadNames = true;
2216  return *this;
2217 }
2218 
2219 
2222 
2223 
2225 {
2226  const string& role = CDiagContext::GetHostRole();
2227  const string& loc = CDiagContext::GetHostLocation();
2228  if ( !role.empty() ) {
2229  Print("ncbi_role", role);
2230  }
2231  if ( !loc.empty() ) {
2232  Print("ncbi_location", loc);
2233  }
2234  return *this;
2235 }
2236 
2238 {
2239  Print("ncbi_app_username", CSystemInfo::GetUserName());
2241  if (ins) {
2242  Print("ncbi_app_path", ins->GetProgramExecutablePath());
2243  const CVersionAPI& ver = ins->GetFullVersion();
2244  if (!ver.GetBuildInfo().date.empty()) {
2245  Print("ncbi_app_build_date", ver.GetBuildInfo().date);
2246  }
2247 #if NCBI_PACKAGE
2248  Print("ncbi_app_package_name", ver.GetPackageName());
2249  string pkv = NStr::NumericToString(ver.GetPackageVersion().GetMajor())
2252  Print("ncbi_app_package_version", pkv);
2253  Print("ncbi_app_package_date", NCBI_SBUILDINFO_DEFAULT().date);
2254 #endif
2255  const SBuildInfo& bi = ver.GetBuildInfo();
2256  initializer_list<SBuildInfo::EExtra> bi_num = {
2262  };
2263  for(SBuildInfo::EExtra key : bi_num) {
2264  string value = bi.GetExtraValue(key);
2265  if (!value.empty()) {
2267  }
2268  }
2269  return *this;
2270  }
2271 #if defined(NCBI_TEAMCITY_PROJECT_NAME)
2272  Print("ncbi_app_tc_project", NCBI_TEAMCITY_PROJECT_NAME);
2273 #endif
2274 #if defined(NCBI_TEAMCITY_BUILDCONF_NAME)
2275  Print("ncbi_app_tc_conf", NCBI_TEAMCITY_BUILDCONF_NAME);
2276 #endif
2277 #if defined(NCBI_TEAMCITY_BUILD_NUMBER)
2278  Print("ncbi_app_tc_build", NStr::NumericToString<Uint8>(NCBI_TEAMCITY_BUILD_NUMBER));
2279 #endif
2280 #if defined(NCBI_TEAMCITY_BUILD_ID)
2281  Print("ncbi_app_build_id", NCBI_TEAMCITY_BUILD_ID);
2282 #endif
2283 
2284  return *this;
2285 }
2286 
2288 {
2290  if (ins) {
2291  const CVersionAPI& ver = ins->GetFullVersion();
2292  const CVersionInfo& vi = ver.GetVersionInfo();
2293 //#if defined (NCBI_SC_VERSION) && NCBI_SC_VERSION <= 21
2294 #if 1
2295  string str = NStr::NumericToString(vi.GetMajor()) + "." +
2296  NStr::NumericToString(vi.GetMinor()) + "." +
2298  Print("ncbi_app_version", str);
2299 #else
2300  initializer_list<int> vi_num = {vi.GetMajor(), vi.GetMinor(), vi.GetPatchLevel()};
2301  Print("ncbi_app_version", NStr::JoinNumeric(vi_num.begin(), vi_num.end(), "."));
2302 #endif
2303 
2304  const SBuildInfo& bi = ver.GetBuildInfo();
2305  initializer_list<SBuildInfo::EExtra> bi_num =
2309  for(SBuildInfo::EExtra key : bi_num) {
2310  string value = bi.GetExtraValue(key);
2311  if (!value.empty()) {
2313  }
2314  }
2315  return *this;
2316  }
2317 #if defined(NCBI_PRODUCTION_VER)
2318  Print("ncbi_app_prod_version", NStr::NumericToString<Uint8>(NCBI_PRODUCTION_VER));
2319 #elif defined(NCBI_DEVELOPMENT_VER)
2320  Print("ncbi_app_dev_version", NStr::NumericToString<Uint8>(NCBI_DEVELOPMENT_VER));
2321 #endif
2322 #if defined(NCBI_SC_VERSION)
2323  Print("ncbi_app_sc_version", NStr::NumericToString<Uint8>(NCBI_SC_VERSION));
2324 #endif
2325 #if defined(NCBI_SUBVERSION_REVISION)
2326  Print("ncbi_app_vcs_revision", NStr::NumericToString<Uint8>(NCBI_SUBVERSION_REVISION));
2327 #endif
2328  return *this;
2329 }
2330 
2331 
2333 {
2335  return;
2336  }
2337 
2338  // Add ncbi-role and ncbi-location just before setting m_Flushed flag.
2341  }
2342  // Prevent double-flush
2343  m_Flushed = true;
2344 
2345  // Ignore extra messages without arguments. Allow request-start/request-stop
2346  // and stop without arguments. Empty start messages are printed only if
2347  // ncbi_role/ncbi_location are available (see above).
2350  (!m_Args || m_Args->empty()) ) {
2351  return;
2352  }
2353 
2355  EDiagAppState app_state = ctx.GetAppState();
2356  bool app_state_updated = false;
2358  if (app_state != eDiagAppState_RequestBegin &&
2359  app_state != eDiagAppState_Request) {
2360  ctx.SetAppState(eDiagAppState_RequestBegin);
2361  app_state_updated = true;
2362  }
2364  }
2366  if (app_state != eDiagAppState_RequestEnd) {
2367  ctx.SetAppState(eDiagAppState_RequestEnd);
2368  app_state_updated = true;
2369  }
2370  }
2371 
2372  string s;
2374  s.append(to_string(m_PerfStatus)).append(1, ' ')
2376  }
2377 
2378  if (!s_DisableAppLog->Get()) {
2379  SDiagMessage mess(eDiag_Info,
2380  s.data(), s.size(),
2381  0, 0, // file, line
2383  NULL,
2384  0, 0, // err code/subcode
2385  NULL,
2386  0, 0, 0); // module/class/function
2387  mess.m_Event = m_EventType;
2388  if (m_Args && !m_Args->empty()) {
2389  mess.m_ExtraArgs.splice(mess.m_ExtraArgs.end(), *m_Args);
2390  }
2391  mess.m_TypedExtra = m_Typed;
2393 
2394  GetDiagBuffer().DiagHandler(mess);
2395  }
2396 
2397  if ( app_state_updated ) {
2399  ctx.SetAppState(eDiagAppState_Request);
2400  }
2402  ctx.SetAppState(eDiagAppState_AppRun);
2403  }
2404  }
2405 }
2406 
2407 
2409 {
2410  if ( m_Counter && --(*m_Counter) == 0) {
2411  Flush();
2412  delete m_Args;
2413  m_Args = 0;
2414  }
2415 }
2416 
2417 
2420 {
2421  if (this != &args) {
2422  x_Release();
2423  m_Args = const_cast<CDiagContext_Extra&>(args).m_Args;
2424  m_Counter = const_cast<CDiagContext_Extra&>(args).m_Counter;
2425  m_Typed = args.m_Typed;
2426  m_PerfStatus = args.m_PerfStatus;
2427  m_PerfTime = args.m_PerfTime;
2428  m_Flushed = args.m_Flushed;
2430  (*m_Counter)++;
2431  }
2432  return *this;
2433 }
2434 
2435 
2437 {
2438  x_Release();
2439  if ( *m_Counter == 0) {
2440  delete m_Counter;
2441  }
2442 }
2443 
2445 {
2446  // Only allow extra events to be printed/flushed multiple times
2448  ERR_POST_ONCE(
2449  "Attempt to set request start/stop arguments after flushing");
2450  return false;
2451  }
2452 
2453  // For extra messages reset flushed state.
2454  m_Flushed = false;
2455  return true;
2456 }
2457 
2458 
2459 static const char* kNcbiApplogKeywordStrings[] = {
2460  // ncbi_phid, self_url and http_referrer are reported by corelib and cgi
2461  // and should not be renamed.
2462  /*
2463  "ncbi_phid",
2464  "self_url",
2465  "http_referrer",
2466  "prev_phid",
2467  */
2468  "app",
2469  "host",
2470  "ip",
2471  "ip_addr",
2472  "session_id",
2473  "date",
2474  "datetime",
2475  "time",
2476  "guid",
2477  "acc_time_secs",
2478  "bytes_in_bit",
2479  "bytes_out_bit",
2480  "day",
2481  "day_of_week",
2482  "day_of_year",
2483  "exec_time_msec_bit",
2484  "start_time_msec",
2485  "second",
2486  "minute",
2487  "hour",
2488  "iter",
2489  "month",
2490  "pid",
2491  "status",
2492  "tid",
2493  "year",
2494  "geo_metro_code",
2495  "geo_latitude_hundredths",
2496  "geo_longitude_hundredths",
2497  "session_pageviews",
2498  "ping_count",
2499  "session_duration",
2500  "visit_duration",
2501  "time_on_page",
2502  "time_to_last_ping",
2503  "bytes_in",
2504  "bytes_out",
2505  "exec_time_msec",
2506  "self_url_path",
2507  "self_url_host",
2508  "self_url_file",
2509  "self_url_file_ext",
2510  "self_url_top_level_dir",
2511  "self_url_directory",
2512  "referrer",
2513  "ref_url",
2514  "ref_host",
2515  "http_referer",
2516  "referer",
2517  "interactive_hit",
2518  "statusuidlookup",
2519  "usageuidlookup",
2520  "all",
2521  "has_app_data",
2522  "has_critical",
2523  "has_duplicate_nexus",
2524  "has_duplicate_phid",
2525  "has_error",
2526  "has_fatal",
2527  "has_info",
2528  "has_multiple_phid",
2529  "has_ping",
2530  "has_start",
2531  "has_stop",
2532  "has_warning",
2533  "is_app_hit",
2534  "is_perf",
2535  "multiple_session_hit",
2536  "multiple_session_ip",
2537  "ncbi_phid_first_render",
2538  "phid_from_other_request",
2539  "phid_stack_ambiguous_relationship",
2540  "phid_stack_has_fake_child",
2541  "phid_stack_has_fake_parent",
2542  "phid_stack_missing_child",
2543  "phid_stack_missing_parent",
2544  "phid_stack_multivalued",
2545  "session_bot",
2546  "session_bot_by_ip",
2547  "session_bot_by_rate",
2548  "session_bot_by_user_agent",
2549  "session_bot_ip",
2550  "session_has_ping",
2551  "browser_name",
2552  "browser_version",
2553  "browser_major_version",
2554  "browser_platform",
2555  "browser_engine",
2556  "is_mobile",
2557  "is_tablet",
2558  "is_phone",
2559  "is_browser_bot",
2560  "applog_db_path",
2561  "applog_db_size",
2562  "applog_db_size_hit_fraction",
2563  "applog_db_machine",
2564  "applog_db_vol_id",
2565  "applog_db_type",
2566  "applog_db_date_tag",
2567  "applog_db_date_range",
2568  "applog_db_stream",
2569  "applog_db_create_time",
2570  "applog_db_modify_time",
2571  "applog_db_state",
2572  "applog_db_uid",
2573  "applog_db_agent_host",
2574  "applog_db_agent_pid",
2575  "applog_db_agent_tid",
2576  "applog_db_volume_served_time",
2577  "phid_stack_missing_parent",
2578  "phid_stack_missing_child",
2579  "phid_stack_has_fake_child",
2580  "phid_stack_has_fake_parent",
2581  "phid_stack_ambiguous_relationship",
2582  "phid_stack_multivalued",
2583  "ncbi_phid_first_render",
2584  "multiple_session_hit",
2585  "has_ping",
2586  "session_has_ping",
2587  "has_duplicate_nexus",
2588  "has_app_data",
2589  "has_multiple_phid",
2590  "multi_hit_phid",
2591  "session_bot_by_rate",
2592  "session_bot_by_user_agent",
2593  "session_bot",
2594  "session_bot_by_ip",
2595  "session_bot_ip",
2596  "multiple_session_ip",
2597  "collapsed_jsevent"
2598 };
2599 
2600 
2602 {
2603  typedef unordered_set<string> TKeywords;
2604 
2606  {
2607  TKeywords* kw = new TKeywords;
2608  int sz = sizeof(kNcbiApplogKeywordStrings) / sizeof(kNcbiApplogKeywordStrings[0]);
2609  for (int i = 0; i < sz; ++i) {
2610  kw->insert(kNcbiApplogKeywordStrings[i]);
2611  }
2612  return kw;
2613  }
2614 
2615  void Cleanup(TKeywords& /*keywords*/) {}
2616 };
2617 
2618 
2620 
2622 CDiagContext_Extra::Print(const string& name, const string& value)
2623 {
2624  if ( !x_CanPrint() ) {
2625  return *this;
2626  }
2627 
2628  if ( !m_Args ) {
2629  m_Args = new TExtraArgs;
2630  }
2631 
2632  // Optimize inserting new pair into the args list, it is the same as:
2633  // m_Args->push_back(TExtraArg(name, value));
2634  m_Args->push_back(TExtraArg(kEmptyStr, kEmptyStr));
2635 
2636  // If arg name is a reserved applog keywork, rename it and log warning.
2638  if (kw.find(name) == kw.end()) {
2639  m_Args->rbegin()->first.assign(name);
2640  }
2641  else {
2642  string renamed = "auto_renamed_applog_keyword__" + name;
2643  m_Args->rbegin()->first.assign(renamed);
2644  ERR_POST("'" << name
2645  << "' is a reserved NCBI AppLog keyword, so it has been renamed to "
2646  << renamed);
2647  }
2648  m_Args->rbegin()->second.assign(value);
2649  return *this;
2650 }
2651 
2653 CDiagContext_Extra::Print(const string& name, const char* value)
2654 {
2655  return Print(name, string(value));
2656 }
2657 
2659 CDiagContext_Extra::Print(const string& name, int value)
2660 {
2661  return Print(name, NStr::IntToString(value));
2662 }
2663 
2665 CDiagContext_Extra::Print(const string& name, unsigned int value)
2666 {
2667  return Print(name, NStr::UIntToString(value));
2668 }
2669 
2671 CDiagContext_Extra::Print(const string& name, long value)
2672 {
2673  return Print(name, NStr::LongToString(value));
2674 }
2675 
2677 CDiagContext_Extra::Print(const string& name, unsigned long value)
2678 {
2679  return Print(name, NStr::ULongToString(value));
2680 }
2681 
2682 #if !NCBI_INT8_IS_LONG
2685 {
2686  return Print(name, NStr::Int8ToString(value));
2687 }
2690 {
2691  return Print(name, NStr::UInt8ToString(value));
2692 }
2693 #elif SIZEOF_LONG_LONG
2695 CDiagContext_Extra::Print(const string& name, long long value)
2696 {
2697  return Print(name, NStr::Int8ToString(value));
2698 }
2699 
2701 CDiagContext_Extra::Print(const string& name, unsigned long long value)
2702 {
2703  return Print(name, NStr::UInt8ToString(value));
2704 }
2705 #endif
2706 
2708 CDiagContext_Extra::Print(const string& name, char value)
2709 {
2710  return Print(name, string(1,value));
2711 }
2712 
2714 CDiagContext_Extra::Print(const string& name, signed char value)
2715 {
2716  return Print(name, string(1,value));
2717 }
2718 
2720 CDiagContext_Extra::Print(const string& name, unsigned char value)
2721 {
2722  return Print(name, string(1,value));
2723 }
2724 
2726 CDiagContext_Extra::Print(const string& name, double value)
2727 {
2728  return Print(name, NStr::DoubleToString(value));
2729 }
2730 
2732 CDiagContext_Extra::Print(const string& name, bool value)
2733 {
2734  return Print(name, NStr::BoolToString(value));
2735 }
2736 
2739 {
2740  if ( !x_CanPrint() ) {
2741  return *this;
2742  }
2743 
2744  if ( !m_Args ) {
2745  m_Args = new TExtraArgs;
2746  }
2747  m_Args->splice(m_Args->end(), args);
2748  return *this;
2749 }
2750 
2751 
2752 static const char* kExtraTypeArgName = "NCBIEXTRATYPE";
2753 
2755 {
2756  m_Typed = true;
2758  return *this;
2759 }
2760 
2761 
2762 void CDiagContext::PrintRequestStart(const string& message)
2763 {
2764  EDiagAppState app_state = GetAppState();
2765  bool app_state_updated = false;
2766  if (app_state != eDiagAppState_RequestBegin &&
2767  app_state != eDiagAppState_Request) {
2769  app_state_updated = true;
2770  }
2772  if ( app_state_updated ) {
2774  }
2775 }
2776 
2777 
2779 {
2780  EDiagAppState app_state = GetAppState();
2781  bool app_state_updated = false;
2782  if (app_state != eDiagAppState_RequestEnd) {
2784  app_state_updated = true;
2785  }
2787  if ( app_state_updated ) {
2789  // Now back at application level check if a default hit id
2790  // needs to be logged.
2792  }
2793 }
2794 
2795 
2797 {
2798  return m_AppState;
2799 }
2800 
2801 
2803 {
2804  // This checks thread's state first, then calls GetAppState if necessary.
2805  return GetRequestContext().GetAppState();
2806 }
2807 
2808 
2810 {
2811  m_AppState = state;
2812 }
2813 
2814 
2816 {
2818  switch ( state ) {
2820  case eDiagAppState_AppRun:
2821  case eDiagAppState_AppEnd:
2822  {
2823  ctx.SetAppState(eDiagAppState_NotSet);
2824  m_AppState = state;
2825  break;
2826  }
2828  case eDiagAppState_Request:
2830  ctx.SetAppState(state);
2831  break;
2832  default:
2833  ERR_POST_X(17, Warning << "Invalid EDiagAppState value");
2834  }
2835 }
2836 
2837 
2839 {
2840  switch ( mode ) {
2841  case eProp_Default:
2842  SetAppState(state);
2843  break;
2844  case eProp_Global:
2846  break;
2847  case eProp_Thread:
2849  break;
2850  }
2851 }
2852 
2853 
2854 // Session id passed through HTTP
2855 NCBI_PARAM_DECL(string, Log, Http_Session_Id);
2856 NCBI_PARAM_DEF_EX(string, Log, Http_Session_Id, "", eParam_NoThread,
2857  HTTP_NCBI_SID);
2858 static CSafeStatic<NCBI_PARAM_TYPE(Log, Http_Session_Id)> s_HttpSessionId;
2859 
2860 // Session id set in the environment
2861 NCBI_PARAM_DECL(string, Log, Session_Id);
2862 NCBI_PARAM_DEF_EX(string, Log, Session_Id, "", eParam_NoThread,
2863  NCBI_LOG_SESSION_ID);
2865 
2866 
2867 DEFINE_STATIC_MUTEX(s_DefaultSidMutex);
2868 
2869 string CDiagContext::GetDefaultSessionID(void) const
2870 {
2871  CMutexGuard lock(s_DefaultSidMutex);
2872  if (m_DefaultSessionId.get() && !m_DefaultSessionId->IsEmpty()) {
2873  return m_DefaultSessionId->GetOriginalString();
2874  }
2875 
2876  // Needs initialization.
2877  if ( !m_DefaultSessionId.get() ) {
2879  }
2880  if ( m_DefaultSessionId->IsEmpty() ) {
2882  s_HttpSessionId->Get());
2883  if ( sid.empty() ) {
2885  s_DefaultSessionId->Get());
2886  }
2887  m_DefaultSessionId->SetString(sid);
2888  }
2889  return m_DefaultSessionId->GetOriginalString();
2890 }
2891 
2892 
2893 void CDiagContext::SetDefaultSessionID(const string& session_id)
2894 {
2895  CMutexGuard lock(s_DefaultSidMutex);
2896  if ( !m_DefaultSessionId.get() ) {
2898  }
2899  m_DefaultSessionId->SetString(session_id);
2900 }
2901 
2902 
2903 string CDiagContext::GetSessionID(void) const
2904 {
2906  if ( rctx.IsSetExplicitSessionID() ) {
2907  return rctx.GetSessionID();
2908  }
2909  return GetDefaultSessionID();
2910 }
2911 
2912 
2913 string CDiagContext::GetEncodedSessionID(void) const
2914 {
2916  if ( rctx.IsSetExplicitSessionID() ) {
2917  return rctx.GetEncodedSessionID();
2918  }
2919  GetDefaultSessionID(); // Make sure the default value is initialized.
2920  CMutexGuard lock(s_DefaultSidMutex);
2921  _ASSERT(m_DefaultSessionId.get());
2922  return m_DefaultSessionId->GetEncodedString();
2923 }
2924 
2925 
2926 NCBI_PARAM_DECL(string, Log, Client_Ip);
2927 NCBI_PARAM_DEF_EX(string, Log, Client_Ip, "", eParam_NoThread,
2928  NCBI_LOG_CLIENT_IP);
2930 
2931 
2933 {
2934  return s_DefaultClientIp->Get();
2935 }
2936 
2937 
2938 void CDiagContext::SetDefaultClientIP(const string& client_ip)
2939 {
2940  s_DefaultClientIp->Set(client_ip);
2941 }
2942 
2943 
2944 // Hit id passed through HTTP
2945 NCBI_PARAM_DECL(string, Log, Http_Hit_Id);
2946 NCBI_PARAM_DEF_EX(string, Log, Http_Hit_Id, "", eParam_NoThread,
2947  HTTP_NCBI_PHID);
2948 static CSafeStatic<NCBI_PARAM_TYPE(Log, Http_Hit_Id)> s_HttpHitId;
2949 
2950 // Hit id set in the environment or registry
2951 NCBI_PARAM_DECL(string, Log, Hit_Id);
2952 NCBI_PARAM_DEF_EX(string, Log, Hit_Id, "", eParam_NoThread,
2953  NCBI_LOG_HIT_ID);
2954 static CSafeStatic<NCBI_PARAM_TYPE(Log, Hit_Id)> s_HitId;
2955 
2956 
2957 DEFINE_STATIC_MUTEX(s_DefaultHidMutex);
2958 
2960 {
2962  return (state == eDiagAppState_AppBegin) ||
2963  (state == eDiagAppState_AppRun) ||
2965 }
2966 
2967 
2969 {
2970  // NOTE: The method must be always called with s_DefaultHidMutex locked.
2971 
2972  // Log the default hit id only when at application level. Otherwise
2973  // pospone it untill request-stop/app-stop.
2974  if (m_LoggedHitId || !m_DefaultHitId.get() || m_DefaultHitId->Empty() ||
2976  return;
2977  }
2979  m_LoggedHitId = true;
2980 }
2981 
2982 
2984 {
2985  CMutexGuard guard(s_DefaultHidMutex);
2986  x_LogHitID();
2987 }
2988 
2989 
2991 {
2992  CMutexGuard guard(s_DefaultHidMutex);
2993  return m_DefaultHitId.get() && !m_DefaultHitId->Empty();
2994 }
2995 
2996 
2998 {
2999  CMutexGuard guard(s_DefaultHidMutex);
3000  if (m_DefaultHitId.get() && !m_DefaultHitId->Empty()) {
3001  return *m_DefaultHitId;
3002  }
3003 
3004  if ( !m_DefaultHitId.get() ) {
3005  m_DefaultHitId.reset(new CSharedHitId());
3006  }
3007  if ( m_DefaultHitId->Empty() ) {
3009  s_HttpHitId->Get()));
3010  if ( m_DefaultHitId->Empty() ) {
3011  string phid = CRequestContext::SelectLastHitID(
3012  s_HitId->Get());
3013  if ( !phid.empty() ) {
3014  const char* c_env_job_id = getenv("JOB_ID");
3015  string env_job_id = c_env_job_id ? string(c_env_job_id): "";
3016  const char* c_env_task_id = getenv("SGE_TASK_ID");
3017  string env_task_id = c_env_task_id ? string(c_env_task_id) : "";
3018  if (env_task_id.find_first_not_of("0123456789") != NPOS) {
3019  // If task id is 'undefined', set it to 1 - it's a job with
3020  // just one task.
3021  env_task_id = "1";
3022  }
3023  if (!env_job_id.empty() && !env_task_id.empty()) {
3024  // FIX LATER: It's better to use J and T prefix
3025  // to indicate job/task, but currently CRequestContext
3026  // logs errors if sub-hit id contains non-digits.
3027  // Using leading 0s is a temporary solution. Later,
3028  // when most apps are recompiled with the new
3029  // CRequestContext, we can reconsider using J/T.
3030  string jid = ".000" + env_job_id;
3031  string tid = ".00" + env_task_id;
3032  size_t jid_pos = phid.find(jid);
3033  if (jid_pos == NPOS) {
3034  // No current job id in the phid.
3035  phid += jid + tid;
3036  }
3037  else {
3038  // Job id is already in the phid. Check task id.
3039  if (phid.find(tid, jid_pos + jid.size()) == NPOS) {
3040  // New task in the same job.
3041  phid += tid;
3042  }
3043  // else - task id already in the phid. Ignore.
3044  }
3045  }
3046  }
3047  m_DefaultHitId->SetHitId(phid);
3048  }
3049  if (m_DefaultHitId->Empty() && (flag == eHitID_Create)) {
3050  m_DefaultHitId->SetHitId(x_GetNextHitID(true));
3051  }
3052  }
3053  // Default hit id always uses shared sub-hit counter.
3054  m_DefaultHitId->SetShared();
3056  // Log hit id if at application level.
3057  x_LogHitID();
3058  return *m_DefaultHitId;
3059 }
3060 
3061 
3062 void CDiagContext::SetDefaultHitID(const string& hit_id)
3063 {
3064  CMutexGuard guard(s_DefaultHidMutex);
3065  if ( !m_DefaultHitId.get() ) {
3066  m_DefaultHitId.reset(new CSharedHitId());
3067  }
3068  m_DefaultHitId->SetHitId(hit_id);
3069  // Default hit id always uses shared sub-hit counter.
3070  m_DefaultHitId->SetShared();
3071  // Log new hit id when at application level.
3072  m_LoggedHitId = false;
3073  x_LogHitID();
3074 }
3075 
3076 
3077 inline string s_ReadString(const char* filename)
3078 {
3079  string ret;
3080  CNcbiIfstream in(filename);
3081  if ( in.good() ) {
3082  getline(in, ret);
3083  }
3084  return ret;
3085 }
3086 
3087 
3090 
3091 const string& CDiagContext::GetHostRole(void)
3092 {
3093  if ( !s_HostRole->get() ) {
3095  if ( !s_HostRole->get() ) {
3096  unique_ptr<string> role(new string);
3097  const TXChar* env_role = NcbiSys_getenv(_TX("NCBI_ROLE"));
3098  if (env_role && *env_role) {
3099  *role = string(_T_CSTRING(env_role));
3100  }
3101  else {
3102  *role = s_ReadString("/etc/ncbi/role");
3103  }
3104  s_HostRole->reset(role.release());
3105  }
3106  }
3107  return **s_HostRole;
3108 }
3109 
3110 
3112 {
3113  if ( !s_HostLocation->get() ) {
3115  if ( !s_HostLocation->get() ) {
3116  unique_ptr<string> loc(new string);
3117  const TXChar* env_loc = NcbiSys_getenv(_TX("NCBI_LOCATION"));
3118  if (env_loc && *env_loc) {
3119  *loc = string(_T_CSTRING(env_loc));
3120  }
3121  else {
3122  *loc = s_ReadString("/etc/ncbi/location");
3123  }
3124  s_HostLocation->reset(loc.release());
3125  }
3126  }
3127  return **s_HostLocation;
3128 }
3129 
3130 
3131 const char* CDiagContext::kProperty_UserName = "user";
3132 const char* CDiagContext::kProperty_HostName = "host";
3133 const char* CDiagContext::kProperty_HostIP = "host_ip_addr";
3134 const char* CDiagContext::kProperty_ClientIP = "client_ip";
3135 const char* CDiagContext::kProperty_SessionID = "session_id";
3136 const char* CDiagContext::kProperty_AppName = "app_name";
3137 const char* CDiagContext::kProperty_AppState = "app_state";
3138 const char* CDiagContext::kProperty_ExitSig = "exit_signal";
3139 const char* CDiagContext::kProperty_ExitCode = "exit_code";
3140 const char* CDiagContext::kProperty_ReqStatus = "request_status";
3141 const char* CDiagContext::kProperty_ReqTime = "request_time";
3142 const char* CDiagContext::kProperty_BytesRd = "bytes_rd";
3143 const char* CDiagContext::kProperty_BytesWr = "bytes_wr";
3144 
3145 static const char* kDiagTimeFormat = "Y-M-DTh:m:s.rZ";
3146 // Fixed fields' widths
3147 static const int kDiagW_PID = 5;
3148 static const int kDiagW_TID = 3;
3149 static const int kDiagW_RID = 4;
3150 static const int kDiagW_AppState = 2;
3151 static const int kDiagW_SN = 4;
3152 static const int kDiagW_UID = 16;
3153 static const int kDiagW_Host = 15;
3154 static const int kDiagW_Client = 15;
3155 static const int kDiagW_Session = 24;
3156 
3157 static const char* kUnknown_Host = "UNK_HOST";
3158 static const char* kUnknown_Client = "UNK_CLIENT";
3159 static const char* kUnknown_Session = "UNK_SESSION";
3160 static const char* kUnknown_App = "UNK_APP";
3161 
3162 
3164  const SDiagMessage& msg) const
3165 {
3166  char uid[17];
3167  GetStringUID(msg.GetUID(), uid, 17);
3168  const string& host = msg.GetHost();
3169  string client = msg.GetClient();
3170  string session = msg.GetSession();
3171  const string& app = msg.GetAppName();
3172  const char* app_state = s_AppStateToStr(msg.GetAppState());
3173 
3174  // Print common fields
3175  ostr << setfill('0') << setw(kDiagW_PID) << msg.m_PID << '/'
3176  << setw(kDiagW_TID) << msg.m_TID << '/'
3177  << setw(kDiagW_RID) << msg.m_RequestId
3178  << "/"
3179  << setfill(' ') << setw(kDiagW_AppState) << setiosflags(IOS_BASE::left)
3180  << app_state << resetiosflags(IOS_BASE::left)
3181  << ' ' << setw(0) << setfill(' ') << uid << ' '
3182  << setfill('0') << setw(kDiagW_SN) << msg.m_ProcPost << '/'
3183  << setw(kDiagW_SN) << msg.m_ThrPost << ' '
3184  << setw(0) << msg.GetTime().AsString(kDiagTimeFormat) << ' '
3185  << setfill(' ') << setiosflags(IOS_BASE::left)
3186  << setw(kDiagW_Host)
3187  << (host.empty() ? kUnknown_Host : host.c_str()) << ' '
3188  << setw(kDiagW_Client)
3189  << (client.empty() ? kUnknown_Client : client.c_str()) << ' '
3190  << setw(kDiagW_Session)
3191  << (session.empty() ? kUnknown_Session : session.c_str()) << ' '
3192  << resetiosflags(IOS_BASE::left) << setw(0)
3193  << (app.empty() ? kUnknown_App : app.c_str()) << ' ';
3194 }
3195 
3196 
3198 {
3199  // Reset properties
3201  if ( ctx.IsRunning() ) {
3202  // The request is already running -
3203  // duplicate request start or missing request stop
3204  ERR_POST_ONCE(
3205  "Duplicate request-start or missing request-stop");
3206  }
3207 
3208  // Use the default client ip if no other value is set.
3209  if ( !ctx.IsSetExplicitClientIP() ) {
3210  string ip = GetDefaultClientIP();
3211  if ( !ip.empty() ) {
3212  ctx.SetClientIP(ip);
3213  }
3214  }
3215 
3216  ctx.StartRequest();
3217  x_LogEnvironment();
3218 }
3219 
3220 
3222 {
3223  // Print selected environment and registry values.
3224  static CSafeStatic<NCBI_PARAM_TYPE(Log, LogEnvironment)> s_LogEnvironment;
3225  string log_args = s_LogEnvironment->Get();
3226  if ( !log_args.empty() ) {
3227  list<string> log_args_list;
3228  NStr::Split(log_args, " ", log_args_list,
3231  extra.Print("LogEnvironment", "true");
3232  {{
3233  // The guard must be released before flushing the extra -
3234  // otherwise there may be a deadlock when accessing CParam-s
3235  // from other threads.
3237  if ( app ) {
3238  const CNcbiEnvironment& env = app->GetEnvironment();
3239  ITERATE(list<string>, it, log_args_list) {
3240  const string& val = env.Get(*it);
3241  extra.Print(*it, val);
3242  }
3243  }
3244  }}
3245  extra.Flush();
3246  }
3247  static CSafeStatic<NCBI_PARAM_TYPE(Log, LogRegistry)> s_LogRegistry;
3248  log_args = s_LogRegistry->Get();
3249  if ( !log_args.empty() ) {
3250  list<string> log_args_list;
3251  NStr::Split(log_args, " ", log_args_list,
3254  extra.Print("LogRegistry", "true");
3255  {{
3256  // The guard must be released before flushing the extra -
3257  // see above.
3259  if ( app ) {
3260  const CNcbiRegistry& reg = app->GetConfig();
3261  ITERATE(list<string>, it, log_args_list) {
3262  string section, name;
3263  NStr::SplitInTwo(*it, ":", section, name);
3264  const string& val = reg.Get(section, name);
3265  extra.Print(*it, val);
3266  }
3267  }
3268  }}
3269  extra.Flush();
3270  }
3271 }
3272 
3273 
3275  const string& message)
3276 {
3277  if ( IsSetOldPostFormat() ) {
3278  return;
3279  }
3280  string str;
3281  string prop;
3282  bool need_space = false;
3284 
3285  switch ( event ) {
3288  break;
3290  {
3291  x_StartRequest();
3292  break;
3293  }
3295  str.append(to_string(GetExitCode())).append(1, ' ').append(m_StopWatch->AsString());
3296  if (GetExitSignal() != 0) {
3297  str.append(" SIG=").append(to_string(GetExitSignal()));
3298  }
3299  need_space = true;
3300  break;
3302  {
3303  if ( !ctx.IsRunning() ) {
3304  // The request is not running -
3305  // duplicate request stop or missing request start
3306  ERR_POST_ONCE(
3307  "Duplicate request-stop or missing request-start");
3308  }
3309  str.append(to_string(ctx.GetRequestStatus())).append(1, ' ')
3310  .append(ctx.GetRequestTimer().AsString()).append(1, ' ')
3311  .append(to_string(ctx.GetBytesRd())).append(1, ' ')
3312  .append(to_string(ctx.GetBytesWr()));
3313  need_space = true;
3314  break;
3315  }
3316  default:
3317  return; // Prevent warning about other event types.
3318  }
3319  if ( !message.empty() ) {
3320  if (need_space) {
3321  str.append(1, ' ');
3322  }
3323  str.append(message);
3324  }
3325 
3326  if (!s_DisableAppLog->Get()) {
3327  SDiagMessage mess(eDiag_Info,
3328  str.data(), str.size(),
3329  0, 0, // file, line
3331  NULL,
3332  0, 0, // err code/subcode
3333  NULL,
3334  0, 0, 0); // module/class/function
3335  mess.m_Event = event;
3336  CDiagBuffer::DiagHandler(mess);
3337  }
3338 
3339  if (event == SDiagMessage::eEvent_RequestStop) {
3340  // Reset request context after stopping the request.
3341  ctx.StopRequest();
3342  }
3343 }
3344 
3345 
3347 {
3348  return s_OldPostFormat->Get();
3349 }
3350 
3351 
3353 {
3354  s_OldPostFormat->Set(value);
3355 }
3356 
3357 
3359 {
3360  return s_PrintSystemTID->Get();
3361 }
3362 
3363 
3365 {
3366  s_PrintSystemTID->Set(value);
3367 }
3368 
3369 
3370 void CDiagContext::InitMessages(size_t max_size)
3371 {
3372  if ( !m_Messages.get() ) {
3373  m_Messages.reset(new TMessages);
3374  }
3375  m_MaxMessages = max_size;
3376 }
3377 
3378 
3380 {
3381  if ( m_Messages.get() && m_Messages->size() < m_MaxMessages) {
3382  m_Messages->push_back(message);
3383  }
3384 }
3385 
3386 
3388 {
3389  if ( !m_Messages.get() || m_Messages->empty() ) {
3390  return;
3391  }
3392  CTeeDiagHandler* tee = dynamic_cast<CTeeDiagHandler*>(&handler);
3393  if (tee && !tee->GetOriginalHandler()) {
3394  // Tee over STDERR - flushing will create duplicate messages
3395  return;
3396  }
3397  unique_ptr<TMessages> tmp(m_Messages.release());
3398  //ERR_POST_X(1, Note << "***** BEGIN COLLECTED MESSAGES *****");
3399  NON_CONST_ITERATE(TMessages, it, *tmp.get()) {
3400  it->m_NoTee = true; // Do not tee duplicate messages to console.
3401  handler.Post(*it);
3402  if (it->m_Flags & eDPF_IsConsole) {
3403  handler.PostToConsole(*it);
3404  }
3405  }
3406  //ERR_POST_X(2, Note << "***** END COLLECTED MESSAGES *****");
3407  m_Messages.reset(tmp.release());
3408 }
3409 
3410 
3412 {
3413  m_Messages.reset();
3414 }
3415 
3416 
3417 // Diagnostics setup
3418 
3419 static const char* kRootLog = "/log/";
3420 
3422 {
3423  static const char* kToolkitRcPath = "/etc/toolkitrc";
3424  static const char* kWebDirToPort = "Web_dir_to_port";
3425 
3426  string log_path = kRootLog;
3427 
3428  string exe_path = CFile(app.GetProgramExecutablePath()).GetDir();
3429  CNcbiIfstream is(kToolkitRcPath, ios::binary);
3430  CNcbiRegistry reg(is);
3431  list<string> entries;
3433  size_t min_pos = exe_path.length();
3434  string web_dir;
3435  // Find the first dir name corresponding to one of the entries
3436  ITERATE(list<string>, it, entries) {
3437  if (!it->empty() && (*it)[0] != '/') {
3438  // not an absolute path
3439  string mask = "/" + *it;
3440  if (mask[mask.length() - 1] != '/') {
3441  mask += "/";
3442  }
3443  size_t pos = exe_path.find(mask);
3444  if (pos < min_pos) {
3445  min_pos = pos;
3446  web_dir = *it;
3447  }
3448  }
3449  else {
3450  // absolute path
3451  if (exe_path.substr(0, it->length()) == *it) {
3452  web_dir = *it;
3453  break;
3454  }
3455  }
3456  }
3457  if ( !web_dir.empty() ) {
3458  return log_path + reg.GetString(kWebDirToPort, web_dir, kEmptyStr);
3459  }
3460  // Could not find a valid web-dir entry, use /log/port or empty string
3461  // to try /log/srv later.
3462  const TXChar* port = NcbiSys_getenv(_TX("SERVER_PORT"));
3463  return port ? log_path + string(_T_CSTRING(port)) : kEmptyStr;
3464 }
3465 
3466 
3467 typedef NCBI_PARAM_TYPE(Log, Truncate) TLogTruncateParam;
3468 
3470 {
3471  return TLogTruncateParam::GetDefault();
3472 }
3473 
3474 
3476 {
3477  TLogTruncateParam::SetDefault(value);
3478 }
3479 
3480 
3481 bool OpenLogFileFromConfig(const string& logname)
3482 {
3483  if ( !logname.empty() ) {
3484  if (NCBI_PARAM_TYPE(Log, NoCreate)::GetDefault() && !CDirEntry(logname).Exists() ) {
3485  return false;
3486  }
3487  return SetLogFile(logname, eDiagFile_All, true);
3488  }
3489  return false;
3490 }
3491 
3492 
3494 {
3495  if (!s_FinishedSetupDiag) {
3496  SetupDiag();
3497  }
3498 }
3499 
3500 
3502 {
3504  s_FinishedSetupDiag = true;
3505 }
3506 
3507 
3508 // Load string value from config if not null, or from the environment.
3509 static string s_GetLogConfigString(const CTempString name,
3510  const CTempString defval,
3512 {
3513  if ( config ) {
3514  return config->GetString("LOG", name, defval);
3515  }
3516  string envname = "NCBI_CONFIG__LOG__";
3517  envname += name;
3518  const TXChar* val = NcbiSys_getenv(_T_XCSTRING(envname));
3519  return val ? CTempString(_T_STDSTRING(val)) : defval;
3520 }
3521 
3522 
3523 // Load bool value from config if not null, or from the environment.
3524 static bool s_GetLogConfigBool(const CTempString name,
3525  bool defval,
3527 {
3528  if ( config ) {
3529  return config->GetBool("LOG", name, defval);
3530  }
3531  string envname = "NCBI_CONFIG__LOG__";
3532  envname += name;
3533  const TXChar* val = NcbiSys_getenv(_T_XCSTRING(envname));
3534  if ( val ) {
3535  try {
3537  }
3538  catch (const CStringException&) {
3539  }
3540  }
3541  return defval;
3542 }
3543 
3544 
3547  EDiagCollectMessages collect,
3548  const char* cmd_logfile)
3549 {
3551 
3553 
3554  // Check severity level change status now.
3555  // This used to be done in SetDiag(), but if the first logging event is an
3556  // Extra(), new post format is enabled and post level is Trace, the initialization
3557  // results in infinite recursion. To prevent this, call this as soon as possible,
3558  // before any logging is done.
3559  if ( CDiagBuffer::sm_PostSeverityChange == eDiagSC_Unknown ) {
3560  CDiagBuffer::GetSeverityChangeEnabledFirstTime();
3561  }
3562 
3564  // Initialize message collecting
3565  if (collect == eDCM_Init) {
3566  ctx.InitMessages();
3567  }
3568  else if (collect == eDCM_InitNoLimit) {
3569  ctx.InitMessages(size_t(-1));
3570  }
3571 
3572  /*
3573  The default order of checking possible log locations is:
3574 
3575  1. CMD - '-logfile <filename>' command line arg
3576  2. ENV - NCBI_CONFIG__LOG__FILE (or [Log]/File)
3577  3. LOG - /log/ if writable
3578  4. Other locations depending on 'ds' flag (e.g. current directory)
3579 
3580  IgnoreEnvArg: if CMD should be checked before (true) or after (false) ENV/LOG.
3581  TryRootLogFirst: if LOG should be checked berofe (true) or after (false) ENV.
3582 
3583  The resulting order is:
3584 
3585  IgnoreEnvArg: | true | false
3586  -------------------+-------------------+---------------
3587  TryRootLogFirst: | |
3588  true | CMD, LOG, ENV | LOG, ENV, CMD
3589  false | CMD, ENV, LOG | ENV, LOG, CMD
3590 
3591  Successfull opening of CMD/ENV/LOG also overrides any eDS_* flags except eDS_User.
3592  */
3593 
3594  bool log_set = false;
3595  bool to_applog = false;
3596  string old_log_name;
3597  string new_log_name;
3598 
3599  CDiagHandler* old_handler = GetDiagHandler();
3600  if ( old_handler ) {
3601  old_log_name = old_handler->GetLogName();
3602  }
3603 
3604  string config_logfile = s_GetLogConfigString("FILE", kEmptyStr, config);
3605  if ( config_logfile.empty() ) {
3606  // Try the old-style name
3607  config_logfile = s_GetLogConfigString("File", kEmptyStr, config);
3608  }
3609  bool cmdline_first = s_GetLogConfigBool("IgnoreEnvArg", true, config);
3610  bool applog_first = s_GetLogConfigBool("TryRootLogFirst", false, config);
3611 
3612  if (ds != eDS_User) {
3613  // If cmdline_first is not set, it will be checked later, after
3614  // checking all other possibilities.
3615  if (cmdline_first && cmd_logfile) {
3616  if ( SetLogFile(cmd_logfile, eDiagFile_All) ) {
3617  log_set = true;
3618  new_log_name = cmd_logfile;
3619  }
3620  }
3621  // If applog_first is set, config will be checked later for eDS_ToStdlog
3622  // and eDS_Default but only if /log is not available.
3623  if (!log_set && !applog_first && !config_logfile.empty()) {
3624  if ( OpenLogFileFromConfig(config_logfile) ) {
3625  log_set = true;
3626  new_log_name = config_logfile;
3627  }
3628  }
3629  // The following three eDS_* options should check /log/* before using -logfile.
3630  if (!log_set && !cmdline_first && cmd_logfile &&
3631  ds != eDS_Default && ds != eDS_ToStdlog && ds != eDS_ToSyslog) {
3632  if ( SetLogFile(cmd_logfile, eDiagFile_All) ) {
3633  log_set = true;
3634  new_log_name = cmd_logfile;
3635  }
3636  }
3637  }
3638 
3639  if ( !log_set ) {
3640  switch ( ds ) {
3641  case eDS_ToStdout:
3642  if (old_log_name != kLogName_Stdout) {
3644  SetDiagHandler(new CStreamDiagHandler(&cout, true, kLogName_Stdout), true);
3645  log_set = true;
3646  new_log_name = kLogName_Stdout;
3647  }
3648  break;
3649  case eDS_ToStderr:
3650  if (old_log_name != kLogName_Stderr) {
3652  SetDiagHandler(new CStreamDiagHandler(&cerr, true, kLogName_Stderr), true);
3653  log_set = true;
3654  new_log_name = kLogName_Stderr;
3655  }
3656  break;
3657  case eDS_ToMemory:
3658  if (old_log_name != kLogName_Memory) {
3659  ctx.InitMessages(size_t(-1));
3660  SetDiagStream(0, false, 0, 0, kLogName_Memory);
3661  log_set = true;
3662  new_log_name = kLogName_Memory;
3663  }
3664  collect = eDCM_NoChange; // prevent flushing to memory
3665  break;
3666  case eDS_Disable:
3667  if (old_log_name != kLogName_None) {
3668  SetDiagStream(0, false, 0, 0, kLogName_None);
3669  log_set = true;
3670  new_log_name = kLogName_None;
3671  }
3672  break;
3673  case eDS_User:
3674  collect = eDCM_Discard;
3675  break;
3676  case eDS_AppSpecific:
3677  collect = eDCM_Discard;
3678  break;
3679  case eDS_ToSyslog:
3680  if (old_log_name != CSysLog::kLogName_Syslog) {
3681  try {
3683  SetDiagHandler(new CSysLog);
3684  log_set = true;
3685  new_log_name = CSysLog::kLogName_Syslog;
3686  break;
3687  } catch (...) {
3688  // fall through
3689  }
3690  } else {
3691  break;
3692  }
3693  case eDS_ToStdlog:
3694  case eDS_Default:
3695  {
3696  // When using applog, create separate log file for each
3697  // user to avoid permission problems.
3698  string euid;
3699 #if defined(NCBI_OS_UNIX)
3700  euid = "." + NStr::NumericToString(geteuid());
3701 #endif
3702  string log_base;
3703  string def_log_dir;
3704  {{
3706  if ( app ) {
3707  log_base = app->GetProgramExecutablePath();
3708  def_log_dir = GetDefaultLogLocation(*app);
3709  }
3710  }}
3711  if ( !log_base.empty() ) {
3712  log_base = CFile(log_base).GetBase() + euid + ".log";
3713  string log_name;
3714  // Try /log/<port>
3715  if ( !def_log_dir.empty() ) {
3716  log_name = CFile::ConcatPath(def_log_dir, log_base);
3717  if ( SetLogFile(log_name) ) {
3718  log_set = true;
3719  new_log_name = log_name;
3720  to_applog = true;
3721  break;
3722  }
3723  }
3724  // Try /log/srv if port is unknown or not writable
3725  log_name = CFile::ConcatPath(CFile::ConcatPath(kRootLog, "srv"), log_base);
3726  if ( SetLogFile(log_name) ) {
3727  log_set = true;
3728  new_log_name = log_name;
3729  to_applog = true;
3730  break;
3731  }
3732  // Have we skipped config_logfile above?
3733  if (applog_first &&
3734  OpenLogFileFromConfig(config_logfile)) {
3735  log_set = true;
3736  new_log_name = config_logfile;
3737  break;
3738  }
3739  // Try to switch to /log/fallback/
3740  log_name = CFile::ConcatPath(CFile::ConcatPath(kRootLog, "fallback"), log_base);
3741  if ( SetLogFile(log_name) ) {
3742  log_set = true;
3743  new_log_name = log_name;
3744  to_applog = true;
3745  break;
3746  }
3747  // Try cwd/ for eDS_ToStdlog only
3748  if (ds == eDS_ToStdlog) {
3749  // Don't include euid in file name when using cwd.
3750  log_name = CFile::ConcatPath(".", CFile(log_base).GetBase() + ".log");
3751  if ( SetLogFile(log_name, eDiagFile_All) ) {
3752  log_set = true;
3753  new_log_name = log_name;
3754  break;
3755  }
3756  }
3757  _TRACE_X(3, "Failed to set log file to " +
3758  CFile::NormalizePath(log_name));
3759  }
3760  // Have we skipped cmd_logfile above?
3761  if (!log_set && !cmdline_first && cmd_logfile) {
3762  if ( SetLogFile(cmd_logfile, eDiagFile_All) ) {
3763  log_set = true;
3764  new_log_name = cmd_logfile;
3765  break;
3766  }
3767  }
3768  // No command line and no base name.
3769  // Try to switch to /log/fallback/UNKNOWN.<euid>
3770  if (!log_set && log_base.empty()) {
3771  string default_fallback = CFile::ConcatPath(kRootLog, "fallback/UNKNOWN.log");
3772  if ( SetLogFile(default_fallback + euid) ) {
3773  log_set = true;
3774  new_log_name = default_fallback;
3775  to_applog = true;
3776  break;
3777  }
3778  _TRACE_X(4, "Failed to set log file to " <<
3779  CFile::NormalizePath(default_fallback));
3780  }
3781  const char* log_name = TTeeToStderr::GetDefault() ?
3783  if (!log_set && old_log_name != log_name) {
3786  true, kLogName_Stderr), true);
3787  log_set = true;
3788  new_log_name = log_name;
3789  }
3790  break;
3791  }
3792  default:
3793  ERR_POST_X(5, Warning << "Unknown EAppDiagStream value");
3794  _ASSERT(0);
3795  break;
3796  }
3797  }
3798 
3799  // Unlock severity level
3800  SetApplogSeverityLocked(false);
3801  if ( to_applog ) {
3802  ctx.SetOldPostFormat(false);
3803  s_LogSizeLimit->Set(0); // No log size limit
3805  // Lock severity level
3807  }
3808  else {
3809  // Disable throttling
3810  ctx.SetLogRate_Limit(eLogRate_App, CRequestRateControl::kNoLimit);
3811  ctx.SetLogRate_Limit(eLogRate_Err, CRequestRateControl::kNoLimit);
3813  }
3814 
3815  CDiagHandler* new_handler = GetDiagHandler();
3816  // Flush collected messages to the log if either its name has changed
3817  // or log file truncation is enabled.
3818  if (log_set && new_handler && new_handler->GetLogName() == old_log_name) {
3819  bool is_file = dynamic_cast<CFileHandleDiagHandler*>(new_handler) ||
3820  dynamic_cast<CFileDiagHandler*>(new_handler);
3821  log_set = log_set && is_file && GetLogTruncate();
3822  }
3823 
3824  if (collect == eDCM_Flush) {
3825  // Flush and discard
3826  if ( log_set && new_handler ) {
3827  ctx.FlushMessages(*new_handler);
3828  }
3829  collect = eDCM_Discard;
3830  }
3831  else if (collect == eDCM_NoChange) {
3832  // Flush but don't discard
3833  if ( log_set && new_handler ) {
3834  ctx.FlushMessages(*new_handler);
3835  }
3836  }
3837  if (collect == eDCM_Discard) {
3838  ctx.DiscardMessages();
3839  }
3840 
3841  // Refresh rate controls
3842  ctx.ResetLogRates();
3843 
3845 }
3846 
3847 
3849 {
3850  static CAtomicCounter s_ProcessPostCount;
3851  return (TCount)(inc == ePostNumber_Increment ?
3852  s_ProcessPostCount.Add(1) : s_ProcessPostCount.Get());
3853 }
3854 
3855 
3857 {
3859 }
3860 
3861 
3863 {
3865 }
3866 
3867 
3869 {
3870  return GetLogFile().substr(0, 5) == kRootLog;
3871 }
3872 
3873 
3875 {
3876  // Make the context live longer than other diag safe-statics
3877  // and +1 longer than TLS safe statics which print app stop
3878  // from their cleanup.
3879  static CSafeStatic<CDiagContext> s_DiagContext(
3881 
3882  return s_DiagContext.Get();
3883 }
3884 
3885 
3886 ///////////////////////////////////////////////////////
3887 // CNcbiLogFields::
3888 
3889 #ifdef NCBI_LOG_FIELDS_CUSTOM
3890 NCBI_PARAM_DECL(string, Log, Ncbi_Log_Fields_Custom);
3891 NCBI_PARAM_DEF_EX(string, Log, Ncbi_Log_Fields_Custom, "",
3892  eParam_NoThread, NCBI_LOG_FIELDS_CUSTOM);
3893 typedef NCBI_PARAM_TYPE(Log, Ncbi_Log_Fields_Custom) TCustomLogFields;
3894 #endif
3895 
3896 
3898  : m_Source(source)
3899 {
3900  const TXChar* env_fields = NcbiSys_getenv(_TX("NCBI_LOG_FIELDS"));
3901  if ( env_fields ) {
3902  string fields = _T_CSTRING(env_fields);
3903  NStr::ToLower(fields);
3904  NStr::ReplaceInPlace(fields, "_", "-");
3906  }
3907 #ifdef NCBI_LOG_FIELDS_CUSTOM
3908  string custom_fields = TCustomLogFields::GetDefault();
3909  NStr::ToLower(custom_fields);
3910  NStr::ReplaceInPlace(custom_fields, "_", "-");
3911  NStr::Split(custom_fields, " ", m_Fields, NStr::fSplit_Tokenize);
3912 #endif
3913 }
3914 
3915 
3917 {
3918 }
3919 
3920 
3921 void CNcbiLogFields::x_Match(const string& name, const string& value, CDiagContext_Extra& extra) const
3922 {
3923  ITERATE(TFields, it, m_Fields) {
3924  if ( it->empty() ) continue; // Skip empty entries since they match anything.
3925  if (NStr::MatchesMask(name, *it, NStr::eNocase)) {
3926  extra.Print((m_Source.empty() ? name : m_Source + "." + name), value);
3927  break; // Stop if a match is found.
3928  }
3929  }
3930 }
3931 
3932 
3933 ///////////////////////////////////////////////////////
3934 // CDiagBuffer::
3935 
3936 #if defined(NDEBUG)
3937 EDiagSev CDiagBuffer::sm_PostSeverity = eDiag_Error;
3938 #else
3939 EDiagSev CDiagBuffer::sm_PostSeverity = eDiag_Warning;
3940 #endif
3941 
3942 EDiagSevChange CDiagBuffer::sm_PostSeverityChange = eDiagSC_Unknown;
3943  // to be set on first request
3944 
3950 static bool s_DiagPostFlagsInitialized = false;
3951 
3952 inline
3953 TDiagPostFlags& CDiagBuffer::sx_GetPostFlags(void)
3954 {
3958  }
3959  return s_PostFlags;
3960 }
3961 
3962 
3963 TDiagPostFlags& CDiagBuffer::s_GetPostFlags(void)
3964 {
3965  return sx_GetPostFlags();
3966 }
3967 
3968 
3969 TDiagPostFlags CDiagBuffer::sm_TraceFlags = eDPF_Trace;
3970 
3971 bool CDiagBuffer::sm_IgnoreToDie = false;
3972 EDiagSev CDiagBuffer::sm_DieSeverity = eDiag_Fatal;
3973 
3974 EDiagTrace CDiagBuffer::sm_TraceDefault = eDT_Default;
3975 bool CDiagBuffer::sm_TraceEnabled; // to be set on first request
3976 
3977 
3978 const char* CDiagBuffer::sm_SeverityName[eDiag_Trace+1] = {
3979  "Info", "Warning", "Error", "Critical", "Fatal", "Trace" };
3980 
3981 
3982 NCBI_PARAM_ENUM_DECL(EDiagSev, DEBUG, Stack_Trace_Level);
3984 {
3985  {"Trace", eDiag_Trace},
3986  { "Info", eDiag_Info },
3987  { "Warning", eDiag_Warning },
3988  { "Error", eDiag_Error },
3989  { "Critical", eDiag_Critical },
3990  { "Fatal", eDiag_Fatal }
3991 };
3993  DEBUG,
3994  Stack_Trace_Level,
3995  eDiag_Fatal,
3996  eParam_NoThread, // No per-thread values
3997  DEBUG_STACK_TRACE_LEVEL);
3998 
3999 
4000 void* InitDiagHandler(void)
4001 {
4003 
4004  static bool s_DiagInitialized = false;
4005  if (!s_DiagInitialized) {
4007 #ifdef NCBI_OS_MSWIN
4008  // Check environment variable for silent exit, suppress popup messages if enabled.
4009  // _ASSERT uses NCBI Diag API, and this allow it to work properly.
4010  const TXChar* value = NcbiSys_getenv(_TX("DIAG_SILENT_ABORT"));
4011  if (value && (*value == _TX('Y') || *value == _TX('y') || *value == _TX('1'))) {
4013  }
4014 #endif
4015  s_DiagInitialized = true;
4016  }
4017  return 0;
4018 }
4019 
4020 
4021 // For initialization only
4023 
4024 
4025 // MT-safe initialization of the default handler
4027 
4028 
4029 // Use s_DefaultHandler only for purposes of comparison, as installing
4030 // another handler will normally delete it.
4031 // NOTE: If SetDiagHandler is never called, the default handler will not
4032 // be destroyed on application termination. This is a tradeoff for having
4033 // the handler always available.
4035 CDiagHandler* CDiagBuffer::sm_Handler = s_DefaultHandler;
4036 bool CDiagBuffer::sm_CanDeleteHandler = true;
4037 CDiagErrCodeInfo* CDiagBuffer::sm_ErrCodeInfo = 0;
4038 bool CDiagBuffer::sm_CanDeleteErrCodeInfo = false;
4039 
4040 
4042 {
4044  static bool s_DefaultDiagHandlerInitialized = false;
4045  if ( !s_DefaultDiagHandlerInitialized ) {
4046  s_DefaultDiagHandlerInitialized = true;
4048  if ( TTeeToStderr::GetDefault() ) {
4049  // Need to tee?
4050  handler = new CTeeDiagHandler(handler, true);
4051  }
4052  return handler;
4053  }
4054  return s_DefaultHandler;
4055 }
4056 
4057 
4058 
4059 // Note: Intel Thread Checker detects a memory leak at the line:
4060 // m_Stream(new CNcbiOstrstream) below
4061 // This is not a fault of the toolkit code as soon as a code like:
4062 // int main() {
4063 // ostrstream * s = new ostrstream;
4064 // delete s;
4065 // return 0;
4066 // }
4067 // will also report memory leaks.
4068 // Test environment:
4069 // - Intel Thread Checker for Linux 3.1
4070 // - gcc 4.0.1, gcc 4.1.2, icc 10.1
4071 // - Linux64
4072 CDiagBuffer::CDiagBuffer(void)
4073  : m_Stream(new CNcbiOstrstream),
4074  m_InitialStreamFlags(m_Stream->flags()),
4075  m_InUse(false)
4076 {
4077  m_Diag = 0;
4078 }
4079 
4080 CDiagBuffer::~CDiagBuffer(void)
4081 {
4082 #if (_DEBUG > 1)
4083  if (m_Diag || m_Stream->pcount())
4084  Abort();
4085 #endif
4086  delete m_Stream;
4087  m_Stream = 0;
4088 }
4089 
4090 void CDiagBuffer::DiagHandler(SDiagMessage& mess)
4091 {
4092  bool is_console = (mess.m_Flags & eDPF_IsConsole) > 0;
4093  bool applog = (mess.m_Flags & eDPF_AppLog) > 0;
4094  bool is_printable = applog || SeverityPrintable(mess.m_Severity);
4095  if (!is_console && !is_printable) {
4096  return;
4097  }
4098  if ( CDiagBuffer::sm_Handler ) {
4100  if ( CDiagBuffer::sm_Handler ) {
4101  // The mutex must be locked before approving.
4102  CDiagBuffer& diag_buf = GetDiagBuffer();
4103  bool show_warning = false;
4105  const CRequestContext& rctx = ctx.GetRequestContext();
4106  mess.m_Prefix = diag_buf.m_PostPrefix.empty() ?
4107  0 : diag_buf.m_PostPrefix.c_str();
4108  if (is_console) {
4109  // No throttling for console
4110  CDiagBuffer::sm_Handler->PostToConsole(mess);
4111  if ( !is_printable ) {
4112  return;
4113  }
4114  }
4115  if ( ctx.ApproveMessage(mess, &show_warning) ) {
4116  if (mess.m_Severity >= eDiag_Error &&
4117  mess.m_Severity != eDiag_Trace &&
4118  s_DisableAppLog->Get() &&
4119  rctx.x_LogHitIDOnError()) {
4120  const CNcbiDiag diag(DIAG_COMPILE_INFO);
4121  SDiagMessage phid_msg(eDiag_Error,
4122  0, 0,
4123  diag.GetFile(),
4124  diag.GetLine(),
4125  diag.GetPostFlags() | eDPF_AppLog,
4126  NULL,
4127  0, // Error code
4128  0, // Err subcode
4129  NULL,
4130  diag.GetModule(),
4131  diag.GetClass(),
4132  diag.GetFunction());
4133  phid_msg.m_Event = SDiagMessage::eEvent_Extra;
4134  phid_msg.m_ExtraArgs.push_back(SDiagMessage::TExtraArg(
4136  rctx.GetHitID()));
4137  CDiagBuffer::sm_Handler->Post(phid_msg);
4138  }
4139  CDiagBuffer::sm_Handler->Post(mess);
4140  }
4141  else if ( show_warning ) {
4142  // Substitute the original message with the error.
4143  // ERR_POST cannot be used here since nested posts
4144  // are blocked. Have to create the message manually.
4145  string limit_name = "error";
4146  CDiagContext::ELogRate_Type limit_type =
4148  if ( IsSetDiagPostFlag(eDPF_AppLog, mess.m_Flags) ) {
4149  limit_name = "applog";
4150  limit_type = CDiagContext::eLogRate_App;
4151  }
4152  else if (mess.m_Severity == eDiag_Info ||
4153  mess.m_Severity == eDiag_Trace) {
4154  limit_name = "trace";
4155  limit_type = CDiagContext::eLogRate_Trace;
4156  }
4157  string txt = "Maximum logging rate for " + limit_name + " ("
4158  + NStr::UIntToString(ctx.GetLogRate_Limit(limit_type))
4159  + " messages per "
4160  + NStr::UIntToString(ctx.GetLogRate_Period(limit_type))
4161  + " sec) exceeded, suspending the output.";
4162  const CNcbiDiag diag(DIAG_COMPILE_INFO);
4163  SDiagMessage err_msg(eDiag_Error,
4164  txt.c_str(), txt.length(),
4165  diag.GetFile(),
4166  diag.GetLine(),
4167  diag.GetPostFlags(),
4168  NULL,
4169  err_code_x::eErrCodeX_Corelib_Diag, // Error code
4170  23, // Err subcode
4171  NULL,
4172  diag.GetModule(),
4173  diag.GetClass(),
4174  diag.GetFunction());
4175  CDiagBuffer::sm_Handler->Post(err_msg);
4176  return;
4177  }
4178  }
4179  }
4180  GetDiagContext().PushMessage(mess);
4181 }
4182 
4183 
4184 inline
4185 bool CDiagBuffer::SeverityDisabled(EDiagSev sev)
4186 {
4187  CDiagContextThreadData& thr_data =
4189  CDiagCollectGuard* guard = thr_data.GetCollectGuard();
4190  EDiagSev post_sev = AdjustApplogPrintableSeverity(sm_PostSeverity);
4191  bool allow_trace = GetTraceEnabled();
4192  if ( guard ) {
4193  post_sev = guard->GetCollectSeverity();
4194  allow_trace = post_sev == eDiag_Trace;
4195  }
4196  if (sev == eDiag_Trace && !allow_trace) {
4197  return true; // trace is disabled
4198  }
4199  if (post_sev == eDiag_Trace && allow_trace) {
4200  return false; // everything is enabled
4201  }
4202  return (sev < post_sev) && (sev < sm_DieSeverity || sm_IgnoreToDie);
4203 }
4204 
4205 
4206 inline
4207 bool CDiagBuffer::SeverityPrintable(EDiagSev sev)
4208 {
4209  CDiagContextThreadData& thr_data =
4211  CDiagCollectGuard* guard = thr_data.GetCollectGuard();
4212  EDiagSev post_sev = AdjustApplogPrintableSeverity(sm_PostSeverity);
4213  bool allow_trace = GetTraceEnabled();
4214  if ( guard ) {
4215  post_sev = AdjustApplogPrintableSeverity(guard->GetPrintSeverity());
4216  allow_trace = post_sev == eDiag_Trace;
4217  }
4218  if (sev == eDiag_Trace && !allow_trace) {
4219  return false; // trace is disabled
4220  }
4221  if (post_sev == eDiag_Trace && allow_trace) {
4222  return true; // everything is enabled
4223  }
4224  return !((sev < post_sev) && (sev < sm_DieSeverity || sm_IgnoreToDie));
4225 }
4226 
4227 
4228 bool CDiagBuffer::SetDiag(const CNcbiDiag& diag)
4229 {
4230  if ( m_InUse || !m_Stream ) {
4231  return false;
4232  }
4233 
4234  EDiagSev sev = diag.GetSeverity();
4235  bool is_console = (diag.GetPostFlags() & eDPF_IsConsole) > 0;
4236  if (!is_console && SeverityDisabled(sev)) {
4237  return false;
4238  }
4239 
4240  if (m_Diag != &diag) {
4241  if ( !IsOssEmpty(*m_Stream) ) {
4242  Flush();
4243  }
4244  m_Diag = &diag;
4245  }
4246 
4247  return true;
4248 }
4249 
4250 
4252 {
4253 public:
4254  CRecursionGuard(bool& flag) : m_Flag(flag) { m_Flag = true; }
4255  ~CRecursionGuard(void) { m_Flag = false; }
4256 private:
4257  bool& m_Flag;
4258 };
4259 
4260 
4262 {
4263  static bool Check();
4264  static SDiagMessage Report(EDiagSev& sev);
4265 
4266 private:
4267  static atomic<thread::id> sm_ThreadID;
4268  static atomic<bool> sm_Reported;
4269 };
4270 
4271 
4272 atomic<thread::id> SThreadsInSTBuild::sm_ThreadID;
4273 atomic<bool> SThreadsInSTBuild::sm_Reported;
4274 
4275 
4277 {
4278 #ifndef NCBI_THREADS
4279  thread::id stored_thread_id;
4280  thread::id this_thread_id = this_thread::get_id();
4281 
4282  // If this thread has just initialized sm_ThreadID
4283  if (sm_ThreadID.compare_exchange_strong(stored_thread_id, this_thread_id)) return false;
4284 
4285  // If sm_ThreadID contains same thread ID
4286  if (stored_thread_id == this_thread_id) return false;
4287 
4288  bool reported = false;
4289 
4290  // Whether to report this (or it has already been reported)
4291  if (sm_Reported.compare_exchange_strong(reported, true)) return true;
4292 #endif
4293 
4294  return false;
4295 }
4296 
4297 
4299 {
4300 #ifdef _DEBUG
4301  sev = eDiag_Fatal;
4302 #else
4303  sev = eDiag_Critical;
4304 #endif
4305 
4306  const auto msg = "Detected different threads using C++ Toolkit built in single thread mode."sv;
4307  const CNcbiDiag diag(DIAG_COMPILE_INFO);
4308  return SDiagMessage(
4309  sev,
4310  msg.data(),
4311  msg.length(),
4312  diag.GetFile(),
4313  diag.GetLine(),
4314  diag.GetPostFlags(),
4315  nullptr,
4316  0,
4317  0,
4318  nullptr,
4319  diag.GetModule(),
4320  diag.GetClass(),
4321  diag.GetFunction());
4322 }
4323 
4324 
4325 void CDiagBuffer::Flush(void)
4326 {
4327  if ( m_InUse || !m_Diag ) {
4328  if ( !m_InUse && m_Stream && !IsOssEmpty(*m_Stream) ) {
4329  string message = CNcbiOstrstreamToString(*m_Stream);
4330  // Can not use Reset() without CNcbiDiag.
4331  m_Stream->rdbuf()->PUBSEEKOFF(0, IOS_BASE::beg, IOS_BASE::out);
4332  // _TRACE("Discarding junk data from CDiagBuffer: " << message);
4333  }
4334  return;
4335  }
4336  CRecursionGuard guard(m_InUse);
4337 
4338  EDiagSev sev = m_Diag->GetSeverity();
4339  bool is_console = (m_Diag->GetPostFlags() & eDPF_IsConsole) != 0;
4340  bool is_disabled = SeverityDisabled(sev);
4341 
4342  // Do nothing if diag severity is lower than allowed.
4343  if (!is_console && is_disabled) {
4344  return;
4345  }
4346 
4347  string message = CNcbiOstrstreamToString(*m_Stream);
4348 
4349  TDiagPostFlags flags = m_Diag->GetPostFlags();
4350  if (sev == eDiag_Trace) {
4351  flags |= sm_TraceFlags;
4352  } else if (sev == eDiag_Fatal) {
4353  // normally only happens once, so might as well pull everything
4354  // in for the record...
4355  flags |= sm_TraceFlags | eDPF_Trace;
4356  }
4357 
4358  if ( m_Diag->CheckFilters() ) {
4359  SDiagMessage mess(sev, message.data(), message.size(),
4360  m_Diag->GetFile(),
4361  m_Diag->GetLine(),
4362  flags,
4363  NULL,
4364  m_Diag->GetErrorCode(),
4365  m_Diag->GetErrorSubCode(),
4366  NULL,
4367  m_Diag->GetModule(),
4368  m_Diag->GetClass(),
4369  m_Diag->GetFunction());
4370  PrintMessage(mess, *m_Diag);
4371  }
4372 
4373  if (SThreadsInSTBuild::Check()) {
4374  // Change severity and print error message
4375  auto mess = SThreadsInSTBuild::Report(sev);
4376  PrintMessage(mess, *m_Diag);
4377  }
4378 
4379 #if defined(NCBI_COMPILER_KCC)
4380  // KCC's implementation of "freeze(false)" makes the ostrstream buffer
4381  // stuck. We need to replace the frozen stream with the new one.
4382  delete ostr;
4383  m_Stream = new CNcbiOstrstream;
4384 #else
4385  // reset flags to initial value
4386  m_Stream->flags(m_InitialStreamFlags);
4387 # ifdef NCBI_SHUN_OSTRSTREAM
4388  // m_Stream->rdbuf()->PUBSEEKOFF(0, IOS_BASE::beg);
4389  m_Stream->str(kEmptyStr);
4390 # endif
4391 #endif
4392 
4393  Reset(*m_Diag);
4394 
4395  if (sev >= sm_DieSeverity && sev != eDiag_Trace && !sm_IgnoreToDie) {
4396  m_Diag = 0;
4397 
4398 #ifdef NCBI_COMPILER_MSVC
4399  if (NCBI_PARAM_TYPE(Diag, Assert_On_Abort)::GetDefault() ) {
4400  int old_mode = _set_error_mode(_OUT_TO_MSGBOX);
4401  _ASSERT(false); // Show assertion dialog
4402  _set_error_mode(old_mode);
4403  }
4404  else {
4405  Abort();
4406  }
4407 #else // NCBI_COMPILER_MSVC
4408  Abort();
4409 #endif // NCBI_COMPILER_MSVC
4410  }
4411 }
4412 
4413 
4414 void CDiagBuffer::PrintMessage(SDiagMessage& mess, const CNcbiDiag& diag)
4415 {
4416  EDiagSev sev = diag.GetSeverity();
4417  if (!SeverityPrintable(sev)) {
4418  CDiagContextThreadData& thr_data =
4420  bool can_collect = thr_data.GetCollectGuard() != NULL;
4421  bool is_console = (diag.GetPostFlags() & eDPF_IsConsole) != 0;
4422  bool is_disabled = SeverityDisabled(sev);
4423  if (!is_disabled || (is_console && can_collect)) {
4424  thr_data.CollectDiagMessage(mess);
4425  Reset(diag);
4426  // The message has been collected, don't print to
4427  // the console now.
4428  return;
4429  }
4430  }
4431  if ( !diag.GetOmitStackTrace() ) {
4432  static CSafeStatic<NCBI_PARAM_TYPE(DEBUG, Stack_Trace_Level)> s_StackTraceLevel;
4433  EDiagSev stack_sev = s_StackTraceLevel->Get();
4434  mess.m_PrintStackTrace = (sev == stack_sev) || (sev > stack_sev && sev != eDiag_Trace);
4435  }
4436  DiagHandler(mess);
4437 }
4438 
4439 
4440 bool CDiagBuffer::GetTraceEnabledFirstTime(void)
4441 {
4444  if (str && *str) {
4445  sm_TraceDefault = eDT_Enable;
4446  } else {
4447  sm_TraceDefault = eDT_Disable;
4448  }
4449  sm_TraceEnabled = (sm_TraceDefault == eDT_Enable);
4450  return sm_TraceEnabled;
4451 }
4452 
4453 
4454 bool CDiagBuffer::GetSeverityChangeEnabledFirstTime(void)
4455 {
4456  if ( sm_PostSeverityChange != eDiagSC_Unknown ) {
4457  return sm_PostSeverityChange == eDiagSC_Enable;
4458  }
4460  EDiagSev sev;
4461  if (str && *str && CNcbiDiag::StrToSeverityLevel(_T_CSTRING(str), sev)) {
4462  SetDiagFixedPostLevel(sev);
4463  } else {
4464  sm_PostSeverityChange = eDiagSC_Enable;
4465  }
4466  return sm_PostSeverityChange == eDiagSC_Enable;
4467 }
4468 
4469 
4470 void CDiagBuffer::UpdatePrefix(void)
4471 {
4472  m_PostPrefix.erase();
4473  ITERATE(TPrefixList, prefix, m_PrefixList) {
4474  if (prefix != m_PrefixList.begin()) {
4475  m_PostPrefix += "::";
4476  }
4477  m_PostPrefix += *prefix;
4478  }
4479 }
4480 
4481 
4482 ///////////////////////////////////////////////////////
4483 // CDiagMessage::
4484 
4485 
4487  const char* buf, size_t len,
4488  const char* file, size_t line,
4489  TDiagPostFlags flags, const char* prefix,
4490  int err_code, int err_subcode,
4491  const char* err_text,
4492  const char* module,
4493  const char* nclass,
4494  const char* function)
4495  : m_Event(eEvent_Start),
4496  m_TypedExtra(false),
4497  m_NoTee(false),
4498  m_PrintStackTrace(false),
4499  m_Data(0),
4500  m_Format(eFormat_Auto),
4501  m_AllowBadExtraNames(false)
4502 {
4503  m_Severity = severity;
4504  m_Buffer = buf;
4505  m_BufferLen = len;
4506  m_File = file;
4507  m_Line = line;
4508  m_Flags = flags;
4509  m_Prefix = prefix;
4510  m_ErrCode = err_code;
4511  m_ErrSubCode = err_subcode;
4512  m_ErrText = err_text;
4513  m_Module = module;
4514  m_Class = nclass;
4515  m_Function = function;
4516 
4517  CDiagContextThreadData& thr_data =
4519  CRequestContext& rq_ctx = thr_data.GetRequestContext();
4521  m_TID = thr_data.GetTID();
4522  EDiagAppState app_state = GetAppState();
4523  switch (app_state) {
4525  case eDiagAppState_Request:
4527  if ( rq_ctx.GetAutoIncRequestIDOnPost() ) {
4528  rq_ctx.SetRequestID();
4529  }
4530  m_RequestId = rq_ctx.GetRequestID();
4531  break;
4532  default:
4533  m_RequestId = 0;
4534  }
4537 }
4538 
4539 
4540 SDiagMessage::SDiagMessage(const string& message, bool* result)
4541  : m_Severity(eDiagSevMin),
4542  m_Buffer(0),
4543  m_BufferLen(0),
4544  m_File(0),
4545  m_Module(0),
4546  m_Class(0),
4547  m_Function(0),
4548  m_Line(0),
4549  m_ErrCode(0),
4550  m_ErrSubCode(0),
4551  m_Flags(0),
4552  m_Prefix(0),
4553  m_ErrText(0),
4554  m_PID(0),
4555  m_TID(0),
4556  m_ProcPost(0),
4557  m_ThrPost(0),
4558  m_RequestId(0),
4559  m_Event(eEvent_Start),
4560  m_TypedExtra(false),
4561  m_NoTee(false),
4562  m_PrintStackTrace(false),
4563  m_Data(0),
4564  m_Format(eFormat_Auto),
4565  m_AllowBadExtraNames(false)
4566 {
4567  bool res = ParseMessage(message);
4568  if ( result ) {
4569  *result = res;
4570  }
4571 }
4572 
4573 
4575 {
4576  if ( m_Data ) {
4577  delete m_Data;
4578  }
4579 }
4580 
4581 
4583  : m_Severity(eDiagSevMin),
4584  m_Buffer(0),
4585  m_BufferLen(0),
4586  m_File(0),
4587  m_Module(0),
4588  m_Class(0),
4589  m_Function(0),
4590  m_Line(0),
4591  m_ErrCode(0),
4592  m_ErrSubCode(0),
4593  m_Flags(0),
4594  m_Prefix(0),
4595  m_ErrText(0),
4596  m_PID(0),
4597  m_TID(0),
4598  m_ProcPost(0),
4599  m_ThrPost(0),
4600  m_RequestId(0),
4601  m_Event(eEvent_Start),
4602  m_TypedExtra(false),
4603  m_NoTee(false),
4604  m_PrintStackTrace(false),
4605  m_Data(0),
4606  m_Format(eFormat_Auto),
4607  m_AllowBadExtraNames(false)
4608 {
4609  *this = message;
4610 }
4611 
4612 
4614 {
4615  if (&message != this) {
4616  m_Format = message.m_Format;
4618  if ( message.m_Data ) {
4619  m_Data = new SDiagMessageData(*message.m_Data);
4620  m_Data->m_Host = message.m_Data->m_Host;
4621  m_Data->m_Client = message.m_Data->m_Client;
4622  m_Data->m_Session = message.m_Data->m_Session;
4623  m_Data->m_AppName = message.m_Data->m_AppName;
4624  m_Data->m_AppState = message.m_Data->m_AppState;
4625  }
4626  else {
4628  if (message.m_Buffer) {
4629  m_Data->m_Message =
4630  string(message.m_Buffer, message.m_BufferLen);
4631  }
4632  if ( message.m_File ) {
4633  m_Data->m_File = message.m_File;
4634  }
4635  if ( message.m_Module ) {
4636  m_Data->m_Module = message.m_Module;
4637  }
4638  if ( message.m_Class ) {
4639  m_Data->m_Class = message.m_Class;
4640  }
4641  if ( message.m_Function ) {
4642  m_Data->m_Function = message.m_Function;
4643  }
4644  if ( message.m_Prefix ) {
4645  m_Data->m_Prefix = message.m_Prefix;
4646  }
4647  if ( message.m_ErrText ) {
4648  m_Data->m_ErrText = message.m_ErrText;
4649  }
4650  }
4651  m_Severity = message.m_Severity;
4652  m_Line = message.m_Line;
4653  m_ErrCode = message.m_ErrCode;
4654  m_ErrSubCode = message.m_ErrSubCode;
4655  m_Flags = message.m_Flags;
4656  m_PID = message.m_PID;
4657  m_TID = message.m_TID;
4658  m_ProcPost = message.m_ProcPost;
4659  m_ThrPost = message.m_ThrPost;
4660  m_RequestId = message.m_RequestId;
4661  m_Event = message.m_Event;
4662  m_TypedExtra = message.m_TypedExtra;
4663  m_ExtraArgs.assign(message.m_ExtraArgs.begin(),
4664  message.m_ExtraArgs.end());
4665 
4666  m_Buffer = m_Data->m_Message.empty() ? 0 : m_Data->m_Message.c_str();
4667  m_BufferLen = m_Data->m_Message.empty() ? 0 : m_Data->m_Message.length();
4668  m_File = m_Data->m_File.empty() ? 0 : m_Data->m_File.c_str();
4669  m_Module = m_Data->m_Module.empty() ? 0 : m_Data->m_Module.c_str();
4670  m_Class = m_Data->m_Class.empty() ? 0 : m_Data->m_Class.c_str();
4671  m_Function = m_Data->m_Function.empty()
4672  ? 0 : m_Data->m_Function.c_str();
4673  m_Prefix = m_Data->m_Prefix.empty() ? 0 : m_Data->m_Prefix.c_str();
4674  m_ErrText = m_Data->m_ErrText.empty() ? 0 : m_Data->m_ErrText.c_str();
4675  }
4676  return *this;
4677 }
4678 
4679 
4680 Uint8 s_ParseInt(const string& message,
4681  size_t& pos, // start position
4682  size_t width, // fixed width or 0
4683  char sep) // trailing separator (throw if not found)
4684 {
4685  if (pos >= message.length()) {
4687  "Failed to parse diagnostic message");
4688  }
4689  Uint8 ret = 0;
4690  if (width > 0) {
4691  if (message[pos + width] != sep) {
4693  "Missing separator after integer");
4694  }
4695  }
4696  else {
4697  width = message.find(sep, pos);
4698  if (width == NPOS) {
4700  "Missing separator after integer");
4701  }
4702  width -= pos;
4703  }
4704 
4705  ret = NStr::StringToUInt8(CTempString(message.c_str() + pos, width));
4706  pos += width + 1;
4707  return ret;
4708 }
4709 
4710 
4711 CTempString s_ParseStr(const string& message,
4712  size_t& pos, // start position
4713  char sep, // separator
4714  bool optional = false) // do not throw if not found
4715 {
4716  if (pos >= message.length()) {
4718  "Failed to parse diagnostic message");
4719  }
4720  size_t pos1 = pos;
4721  pos = message.find(sep, pos1);
4722  if (pos == NPOS) {
4723  if ( !optional ) {
4725  "Failed to parse diagnostic message");
4726  }
4727  pos = pos1;
4728  return kEmptyStr;
4729  }
4730  if ( pos == pos1 + 1 && !optional ) {
4731  // The separator is in the next position, no empty string allowed
4733  "Failed to parse diagnostic message");
4734  }
4735  // remember end position of the string, skip separators
4736  size_t pos2 = pos;
4737  pos = message.find_first_not_of(sep, pos);
4738  if (pos == NPOS) {
4739  pos = message.length();
4740  }
4741  return CTempString(message.c_str() + pos1, pos2 - pos1);
4742 }
4743 
4744 
4745 static const char s_ExtraEncodeChars[256][4] = {
4746  "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07",
4747  "%08", "%09", "%0A", "%0B", "%0C", "%0D", "%0E", "%0F",
4748  "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17",
4749  "%18", "%19", "%1A", "%1B", "%1C", "%1D", "%1E", "%1F",
4750  "+", "!", "\"", "#", "$", "%25", "%26", "'",
4751  "(", ")", "*", "%2B", ",", "-", ".", "/",
4752  "0", "1", "2", "3", "4", "5", "6", "7",
4753  "8", "9", ":", ";", "<", "%3D", ">", "?",
4754  "@", "A", "B", "C", "D", "E", "F", "G",
4755  "H", "I", "J", "K", "L", "M", "N", "O",
4756  "P", "Q", "R", "S", "T", "U", "V", "W",
4757  "X", "Y", "Z", "[", "\\", "]", "^", "_",
4758  "`", "a", "b", "c", "d", "e", "f", "g",
4759  "h", "i", "j", "k", "l", "m", "n", "o",
4760  "p", "q", "r", "s", "t", "u", "v", "w",
4761  "x", "y", "z", "{", "|", "}", "~", "%7F",
4762  "%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87",
4763  "%88", "%89", "%8A", "%8B", "%8C", "%8D", "%8E", "%8F",
4764  "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97",
4765  "%98", "%99", "%9A", "%9B", "%9C", "%9D", "%9E", "%9F",
4766  "%A0", "%A1", "%A2", "%A3", "%A4", "%A5", "%A6", "%A7",
4767  "%A8", "%A9", "%AA", "%AB", "%AC", "%AD", "%AE", "%AF",
4768  "%B0", "%B1", "%B2", "%B3", "%B4", "%B5", "%B6", "%B7",
4769  "%B8", "%B9", "%BA", "%BB", "%BC", "%BD", "%BE", "%BF",
4770  "%C0", "%C1", "%C2", "%C3", "%C4", "%C5", "%C6", "%C7",
4771  "%C8", "%C9", "%CA", "%CB", "%CC", "%CD", "%CE", "%CF",
4772  "%D0", "%D1", "%D2", "%D3", "%D4", "%D5", "%D6", "%D7",
4773  "%D8", "%D9", "%DA", "%DB", "%DC", "%DD", "%DE", "%DF",
4774  "%E0", "%E1", "%E2", "%E3", "%E4", "%E5", "%E6", "%E7",
4775  "%E8", "%E9", "%EA", "%EB", "%EC", "%ED", "%EE", "%EF",
4776  "%F0", "%F1", "%F2", "%F3", "%F4", "%F5", "%F6", "%F7",
4777  "%F8", "%F9", "%FA", "%FB", "%FC", "%FD", "%FE", "%FF"
4778 };
4779 
4780 
4781 inline
4782 bool x_IsEncodableChar(char c)
4783 {
4784  return s_ExtraEncodeChars[(unsigned char)c][0] != c ||
4785  s_ExtraEncodeChars[(unsigned char)c][1] != 0;
4786 }
4787 
4788 
4790 {
4791 public:
4792  virtual string Decode(const CTempString src, EStringType stype) const;
4793 };
4794 
4795 
4796 string CExtraDecoder::Decode(const CTempString src, EStringType stype) const
4797 {
4798  string str = src; // NStr::TruncateSpaces(src);
4799  size_t len = str.length();
4800  if ( !len && stype == eName ) {
4801  NCBI_THROW2(CStringException, eFormat,
4802  "Empty name in extra-arg", 0);
4803  }
4804 
4805  size_t dst = 0;
4806  for (size_t p = 0; p < len; dst++) {
4807  switch ( str[p] ) {
4808  case '%': {
4809  if (p + 2 > len) {
4810  NCBI_THROW2(CStringException, eFormat,
4811  "Inavild char in extra arg", p);
4812  }
4813  int n1 = NStr::HexChar(str[p+1]);
4814  int n2 = NStr::HexChar(str[p+2]);
4815  if (n1 < 0 || n2 < 0) {
4816  NCBI_THROW2(CStringException, eFormat,
4817  "Inavild char in extra arg", p);
4818  }
4819  str[dst] = char((n1 << 4) | n2);
4820  p += 3;
4821  break;
4822  }
4823  case '+':
4824  str[dst] = ' ';
4825  p++;
4826  break;
4827  default:
4828  str[dst] = str[p++];
4829  if ( x_IsEncodableChar(str[dst]) ) {
4830  NCBI_THROW2(CStringException, eFormat,
4831  "Unencoded special char in extra arg", p);
4832  }
4833  }
4834  }
4835  if (dst < len) {
4836  str[dst] = '\0';
4837  str.resize(dst);
4838  }
4839  return str;
4840 }
4841 
4842 
4843 bool SDiagMessage::x_ParseExtraArgs(const string& str, size_t pos)
4844 {
4845  m_ExtraArgs.clear();
4846  if (str.find('&', pos) == NPOS && str.find('=', pos) == NPOS) {
4847  return false;
4848  }
4849  CStringPairs<TExtraArgs> parser("&", "=", new CExtraDecoder());
4850  try {
4851  parser.Parse(CTempString(str.c_str() + pos));
4852  }
4853  catch (const CStringException&) {
4854  string n, v;
4855  NStr::SplitInTwo(CTempString(str.c_str() + pos), "=", n, v);
4856  // Try to decode only the name, leave the value as-is.
4857  try {
4858  n = parser.GetDecoder()->Decode(n, CExtraDecoder::eName);
4859  if (n == kExtraTypeArgName) {
4860  m_TypedExtra = true;
4861  }
4862  m_ExtraArgs.push_back(TExtraArg(n, v));
4863  return true;
4864  }
4865  catch (const CStringException&) {
4866  return false;
4867  }
4868  }
4869  ITERATE(TExtraArgs, it, parser.GetPairs()) {
4870  if (it->first == kExtraTypeArgName) {
4871  m_TypedExtra = true;
4872  }
4873  m_ExtraArgs.push_back(TExtraArg(it->first, it->second));
4874  }
4875  return true;
4876 }
4877 
4878 
4879 bool SDiagMessage::ParseMessage(const string& message)
4880 {
4882  m_Buffer = 0;
4883  m_BufferLen = 0;
4884  m_File = 0;
4885  m_Module = 0;
4886  m_Class = 0;
4887  m_Function = 0;
4888  m_Line = 0;
4889  m_ErrCode = 0;
4890  m_ErrSubCode = 0;
4891  m_Flags = 0;
4892  m_Prefix = 0;
4893  m_ErrText = 0;
4894  m_PID = 0;
4895  m_TID = 0;
4896  m_ProcPost = 0;
4897  m_ThrPost = 0;
4898  m_RequestId = 0;
4900  m_TypedExtra = false;
4902  if ( m_Data ) {
4903  delete m_Data;
4904  m_Data = 0;
4905  }
4906  m_Data = new SDiagMessageData;
4907 
4908  size_t pos = 0;
4909  try {
4910  // Fixed prefix
4911  m_PID = s_ParseInt(message, pos, 0, '/');
4912  m_TID = s_ParseInt(message, pos, 0, '/');
4913  size_t sl_pos = message.find('/', pos);
4914  size_t sp_pos = message.find(' ', pos);
4915  if (sl_pos < sp_pos) {
4916  // Newer format, app state is present.
4917  m_RequestId = s_ParseInt(message, pos, 0, '/');
4918  m_Data->m_AppState =
4919  s_StrToAppState(s_ParseStr(message, pos, ' ', true));
4920  }
4921  else {
4922  // Older format, no app state.
4923  m_RequestId = s_ParseInt(message, pos, 0, ' ');
4925  }
4926 
4927  if (message[pos + kDiagW_UID] != ' ') {
4928  return false;
4929  }
4931  CTempString(message.c_str() + pos, kDiagW_UID), 0, 16);
4932  pos += kDiagW_UID + 1;
4933 
4934  m_ProcPost = s_ParseInt(message, pos, 0, '/');
4935  m_ThrPost = s_ParseInt(message, pos, 0, ' ');
4936 
4937  // Date and time. Try all known formats.
4938  CTempString tmp = s_ParseStr(message, pos, ' ');
4939  static const char* s_TimeFormats[4] = {
4940  "Y/M/D:h:m:s", "Y-M-DTh:m:s", "Y-M-DTh:m:s.l", kDiagTimeFormat
4941  };
4942  if (tmp.find('T') == NPOS) {
4943  m_Data->m_Time = CTime(tmp, s_TimeFormats[0]);
4944  }
4945  else if (tmp.find('.') == NPOS) {
4946  m_Data->m_Time = CTime(tmp, s_TimeFormats[1]);
4947  }
4948  else {
4949  try {
4950  m_Data->m_Time = CTime(tmp, s_TimeFormats[2]);
4951  }
4952  catch (const CTimeException&) {
4953  m_Data->m_Time = CTime(tmp, s_TimeFormats[3]);
4954  }
4955  }
4956 
4957  // Host
4958  m_Data->m_Host = s_ParseStr(message, pos, ' ');
4959  if (m_Data->m_Host == kUnknown_Host) {
4960  m_Data->m_Host.clear();
4961  }
4962  // Client
4963  m_Data->m_Client = s_ParseStr(message, pos, ' ');
4964  if (m_Data->m_Client == kUnknown_Client) {
4965  m_Data->m_Client.clear();
4966  }
4967  // Session ID
4968  m_Data->m_Session = s_ParseStr(message, pos, ' ');
4969  if (m_Data->m_Session == kUnknown_Session) {
4970  m_Data->m_Session.clear();
4971  }
4972  // Application name
4973  m_Data->m_AppName = s_ParseStr(message, pos, ' ');
4974  if (m_Data->m_AppName == kUnknown_App) {
4975  m_Data->m_AppName.clear();
4976  }
4977 
4978  // Severity or event type
4979  bool have_severity = false;
4980  size_t severity_pos = pos;
4981  tmp = s_ParseStr(message, pos, ':', true);
4982  if ( !tmp.empty() ) {
4983  // Support both old (LOG_POST -> message) and new (NOTE_POST -> note) styles.
4984  size_t sev_pos = NPOS;
4985  if (tmp.length() == 10 && tmp.find("Message[") == 0) {
4986  sev_pos = 8;
4987  }
4988  else if (tmp.length() == 7 && tmp.find("Note[") == 0) {
4989  sev_pos = 5;
4990  }
4991 
4992  if (sev_pos != NPOS) {
4993  // Get the real severity
4994  switch ( tmp[sev_pos] ) {
4995  case 'T':
4997  break;
4998  case 'I':
5000  break;
5001  case 'W':
5003  break;
5004  case 'E':
5006  break;
5007  case 'C':
5009  break;
5010  case 'F':
5012  break;
5013  default:
5014  return false;
5015  }
5016  m_Flags |= eDPF_IsNote;
5017  have_severity = true;
5018  }
5019  else {
5020  have_severity =
5021  CNcbiDiag::StrToSeverityLevel(string(tmp).c_str(), m_Severity);
5022  }
5023  }
5024  if ( have_severity ) {
5025  pos = message.find_first_not_of(' ', pos);
5026  if (pos == NPOS) {
5027  pos = message.length();
5028  }
5029  }
5030  else {
5031  // Check event type rather than severity level
5032  pos = severity_pos;
5033  tmp = s_ParseStr(message, pos, ' ', true);
5034  if (tmp.empty() && severity_pos < message.length()) {
5035  tmp = CTempString(message.c_str() + severity_pos);
5036  pos = message.length();
5037  }
5038  if (tmp == GetEventName(eEvent_Start)) {
5040  }
5041  else if (tmp == GetEventName(eEvent_Stop)) {
5042  m_Event = eEvent_Stop;
5043  }
5044  else if (tmp == GetEventName(eEvent_RequestStart)) {
5046  if (pos < message.length()) {
5047  if ( x_ParseExtraArgs(message, pos) ) {
5048  pos = message.length();
5049  }
5050  }
5051  }
5052  else if (tmp == GetEventName(eEvent_RequestStop)) {
5054  }
5055  else if (tmp == GetEventName(eEvent_Extra)) {
5057  if (pos < message.length()) {
5058  if ( x_ParseExtraArgs(message, pos) ) {
5059  pos = message.length();
5060  }
5061  }
5062  }
5063  else if (tmp == GetEventName(eEvent_PerfLog)) {
5065  if (pos < message.length()) {
5066  // Put status and time to the message,
5067  // parse all the rest as extra.
5068  size_t msg_end = message.find_first_not_of(' ', pos);
5069  msg_end = message.find_first_of(' ', msg_end);
5070  msg_end = message.find_first_not_of(' ', msg_end);
5071  msg_end = message.find_first_of(' ', msg_end);
5072  size_t extra_pos = message.find_first_not_of(' ', msg_end);
5073  m_Data->m_Message = string(message.c_str() + pos).
5074  substr(0, msg_end - pos);
5075  m_BufferLen = m_Data->m_Message.length();
5076  m_Buffer = m_Data->m_Message.empty() ?
5077  0 : &m_Data->m_Message[0];
5078  if ( x_ParseExtraArgs(message, extra_pos) ) {
5079  pos = message.length();
5080  }
5081  }
5082  }
5083  else {
5084  return false;
5085  }
5086  m_Flags |= eDPF_AppLog;
5087  // The rest is the message (do not parse status, bytes etc.)
5088  if (pos < message.length()) {
5089  m_Data->m_Message = message.c_str() + pos;
5090  m_BufferLen = m_Data->m_Message.length();
5091  m_Buffer = m_Data->m_Message.empty() ?
5092  0 : &m_Data->m_Message[0];
5093  }
5095  return true;
5096  }
5097 
5098  // Find message separator
5099  size_t sep_pos = message.find(" --- ", pos);
5100 
5101  // <module>, <module>(<err_code>.<err_subcode>) or <module>(<err_text>)
5102  if (pos < sep_pos && message[pos] != '"') {
5103  size_t mod_pos = pos;
5104  tmp = s_ParseStr(message, pos, ' ');
5105  size_t lbr = tmp.find("(");
5106  if (lbr != NPOS) {
5107  if (tmp[tmp.length() - 1] != ')') {
5108  // Space(s) inside the error text, try to find closing ')'
5109  int open_br = 1;
5110  while (open_br > 0 && pos < message.length()) {
5111  if (message[pos] == '(') {
5112  open_br++;
5113  }
5114  else if (message[pos] == ')') {
5115  open_br--;
5116  }
5117  pos++;
5118  }
5119  if (message[pos] != ' ' || pos >= message.length()) {
5120  return false;
5121  }
5122  tmp = CTempString(message.c_str() + mod_pos, pos - mod_pos);
5123  // skip space(s)
5124  pos = message.find_first_not_of(' ', pos);
5125  if (pos == NPOS) {
5126  pos = message.length();
5127  }
5128  }
5129  m_Data->m_Module = tmp.substr(0, lbr);
5130  tmp = tmp.substr(lbr + 1, tmp.length() - lbr - 2);
5131  size_t dot_pos = tmp.find('.');
5132  if (dot_pos != NPOS) {
5133  // Try to parse error code/subcode
5134  try {
5135  m_ErrCode = NStr::StringToInt(tmp.substr(0, dot_pos));
5136  m_ErrSubCode = NStr::StringToInt(tmp.substr(dot_pos + 1));
5137  }
5138  catch (const CStringException&) {
5139  m_ErrCode = 0;
5140  m_ErrSubCode = 0;
5141  }
5142  }
5143  if (!m_ErrCode && !m_ErrSubCode) {
5144  m_Data->m_ErrText = tmp;
5145  m_ErrText = m_Data->m_ErrText.empty() ?
5146  0 : m_Data->m_ErrText.c_str();
5147  }
5148  }
5149  else {
5150  m_Data->m_Module = tmp;
5151  }
5152  if ( !m_Data->m_Module.empty() ) {
5153  m_Module = m_Data->m_Module.c_str();
5154  }
5155  }
5156 
5157  if (pos < sep_pos && message[pos] == '"') {
5158  // ["<file>", ][line <line>][:]
5159  pos++; // skip "
5160  tmp = s_ParseStr(message, pos, '"');
5161  m_Data->m_File = tmp;
5162  m_File = m_Data->m_File.empty() ? 0 : m_Data->m_File.c_str();
5163  if (CTempString(message.c_str() + pos, 7) != ", line ") {
5164  return false;
5165  }
5166  pos += 7;
5167  m_Line = (size_t)s_ParseInt(message, pos, 0, ':');
5168  pos = message.find_first_not_of(' ', pos);
5169  if (pos == NPOS) {
5170  pos = message.length();
5171  }
5172  }
5173 
5174  if (pos < sep_pos) {
5175  // Class:: Class::Function() ::Function()
5176  if (message.find("::", pos) != NPOS) {
5177  size_t tmp_pos = sep_pos;