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>
22 #include <config_global.h>
23 #include <osl/thread.hxx>
24 #include <rtl/string.h>
25 #include <sal/detail/log.h>
26 #include <sal/log.hxx>
27 #include <sal/types.h>
28 #include <backtraceasstring.hxx>
29 #include <salusesyslog.hxx>
32 #include <android/log.h>
36 #define OSL_DETAIL_GETPID _getpid()
39 #define OSL_DETAIL_GETPID getpid()
44 // sal/osl/unx/salinit.cxx::sal_detail_initialize updates this:
47 bool const sal_use_syslog
= false;
50 // Avoid the use of other sal code in this file as much as possible, so that
51 // this code can be called from other sal code without causing endless
61 osl_getSystemTime(&aTime
);
65 TimeContainer aStartTime
;
68 char const * string1
, std::size_t length1
, char const * string2
,
71 return length1
== length2
&& std::memcmp(string1
, string2
, length1
) == 0;
75 char const * toString(sal_detail_LogLevel level
) {
77 case SAL_DETAIL_LOG_LEVEL_INFO
:
79 case SAL_DETAIL_LOG_LEVEL_WARN
:
81 case SAL_DETAIL_LOG_LEVEL_DEBUG
:
84 assert(false); // this cannot happen
92 char const* setEnvFromLoggingIniFile(const char* env
, const char* key
)
94 char const* sResult
= nullptr;
95 wchar_t buffer
[MAX_PATH
];
96 GetModuleFileNameW(nullptr, buffer
, MAX_PATH
);
97 std::wstring
sProgramDirectory(buffer
);
98 std::wstring::size_type pos
= sProgramDirectory
.find_last_of(L
"\\/");
99 sProgramDirectory
= sProgramDirectory
.substr(0, pos
+1);
100 sProgramDirectory
+= L
"logging.ini";
102 std::ifstream
logFileStream(sProgramDirectory
);
103 if (!logFileStream
.good())
108 std::string
sWantedKey(key
);
110 while (std::getline(logFileStream
, sLine
)) {
111 if (sLine
.find('#') == 0)
113 if ( ( n
= sLine
.find('=') ) != std::string::npos
) {
114 aKey
= sLine
.substr(0, n
);
115 if (aKey
!= sWantedKey
)
117 _putenv_s(env
, sLine
.substr(n
+1, sLine
.length()).c_str());
118 sResult
= std::getenv(env
);
126 char const* pLogSelector
= nullptr;
128 char const* getLogLevelEnvVar() {
129 static char const* const pLevel
= [] {
130 char const* pResult
= nullptr;
132 // First check the environment variable, then the setting in logging.ini
133 char const* env
= std::getenv("SAL_LOG");
137 env
= setEnvFromLoggingIniFile("SAL_LOG", "LogLevel");
142 // Make a copy from the string in environment block
143 static std::string
sLevel(env
);
144 pResult
= sLevel
.c_str();
154 std::ofstream
* getLogFile() {
155 static std::ofstream
* const pFile
= [] {
156 std::ofstream
* pResult
= nullptr;
158 // First check the environment variable, then the setting in logging.ini
159 char const* logFile
= std::getenv("SAL_LOG_FILE");
163 logFile
= setEnvFromLoggingIniFile("SAL_LOG_FILE", "LogFilePath");
168 // stays until process exits
169 static std::ofstream
file(logFile
, std::ios::app
| std::ios::out
);
180 std::pair
<bool, bool> getTimestampFlags(char const *selector
)
182 bool outputTimestamp
= false;
183 bool outputRelativeTimer
= false;
184 for (char const* p
= selector
; p
&& *p
;)
189 while (*p1
!= '.' && *p1
!= '+' && *p1
!= '-' && *p1
!= '\0') {
192 if (equalStrings(p
, p1
- p
, RTL_CONSTASCII_STRINGPARAM("TIMESTAMP")))
193 outputTimestamp
= true;
194 else if (equalStrings(p
, p1
- p
, RTL_CONSTASCII_STRINGPARAM("RELATIVETIMER")))
195 outputRelativeTimer
= true;
196 char const * p2
= p1
;
197 while (*p2
!= '+' && *p2
!= '-' && *p2
!= '\0') {
203 return std::pair(outputTimestamp
, outputRelativeTimer
);
206 void maybeOutputTimestamp(std::ostringstream
&s
) {
207 static const std::pair
<bool, bool> aEnvFlags
= getTimestampFlags(getLogLevelEnvVar());
208 const auto& [outputTimestamp
, outputRelativeTimer
] = (pLogSelector
== nullptr ? aEnvFlags
: getTimestampFlags(pLogSelector
));
210 if (!(outputTimestamp
|| outputRelativeTimer
)) {
214 osl_getSystemTime(&now
);
220 osl_getLocalTimeFromSystemTime(&now
, &localTime
);
221 oslDateTime dateTime
;
222 osl_getDateTimeFromTimeValue(&localTime
, &dateTime
);
224 tm
.tm_sec
= dateTime
.Seconds
;
225 tm
.tm_min
= dateTime
.Minutes
;
226 tm
.tm_hour
= dateTime
.Hours
;
227 tm
.tm_mday
= dateTime
.Day
;
228 tm
.tm_wday
= dateTime
.DayOfWeek
;
229 tm
.tm_mon
= dateTime
.Month
- 1;
230 tm
.tm_year
= dateTime
.Year
- 1900;
232 strftime(ts
, sizeof(ts
), "%Y-%m-%d:%H:%M:%S", &tm
);
234 snprintf(milliSecs
, sizeof(milliSecs
), "%03u",
235 static_cast<unsigned>(dateTime
.NanoSeconds
/ 1000000));
236 s
<< ts
<< '.' << milliSecs
<< ':';
239 if (outputRelativeTimer
)
241 int seconds
= now
.Seconds
- aStartTime
.aTime
.Seconds
;
243 if (now
.Nanosec
< aStartTime
.aTime
.Nanosec
)
246 milliSeconds
= 1000 - (aStartTime
.aTime
.Nanosec
- now
.Nanosec
) / 1000000;
249 milliSeconds
= (now
.Nanosec
- aStartTime
.aTime
.Nanosec
) / 1000000;
250 char relativeTimestamp
[100];
251 snprintf(relativeTimestamp
, sizeof(relativeTimestamp
), "%d.%03d", seconds
, milliSeconds
);
252 s
<< relativeTimestamp
<< ':';
261 sal_detail_LogLevel level
, char const * area
, char const * where
,
262 char const * message
, sal_uInt32 backtraceDepth
)
264 std::ostringstream s
;
266 // On Android, the area will be used as the "tag," and log info already
267 // contains timestamp and PID.
268 if (!sal_use_syslog
) {
269 maybeOutputTimestamp(s
);
270 s
<< toString(level
) << ':';
272 if (level
!= SAL_DETAIL_LOG_LEVEL_DEBUG
) {
275 s
<< OSL_DETAIL_GETPID
<< ':';
277 s
<< osl::Thread::getCurrentIdentifier() << ':';
278 if (level
== SAL_DETAIL_LOG_LEVEL_DEBUG
) {
281 const size_t nStrLen(std::strlen(SRCDIR
"/"));
283 + (std::strncmp(where
, SRCDIR
"/", nStrLen
) == 0
287 if (backtraceDepth
!= 0) {
288 s
<< " at:\n" << osl::detail::backtraceAsString(backtraceDepth
);
292 int android_log_level
;
294 case SAL_DETAIL_LOG_LEVEL_INFO
:
295 android_log_level
= ANDROID_LOG_INFO
;
297 case SAL_DETAIL_LOG_LEVEL_WARN
:
298 android_log_level
= ANDROID_LOG_WARN
;
300 case SAL_DETAIL_LOG_LEVEL_DEBUG
:
301 android_log_level
= ANDROID_LOG_DEBUG
;
304 android_log_level
= ANDROID_LOG_INFO
;
308 android_log_level
, area
== 0 ? "LibreOffice" : area
, "%s",
311 if (sal_use_syslog
) {
315 case SAL_DETAIL_LOG_LEVEL_INFO
:
318 case SAL_DETAIL_LOG_LEVEL_WARN
:
321 case SAL_DETAIL_LOG_LEVEL_DEBUG
:
325 assert(false); // this cannot happen
328 syslog(prio
, "%s", s
.str().c_str());
331 // avoid calling getLogFile() more than once
332 static std::ofstream
* logFile
= getLogFile();
334 *logFile
<< s
.str() << std::endl
;
339 // write to Windows debugger console, too
340 OutputDebugStringA(s
.str().c_str());
342 std::fputs(s
.str().c_str(), stderr
);
349 void sal_detail_set_log_selector(char const *logSelector
)
351 pLogSelector
= logSelector
;
354 void sal_detail_logFormat(
355 sal_detail_LogLevel level
, char const * area
, char const * where
,
356 char const * format
, ...)
358 const sal_detail_LogAction eAction
359 = static_cast<sal_detail_LogAction
>(sal_detail_log_report(level
, area
));
360 if (eAction
== SAL_DETAIL_LOG_ACTION_IGNORE
)
364 va_start(args
, format
);
366 int const len
= sizeof buf
- RTL_CONSTASCII_LENGTH("...");
367 int n
= vsnprintf(buf
, len
, format
, args
);
369 std::strcpy(buf
, "???");
370 } else if (n
>= len
) {
371 std::strcpy(buf
+ len
- 1, "...");
373 sal_detail_log(level
, area
, where
, buf
, 0);
376 if (eAction
== SAL_DETAIL_LOG_ACTION_FATAL
)
380 unsigned char sal_detail_log_report(sal_detail_LogLevel level
, char const * area
)
382 if (level
== SAL_DETAIL_LOG_LEVEL_DEBUG
) {
383 return SAL_DETAIL_LOG_ACTION_LOG
;
385 assert(area
!= nullptr);
386 static char const* const envEnv
= [] {
387 char const* pResult
= getLogLevelEnvVar();
392 char const* const env
= (pLogSelector
== nullptr ? envEnv
: pLogSelector
);
393 std::size_t areaLen
= std::strlen(area
);
394 enum Sense
{ POSITIVE
= 0, NEGATIVE
= 1 };
395 std::size_t senseLen
[2] = { 0, 1 };
396 // initial senseLen[POSITIVE] < senseLen[NEGATIVE], so that if there are
397 // no matching switches at all, the result will be negative (and
398 // initializing with 1 is safe as the length of a valid switch, even
399 // without the "+"/"-" prefix, will always be > 1)
400 bool senseFatal
[2] = { false, false };
401 bool seenWarn
= false;
402 bool bFlagFatal
= false;
403 for (char const * p
= env
;;) {
408 if (level
== SAL_DETAIL_LOG_LEVEL_WARN
&& !seenWarn
)
409 return sal_detail_log_report(SAL_DETAIL_LOG_LEVEL_INFO
, area
);
411 sal_detail_LogAction eAction
= SAL_DETAIL_LOG_ACTION_IGNORE
;
412 // if a specific item is positive and negative (==), default to positive
413 if (senseLen
[POSITIVE
] >= senseLen
[NEGATIVE
])
415 if (senseFatal
[POSITIVE
]) eAction
= SAL_DETAIL_LOG_ACTION_FATAL
;
416 else eAction
= SAL_DETAIL_LOG_ACTION_LOG
;
427 return SAL_DETAIL_LOG_ACTION_LOG
; // upon an illegal SAL_LOG value, enable everything
430 while (*p1
!= '.' && *p1
!= '+' && *p1
!= '-' && *p1
!= '\0') {
434 if (equalStrings(p
, p1
- p
, RTL_CONSTASCII_STRINGPARAM("INFO"))) {
435 match
= level
== SAL_DETAIL_LOG_LEVEL_INFO
;
436 } else if (equalStrings(p
, p1
- p
, RTL_CONSTASCII_STRINGPARAM("WARN")))
438 match
= level
== SAL_DETAIL_LOG_LEVEL_WARN
;
440 } else if (equalStrings(p
, p1
- p
, RTL_CONSTASCII_STRINGPARAM("FATAL")))
442 bFlagFatal
= (sense
== POSITIVE
);
444 } else if (equalStrings(p
, p1
- p
, RTL_CONSTASCII_STRINGPARAM("TIMESTAMP")) ||
445 equalStrings(p
, p1
- p
, RTL_CONSTASCII_STRINGPARAM("RELATIVETIMER")))
450 return SAL_DETAIL_LOG_ACTION_LOG
;
451 // upon an illegal SAL_LOG value, everything is considered
454 char const * p2
= p1
;
455 while (*p2
!= '+' && *p2
!= '-' && *p2
!= '\0') {
461 std::size_t n
= p2
- p1
;
462 if ((n
== areaLen
&& equalStrings(p1
, n
, area
, areaLen
))
463 || (n
< areaLen
&& area
[n
] == '.'
464 && equalStrings(p1
, n
, area
, n
)))
466 senseLen
[sense
] = p2
- p
;
467 senseFatal
[sense
] = bFlagFatal
;
470 senseLen
[sense
] = p1
- p
;
471 senseFatal
[sense
] = bFlagFatal
;
478 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */