/* * Phusion Passenger - https://www.phusionpassenger.com/ * Copyright (c) 2010-2018 Phusion Holding B.V. * * "Passenger", "Phusion Passenger" and "Union Station" are registered * trademarks of Phusion Holding B.V. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Passenger { namespace LoggingKit { using namespace std; #define TRUNCATE_LOGPATHS_TO_MAXCHARS 3 // set to 0 to disable truncation Context *context = NULL; AssertionFailureInfo lastAssertionFailure; void initialize(const Json::Value &initialConfig, const ConfigKit::Translator &translator) { context = new Context(initialConfig, translator); } void shutdown() { delete context; context = NULL; } Level getLevel() { if (OXT_LIKELY(context != NULL)) { return context->getConfigRealization()->level; } else { return Level(DEFAULT_LOG_LEVEL); } } void setLevel(Level level) { Json::Value config; vector errors; ConfigChangeRequest req; config["level"] = levelToString(level).toString(); if (context->prepareConfigChange(config, errors, req)) { context->commitConfigChange(req); } else { P_BUG("Error setting log level: " << ConfigKit::toString(errors)); } } Level parseLevel(const StaticString &name) { if (name == "crit" || name == "0") { return CRIT; } else if (name == "error" || name == "1") { return ERROR; } else if (name == "warn" || name == "2") { return WARN; } else if (name == "notice" || name == "3") { return NOTICE; } else if (name == "info" || name == "4") { return INFO; } else if (name == "debug" || name == "5") { return DEBUG; } else if (name == "debug2" || name == "6") { return DEBUG2; } else if (name == "debug3" || name == "7") { return DEBUG3; } else { return UNKNOWN_LEVEL; } } StaticString levelToString(Level level) { switch (level) { case CRIT: return P_STATIC_STRING("crit"); case ERROR: return P_STATIC_STRING("error"); case WARN: return P_STATIC_STRING("warn"); case NOTICE: return P_STATIC_STRING("notice"); case INFO: return P_STATIC_STRING("info"); case DEBUG: return P_STATIC_STRING("debug"); case DEBUG2: return P_STATIC_STRING("debug2"); case DEBUG3: return P_STATIC_STRING("debug3"); default: return P_STATIC_STRING("unknown"); } } const char * _strdupFastStringStream(const FastStringStream<> &stream) { char *buf = (char *) malloc(stream.size() + 1); memcpy(buf, stream.data(), stream.size()); buf[stream.size()] = '\0'; return buf; } bool _passesLogLevel(const Context *context, Level level, const ConfigRealization **outputConfigRlz) { if (OXT_UNLIKELY(context == NULL)) { *outputConfigRlz = NULL; return Level(DEFAULT_LOG_LEVEL) >= level; } else { const ConfigRealization *configRlz = context->getConfigRealization(); *outputConfigRlz = configRlz; return configRlz->level >= level; } } bool _shouldLogFileDescriptors(const Context *context, const ConfigRealization **outputConfigRlz) { if (OXT_UNLIKELY(context == NULL)) { return false; } else { const ConfigRealization *configRlz = context->getConfigRealization(); *outputConfigRlz = configRlz; return configRlz->fileDescriptorLogTargetType != NO_TARGET; } } void _prepareLogEntry(FastStringStream<> &sstream, Level level, const char *file, unsigned int line) { struct tm the_tm; char datetime_buf[32]; char threadIdBuf[std::max( std::max( 2 * sizeof(boost::uintptr_t) + 1, 2 * sizeof(unsigned int) + 1 ), 32 )]; int datetime_size; unsigned int threadIdSize; struct timeval tv; StaticString logLevelMarkers[] = { P_STATIC_STRING("C"), P_STATIC_STRING("E"), P_STATIC_STRING("W"), P_STATIC_STRING("N"), P_STATIC_STRING("I"), P_STATIC_STRING("D"), P_STATIC_STRING("D2"), P_STATIC_STRING("D3") }; gettimeofday(&tv, NULL); localtime_r(&tv.tv_sec, &the_tm); datetime_size = snprintf(datetime_buf, sizeof(datetime_buf), "%d-%02d-%02d %02d:%02d:%02d.%04llu", the_tm.tm_year + 1900, the_tm.tm_mon + 1, the_tm.tm_mday, the_tm.tm_hour, the_tm.tm_min, the_tm.tm_sec, (unsigned long long) tv.tv_usec / 100); #ifdef OXT_THREAD_LOCAL_KEYWORD_SUPPORTED // We only use oxt::get_thread_local_context() if it is fast enough. oxt::thread_local_context *ctx = oxt::get_thread_local_context(); if (OXT_LIKELY(ctx != NULL)) { threadIdSize = integerToHexatri(ctx->thread_number, threadIdBuf); } else { threadIdSize = integerToHexatri((boost::uintptr_t) pthread_self(), threadIdBuf); } #else threadIdSize = integerToHexatri((boost::uintptr_t) pthread_self(), threadIdBuf); #endif sstream << P_STATIC_STRING("[ ") << logLevelMarkers[int(level)] << P_STATIC_STRING(" ") << StaticString(datetime_buf, datetime_size) << P_STATIC_STRING(" ") << std::dec << getpid() << P_STATIC_STRING("/T") << StaticString(threadIdBuf, threadIdSize) << P_STATIC_STRING(" "); if (startsWith(file, P_STATIC_STRING("src/"))) { // special reduncancy filter because most code resides in these paths file += sizeof("src/") - 1; if (startsWith(file, P_STATIC_STRING("cxx_supportlib/"))) { file += sizeof("cxx_supportlib/") - 1; } } if (TRUNCATE_LOGPATHS_TO_MAXCHARS > 0) { truncateBeforeTokens(file, P_STATIC_STRING("/\\"), TRUNCATE_LOGPATHS_TO_MAXCHARS, sstream); } else { sstream << file; } sstream << P_STATIC_STRING(":") << line << P_STATIC_STRING(" ]: "); } static void writeExactWithoutOXT(int fd, const char *str, unsigned int size) { /* We do not use writeExact() here because writeExact() * uses oxt::syscalls::write(), which is an interruption point and * which is slightly more expensive than a plain write(). * Logging may block, but in most cases not indefinitely, * so we don't care if the write() here is not an interruption * point. If the write does block indefinitely then it's * probably a FIFO that is not opened on the other side. * In that case we can blame the user. */ ssize_t ret; unsigned int written = 0; while (written < size) { do { ret = write(fd, str + written, size - written); } while (ret == -1 && errno == EINTR); if (ret == -1) { /* The most likely reason why this fails is when the user has setup * Apache to log to a pipe (e.g. to a log rotation script). Upon * restarting the web server, the process that reads from the pipe * shuts down, so we can't write to it anymore. That's why we * just ignore write errors. It doesn't make sense to abort for * something like this. */ break; } else { written += ret; } } } void _writeLogEntry(const ConfigRealization *configRealization, const char *str, unsigned int size) { if (OXT_LIKELY(configRealization != NULL)) { writeExactWithoutOXT(configRealization->targetFd, str, size); } else { writeExactWithoutOXT(STDERR_FILENO, str, size); } } void _writeFileDescriptorLogEntry(const ConfigRealization *configRealization, const char *str, unsigned int size) { assert(configRealization != NULL); assert(configRealization->fileDescriptorLogTargetType != UNKNOWN_TARGET); assert(configRealization->fileDescriptorLogTargetFd != -1); writeExactWithoutOXT(configRealization->fileDescriptorLogTargetFd, str, size); } void Context::saveNewLog(const HashedStaticString &groupName, const char *sourceStr, unsigned int sourceStrLen, const char *message, unsigned int messageLen) { boost::lock_guard l(syncher); //lock unsigned long long timestamp = SystemTime::getUsec(); LogStore::Cell *c = logStore.lookupCell(groupName); if (c == NULL) { AppGroupLog appGroupLog; appGroupLog.pidLog = TimestampedLogBuffer(LOG_MONITORING_MAX_LINES * 5); c = logStore.insert(groupName, appGroupLog); } AppGroupLog &rec = c->value; TimestampedLog ll; ll.timestamp = timestamp; ll.sourceId = string(sourceStr, sourceStrLen); ll.lineText = string(message, messageLen); rec.pidLog.push_back(ll); //unlock } void Context::saveMonitoredFileLog(const HashedStaticString &groupName, const char *sourceStr, unsigned int sourceStrLen, const char *content, unsigned int contentLen) { vector lines; split(StaticString(content, contentLen), '\n', lines); boost::lock_guard l(syncher); //lock LogStore::Cell *c = logStore.lookupCell(groupName); if (c == NULL) { AppGroupLog appGroupLog; appGroupLog.pidLog = TimestampedLogBuffer(LOG_MONITORING_MAX_LINES * 5); c = logStore.insert(groupName, appGroupLog); } AppGroupLog &rec = c->value; HashedStaticString source(sourceStr, sourceStrLen); SimpleLogMap::Cell *c2 = rec.watchFileLog.lookupCell(source); if (c2 == NULL) { SimpleLogBuffer logBuffer(LOG_MONITORING_MAX_LINES); c2 = rec.watchFileLog.insert(source, logBuffer); } c2->value.clear(); foreach (StaticString line, lines) { c2->value.push_back(string(line.data(), line.size())); } //unlock } Json::Value Context::convertLog(){ boost::lock_guard l(syncher); //lock Json::Value reply = Json::objectValue; if (!logStore.empty()) { Context::LogStore::ConstIterator appGroupIter(logStore); while (*appGroupIter != NULL) { reply[appGroupIter.getKey()] = Json::objectValue; Json::Value &processLog = reply[appGroupIter.getKey()]["Application process log (combined)"]; if (processLog.isNull()) { processLog = Json::arrayValue; } foreach (TimestampedLog logLine, appGroupIter->value.pidLog) { Json::Value logLineJson = Json::objectValue; logLineJson["source_id"] = logLine.sourceId; logLineJson["timestamp"] = (Json::UInt64) logLine.timestamp; logLineJson["line"] = logLine.lineText; processLog.append(logLineJson); } Context::SimpleLogMap::ConstIterator watchFileLogIter(appGroupIter->value.watchFileLog); while (*watchFileLogIter != NULL) { if (!reply[appGroupIter.getKey()].isMember(watchFileLogIter.getKey())){ reply[appGroupIter.getKey()][watchFileLogIter.getKey()] = Json::arrayValue; } foreach (string line, watchFileLogIter->value) { reply[appGroupIter.getKey()][watchFileLogIter.getKey()].append(line); } watchFileLogIter.next(); } appGroupIter.next(); } } return reply; //unlock } static void realLogAppOutput(const HashedStaticString &groupName, int targetFd, char *buf, unsigned int bufSize, const char *pidStr, unsigned int pidStrLen, const char *channelName, unsigned int channelNameLen, const char *message, unsigned int messageLen, int appLogFile, bool saveLog, bool prefixLogs) { char *pos = buf; char *end = buf + bufSize; if (prefixLogs) { pos = appendData(pos, end, "App "); pos = appendData(pos, end, pidStr, pidStrLen); pos = appendData(pos, end, " "); pos = appendData(pos, end, channelName, channelNameLen); pos = appendData(pos, end, ": "); } pos = appendData(pos, end, message, messageLen); pos = appendData(pos, end, "\n"); if (OXT_UNLIKELY(context != NULL && saveLog)) { context->saveNewLog(groupName, pidStr, pidStrLen, message, messageLen); } if (appLogFile > -1) { writeExactWithoutOXT(appLogFile, buf, pos - buf); } writeExactWithoutOXT(targetFd, buf, pos - buf); } void logAppOutput(const HashedStaticString &groupName, pid_t pid, const StaticString &channelName, const char *message, unsigned int size, const StaticString &appLogFile) { int targetFd; bool saveLog = false; bool prefixLogs = true; if (OXT_LIKELY(context != NULL)) { const ConfigRealization *configRealization = context->getConfigRealization(); if (configRealization->level < configRealization->appOutputLogLevel) { return; } targetFd = configRealization->targetFd; saveLog = configRealization->saveLog; prefixLogs = !configRealization->disableLogPrefix; } else { targetFd = STDERR_FILENO; } int fd = -1; if (!appLogFile.empty()) { fd = open(appLogFile.data(), O_WRONLY | O_APPEND | O_CREAT, 0640); if (fd == -1) { int e = errno; P_ERROR("opening file: " << appLogFile << " for logging " << groupName << " failed. Error: " << strerror(e)); } } char pidStr[sizeof("4294967295")]; unsigned int pidStrLen, totalLen; try { pidStrLen = integerToOtherBase(pid, pidStr, sizeof(pidStr)); } catch (const std::length_error &) { pidStr[0] = '?'; pidStr[1] = '\0'; pidStrLen = 1; } totalLen = (sizeof("App X Y: \n") - 2) + pidStrLen + channelName.size() + size; if (totalLen < 1024) { char buf[1024]; realLogAppOutput(groupName, targetFd, buf, sizeof(buf), pidStr, pidStrLen, channelName.data(), channelName.size(), message, size, fd, saveLog, prefixLogs); } else { DynamicBuffer buf(totalLen); realLogAppOutput(groupName, targetFd, buf.data, totalLen, pidStr, pidStrLen, channelName.data(), channelName.size(), message, size, fd, saveLog, prefixLogs); } if(fd > -1){close(fd);} } static Json::Value normalizeConfig(const Json::Value &effectiveValues) { Json::Value updates(Json::objectValue); updates["level"] = levelToString(parseLevel( effectiveValues["level"].asString())).toString(); updates["app_output_log_level"] = levelToString(parseLevel( effectiveValues["app_output_log_level"].asString())).toString(); if (effectiveValues["target"].isString()) { updates["target"]["path"] = absolutizePath(effectiveValues["target"].asString()); } else if (!effectiveValues["target"]["path"].isNull()) { updates["target"] = effectiveValues["target"]; updates["target"]["path"] = absolutizePath(effectiveValues["target"]["path"].asString()); } if (effectiveValues["file_descriptor_log_target"].isString()) { updates["file_descriptor_log_target"]["path"] = absolutizePath(effectiveValues["file_descriptor_log_target"].asString()); } else if (effectiveValues["file_descriptor_log_target"].isObject() && !effectiveValues["file_descriptor_log_target"]["path"].isNull()) { updates["file_descriptor_log_target"] = effectiveValues["file_descriptor_log_target"]; updates["file_descriptor_log_target"]["path"] = absolutizePath(effectiveValues["file_descriptor_log_target"]["path"].asString()); } return updates; } Context::Context(const Json::Value &initialConfig, const ConfigKit::Translator &translator) : config(schema, initialConfig, translator), gcShuttingDown(false) { configRlz.store(new ConfigRealization(config)); configRlz.load()->apply(config, NULL); configRlz.load()->finalize(); gcThread = new oxt::thread(boost::bind(&Context::gcThreadMain, this), "LoggingKit config garbage collector thread", 128 * 1024); } Context::~Context() { { boost::unique_lock l(gcSyncher); gcShuttingDown = true; gcSyncherCond.notify_one(); } gcThread->join(); delete gcThread; delete configRlz.load(); while (!oldConfigRlzs.empty()) { delete oldConfigRlzs.front().first; oldConfigRlzs.pop(); } } ConfigKit::Store Context::getConfig() const { boost::lock_guard l(syncher); return config; } bool Context::prepareConfigChange(const Json::Value &updates, vector &errors, LoggingKit::ConfigChangeRequest &req) { { boost::lock_guard l(syncher); req.config.reset(new ConfigKit::Store(config, updates, errors)); } if (!errors.empty()) { return false; } req.configRlz = new ConfigRealization(*req.config); return true; } void Context::commitConfigChange(LoggingKit::ConfigChangeRequest &req) BOOST_NOEXCEPT_OR_NOTHROW { boost::lock_guard l(syncher); ConfigRealization *oldConfigRlz = configRlz.load(); ConfigRealization *newConfigRlz = req.configRlz; req.configRlz->apply(*req.config, oldConfigRlz); config.swap(*req.config); configRlz.store(newConfigRlz, boost::memory_order_release); req.configRlz = NULL; // oldConfigRlz will be garbage collected by apply() newConfigRlz->finalize(); } Json::Value Context::inspectConfig() const { boost::lock_guard l(syncher); return config.inspect(); } void Context::freeOldConfigRlzLater(ConfigRealization *oldConfigRlz, MonotonicTimeUsec monotonicNow) { // Garbage collect old config realization in 5 minutes. // There is no way to cheaply find out whether oldConfigRlz // is still being used (we don't want to resort to more atomic // operations, or conservative garbage collection) but // waiting 5 minutes should be good enough. MonotonicTimeUsec gcTime = monotonicNow + 5llu * 60llu * 1000000llu; boost::unique_lock l(gcSyncher); oldConfigRlzs.push(make_pair(oldConfigRlz, gcTime)); gcSyncherCond.notify_one(); } void Context::gcThreadMain() { boost::unique_lock l(gcSyncher); while (true) { while (oldConfigRlzs.empty() && !gcShuttingDown) { gcSyncherCond.wait(l); } if (gcShuttingDown) { break; } pair p = oldConfigRlzs.front(); MonotonicTimeUsec now = SystemTime::getMonotonicUsecWithGranularity(); // Wait until it's time to GC this config object, // or until the destructor tells us that we're shutting down. while (!gcShuttingDown && now < p.second) { gcSyncherCond.timed_wait(l, boost::posix_time::microseconds(p.second - now)); now = SystemTime::getMonotonicUsecWithGranularity(); } if (!gcShuttingDown) { delete p.first; oldConfigRlzs.pop(); } } } Json::Value Schema::createStderrTarget() { Json::Value doc; doc["stderr"] = true; return doc; } void Schema::validateLogLevel(const string &key, const ConfigKit::Store &store, vector &errors) { typedef ConfigKit::Error Error; Level level = parseLevel(store[key].asString()); if (level == UNKNOWN_LEVEL) { errors.push_back(Error("'{{" + key + "}}' must be one of" " 'crit', 'error', 'warn', 'notice', 'info', 'debug', 'debug2' or 'debug3'")); } } void Schema::validateTarget(const string &key, const ConfigKit::Store &store, vector &errors) { typedef ConfigKit::Error Error; Json::Value value = store[key]; string keyQuote = "'{{" + key + "}}'"; if (value.isNull()) { return; } // Allowed formats: // "/path-to-file" // { "stderr": true } // { "path": "/path" } // { "path": "/path", "fd": 123 } // { "path": "/path", "stderr": true } if (value.isObject()) { if (value.isMember("stderr")) { if (!value["stderr"].isBool() || !value["stderr"].asBool()) { errors.push_back(Error("When " + keyQuote + " is an object containing the 'stderr' key," " it must have the 'true' value")); return; } } if (value.isMember("path")) { if (!value["path"].isString()) { errors.push_back(Error("When " + keyQuote + " is an object containing the 'path' key," " it must be a string")); } if (value.isMember("fd")) { if (!value["fd"].isInt()) { errors.push_back(Error("When " + keyQuote + " is an object containing the 'fd' key," " it must be an integer")); } else if (value["fd"].asInt() < 0) { errors.push_back(Error("When " + keyQuote + " is an object containing the 'fd' key," " it must be 0 or greater")); } } if (value.isMember("fd") && value.isMember("stderr")) { errors.push_back(Error(keyQuote + " may contain either the 'fd' or the" " 'stderr' key, but not both")); } } else if (value.isMember("stderr")) { if (value.size() > 1) { errors.push_back(Error("When " + keyQuote + " is an object containing the 'stderr' key," " it may not contain any other keys")); } else if (!value["stderr"].asBool()) { errors.push_back(Error("When " + keyQuote + " is an object containing the 'stderr' key," " it must have the 'true' value")); } } else { errors.push_back(Error("When " + keyQuote + " is an object, it must contain either" " the 'stderr' or 'path' key")); } } else if (!value.isString()) { errors.push_back(Error(keyQuote + " must be either a string or an object")); } } static Json::Value filterTargetFd(const Json::Value &value) { Json::Value result = value; result.removeMember("fd"); return result; } Schema::Schema() { using namespace ConfigKit; add("level", STRING_TYPE, OPTIONAL, DEFAULT_LOG_LEVEL_NAME); add("target", ANY_TYPE, OPTIONAL, createStderrTarget()) .setInspectFilter(filterTargetFd); add("file_descriptor_log_target", ANY_TYPE, OPTIONAL) .setInspectFilter(filterTargetFd); add("redirect_stderr", BOOL_TYPE, OPTIONAL, true); add("app_output_log_level", STRING_TYPE, OPTIONAL, DEFAULT_APP_OUTPUT_LOG_LEVEL_NAME); add("buffer_logs", BOOL_TYPE, OPTIONAL, false); add("disable_log_prefix", BOOL_TYPE, OPTIONAL, false); addValidator(boost::bind(validateLogLevel, "level", boost::placeholders::_1, boost::placeholders::_2)); addValidator(boost::bind(validateLogLevel, "app_output_log_level", boost::placeholders::_1, boost::placeholders::_2)); addValidator(boost::bind(validateTarget, "target", boost::placeholders::_1, boost::placeholders::_2)); addValidator(boost::bind(validateTarget, "file_descriptor_log_target", boost::placeholders::_1, boost::placeholders::_2)); addNormalizer(normalizeConfig); finalize(); } ConfigRealization::ConfigRealization(const ConfigKit::Store &store) : level(parseLevel(store["level"].asString())), appOutputLogLevel(parseLevel(store["app_output_log_level"].asString())), saveLog(store["buffer_logs"].asBool()), finalized(false), disableLogPrefix(store["disable_log_prefix"].asBool()) { if (store["target"].isMember("stderr")) { targetType = STDERR_TARGET; targetFd = STDERR_FILENO; targetFdClosePolicy = NEVER_CLOSE; } else if (store["target"]["fd"].isNull()) { string path = store["target"]["path"].asString(); targetType = FILE_TARGET; if (store["target"]["stderr"].asBool()) { targetFd = STDERR_FILENO; targetFdClosePolicy = NEVER_CLOSE; } else { targetFd = syscalls::open(path.c_str(), O_WRONLY | O_APPEND | O_CREAT, 0644); if (targetFd == -1) { int e = errno; throw FileSystemException( "Cannot open " + path + " for writing", e, path); } targetFdClosePolicy = ALWAYS_CLOSE; } } else { targetType = FILE_TARGET; targetFd = store["target"]["fd"].asInt(); // If anything goes wrong before finalization, then // the caller is responsible for cleaning up the fd. // See the Context class description. targetFdClosePolicy = CLOSE_WHEN_FINALIZED; } if (store["file_descriptor_log_target"].isNull()) { fileDescriptorLogTargetType = NO_TARGET; fileDescriptorLogTargetFd = -1; fileDescriptorLogTargetFdClosePolicy = NEVER_CLOSE; } else if (store["file_descriptor_log_target"].isMember("stderr")) { fileDescriptorLogTargetType = STDERR_TARGET; fileDescriptorLogTargetFd = STDERR_FILENO; fileDescriptorLogTargetFdClosePolicy = NEVER_CLOSE; } else if (store["file_descriptor_log_target"]["fd"].isNull()) { string path = store["file_descriptor_log_target"]["path"].asString(); fileDescriptorLogTargetType = FILE_TARGET; if (store["file_descriptor_log_target"]["stderr"].asBool()) { fileDescriptorLogTargetFd = STDERR_FILENO; fileDescriptorLogTargetFdClosePolicy = NEVER_CLOSE; } else { fileDescriptorLogTargetFd = syscalls::open(path.c_str(), O_WRONLY | O_APPEND | O_CREAT, 0644); if (fileDescriptorLogTargetFd == -1) { int e = errno; throw FileSystemException( "Cannot open " + path + " for writing", e, path); } fileDescriptorLogTargetFdClosePolicy = ALWAYS_CLOSE; } } else { fileDescriptorLogTargetType = FILE_TARGET; fileDescriptorLogTargetFd = store["file_descriptor_log_target"]["fd"].asInt(); // If anything goes wrong before finalization, then // the caller is responsible for cleaning up the fd. // See the Context class description. fileDescriptorLogTargetFdClosePolicy = CLOSE_WHEN_FINALIZED; } } ConfigRealization::~ConfigRealization() { switch (targetFdClosePolicy) { case NEVER_CLOSE: // Do nothing. break; case ALWAYS_CLOSE: syscalls::close(targetFd); break; case CLOSE_WHEN_FINALIZED: if (finalized) { syscalls::close(targetFd); } break; } switch (fileDescriptorLogTargetFdClosePolicy) { case NEVER_CLOSE: // Do nothing. break; case ALWAYS_CLOSE: syscalls::close(fileDescriptorLogTargetFd); break; case CLOSE_WHEN_FINALIZED: if (finalized) { syscalls::close(fileDescriptorLogTargetFd); } break; } } void ConfigRealization::apply(const ConfigKit::Store &config, ConfigRealization *oldConfigRlz) BOOST_NOEXCEPT_OR_NOTHROW { if (config["redirect_stderr"].asBool()) { int ret = syscalls::dup2(targetFd, STDERR_FILENO); if (ret == -1) { int e = errno; P_ERROR("Error redirecting logging target to stderr: " << strerror(e) << " (errno=" << e << ")"); } } if (oldConfigRlz != NULL) { MonotonicTimeUsec monotonicNow = SystemTime::getMonotonicUsecWithGranularity(); context->freeOldConfigRlzLater(oldConfigRlz, monotonicNow); } } void ConfigRealization::finalize() { finalized = true; } ConfigChangeRequest::ConfigChangeRequest() : configRlz(NULL) { // Do nothing. } ConfigChangeRequest::~ConfigChangeRequest() { delete configRlz; } } // namespace LoggingKit } // namespace Passenger