Arquivos
hhvm/hphp/runtime/base/server/access_log.cpp
T
Edwin Smith f29ee5314d Remove String::operator const char*().
Too many ways to shoot self in foot with this gem.
2013-04-25 11:34:21 -07:00

438 linhas
12 KiB
C++

/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010- Facebook, Inc. (http://www.facebook.com) |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
*/
#include <runtime/base/server/access_log.h>
#include <runtime/base/time/datetime.h>
#include <runtime/base/time/timestamp.h>
#include <time.h>
#include <runtime/base/runtime_option.h>
#include <runtime/base/server/server_note.h>
#include <runtime/base/server/server_stats.h>
#include <runtime/base/server/request_uri.h>
#include <util/process.h>
#include <util/atomic.h>
#include <util/compatibility.h>
#include <util/util.h>
#include <runtime/base/hardware_counter.h>
using std::endl;
namespace HPHP {
///////////////////////////////////////////////////////////////////////////////
AccessLog::~AccessLog() {
signal(SIGCHLD, SIG_DFL);
for (uint i = 0; i < m_output.size(); ++i) {
if (m_output[i].log) {
if (m_files[i].file[0] == '|') {
pclose(m_output[i].log);
} else {
fclose(m_output[i].log);
}
}
}
}
void AccessLog::init(const string &defaultFormat,
vector<AccessLogFileData> &files,
const string &username) {
Lock l(m_lock);
if (m_initialized) return;
m_initialized = true;
m_defaultFormat = defaultFormat;
m_files = files;
openFiles(username);
}
void AccessLog::init(const string &format,
const string &symLink,
const string &file,
const string &username) {
Lock l(m_lock);
if (m_initialized) return;
m_initialized = true;
m_defaultFormat = format;
if (!file.empty() && !format.empty()) {
m_files.push_back(AccessLogFileData(file, symLink, format));
}
openFiles(username);
}
void AccessLog::openFiles(const string &username) {
assert(m_output.empty() && m_cronOutput.empty());
if (m_files.empty()) return;
for (vector<AccessLogFileData>::const_iterator it = m_files.begin();
it != m_files.end(); ++it) {
const string &file = it->file;
const string &symLink = it->symLink;
assert(!file.empty());
FILE *fp = nullptr;
if (Logger::UseCronolog) {
CronologPtr cl(new Cronolog);
if (strchr(file.c_str(), '%')) {
cl->m_template = file;
cl->setPeriodicity();
cl->m_linkName = symLink;
Cronolog::changeOwner(username, symLink);
} else {
cl->m_file = fopen(file.c_str(), "a");
}
m_cronOutput.push_back(cl);
} else {
if (file[0] == '|') {
string plog = file.substr(1);
fp = popen(plog.c_str(), "w");
} else {
fp = fopen(file.c_str(), "a");
}
if (!fp) {
Logger::Error("Could not open access log file %s", file.c_str());
}
m_output.emplace_back(fp);
}
}
}
void AccessLog::log(Transport *transport, const VirtualHost *vhost) {
assert(transport);
if (!m_initialized) return;
AccessLog::ThreadData *threadData = m_fGetThreadData();
FILE *threadLog = threadData->log;
if (threadLog) {
threadData->bytesWritten +=
writeLog(transport, vhost, threadLog, m_defaultFormat.c_str());
threadData->prevBytesWritten =
Logger::checkDropCache(threadData->bytesWritten,
threadData->prevBytesWritten,
threadLog);
}
if (Logger::UseCronolog) {
for (uint i = 0; i < m_cronOutput.size(); ++i) {
Cronolog &cronOutput = *m_cronOutput[i];
FILE *outFile = cronOutput.getOutputFile();
if (!outFile) continue;
const char *format = m_files[i].format.c_str();
int bytes = writeLog(transport, vhost, outFile, format);
cronOutput.m_bytesWritten.fetch_add(bytes, std::memory_order_relaxed);
cronOutput.m_prevBytesWritten = Logger::checkDropCache(
cronOutput.m_bytesWritten.load(std::memory_order_relaxed),
cronOutput.m_prevBytesWritten,
outFile);
}
} else {
for (uint i = 0; i < m_output.size(); ++i) {
LogFileData& output = m_output[i];
FILE *outFile = output.log;
if (!outFile) continue;
const char *format = m_files[i].format.c_str();
int bytes = writeLog(transport, vhost, outFile, format);
output.bytesWritten.fetch_add(bytes, std::memory_order_relaxed);
if (m_files[i].file[0] != '|') {
output.prevBytesWritten =
Logger::checkDropCache(output.bytesWritten,
output.prevBytesWritten,
outFile);
}
}
}
}
int AccessLog::writeLog(Transport *transport, const VirtualHost *vhost,
FILE *outFile, const char *format) {
char c;
std::ostringstream out;
while ((c = *format++)) {
if (c != '%') {
out << c;
continue;
}
if (parseConditions(format, transport->getResponseCode())) {
string arg = parseArgument(format);
if (!genField(out, format, transport, vhost, arg)) {
out << "-";
}
} else {
skipField(format);
out << "-";
}
}
out << endl;
string output = out.str();
int nbytes = fprintf(outFile, "%s", output.c_str());
fflush(outFile);
return nbytes;
}
bool AccessLog::parseConditions(const char* &format, int code) {
bool wantMatch = true;
if (*format == '!') {
wantMatch = false;
format++;
} else if (!isdigit(*format)) {
// No conditions
return true;
}
char buf[4];
buf[3] = '\0';
bool matched = false;
while(isdigit(*format)) {
buf[0] = format[0];
buf[1] = format[1];
buf[2] = format[2];
int c = atoi(buf);
if (c == code) {
matched = true;
break;
}
format+=4;
}
while (!(*format == '{' || isalpha(*format))) {
format++;
}
return wantMatch == matched;
}
string AccessLog::parseArgument(const char* &format) {
if (*format != '{') return string();
format++;
const char *start = format;
while (*format != '}') { format++; }
string res(start, format - start);
format++;
return res;
}
void AccessLog::skipField(const char* &format) {
// Skip argument
if (*format == '{') {
while (*format != '}') { format++; }
format++;
}
// Find control letter
while (!isalpha(*format)) { format++; }
// Skip it
format++;
}
static void escape_data(std::ostringstream &out, const char *s, int len)
{
static const char digits[] = "0123456789abcdef";
for (int i = 0; i < len; i++) {
unsigned char uc = *s++;
switch (uc) {
case '"': out << "\\\""; break;
case '\\': out << "\\\\"; break;
case '\b': out << "\\b"; break;
case '\f': out << "\\f"; break;
case '\n': out << "\\n"; break;
case '\r': out << "\\r"; break;
case '\t': out << "\\t"; break;
default:
if (uc >= ' ' && (uc & 127) == uc) {
out << (char)uc;
} else {
out << "\\x" << digits[(uc >> 4) & 15] << digits[(uc >> 0) & 15];
}
break;
}
}
}
bool AccessLog::genField(std::ostringstream &out, const char* &format,
Transport *transport, const VirtualHost *vhost,
const string &arg) {
int responseSize = transport->getResponseSize();
int code = transport->getResponseCode();
while (!isalpha(*format)) { format++; }
char type = *format;
format++;
switch (type) {
case 'b':
if (responseSize == 0) return false;
// Fall through
case 'B':
out << responseSize;
break;
case 'C':
if (arg.empty()) {
return false;
} else {
string cookie = transport->getCookie(arg);
if (cookie.empty()) return false;
escape_data(out, cookie.c_str(), cookie.size());
}
break;
case 'D':
{
struct timespec now;
gettime(CLOCK_MONOTONIC, &now);
out << gettime_diff_us(transport->getWallTime(), now);
}
break;
case 'd':
{
struct timespec now;
gettime(CLOCK_THREAD_CPUTIME_ID, &now);
out << gettime_diff_us(transport->getCpuTime(), now);
}
break;
case 'h':
out << transport->getRemoteHost();
break;
case 'i':
if (arg.empty()) return false;
{
string header = transport->getHeader(arg.c_str());
if (header.empty()) return false;
if (vhost && vhost->hasLogFilter() &&
strcasecmp(arg.c_str(), "Referer") == 0) {
out << vhost->filterUrl(header);
} else {
out << header;
}
}
break;
case 'I':
out << transport->getRequestSize();
break;
case 'n':
if (arg.empty()) return false;
{
String note = ServerNote::Get(arg);
if (note.isNull()) return false;
out << note.c_str();
}
break;
case 'r':
{
const char *method = nullptr;
switch (transport->getMethod()) {
case Transport::GET: method = "GET"; break;
case Transport::POST: method = "POST"; break;
case Transport::HEAD: method = "HEAD"; break;
default: break;
}
if (!method) return false;
out << method << " ";
const char *url = transport->getUrl();
if (vhost && vhost->hasLogFilter()) {
out << vhost->filterUrl(url);
} else {
out << url;
}
string httpVersion = transport->getHTTPVersion();
out << " HTTP/" << httpVersion;
}
break;
case 's':
out << code;
break;
case 'S':
// %S is not defined in Apache, we grab it here
{
const std::string &info (transport->getResponseInfo());
if (info.empty()) return false;
out << info;
}
break;
case 't':
{
const char *format;
if (arg.empty()) {
format = "[%d/%b/%Y:%H:%M:%S %z]";
} else {
format = arg.c_str();
}
char buf[256];
time_t rawtime;
struct tm * timeinfo;
time(&rawtime);
timeinfo = localtime(&rawtime);
strftime(buf, 256, format, timeinfo);
out << buf;
}
break;
case 'T':
out << TimeStamp::Current() - m_fGetThreadData()->startTime;
break;
case 'U':
{
String b, q;
RequestURI::splitURL(transport->getUrl(), b, q);
out << b.c_str();
}
break;
case 'v':
{
string host = transport->getHeader("Host");
const string &sname = VirtualHost::GetCurrent()->serverName(host);
if (sname.empty() || RuntimeOption::ForceServerNameToHeader) {
out << host;
} else {
out << sname;
}
}
break;
case 'Y':
{
int64_t now = HardwareCounter::GetInstructionCount();
out << now - transport->getInstructions();
}
break;
case 'y':
out << ServerStats::Get("page.inst.psp");
break;
case 'Z':
out << ServerStats::Get("page.wall.psp");
break;
case 'z':
out << ServerStats::Get("page.cpu.psp");
break;
default:
return false;
}
return true;
}
void AccessLog::onNewRequest() {
if (!m_initialized) return;
ThreadData *threadData = m_fGetThreadData();
threadData->startTime = TimeStamp::Current();
}
bool AccessLog::setThreadLog(const char *file) {
return (m_fGetThreadData()->log = fopen(file, "a")) != nullptr;
}
void AccessLog::clearThreadLog() {
FILE* &threadLog = m_fGetThreadData()->log;
if (threadLog) {
fclose(threadLog);
}
threadLog = nullptr;
}
///////////////////////////////////////////////////////////////////////////////
}