2 ** NetXMS - Network Management System
4 ** Copyright (C) 2003-2017 Victor Kirhenshtein
6 ** This program is free software; you can redistribute it and/or modify
7 ** it under the terms of the GNU Lesser General Public License as published
8 ** by the Free Software Foundation; either version 3 of the License, or
9 ** (at your option) any later version.
11 ** This program is distributed in the hope that it will be useful,
12 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
13 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 ** GNU General Public License for more details.
16 ** You should have received a copy of the GNU Lesser General Public License
17 ** along with this program; if not, write to the Free Software
18 ** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
24 #include "libnetxms.h"
31 #define MAX_LOG_HISTORY_SIZE 128
37 static HANDLE m_eventLogHandle
= NULL
;
38 static HMODULE m_msgModuleHandle
= NULL
;
40 static unsigned int m_numMessages
;
41 static const TCHAR
**m_messages
;
42 static char s_syslogName
[64];
44 static TCHAR m_logFileName
[MAX_PATH
] = _T("");
45 static FILE *m_logFileHandle
= NULL
;
46 static MUTEX m_mutexLogAccess
= INVALID_MUTEX_HANDLE
;
47 static UINT32 s_flags
= 0;
48 static int s_debugLevel
= 0;
49 static DWORD s_debugMsg
= 0;
50 static DWORD s_genericMsg
= 0;
51 static int s_rotationMode
= NXLOG_ROTATION_BY_SIZE
;
52 static UINT64 s_maxLogSize
= 4096 * 1024; // 4 MB
53 static int s_logHistorySize
= 4; // Keep up 4 previous log files
54 static TCHAR s_dailyLogSuffixTemplate
[64] = _T("%Y%m%d");
55 static time_t m_currentDayStart
= 0;
56 static NxLogConsoleWriter m_consoleWriter
= (NxLogConsoleWriter
)_tprintf
;
57 static String s_logBuffer
;
58 static THREAD s_writerThread
= INVALID_THREAD_HANDLE
;
59 static CONDITION s_writerStopCondition
= INVALID_CONDITION_HANDLE
;
60 static NxLogDebugWriter s_debugWriter
= NULL
;
65 void LIBNETXMS_EXPORTABLE
nxlog_set_debug_level(int level
)
67 if ((level
>= 0) && (level
<= 9))
72 * Get current debug level
74 int LIBNETXMS_EXPORTABLE
nxlog_get_debug_level()
80 * Set additional debug writer callback. It will be called for each line written with nxlog_debug.
82 extern "C" void LIBNETXMS_EXPORTABLE
nxlog_set_debug_writer(NxLogDebugWriter writer
)
84 s_debugWriter
= writer
;
88 * Format current time for output
90 static TCHAR
*FormatLogTimestamp(TCHAR
*buffer
)
92 INT64 now
= GetCurrentTimeMs();
93 time_t t
= now
/ 1000;
96 struct tm
*loc
= localtime_r(&t
, <mBuffer
);
98 struct tm
*loc
= localtime(&t
);
100 _tcsftime(buffer
, 32, _T("[%d-%b-%Y %H:%M:%S"), loc
);
101 _sntprintf(&buffer
[21], 8, _T(".%03d]"), (int)(now
% 1000));
106 * Set timestamp of start of the current day
108 static void SetDayStart()
110 time_t now
= time(NULL
);
113 localtime_r(&now
, &dayStart
);
115 struct tm
*ltm
= localtime(&now
);
116 memcpy(&dayStart
, ltm
, sizeof(struct tm
));
118 dayStart
.tm_hour
= 0;
121 m_currentDayStart
= mktime(&dayStart
);
125 * Set log rotation policy
126 * Setting log size to 0 or mode to NXLOG_ROTATION_DISABLED disables log rotation
128 bool LIBNETXMS_EXPORTABLE
nxlog_set_rotation_policy(int rotationMode
, UINT64 maxLogSize
, int historySize
, const TCHAR
*dailySuffix
)
132 if ((rotationMode
>= 0) || (rotationMode
<= 2))
134 s_rotationMode
= rotationMode
;
135 if (rotationMode
== NXLOG_ROTATION_BY_SIZE
)
137 if ((maxLogSize
== 0) || (maxLogSize
>= 1024))
139 s_maxLogSize
= maxLogSize
;
147 if ((historySize
>= 0) && (historySize
<= MAX_LOG_HISTORY_SIZE
))
149 s_logHistorySize
= historySize
;
153 if (historySize
> MAX_LOG_HISTORY_SIZE
)
154 s_logHistorySize
= MAX_LOG_HISTORY_SIZE
;
158 else if (rotationMode
== NXLOG_ROTATION_DAILY
)
160 if ((dailySuffix
!= NULL
) && (dailySuffix
[0] != 0))
161 nx_strncpy(s_dailyLogSuffixTemplate
, dailySuffix
, sizeof(s_dailyLogSuffixTemplate
) / sizeof(TCHAR
));
171 nxlog_debug(0, _T("Log rotation policy set to %d (size=") UINT64_FMT
_T(", count=%d)"), rotationMode
, maxLogSize
, historySize
);
179 void LIBNETXMS_EXPORTABLE
nxlog_set_console_writer(NxLogConsoleWriter writer
)
181 m_consoleWriter
= writer
;
187 static bool RotateLog(bool needLock
)
190 TCHAR oldName
[MAX_PATH
], newName
[MAX_PATH
];
192 if (s_flags
& NXLOG_USE_SYSLOG
)
193 return FALSE
; // Cannot rotate system logs
196 MutexLock(m_mutexLogAccess
);
198 if ((m_logFileHandle
!= NULL
) && (s_flags
& NXLOG_IS_OPEN
))
200 fclose(m_logFileHandle
);
201 s_flags
&= ~NXLOG_IS_OPEN
;
204 if (s_rotationMode
== NXLOG_ROTATION_BY_SIZE
)
207 for(i
= MAX_LOG_HISTORY_SIZE
; i
>= s_logHistorySize
; i
--)
209 _sntprintf(oldName
, MAX_PATH
, _T("%s.%d"), m_logFileName
, i
);
216 _sntprintf(oldName
, MAX_PATH
, _T("%s.%d"), m_logFileName
, i
);
217 _sntprintf(newName
, MAX_PATH
, _T("%s.%d"), m_logFileName
, i
+ 1);
218 _trename(oldName
, newName
);
221 // Rename current log to name.0
222 _sntprintf(newName
, MAX_PATH
, _T("%s.0"), m_logFileName
);
223 _trename(m_logFileName
, newName
);
225 else if (s_rotationMode
== NXLOG_ROTATION_DAILY
)
229 struct tm
*loc
= localtime_r(&m_currentDayStart
, <mBuffer
);
231 struct tm
*loc
= localtime(&m_currentDayStart
);
234 _tcsftime(buffer
, 64, s_dailyLogSuffixTemplate
, loc
);
236 // Rename current log to name.suffix
237 _sntprintf(newName
, MAX_PATH
, _T("%s.%s"), m_logFileName
, buffer
);
238 _trename(m_logFileName
, newName
);
245 m_logFileHandle
= _tfopen64(m_logFileName
, _T("w"));
247 m_logFileHandle
= _tfopen(m_logFileName
, _T("w"));
249 if (m_logFileHandle
!= NULL
)
251 s_flags
|= NXLOG_IS_OPEN
;
253 _ftprintf(m_logFileHandle
, _T("%s Log file truncated.\n"), FormatLogTimestamp(buffer
));
254 fflush(m_logFileHandle
);
256 int fd
= fileno(m_logFileHandle
);
257 fcntl(fd
, F_SETFD
, fcntl(fd
, F_GETFD
) | FD_CLOEXEC
);
262 MutexUnlock(m_mutexLogAccess
);
264 return (s_flags
& NXLOG_IS_OPEN
) ?
true : false;
268 * API for application to force log rotation
270 bool LIBNETXMS_EXPORTABLE
nxlog_rotate()
272 return RotateLog(true);
276 * Background writer thread
278 static THREAD_RESULT THREAD_CALL
BackgroundWriterThread(void *arg
)
283 stop
= ConditionWait(s_writerStopCondition
, 1000);
285 // Check for new day start
286 time_t t
= time(NULL
);
287 if ((s_rotationMode
== NXLOG_ROTATION_DAILY
) && (t
>= m_currentDayStart
+ 86400))
292 MutexLock(m_mutexLogAccess
);
293 if (!s_logBuffer
.isEmpty())
295 if (s_flags
& NXLOG_PRINT_TO_STDOUT
)
296 m_consoleWriter(_T("%s"), s_logBuffer
.getBuffer());
298 size_t buflen
= s_logBuffer
.length();
299 char *data
= s_logBuffer
.getUTF8String();
301 MutexUnlock(m_mutexLogAccess
);
303 if (s_flags
& NXLOG_DEBUG_MODE
)
306 sprintf(marker
, "##(" INT64_FMTA
")" INT64_FMTA
" @" INT64_FMTA
"\n",
307 (INT64
)buflen
, (INT64
)strlen(data
), GetCurrentTimeMs());
309 fwrite(marker
, 1, strlen(marker
), m_logFileHandle
);
311 write(fileno(m_logFileHandle
), marker
, strlen(marker
));
316 fwrite(data
, 1, strlen(data
), m_logFileHandle
);
318 // write is used here because on linux fwrite is not working
319 // after calling fwprintf on a stream
320 size_t size
= strlen(data
);
324 int bw
= write(fileno(m_logFileHandle
), &data
[offset
], size
);
334 if ((m_logFileHandle
!= NULL
) && (s_rotationMode
== NXLOG_ROTATION_BY_SIZE
) && (s_maxLogSize
!= 0))
337 NX_FSTAT(fileno(m_logFileHandle
), &st
);
338 if ((UINT64
)st
.st_size
>= s_maxLogSize
)
344 MutexUnlock(m_mutexLogAccess
);
353 bool LIBNETXMS_EXPORTABLE
nxlog_open(const TCHAR
*logName
, UINT32 flags
,
354 const TCHAR
*msgModule
, unsigned int msgCount
, const TCHAR
**messages
,
355 DWORD debugMsg
, DWORD genericMsg
)
357 s_flags
= flags
& 0x7FFFFFFF;
359 m_msgModuleHandle
= GetModuleHandle(msgModule
);
361 m_numMessages
= msgCount
;
362 m_messages
= messages
;
364 s_debugMsg
= debugMsg
;
365 s_genericMsg
= genericMsg
;
367 if (s_flags
& NXLOG_USE_SYSLOG
)
370 m_eventLogHandle
= RegisterEventSource(NULL
, logName
);
371 if (m_eventLogHandle
!= NULL
)
372 s_flags
|= NXLOG_IS_OPEN
;
375 WideCharToMultiByte(CP_ACP
, WC_DEFAULTCHAR
| WC_COMPOSITECHECK
, logName
, -1, s_syslogName
, 64, NULL
, NULL
);
376 s_syslogName
[63] = 0;
378 nx_strncpy(s_syslogName
, logName
, 64);
380 openlog(s_syslogName
, LOG_PID
, LOG_DAEMON
);
381 s_flags
|= NXLOG_IS_OPEN
;
388 nx_strncpy(m_logFileName
, logName
, MAX_PATH
);
390 m_logFileHandle
= _tfopen64(logName
, _T("a"));
392 m_logFileHandle
= _tfopen(logName
, _T("a"));
394 if (m_logFileHandle
!= NULL
)
396 s_flags
|= NXLOG_IS_OPEN
;
397 _ftprintf(m_logFileHandle
, _T("\n%s Log file opened (rotation policy %d, max size ") UINT64_FMT
_T(")\n"),
398 FormatLogTimestamp(buffer
), s_rotationMode
, s_maxLogSize
);
399 fflush(m_logFileHandle
);
402 int fd
= fileno(m_logFileHandle
);
403 fcntl(fd
, F_SETFD
, fcntl(fd
, F_GETFD
) | FD_CLOEXEC
);
406 if (s_flags
& NXLOG_BACKGROUND_WRITER
)
408 s_logBuffer
.setAllocationStep(8192);
409 s_writerStopCondition
= ConditionCreate(TRUE
);
410 s_writerThread
= ThreadCreateEx(BackgroundWriterThread
, 0, NULL
);
414 m_mutexLogAccess
= MutexCreate();
417 return (s_flags
& NXLOG_IS_OPEN
) ?
true : false;
423 void LIBNETXMS_EXPORTABLE
nxlog_close()
425 if (s_flags
& NXLOG_IS_OPEN
)
427 if (s_flags
& NXLOG_USE_SYSLOG
)
430 DeregisterEventSource(m_eventLogHandle
);
437 if (s_flags
& NXLOG_BACKGROUND_WRITER
)
439 ConditionSet(s_writerStopCondition
);
440 ThreadJoin(s_writerThread
);
441 ConditionDestroy(s_writerStopCondition
);
443 if (m_logFileHandle
!= NULL
)
444 fclose(m_logFileHandle
);
445 if (m_mutexLogAccess
!= INVALID_MUTEX_HANDLE
)
446 MutexDestroy(m_mutexLogAccess
);
448 s_flags
&= ~NXLOG_IS_OPEN
;
453 * Write record to log file
455 static void WriteLogToFile(TCHAR
*message
, const WORD wType
)
462 case EVENTLOG_ERROR_TYPE
:
463 _sntprintf(loglevel
, 16, _T("[%s] "), _T("ERROR"));
465 case EVENTLOG_WARNING_TYPE
:
466 _sntprintf(loglevel
, 16, _T("[%s] "), _T("WARN "));
468 case EVENTLOG_INFORMATION_TYPE
:
469 _sntprintf(loglevel
, 16, _T("[%s] "), _T("INFO "));
471 case EVENTLOG_DEBUG_TYPE
:
472 _sntprintf(loglevel
, 16, _T("[%s] "), _T("DEBUG"));
475 _tcsncpy(loglevel
, _T(""), 1);
479 if (s_flags
& NXLOG_BACKGROUND_WRITER
)
481 MutexLock(m_mutexLogAccess
);
483 FormatLogTimestamp(buffer
);
484 s_logBuffer
.append(buffer
);
485 s_logBuffer
.append(_T(" "));
486 s_logBuffer
.append(loglevel
);
487 s_logBuffer
.append(message
);
489 MutexUnlock(m_mutexLogAccess
);
493 // Prevent simultaneous write to log file
494 MutexLock(m_mutexLogAccess
);
496 // Check for new day start
497 time_t t
= time(NULL
);
498 if ((s_rotationMode
== NXLOG_ROTATION_DAILY
) && (t
>= m_currentDayStart
+ 86400))
503 FormatLogTimestamp(buffer
);
504 if (m_logFileHandle
!= NULL
)
506 _ftprintf(m_logFileHandle
, _T("%s %s%s"), buffer
, loglevel
, message
);
507 fflush(m_logFileHandle
);
509 if (s_flags
& NXLOG_PRINT_TO_STDOUT
)
510 m_consoleWriter(_T("%s %s%s"), buffer
, loglevel
, message
);
513 if ((m_logFileHandle
!= NULL
) && (s_rotationMode
== NXLOG_ROTATION_BY_SIZE
) && (s_maxLogSize
!= 0))
516 NX_FSTAT(fileno(m_logFileHandle
), &st
);
517 if ((UINT64
)st
.st_size
>= s_maxLogSize
)
521 MutexUnlock(m_mutexLogAccess
);
526 * Format message (UNIX version)
530 static TCHAR
*FormatMessageUX(UINT32 dwMsgId
, TCHAR
**ppStrings
)
536 if (dwMsgId
>= m_numMessages
)
538 // No message with this ID
539 pMsg
= (TCHAR
*)malloc(64 * sizeof(TCHAR
));
540 _sntprintf(pMsg
, 64, _T("MSG 0x%08X - Unable to find message text\n"), (unsigned int)dwMsgId
);
544 iSize
= (_tcslen(m_messages
[dwMsgId
]) + 2) * sizeof(TCHAR
);
545 pMsg
= (TCHAR
*)malloc(iSize
);
547 for(i
= 0, p
= m_messages
[dwMsgId
]; *p
!= 0; p
++)
551 if ((*p
>= _T('1')) && (*p
<= _T('9')))
553 iLen
= _tcslen(ppStrings
[*p
- _T('1')]);
554 iSize
+= iLen
* sizeof(TCHAR
);
555 pMsg
= (TCHAR
*)realloc(pMsg
, iSize
);
556 _tcscpy(&pMsg
[i
], ppStrings
[*p
- _T('1')]);
561 if (*p
== 0) // Handle single % character at the string end
570 pMsg
[i
++] = _T('\n');
577 #endif /* ! _WIN32 */
583 * wType - Message type (see ReportEvent() for details)
584 * format - Parameter format string, each parameter represented by one character.
585 * The following format characters can be used:
586 * s - String (multibyte or UNICODE, depending on build)
587 * m - multibyte string
589 * d - Decimal integer
591 * e - System error code (will appear in log as textual description)
592 * a - IP address in network byte order
593 * A - IPv6 address in network byte order
594 * H - IPv6 address in network byte order (will appear in [])
595 * I - pointer to InetAddress object
597 void LIBNETXMS_EXPORTABLE
nxlog_write(DWORD msg
, WORD wType
, const char *format
, ...)
600 TCHAR
*strings
[16], *pMsg
, szBuffer
[256];
603 #if defined(UNICODE) && !defined(_WIN32)
607 if (!(s_flags
& NXLOG_IS_OPEN
) || (msg
== 0))
610 memset(strings
, 0, sizeof(TCHAR
*) * 16);
614 va_start(args
, format
);
616 for(; (format
[numStrings
] != 0) && (numStrings
< 16); numStrings
++)
618 switch(format
[numStrings
])
621 strings
[numStrings
] = _tcsdup(va_arg(args
, TCHAR
*));
625 strings
[numStrings
] = WideStringFromMBString(va_arg(args
, char *));
627 strings
[numStrings
] = strdup(va_arg(args
, char *));
632 strings
[numStrings
] = wcsdup(va_arg(args
, WCHAR
*));
634 strings
[numStrings
] = MBStringFromWideString(va_arg(args
, WCHAR
*));
638 strings
[numStrings
] = (TCHAR
*)malloc(16 * sizeof(TCHAR
));
639 _sntprintf(strings
[numStrings
], 16, _T("%d"), (int)(va_arg(args
, LONG
)));
642 strings
[numStrings
] = (TCHAR
*)malloc(16 * sizeof(TCHAR
));
643 _sntprintf(strings
[numStrings
], 16, _T("0x%08X"), (unsigned int)(va_arg(args
, UINT32
)));
646 strings
[numStrings
] = (TCHAR
*)malloc(20 * sizeof(TCHAR
));
647 IpToStr(va_arg(args
, UINT32
), strings
[numStrings
]);
650 strings
[numStrings
] = (TCHAR
*)malloc(48 * sizeof(TCHAR
));
651 Ip6ToStr(va_arg(args
, BYTE
*), strings
[numStrings
]);
654 strings
[numStrings
] = (TCHAR
*)malloc(48 * sizeof(TCHAR
));
655 strings
[numStrings
][0] = _T('[');
656 Ip6ToStr(va_arg(args
, BYTE
*), strings
[numStrings
] + 1);
657 _tcscat(strings
[numStrings
], _T("]"));
660 strings
[numStrings
] = (TCHAR
*)malloc(48 * sizeof(TCHAR
));
661 va_arg(args
, InetAddress
*)->toString(strings
[numStrings
]);
664 error
= va_arg(args
, UINT32
);
666 if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER
|
667 FORMAT_MESSAGE_FROM_SYSTEM
|
668 FORMAT_MESSAGE_IGNORE_INSERTS
,
670 MAKELANGID(LANG_NEUTRAL
,SUBLANG_DEFAULT
), // Default language
671 (LPTSTR
)&pMsg
, 0, NULL
) > 0)
673 pMsg
[_tcscspn(pMsg
, _T("\r\n"))] = 0;
674 strings
[numStrings
] = (TCHAR
*)malloc((_tcslen(pMsg
) + 1) * sizeof(TCHAR
));
675 _tcscpy(strings
[numStrings
], pMsg
);
680 strings
[numStrings
] = (TCHAR
*)malloc(64 * sizeof(TCHAR
));
681 _sntprintf(strings
[numStrings
], 64, _T("MSG 0x%08X - Unable to find message text"), error
);
685 #if HAVE_POSIX_STRERROR_R
686 strings
[numStrings
] = (TCHAR
*)malloc(256 * sizeof(TCHAR
));
687 _tcserror_r((int)error
, strings
[numStrings
], 256);
689 strings
[numStrings
] = _tcsdup(_tcserror_r((int)error
, szBuffer
, 256));
692 strings
[numStrings
] = _tcsdup(_tcserror((int)error
));
697 strings
[numStrings
] = (TCHAR
*)malloc(32 * sizeof(TCHAR
));
698 _sntprintf(strings
[numStrings
], 32, _T("BAD FORMAT (0x%08X)"), (unsigned int)(va_arg(args
, UINT32
)));
706 if (!(s_flags
& NXLOG_USE_SYSLOG
) || (s_flags
& NXLOG_PRINT_TO_STDOUT
))
710 if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER
|
711 FORMAT_MESSAGE_FROM_HMODULE
| FORMAT_MESSAGE_ARGUMENT_ARRAY
,
712 m_msgModuleHandle
, msg
, 0, (LPTSTR
)&lpMsgBuf
, 0, (va_list *)strings
) > 0)
716 // Replace trailing CR/LF pair with LF
717 pCR
= _tcschr((TCHAR
*)lpMsgBuf
, _T('\r'));
724 if (s_flags
& NXLOG_USE_SYSLOG
)
726 m_consoleWriter(_T("%s %s"), FormatLogTimestamp(szBuffer
), (TCHAR
*)lpMsgBuf
);
730 WriteLogToFile((TCHAR
*)lpMsgBuf
, wType
);
738 _sntprintf(message
, 64, _T("MSG 0x%08X - cannot format message"), msg
);
739 if (s_flags
& NXLOG_USE_SYSLOG
)
741 m_consoleWriter(_T("%s %s"), FormatLogTimestamp(szBuffer
), message
);
745 WriteLogToFile(message
, wType
);
750 if (s_flags
& NXLOG_USE_SYSLOG
)
752 ReportEvent(m_eventLogHandle
, (wType
== EVENTLOG_DEBUG_TYPE
) ? EVENTLOG_INFORMATION_TYPE
: wType
, 0, msg
, NULL
, numStrings
, 0, (const TCHAR
**)strings
, NULL
);
755 pMsg
= FormatMessageUX(msg
, strings
);
756 if (s_flags
& NXLOG_USE_SYSLOG
)
762 case EVENTLOG_ERROR_TYPE
:
765 case EVENTLOG_WARNING_TYPE
:
768 case EVENTLOG_INFORMATION_TYPE
:
771 case EVENTLOG_DEBUG_TYPE
:
779 mbMsg
= MBStringFromWideString(pMsg
);
780 syslog(level
, "%s", mbMsg
);
783 syslog(level
, "%s", pMsg
);
786 if (s_flags
& NXLOG_PRINT_TO_STDOUT
)
788 m_consoleWriter(_T("%s %s"), FormatLogTimestamp(szBuffer
), pMsg
);
793 WriteLogToFile(pMsg
, wType
);
798 while(--numStrings
>= 0)
799 free(strings
[numStrings
]);
803 * Write generic message to log (useful for warning and error messages generated within libraries)
805 void LIBNETXMS_EXPORTABLE
nxlog_write_generic(WORD type
, const TCHAR
*format
, ...)
808 va_start(args
, format
);
810 _vsntprintf(msg
, 8192, format
, args
);
812 nxlog_write(s_genericMsg
, type
, "s", msg
);
816 * Write debug message
818 void LIBNETXMS_EXPORTABLE
nxlog_debug(int level
, const TCHAR
*format
, ...)
820 if (level
> s_debugLevel
)
824 va_start(args
, format
);
826 _vsntprintf(buffer
, 8192, format
, args
);
828 nxlog_write(s_debugMsg
, NXLOG_DEBUG
, "s", buffer
);
830 if (s_debugWriter
!= NULL
)
831 s_debugWriter(buffer
);
835 * Write debug message
837 void LIBNETXMS_EXPORTABLE
nxlog_debug2(int level
, const TCHAR
*format
, va_list args
)
839 if (level
> s_debugLevel
)
843 _vsntprintf(buffer
, 8192, format
, args
);
844 nxlog_write(s_debugMsg
, NXLOG_DEBUG
, "s", buffer
);
846 if (s_debugWriter
!= NULL
)
847 s_debugWriter(buffer
);