2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
16 #include "hphp/util/logger.h"
18 #include "hphp/util/assertions.h"
19 #include "hphp/util/exception.h"
20 #include "hphp/util/process.h"
21 #include "hphp/util/stack-trace.h"
22 #include "hphp/util/string-vsnprintf.h"
23 #include "hphp/util/text-color.h"
25 #include <folly/portability/Syslog.h>
26 #include <folly/portability/Unistd.h>
30 #define IMPLEMENT_LOGLEVEL(LOGLEVEL) \
31 void Logger::LOGLEVEL(const char *fmt, ...) { \
32 if (LogLevel < Log ## LOGLEVEL) return; \
33 if (!IsEnabled()) return; \
35 va_list ap; va_start(ap, fmt); \
36 string_vsnprintf(msg, fmt, ap); \
38 LogImpl(Log ## LOGLEVEL, msg, nullptr); \
40 void Logger::LOGLEVEL(const std::string &msg) { \
41 if (LogLevel < Log ## LOGLEVEL) return; \
42 LogImpl(Log ## LOGLEVEL, msg, nullptr); \
47 ///////////////////////////////////////////////////////////////////////////////
49 IMPLEMENT_LOGLEVEL(Error
);
50 IMPLEMENT_LOGLEVEL(Warning
);
51 IMPLEMENT_LOGLEVEL(Info
);
52 IMPLEMENT_LOGLEVEL(Verbose
);
54 ///////////////////////////////////////////////////////////////////////////////
56 constexpr const char* Logger::DEFAULT
;
58 bool Logger::AlwaysEscapeLog
= true;
59 bool Logger::UseSyslog
= false;
60 bool Logger::UseLogFile
= true;
61 bool Logger::UseRequestLog
= false;
62 bool Logger::UseCronolog
= false;
63 Logger::LogLevelType
Logger::LogLevel
= LogInfo
;
64 bool Logger::LogHeader
= false;
65 bool Logger::LogNativeStackTrace
= true;
66 std::string
Logger::ExtraHeader
;
67 int Logger::MaxMessagesPerRequest
= -1;
68 bool Logger::Escape
= true;
70 ServiceData::ExportedCounter
* Logger::s_errorLines
=
71 ServiceData::createCounter("errorlog_lines");
72 // ideally this should be "errorlog_serialized_bytes" but this was
73 // added long ago and many tools rely on it.
74 ServiceData::ExportedCounter
* Logger::s_errorSerializedBytes
=
75 ServiceData::createCounter("errorlog_bytes");
76 ServiceData::ExportedCounter
* Logger::s_errorCompressedBytes
=
77 ServiceData::createCounter("errorlog_bytes_compressed");
78 THREAD_LOCAL(Logger::ThreadData
, Logger::s_threadData
);
80 std::map
<std::string
, Logger
*> Logger::s_loggers
= {
81 {Logger::DEFAULT
, new Logger()},
84 void Logger::Log(LogLevelType level
, const char* type
, const Exception
& e
,
85 const char *file
/* = NULL */, int line
/* = 0 */) {
86 if (!IsEnabled()) return;
87 auto msg
= type
+ e
.getMessage();
88 if (file
&& file
[0]) {
89 msg
+= folly::sformat(" in {} on line {}", file
, line
);
91 LogImpl(level
, msg
, nullptr);
94 void Logger::OnNewRequest() {
95 ThreadData
*threadData
= s_threadData
.get();
96 ++threadData
->request
;
97 threadData
->message
= 0;
100 void Logger::ResetRequestCount() {
101 ThreadData
*threadData
= s_threadData
.get();
102 threadData
->request
= 0;
103 threadData
->message
= 0;
106 int64_t Logger::GetRequestId() {
107 ThreadData
*threadData
= s_threadData
.get();
108 return threadData
->request
;
111 void Logger::LogImpl(LogLevelType level
, const std::string
&msg
,
112 const StackTrace
*stackTrace
,
113 bool escape
/* = false */, bool escapeMore
/* = false */) {
115 ThreadData
*threadData
= s_threadData
.get();
116 if (threadData
->message
!= -1 &&
117 ++threadData
->message
> MaxMessagesPerRequest
&&
118 MaxMessagesPerRequest
>= 0) {
121 for (auto& l
: s_loggers
) {
122 auto& logger
= l
.second
;
124 auto growth
= logger
->log(level
, msg
, stackTrace
, escape
, escapeMore
);
125 s_errorLines
->addValue(growth
.lines
);
126 s_errorSerializedBytes
->addValue(growth
.serializedBytes
);
127 s_errorCompressedBytes
->addValue(growth
.compressedBytes
);
132 void Logger::SetStandardOut(const std::string
&name
, FILE *file
) {
133 auto it
= s_loggers
.find(name
);
134 if (it
!= s_loggers
.end()) {
135 auto& logger
= it
->second
;
136 logger
->m_standardOut
= file
;
140 void Logger::FlushAll() {
141 for (auto& l
: s_loggers
) {
142 auto& logger
= l
.second
;
144 auto growth
= logger
->flush();
145 s_errorLines
->addValue(growth
.lines
);
146 s_errorSerializedBytes
->addValue(growth
.serializedBytes
);
147 s_errorCompressedBytes
->addValue(growth
.compressedBytes
);
152 void Logger::SetBatchSize(size_t bsize
) {
153 for (auto& l
: s_loggers
) {
154 auto& logger
= l
.second
;
155 logger
->setBatchSize(bsize
);
159 void Logger::SetFlushTimeout(std::chrono::milliseconds timeoutMs
) {
160 for (auto& l
: s_loggers
) {
161 auto& logger
= l
.second
;
162 logger
->setFlushTimeout(timeoutMs
);
166 int Logger::GetSyslogLevel(LogLevelType level
) {
168 case LogError
: return LOG_ERR
;
169 case LogWarning
: return LOG_WARNING
;
170 case LogInfo
: return LOG_INFO
;
171 case LogVerbose
: return LOG_DEBUG
;
172 default: return LOG_NOTICE
;
176 LogGrowth
Logger::log(LogLevelType level
, const std::string
&msg
,
177 const StackTrace
*stackTrace
,
178 bool escape
/* = false */,
179 bool escapeMore
/* = false */) {
180 if (Logger::AlwaysEscapeLog
&& Logger::Escape
) {
183 assertx(!escapeMore
|| escape
); // escape must be enabled to escapeMore
185 std::unique_ptr
<StackTrace
> deleter
;
186 if (LogNativeStackTrace
&& stackTrace
== nullptr) {
187 deleter
.reset(new StackTrace());
188 stackTrace
= deleter
.get();
192 syslog(GetSyslogLevel(level
), "%s", msg
.c_str());
196 ThreadData
*threadData
= s_threadData
.get();
197 FILE* tf
= threadData
->log
;
199 std::string header
, sheader
;
201 header
= GetHeader();
202 if (LogNativeStackTrace
) {
203 sheader
= header
+ "[" + stackTrace
->hexEncode(5) + "] ";
208 const char *escaped
= escape
? EscapeString(msg
) : msg
.c_str();
209 const char *ending
= escapeMore
? "\\n" : "\n";
210 if (f
== m_standardOut
&& s_stderr_color
) {
212 fprintf(f
, "%s%s%s%s%s",
213 s_stderr_color
, sheader
.c_str(), msg
.c_str(), ending
,
216 bytes
= fprintf(f
, "%s%s%s", sheader
.c_str(), escaped
, ending
);
220 fprintf(tf
, "%s%s%s", header
.c_str(), escaped
, ending
);
222 threadData
->flusher
.recordWriteAndMaybeDropCaches(tf
, threadBytes
);
224 if (threadData
->hook
) {
225 (*threadData
->hook
)(header
.c_str(), msg
.c_str(), ending
);
228 free((void*)escaped
);
231 if (UseCronolog
|| (m_output
&& !m_isPipeOutput
)) {
232 m_flusher
.recordWriteAndMaybeDropCaches(f
, bytes
);
235 return LogGrowth(1, static_cast<uint64_t>(bytes
), static_cast<uint64_t>(bytes
));
238 void Logger::ResetPid() {
242 std::string
Logger::GetHeader() {
243 static std::string host
= Process::GetHostName();
245 time_t now
= time(nullptr);
248 // Eliminate trailing newline from ctime_r.
252 ThreadData
*threadData
= s_threadData
.get();
253 snprintf(header
, sizeof(header
), "[%s] [hphp] [%lld:%llx:%d:%06d%s] ",
255 (unsigned long long)s_pid
,
256 (unsigned long long)Process::GetThreadId(),
258 (threadData
->message
== -1 ? 0 : threadData
->message
),
259 ExtraHeader
.c_str());
263 char *Logger::EscapeString(const std::string
&msg
) {
264 auto new_size
= msg
.size() * 4 + 1;
265 char *new_str
= (char *)malloc(new_size
);
269 for (source
= msg
.c_str(), end
= source
+ msg
.size(), target
= new_str
;
270 source
< end
&& *source
; source
++) {
272 if ((unsigned char) c
< 32 || (unsigned char) c
> 126) {
275 case '\n': *target
++ = 'n'; break;
276 case '\t': *target
++ = 't'; break;
277 case '\r': *target
++ = 'r'; break;
278 case '\v': *target
++ = 'v'; break;
279 case '\b': *target
++ = 'b'; break;
281 auto avail
= new_size
- (target
- new_str
);
282 target
+= snprintf(target
, avail
, "x%02x", (unsigned char)c
);
283 assertx(target
< new_str
+ new_size
); // we allocated 4x space
285 } else if (c
== '\\') {
296 bool Logger::SetThreadLog(const char *file
, bool threadOnly
) {
297 if (auto log
= fopen(file
, "a")) {
299 s_threadData
->log
= log
;
300 s_threadData
->threadLogOnly
= threadOnly
;
305 void Logger::ClearThreadLog() {
306 ThreadData
*threadData
= s_threadData
.get();
307 if (threadData
->log
) {
308 fclose(threadData
->log
);
309 threadData
->log
= nullptr;
313 void Logger::SetThreadHook(LoggerHook
* hook
) {
314 s_threadData
.get()->hook
= hook
;
317 void Logger::SetTheLogger(const std::string
&name
, Logger
* newLogger
) {
318 auto& logger
= s_loggers
[name
];
319 if (logger
!= nullptr) delete logger
;
323 s_loggers
.erase(name
);
327 bool Logger::IsDefaultLogger(const std::string
&name
) {
328 auto it
= s_loggers
.find(name
);
329 if (it
== s_loggers
.end()) return true;
330 return typeid(*it
->second
) == typeid(Logger
);
333 void Logger::UnlimitThreadMessages() {
334 ThreadData
*threadData
= s_threadData
.get();
335 threadData
->message
= -1;
338 Cronolog
*Logger::CronoOutput(const std::string
&name
) {
339 auto it
= s_loggers
.find(name
);
340 if (it
!= s_loggers
.end()) {
341 auto& logger
= it
->second
;
342 return &logger
->m_cronOutput
;
347 void Logger::SetOutput(const std::string
&name
, FILE *output
, bool isPipe
) {
348 auto it
= s_loggers
.find(name
);
349 if (it
!= s_loggers
.end()) {
350 auto& logger
= it
->second
;
351 if (logger
->m_output
&& logger
->m_output
!= output
) {
352 if (logger
->m_isPipeOutput
) {
353 pclose(logger
->m_output
);
355 fclose(logger
->m_output
);
358 logger
->m_output
= output
;
359 logger
->m_isPipeOutput
= isPipe
;
363 std::pair
<FILE*, bool> Logger::GetOutput(const std::string
&name
) {
364 const auto it
= s_loggers
.find(name
);
365 if (it
!= s_loggers
.end()) {
366 const auto& logger
= it
->second
;
367 return std::make_pair(logger
->m_output
, logger
->m_isPipeOutput
);
369 return std::make_pair(nullptr, false);
372 FILE* Logger::output() {
373 ThreadData
*threadData
= s_threadData
.get();
374 if (threadData
->log
&& threadData
->threadLogOnly
) {
375 return threadData
->log
;
377 FILE* cronOut
= m_cronOutput
.getOutputFile();
378 return cronOut
!= nullptr ? cronOut
:
379 m_output
!= nullptr ? m_output
: m_standardOut
;
382 ///////////////////////////////////////////////////////////////////////////////