1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 #include <sal/config.h>
20 #include <config_global.h>
21 #include <osl/thread.hxx>
22 #include <rtl/string.h>
23 #include <sal/detail/log.h>
24 #include <sal/log.hxx>
25 #include <sal/types.h>
26 #include <backtraceasstring.hxx>
27 #include <salusesyslog.hxx>
30 #include <android/log.h>
34 #define OSL_DETAIL_GETPID _getpid()
37 #define OSL_DETAIL_GETPID getpid()
42 // sal/osl/unx/salinit.cxx::sal_detail_initialize updates this:
45 bool const sal_use_syslog
= false;
48 // Avoid the use of other sal code in this file as much as possible, so that
49 // this code can be called from other sal code without causing endless
59 osl_getSystemTime(&aTime
);
63 TimeContainer aStartTime
;
66 char const * string1
, std::size_t length1
, char const * string2
,
69 return length1
== length2
&& std::memcmp(string1
, string2
, length1
) == 0;
73 char const * toString(sal_detail_LogLevel level
) {
75 case SAL_DETAIL_LOG_LEVEL_INFO
:
77 case SAL_DETAIL_LOG_LEVEL_WARN
:
79 case SAL_DETAIL_LOG_LEVEL_DEBUG
:
82 assert(false); // this cannot happen
90 char const* setEnvFromLoggingIniFile(const char* env
, const char* key
)
92 char const* sResult
= nullptr;
93 wchar_t buffer
[MAX_PATH
];
94 GetModuleFileNameW(nullptr, buffer
, MAX_PATH
);
95 std::wstring
sProgramDirectory(buffer
);
96 std::wstring::size_type pos
= sProgramDirectory
.find_last_of(L
"\\/");
97 sProgramDirectory
= sProgramDirectory
.substr(0, pos
+1);
98 sProgramDirectory
+= L
"logging.ini";
100 std::ifstream
logFileStream(sProgramDirectory
);
101 if (!logFileStream
.good())
106 std::string
sWantedKey(key
);
108 while (std::getline(logFileStream
, sLine
)) {
109 if (sLine
.find('#') == 0)
111 if ( ( n
= sLine
.find('=') ) != std::string::npos
) {
112 aKey
= sLine
.substr(0, n
);
113 if (aKey
!= sWantedKey
)
115 _putenv_s(env
, sLine
.substr(n
+1, sLine
.length()).c_str());
116 sResult
= std::getenv(env
);
124 char const* pLogSelector
= nullptr;
126 char const* getLogLevelEnvVar() {
127 static char const* const pLevel
= [] {
128 char const* pResult
= nullptr;
130 // First check the environment variable, then the setting in logging.ini
131 char const* env
= std::getenv("SAL_LOG");
135 env
= setEnvFromLoggingIniFile("SAL_LOG", "LogLevel");
140 // Make a copy from the string in environment block
141 static std::string
sLevel(env
);
142 pResult
= sLevel
.c_str();
152 std::ofstream
* getLogFile() {
153 static std::ofstream
* const pFile
= [] {
154 std::ofstream
* pResult
= nullptr;
156 // First check the environment variable, then the setting in logging.ini
157 char const* logFile
= std::getenv("SAL_LOG_FILE");
161 logFile
= setEnvFromLoggingIniFile("SAL_LOG_FILE", "LogFilePath");
166 // stays until process exits
167 static std::ofstream
file(logFile
, std::ios::app
| std::ios::out
);
178 std::pair
<bool, bool> getTimestampFlags(char const *selector
)
180 bool outputTimestamp
= false;
181 bool outputRelativeTimer
= false;
182 for (char const* p
= selector
; p
&& *p
;)
187 while (*p1
!= '.' && *p1
!= '+' && *p1
!= '-' && *p1
!= '\0') {
190 if (equalStrings(p
, p1
- p
, RTL_CONSTASCII_STRINGPARAM("TIMESTAMP")))
191 outputTimestamp
= true;
192 else if (equalStrings(p
, p1
- p
, RTL_CONSTASCII_STRINGPARAM("RELATIVETIMER")))
193 outputRelativeTimer
= true;
194 char const * p2
= p1
;
195 while (*p2
!= '+' && *p2
!= '-' && *p2
!= '\0') {
201 return std::pair(outputTimestamp
, outputRelativeTimer
);
204 void maybeOutputTimestamp(std::ostringstream
&s
) {
205 static const std::pair
<bool, bool> aEnvFlags
= getTimestampFlags(getLogLevelEnvVar());
206 const auto& [outputTimestamp
, outputRelativeTimer
] = (pLogSelector
== nullptr ? aEnvFlags
: getTimestampFlags(pLogSelector
));
208 if (!(outputTimestamp
|| outputRelativeTimer
)) {
212 osl_getSystemTime(&now
);
218 osl_getLocalTimeFromSystemTime(&now
, &localTime
);
219 oslDateTime dateTime
;
220 osl_getDateTimeFromTimeValue(&localTime
, &dateTime
);
222 tm
.tm_sec
= dateTime
.Seconds
;
223 tm
.tm_min
= dateTime
.Minutes
;
224 tm
.tm_hour
= dateTime
.Hours
;
225 tm
.tm_mday
= dateTime
.Day
;
226 tm
.tm_wday
= dateTime
.DayOfWeek
;
227 tm
.tm_mon
= dateTime
.Month
- 1;
228 tm
.tm_year
= dateTime
.Year
- 1900;
230 strftime(ts
, sizeof(ts
), "%Y-%m-%d:%H:%M:%S", &tm
);
232 snprintf(milliSecs
, sizeof(milliSecs
), "%03u",
233 static_cast<unsigned>(dateTime
.NanoSeconds
/ 1000000));
234 s
<< ts
<< '.' << milliSecs
<< ':';
237 if (outputRelativeTimer
)
239 int seconds
= now
.Seconds
- aStartTime
.aTime
.Seconds
;
241 if (now
.Nanosec
< aStartTime
.aTime
.Nanosec
)
244 milliSeconds
= 1000 - (aStartTime
.aTime
.Nanosec
- now
.Nanosec
) / 1000000;
247 milliSeconds
= (now
.Nanosec
- aStartTime
.aTime
.Nanosec
) / 1000000;
248 char relativeTimestamp
[100];
249 snprintf(relativeTimestamp
, sizeof(relativeTimestamp
), "%d.%03d", seconds
, milliSeconds
);
250 s
<< relativeTimestamp
<< ':';
259 sal_detail_LogLevel level
, char const * area
, char const * where
,
260 char const * message
, sal_uInt32 backtraceDepth
)
262 std::ostringstream s
;
264 // On Android, the area will be used as the "tag," and log info already
265 // contains timestamp and PID.
266 if (!sal_use_syslog
) {
267 maybeOutputTimestamp(s
);
268 s
<< toString(level
) << ':';
270 if (level
!= SAL_DETAIL_LOG_LEVEL_DEBUG
) {
273 s
<< OSL_DETAIL_GETPID
<< ':';
275 s
<< osl::Thread::getCurrentIdentifier() << ':';
276 if (level
== SAL_DETAIL_LOG_LEVEL_DEBUG
) {
279 const size_t nStrLen(std::strlen(SRCDIR
"/"));
281 + (std::strncmp(where
, SRCDIR
"/", nStrLen
) == 0
285 if (backtraceDepth
!= 0) {
286 s
<< " at:\n" << osl::detail::backtraceAsString(backtraceDepth
);
290 int android_log_level
;
292 case SAL_DETAIL_LOG_LEVEL_INFO
:
293 android_log_level
= ANDROID_LOG_INFO
;
295 case SAL_DETAIL_LOG_LEVEL_WARN
:
296 android_log_level
= ANDROID_LOG_WARN
;
298 case SAL_DETAIL_LOG_LEVEL_DEBUG
:
299 android_log_level
= ANDROID_LOG_DEBUG
;
302 android_log_level
= ANDROID_LOG_INFO
;
306 android_log_level
, area
== 0 ? "LibreOffice" : area
, "%s",
309 if (sal_use_syslog
) {
313 case SAL_DETAIL_LOG_LEVEL_INFO
:
316 case SAL_DETAIL_LOG_LEVEL_WARN
:
319 case SAL_DETAIL_LOG_LEVEL_DEBUG
:
323 assert(false); // this cannot happen
326 syslog(prio
, "%s", s
.str().c_str());
329 // avoid calling getLogFile() more than once
330 static std::ofstream
* logFile
= getLogFile();
332 *logFile
<< s
.str() << std::endl
;
337 // write to Windows debugger console, too
338 OutputDebugStringA(s
.str().c_str());
340 std::fputs(s
.str().c_str(), stderr
);
347 void sal_detail_set_log_selector(char const *logSelector
)
349 pLogSelector
= logSelector
;
352 void sal_detail_logFormat(
353 sal_detail_LogLevel level
, char const * area
, char const * where
,
354 char const * format
, ...)
356 const sal_detail_LogAction eAction
357 = static_cast<sal_detail_LogAction
>(sal_detail_log_report(level
, area
));
358 if (eAction
== SAL_DETAIL_LOG_ACTION_IGNORE
)
362 va_start(args
, format
);
364 int const len
= sizeof buf
- RTL_CONSTASCII_LENGTH("...");
365 int n
= vsnprintf(buf
, len
, format
, args
);
367 std::strcpy(buf
, "???");
368 } else if (n
>= len
) {
369 std::strcpy(buf
+ len
- 1, "...");
371 sal_detail_log(level
, area
, where
, buf
, 0);
374 if (eAction
== SAL_DETAIL_LOG_ACTION_FATAL
)
378 unsigned char sal_detail_log_report(sal_detail_LogLevel level
, char const * area
)
380 if (level
== SAL_DETAIL_LOG_LEVEL_DEBUG
) {
381 return SAL_DETAIL_LOG_ACTION_LOG
;
383 assert(area
!= nullptr);
384 static char const* const envEnv
= [] {
385 char const* pResult
= getLogLevelEnvVar();
390 char const* const env
= (pLogSelector
== nullptr ? envEnv
: pLogSelector
);
391 std::size_t areaLen
= std::strlen(area
);
392 enum Sense
{ POSITIVE
= 0, NEGATIVE
= 1 };
393 std::size_t senseLen
[2] = { 0, 1 };
394 // initial senseLen[POSITIVE] < senseLen[NEGATIVE], so that if there are
395 // no matching switches at all, the result will be negative (and
396 // initializing with 1 is safe as the length of a valid switch, even
397 // without the "+"/"-" prefix, will always be > 1)
398 bool senseFatal
[2] = { false, false };
399 bool seenWarn
= false;
400 bool bFlagFatal
= false;
401 for (char const * p
= env
;;) {
406 if (level
== SAL_DETAIL_LOG_LEVEL_WARN
&& !seenWarn
)
407 return sal_detail_log_report(SAL_DETAIL_LOG_LEVEL_INFO
, area
);
409 sal_detail_LogAction eAction
= SAL_DETAIL_LOG_ACTION_IGNORE
;
410 // if a specific item is positive and negative (==), default to positive
411 if (senseLen
[POSITIVE
] >= senseLen
[NEGATIVE
])
413 if (senseFatal
[POSITIVE
]) eAction
= SAL_DETAIL_LOG_ACTION_FATAL
;
414 else eAction
= SAL_DETAIL_LOG_ACTION_LOG
;
425 return SAL_DETAIL_LOG_ACTION_LOG
; // upon an illegal SAL_LOG value, enable everything
428 while (*p1
!= '.' && *p1
!= '+' && *p1
!= '-' && *p1
!= '\0') {
432 if (equalStrings(p
, p1
- p
, RTL_CONSTASCII_STRINGPARAM("INFO"))) {
433 match
= level
== SAL_DETAIL_LOG_LEVEL_INFO
;
434 } else if (equalStrings(p
, p1
- p
, RTL_CONSTASCII_STRINGPARAM("WARN")))
436 match
= level
== SAL_DETAIL_LOG_LEVEL_WARN
;
438 } else if (equalStrings(p
, p1
- p
, RTL_CONSTASCII_STRINGPARAM("FATAL")))
440 bFlagFatal
= (sense
== POSITIVE
);
442 } else if (equalStrings(p
, p1
- p
, RTL_CONSTASCII_STRINGPARAM("TIMESTAMP")) ||
443 equalStrings(p
, p1
- p
, RTL_CONSTASCII_STRINGPARAM("RELATIVETIMER")))
448 return SAL_DETAIL_LOG_ACTION_LOG
;
449 // upon an illegal SAL_LOG value, everything is considered
452 char const * p2
= p1
;
453 while (*p2
!= '+' && *p2
!= '-' && *p2
!= '\0') {
459 std::size_t n
= p2
- p1
;
460 if ((n
== areaLen
&& equalStrings(p1
, n
, area
, areaLen
))
461 || (n
< areaLen
&& area
[n
] == '.'
462 && equalStrings(p1
, n
, area
, n
)))
464 senseLen
[sense
] = p2
- p
;
465 senseFatal
[sense
] = bFlagFatal
;
468 senseLen
[sense
] = p1
- p
;
469 senseFatal
[sense
] = bFlagFatal
;
476 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */