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>
24 #include <config_global.h>
25 #include <config_options.h>
26 #include <osl/thread.hxx>
27 #include <rtl/string.h>
28 #include <sal/detail/log.h>
29 #include <sal/log.hxx>
30 #include <sal/types.h>
31 #include <backtraceasstring.hxx>
32 #include <salusesyslog.hxx>
35 #include <android/log.h>
39 #define OSL_DETAIL_GETPID _getpid()
42 #define OSL_DETAIL_GETPID getpid()
47 // sal/osl/unx/salinit.cxx::sal_detail_initialize updates this:
50 bool const sal_use_syslog
= false;
53 // Avoid the use of other sal code in this file as much as possible, so that
54 // this code can be called from other sal code without causing endless
60 char const * string1
, std::size_t length1
, char const * string2
,
63 return length1
== length2
&& std::memcmp(string1
, string2
, length1
) == 0;
67 char const * toString(sal_detail_LogLevel level
) {
69 case SAL_DETAIL_LOG_LEVEL_INFO
:
71 case SAL_DETAIL_LOG_LEVEL_WARN
:
73 case SAL_DETAIL_LOG_LEVEL_DEBUG
:
76 assert(false); // this cannot happen
82 // getenv is not thread safe, so minimize use of result; except on Android, see
83 // 60628799633ffde502cb105b98d3f254f93115aa "Notice if SAL_LOG is changed while
84 // the process is running":
87 char const * getLogLevel() {
88 return std::getenv("SAL_LOG");
93 char const * getEnvironmentVariable(const char* env
) {
94 char const * p1
= std::getenv(env
);
98 char const * p2
= strdup(p1
); // leaked
100 std::abort(); // cannot do much here
106 # define INI_STRINGBUF_SIZE 1024
108 bool getValueFromLoggingIniFile(const char* key
, char* value
) {
109 char buffer
[MAX_PATH
];
110 GetModuleFileName(nullptr, buffer
, MAX_PATH
);
111 std::string sProgramDirectory
= std::string(buffer
);
112 std::string::size_type pos
= sProgramDirectory
.find_last_of( "\\/" );
113 sProgramDirectory
= sProgramDirectory
.substr(0, pos
+1);
114 sProgramDirectory
+= "logging.ini";
116 std::ifstream
logFileStream(sProgramDirectory
);
117 if (!logFileStream
.good())
123 std::string
sWantedKey(key
);
125 while (std::getline(logFileStream
, sLine
)) {
126 if (sLine
.find('#') == 0)
128 if ( ( n
= sLine
.find('=') ) != std::string::npos
) {
129 aKey
= sLine
.substr(0, n
);
130 if (aKey
!= sWantedKey
)
132 aValue
= sLine
.substr(n
+1, sLine
.length());
133 snprintf(value
, INI_STRINGBUF_SIZE
, "%s", aValue
.c_str());
141 char const * getLogLevel() {
142 // First check the environment variable, then the setting in logging.ini
143 static char const * env
= getEnvironmentVariable("SAL_LOG");
149 static char logLevel
[INI_STRINGBUF_SIZE
];
150 if (getValueFromLoggingIniFile("LogLevel", logLevel
))
157 std::ofstream
* getLogFile() {
158 // First check the environment variable, then the setting in logging.ini
159 static char const * logFile
= getEnvironmentVariable("SAL_LOG_FILE");
164 static char logFilePath
[INI_STRINGBUF_SIZE
];
165 if (getValueFromLoggingIniFile("LogFilePath", logFilePath
))
166 logFile
= logFilePath
;
169 // stays until process exits
170 static std::ofstream
file(logFile
, std::ios::app
| std::ios::out
);
175 void maybeOutputTimestamp(std::ostringstream
&s
) {
176 static char const * env
= getLogLevel();
179 bool outputTimestamp
= false;
180 bool outputRelativeTimer
= false;
181 for (char const * p
= env
;;) {
184 if (outputTimestamp
) {
186 TimeValue systemTime
;
187 osl_getSystemTime(&systemTime
);
189 osl_getLocalTimeFromSystemTime(&systemTime
, &localTime
);
190 oslDateTime dateTime
;
191 osl_getDateTimeFromTimeValue(&localTime
, &dateTime
);
193 tm
.tm_sec
= dateTime
.Seconds
;
194 tm
.tm_min
= dateTime
.Minutes
;
195 tm
.tm_hour
= dateTime
.Hours
;
196 tm
.tm_mday
= dateTime
.Day
;
197 tm
.tm_mon
= dateTime
.Month
- 1;
198 tm
.tm_year
= dateTime
.Year
- 1900;
199 strftime(ts
, sizeof(ts
), "%Y-%m-%d:%H:%M:%S", &tm
);
201 snprintf(milliSecs
, sizeof(milliSecs
), "%03u", static_cast<unsigned>(dateTime
.NanoSeconds
/1000000));
202 s
<< ts
<< '.' << milliSecs
<< ':';
204 if (outputRelativeTimer
) {
205 static bool beenHere
= false;
206 static TimeValue first
;
208 osl_getSystemTime(&first
);
212 osl_getSystemTime(&now
);
213 int seconds
= now
.Seconds
- first
.Seconds
;
215 if (now
.Nanosec
< first
.Nanosec
) {
217 milliSeconds
= 1000-(first
.Nanosec
-now
.Nanosec
)/1000000;
220 milliSeconds
= (now
.Nanosec
-first
.Nanosec
)/1000000;
221 char relativeTimestamp
[100];
222 snprintf(relativeTimestamp
, sizeof(relativeTimestamp
), "%d.%03d", seconds
, milliSeconds
);
223 s
<< relativeTimestamp
<< ':';
229 while (*p1
!= '.' && *p1
!= '+' && *p1
!= '-' && *p1
!= '\0') {
232 if (equalStrings(p
, p1
- p
, RTL_CONSTASCII_STRINGPARAM("TIMESTAMP")))
233 outputTimestamp
= true;
234 else if (equalStrings(p
, p1
- p
, RTL_CONSTASCII_STRINGPARAM("RELATIVETIMER")))
235 outputRelativeTimer
= true;
236 char const * p2
= p1
;
237 while (*p2
!= '+' && *p2
!= '-' && *p2
!= '\0') {
254 sal_detail_LogLevel level
, char const * area
, char const * where
,
255 char const * message
, sal_uInt32 backtraceDepth
)
257 std::ostringstream s
;
259 // On Android, the area will be used as the "tag," and log info already
260 // contains timestamp and PID.
261 if (!sal_use_syslog
) {
262 maybeOutputTimestamp(s
);
263 s
<< toString(level
) << ':';
265 if (level
!= SAL_DETAIL_LOG_LEVEL_DEBUG
) {
268 s
<< OSL_DETAIL_GETPID
<< ':';
270 s
<< osl::Thread::getCurrentIdentifier() << ':';
271 if (level
== SAL_DETAIL_LOG_LEVEL_DEBUG
) {
274 const size_t nStrLen(std::strlen(SRCDIR
"/"));
276 + (std::strncmp(where
, SRCDIR
"/", nStrLen
) == 0
280 if (backtraceDepth
!= 0) {
281 s
<< " at:\n" << osl::detail::backtraceAsString(backtraceDepth
);
285 int android_log_level
;
287 case SAL_DETAIL_LOG_LEVEL_INFO
:
288 android_log_level
= ANDROID_LOG_INFO
;
290 case SAL_DETAIL_LOG_LEVEL_WARN
:
291 android_log_level
= ANDROID_LOG_WARN
;
293 case SAL_DETAIL_LOG_LEVEL_DEBUG
:
294 android_log_level
= ANDROID_LOG_DEBUG
;
297 android_log_level
= ANDROID_LOG_INFO
;
301 android_log_level
, area
== 0 ? "LibreOffice" : area
, "%s",
304 if (sal_use_syslog
) {
308 case SAL_DETAIL_LOG_LEVEL_INFO
:
311 case SAL_DETAIL_LOG_LEVEL_WARN
:
314 case SAL_DETAIL_LOG_LEVEL_DEBUG
:
318 assert(false); // this cannot happen
321 syslog(prio
, "%s", s
.str().c_str());
324 // avoid calling getLogFile() more than once
325 static std::ofstream
* logFile
= getLogFile();
327 *logFile
<< s
.str() << std::endl
;
331 // write to Windows debugger console, too
332 OutputDebugStringA(s
.str().c_str());
334 #if ! (defined(WNT) && ENABLE_RELEASE_BUILD)
335 // on Windows deployments, no one reads console output
337 std::fputs(s
.str().c_str(), stderr
);
345 void sal_detail_logFormat(
346 sal_detail_LogLevel level
, char const * area
, char const * where
,
347 char const * format
, ...)
349 if (sal_detail_log_report(level
, area
)) {
351 va_start(args
, format
);
353 int const len
= sizeof buf
- RTL_CONSTASCII_LENGTH("...");
354 int n
= vsnprintf(buf
, len
, format
, args
);
356 std::strcpy(buf
, "???");
357 } else if (n
>= len
) {
358 std::strcpy(buf
+ len
- 1, "...");
360 sal_detail_log(level
, area
, where
, buf
, 0);
365 sal_Bool
sal_detail_log_report(sal_detail_LogLevel level
, char const * area
) {
366 if (level
== SAL_DETAIL_LOG_LEVEL_DEBUG
) {
369 assert(area
!= nullptr);
370 static char const * env
= getLogLevel();
371 if (env
== nullptr) {
374 std::size_t areaLen
= std::strlen(area
);
375 enum Sense
{ POSITIVE
= 0, NEGATIVE
= 1 };
376 std::size_t senseLen
[2] = { 0, 1 };
377 // initial senseLen[POSITIVE] < senseLen[NEGATIVE], so that if there are
378 // no matching switches at all, the result will be negative (and
379 // initializing with 1 is safe as the length of a valid switch, even
380 // without the "+"/"-" prefix, will always be > 1)
381 bool seenWarn
= false;
382 for (char const * p
= env
;;) {
386 if (level
== SAL_DETAIL_LOG_LEVEL_WARN
&& !seenWarn
)
387 return sal_detail_log_report(SAL_DETAIL_LOG_LEVEL_INFO
, area
);
388 return senseLen
[POSITIVE
] >= senseLen
[NEGATIVE
];
389 // if a specific item is both positive and negative
390 // (senseLen[POSITIVE] == senseLen[NEGATIVE]), default to
399 return true; // upon an illegal SAL_LOG value, enable everything
402 while (*p1
!= '.' && *p1
!= '+' && *p1
!= '-' && *p1
!= '\0') {
406 if (equalStrings(p
, p1
- p
, RTL_CONSTASCII_STRINGPARAM("INFO"))) {
407 match
= level
== SAL_DETAIL_LOG_LEVEL_INFO
;
408 } else if (equalStrings(p
, p1
- p
, RTL_CONSTASCII_STRINGPARAM("WARN")))
410 match
= level
== SAL_DETAIL_LOG_LEVEL_WARN
;
412 } else if (equalStrings(p
, p1
- p
, RTL_CONSTASCII_STRINGPARAM("TIMESTAMP")) ||
413 equalStrings(p
, p1
- p
, RTL_CONSTASCII_STRINGPARAM("RELATIVETIMER")))
419 // upon an illegal SAL_LOG value, everything is considered
422 char const * p2
= p1
;
423 while (*p2
!= '+' && *p2
!= '-' && *p2
!= '\0') {
429 std::size_t n
= p2
- p1
;
430 if ((n
== areaLen
&& equalStrings(p1
, n
, area
, areaLen
))
431 || (n
< areaLen
&& area
[n
] == '.'
432 && equalStrings(p1
, n
, area
, n
)))
434 senseLen
[sense
] = p2
- p
;
437 senseLen
[sense
] = p1
- p
;
444 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */