#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 #include #include #include #include #include #include #include #include #include #include #include #include #include using Poco::Logger; using Poco::AutoPtr; using Poco::Observer; using Poco::FormattingChannel; using Poco::SplitterChannel; using Poco::ConsoleChannel; using Poco::FileChannel; using Poco::Path; using Poco::Message; using Poco::Util::AbstractConfiguration; /** Для передачи информации из обработчика сигнала для обработки в другом потоке. * Если при получении сигнала надо делать что-нибудь серьёзное (например, вывести сообщение в лог), * то передать нужную информацию через pipe в другой поток и сделать там всю работу * - один из немногих безопасных способов сделать это. */ struct Pipe { union { int fds[2]; struct { int read_fd; int write_fd; }; }; Pipe() { if (0 != pipe(fds)) DB::throwFromErrno("Cannot create pipe"); } ~Pipe() { close(fds[0]); close(fds[1]); } }; Pipe signal_pipe; /** Устанавливает обработчик сигнала по умолчанию, и отправляет себе сигнал sig. * Вызывается изнутри пользовательского обработчика сигнала, чтобы записать core dump. */ static void call_default_signal_handler(int sig) { signal(sig, SIG_DFL); kill(getpid(), sig); } typedef decltype(Poco::ThreadNumber::get()) ThreadNumber; static const size_t buf_size = sizeof(int) + sizeof(siginfo_t) + sizeof(ucontext_t) + sizeof(ThreadNumber); /** Обработчик сигналов HUP / USR1 */ static void close_logs_signal_handler(int sig, siginfo_t * info, void * context) { char buf[buf_size]; DB::WriteBufferFromFileDescriptor out(signal_pipe.write_fd, buf_size, buf); DB::writeBinary(sig, out); out.next(); } /** Обработчик некоторых сигналов. Выводит информацию в лог (если получится). */ static void fault_signal_handler(int sig, siginfo_t * info, void * context) { char buf[buf_size]; DB::WriteBufferFromFileDescriptor out(signal_pipe.write_fd, buf_size, buf); DB::writeBinary(sig, out); DB::writePODBinary(*info, out); DB::writePODBinary(*reinterpret_cast(context), out); DB::writeBinary(Poco::ThreadNumber::get(), out); out.next(); /// Время, за которое читающий из pipe поток должен попытаться успеть вывести в лог stack trace. ::sleep(10); call_default_signal_handler(sig); } static bool already_printed_stack_trace = false; /** Получает информацию через pipe. * При получении сигнала HUP / USR1 закрывает лог-файлы. * При получении информации из std::terminate, выводит её в лог. * При получении других сигналов, выводит информацию в лог. */ class SignalListener : public Poco::Runnable { public: SignalListener() : log(&Logger::get("Daemon")) { } void run() { char buf[buf_size]; DB::ReadBufferFromFileDescriptor in(signal_pipe.read_fd, buf_size, buf); while (!in.eof()) { int sig = 0; DB::readBinary(sig, in); if (sig == SIGHUP || sig == SIGUSR1) { LOG_DEBUG(log, "Received signal to close logs."); Daemon::instance().closeLogs(); LOG_INFO(log, "Opened new log file after received signal."); } else if (sig == -1) /// -1 для обозначения std::terminate. { ThreadNumber thread_num; std::string message; DB::readBinary(thread_num, in); DB::readBinary(message, in); onTerminate(message, thread_num); } else { siginfo_t info; ucontext_t context; ThreadNumber thread_num; DB::readPODBinary(info, in); DB::readPODBinary(context, in); DB::readBinary(thread_num, in); onFault(sig, info, context, thread_num); } } } private: Logger * log; void onTerminate(const std::string & message, ThreadNumber thread_num) const { LOG_ERROR(log, "(from thread " << thread_num << ") " << message); } void onFault(int sig, siginfo_t & info, ucontext_t & context, ThreadNumber thread_num) const { LOG_ERROR(log, "########################################"); LOG_ERROR(log, "(from thread " << thread_num << ") " << "Received signal " << strsignal(sig) << " (" << sig << ")" << "."); if (sig == SIGSEGV) { /// Выводим информацию об адресе и о причине. if (nullptr == info.si_addr) LOG_ERROR(log, "Address: NULL pointer."); else LOG_ERROR(log, "Address: " << info.si_addr << (info.si_code == SEGV_ACCERR ? ". Attempted access has violated the permissions assigned to the memory area." : "")); } if (already_printed_stack_trace) return; void * caller_address = nullptr; /// Get the address at the time the signal was raised from the RIP (x86-64) caller_address = reinterpret_cast(context.uc_mcontext.gregs[REG_RIP]); static const int max_frames = 50; void * frames[max_frames]; int frames_size = backtrace(frames, max_frames); if (frames_size >= 2) { /// Overwrite sigaction with caller's address if (caller_address && (frames_size < 3 || caller_address != frames[2])) frames[1] = caller_address; char ** symbols = backtrace_symbols(frames, frames_size); if (!symbols) { if (caller_address) LOG_ERROR(log, "Caller address: " << caller_address); } else { for (int i = 1; i < frames_size; ++i) { /// Делаем demangling имён. Имя находится в скобках, до символа '+'. char * name_start = nullptr; char * name_end = nullptr; char * demangled_name = nullptr; int status = 0; if (nullptr != (name_start = strchr(symbols[i], '(')) && nullptr != (name_end = strchr(name_start, '+'))) { ++name_start; *name_end = '\0'; demangled_name = abi::__cxa_demangle(name_start, 0, 0, &status); *name_end = '+'; } std::stringstream res; res << i << ". "; if (nullptr != demangled_name && 0 == status) { res.write(symbols[i], name_start - symbols[i]); res << demangled_name << name_end; } else res << symbols[i]; LOG_ERROR(log, res.rdbuf()); } } } } }; /** Для использования с помощью std::set_terminate. * Собирает чуть больше информации, чем __gnu_cxx::__verbose_terminate_handler, * и отправляет её в pipe. Другой поток читает из pipe и выводит её в лог. * См. исходники в libstdc++-v3/libsupc++/vterminate.cc */ static void terminate_handler() { static __thread bool terminating = false; if (terminating) { abort(); return; } terminating = true; /// Сюда записываем информацию для логгирования. std::stringstream log; std::type_info * t = abi::__cxa_current_exception_type(); if (t) { /// Note that "name" is the mangled name. char const * name = t->name(); { int status = -1; char * dem = 0; dem = abi::__cxa_demangle(name, 0, 0, &status); log << "Terminate called after throwing an instance of " << (status == 0 ? dem : name) << std::endl; if (status == 0) free(dem); } already_printed_stack_trace = true; /// If the exception is derived from std::exception, we can give more information. try { throw; } catch (DB::Exception & e) { log << "Code: " << e.code() << ", e.displayText() = " << e.displayText() << ", e.what() = " << e.what() << std::endl; } catch (Poco::Exception & e) { log << "Code: " << e.code() << ", e.displayText() = " << e.displayText() << ", e.what() = " << e.what() << std::endl; } catch (const std::exception & e) { log << "what(): " << e.what() << std::endl; } catch (...) { } log << "Stack trace:\n\n" << StackTrace().toString() << std::endl; } else { log << "Terminate called without an active exception" << std::endl; } static const size_t buf_size = 1024; std::string log_message = log.str(); if (log_message.size() > buf_size - 16) log_message.resize(buf_size - 16); char buf[buf_size]; DB::WriteBufferFromFileDescriptor out(signal_pipe.write_fd, buf_size, buf); DB::writeBinary(-1, out); DB::writeBinary(Poco::ThreadNumber::get(), out); DB::writeBinary(log_message, out); out.next(); abort(); } static std::string createDirectory(const std::string & _path) { Poco::Path path(_path); std::string str; for(int j=0;j layer = daemon.getLayer(); if (layer) { writeCString("layer[", wb); DB::writeIntText(*layer, wb); writeCString("]: ", wb); } } /// Выведем время с точностью до миллисекунд. timeval tv; if (0 != gettimeofday(&tv, nullptr)) DB::throwFromErrno("Cannot gettimeofday"); /// Поменяем разделители у даты для совместимости. DB::writeDateTimeText<'.', ':'>(tv.tv_sec, wb); int milliseconds = tv.tv_usec / 1000; DB::writeChar('.', wb); DB::writeChar('0' + ((milliseconds / 100) % 10), wb); DB::writeChar('0' + ((milliseconds / 10) % 10), wb); DB::writeChar('0' + (milliseconds % 10), wb); writeCString(" [ ", wb); DB::writeIntText(Poco::ThreadNumber::get(), wb); writeCString(" ] <", wb); DB::writeString(getPriorityName(static_cast(msg.getPriority())), wb); writeCString("> ", wb); DB::writeString(msg.getSource(), wb); writeCString(": ", wb); DB::writeString(msg.getText(), wb); } private: const Daemon & daemon; Options options; }; /// Для создания и уничтожения unique_ptr, который в заголовочном файле объявлен от incomplete type. Daemon::Daemon() = default; Daemon::~Daemon() = default; void Daemon::terminate() { getTaskManager().cancelAll(); ServerApplication::terminate(); } void Daemon::kill() { pid.clear(); Poco::Process::kill(getpid()); } void Daemon::sleep(double seconds) { wakeup_event.reset(); wakeup_event.tryWait(seconds * 1000); } void Daemon::wakeup() { wakeup_event.set(); } /// Строит необходимые логгеры void Daemon::buildLoggers() { /// Сменим директорию для лога if (config().hasProperty("logger.log") && !log_to_console) { std::string path = createDirectory(config().getString("logger.log")); if (config().getBool("application.runAsDaemon", false) && chdir(path.c_str()) != 0) throw Poco::Exception("Cannot change directory to " + path); } else { if (config().getBool("application.runAsDaemon", false) && chdir("/tmp") != 0) throw Poco::Exception("Cannot change directory to /tmp"); } if (config().hasProperty("logger.errorlog") && !log_to_console) createDirectory(config().getString("logger.errorlog")); if (config().hasProperty("logger.log") && !log_to_console) { std::cerr << "Should logs to " << config().getString("logger.log") << std::endl; // splitter Poco::AutoPtr split = new SplitterChannel; // set up two channel chains Poco::AutoPtr pf = new OwnPatternFormatter(*this); pf->setProperty("times", "local"); Poco::AutoPtr log = new FormattingChannel(pf); log_file = new FileChannel; log_file->setProperty("path", Poco::Path(config().getString("logger.log")).absolute().toString()); log_file->setProperty("rotation", config().getRawString("logger.size", "100M")); log_file->setProperty("archive", "number"); log_file->setProperty("compress", config().getRawString("logger.compress", "true")); log_file->setProperty("purgeCount", config().getRawString("logger.count", "1")); log->setChannel(log_file); split->addChannel(log); log_file->open(); if (config().hasProperty("logger.errorlog")) { std::cerr << "Should error logs to " << config().getString("logger.errorlog") << std::endl; Poco::AutoPtr level = new Poco::LevelFilterChannel; level->setLevel(Message::PRIO_NOTICE); Poco::AutoPtr pf = new OwnPatternFormatter(*this); pf->setProperty("times", "local"); Poco::AutoPtr errorlog = new FormattingChannel(pf); error_log_file = new FileChannel; error_log_file->setProperty("path", Poco::Path(config().getString("logger.errorlog")).absolute().toString()); error_log_file->setProperty("rotation", config().getRawString("logger.size", "100M")); error_log_file->setProperty("archive", "number"); error_log_file->setProperty("compress", config().getRawString("logger.compress", "true")); error_log_file->setProperty("purgeCount", config().getRawString("logger.count", "1")); errorlog->setChannel(error_log_file); level->setChannel(errorlog); split->addChannel(level); errorlog->open(); } if (config().getBool("logger.use_syslog", false) || config().getBool("dynamic_layer_selection", false)) { Poco::AutoPtr pf = new OwnPatternFormatter(*this, OwnPatternFormatter::ADD_LAYER_TAG); pf->setProperty("times", "local"); Poco::AutoPtr log = new FormattingChannel(pf); syslog_channel = new Poco::SyslogChannel(commandName(), Poco::SyslogChannel::SYSLOG_CONS | Poco::SyslogChannel::SYSLOG_PID, Poco::SyslogChannel::SYSLOG_DAEMON); log->setChannel(syslog_channel); split->addChannel(log); syslog_channel->open(); } split->open(); logger().close(); logger().setChannel(split); } else { // Выводим на консоль Poco::AutoPtr file = new ConsoleChannel; Poco::AutoPtr pf = new OwnPatternFormatter(*this); pf->setProperty("times", "local"); Poco::AutoPtr log = new FormattingChannel(pf); log->setChannel(file); logger().close(); logger().setChannel(log); logger().warning("Logging to console"); } // Уровни для всех logger().setLevel(config().getString("logger.level", "trace")); // Прикрутим к корневому логгеру Logger::root().setLevel(logger().getLevel()); Logger::root().setChannel(logger().getChannel()); // Уровни для явно указанных логгеров AbstractConfiguration::Keys levels; config().keys("logger.levels", levels); if(!levels.empty()) for(AbstractConfiguration::Keys::iterator it = levels.begin(); it != levels.end(); ++it) Logger::get(*it).setLevel(config().getString("logger.levels." + *it, "trace")); } void Daemon::closeLogs() { if (log_file) log_file->close(); if (error_log_file) error_log_file->close(); if (!log_file) logger().warning("Logging to console but received signal to close log file (ignoring)."); } void Daemon::initialize(Application& self) { /// В случае падения - сохраняем коры { struct rlimit rlim; if (getrlimit(RLIMIT_CORE, &rlim)) throw Poco::Exception("Cannot getrlimit"); rlim.rlim_cur = 1024 * 1024 * 1024; /// 1 GiB. Если больше - они слишком долго пишутся на диск. if (setrlimit(RLIMIT_CORE, &rlim)) throw Poco::Exception("Cannot setrlimit"); } task_manager.reset(new Poco::TaskManager); ServerApplication::initialize(self); bool is_daemon = config().getBool("application.runAsDaemon", false); if (is_daemon) { /** При создании pid файла и поиске конфигурации, будем интерпретировать относительные пути * от директории запуска программы. */ std::string path = config().getString("application.path"); if (0 != chdir(Poco::Path(path).setFileName("").toString().c_str())) throw Poco::Exception("Cannot change directory to " + path); /// Создадим pid-file. if (is_daemon && config().has("pid")) pid.seed(config().getString("pid")); } /// Считаем конфигурацию reloadConfiguration(); buildLoggers(); /// Ставим terminate_handler std::set_terminate(terminate_handler); /// Ставим обработчики сигналов struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_sigaction = fault_signal_handler; sa.sa_flags = SA_SIGINFO; { int signals[] = {SIGABRT, SIGSEGV, SIGILL, SIGBUS, SIGSYS, SIGFPE, SIGPIPE, 0}; if (sigemptyset(&sa.sa_mask)) throw Poco::Exception("Cannot set signal handler."); for (size_t i = 0; signals[i]; ++i) if (sigaddset(&sa.sa_mask, signals[i])) throw Poco::Exception("Cannot set signal handler."); for (size_t i = 0; signals[i]; ++i) if (sigaction(signals[i], &sa, 0)) throw Poco::Exception("Cannot set signal handler."); } sa.sa_sigaction = close_logs_signal_handler; { int signals[] = {SIGHUP, SIGUSR1, 0}; if (sigemptyset(&sa.sa_mask)) throw Poco::Exception("Cannot set signal handler."); for (size_t i = 0; signals[i]; ++i) if (sigaddset(&sa.sa_mask, signals[i])) throw Poco::Exception("Cannot set signal handler."); for (size_t i = 0; signals[i]; ++i) if (sigaction(signals[i], &sa, 0)) throw Poco::Exception("Cannot set signal handler."); } /// Ставим ErrorHandler для потоков static KillingErrorHandler killing_error_handler; Poco::ErrorHandler::set(&killing_error_handler); /// Выведем ревизию демона Logger::root().information("Starting daemon with revision " + Poco::NumberFormatter::format(Revision::get())); close_logs_listener.reset(new SignalListener); close_logs_thread.start(*close_logs_listener); if (is_daemon) { /// Сменим директорию на ту, куда надо писать core файлы. Poco::File opt_cores = "/opt/cores"; std::string log_dir = config().getString("logger.log", ""); size_t pos_of_last_slash = log_dir.rfind("/"); if (pos_of_last_slash != std::string::npos) log_dir.resize(pos_of_last_slash); std::string core_path = config().getString("core_path", opt_cores.exists() && opt_cores.isDirectory() ? "/opt/cores/" : (!log_dir.empty() ? log_dir : "/opt/")); if (0 != chdir(core_path.c_str())) throw Poco::Exception("Cannot change directory to " + core_path); /** Переназначим stdout, stderr в отдельные файлы в директориях с логами. * Некоторые библиотеки пишут в stderr в случае ошибок или в отладочном режиме, * и этот вывод иногда имеет смысл смотреть даже когда программа запущена в режиме демона. */ if (!log_dir.empty()) { std::string stdout_path = log_dir + "/stdout"; if (!freopen(stdout_path.c_str(), "a+", stdout)) throw Poco::OpenFileException("Cannot attach stdout to " + stdout_path); std::string stderr_path = log_dir + "/stderr"; if (!freopen(stderr_path.c_str(), "a+", stderr)) throw Poco::OpenFileException("Cannot attach stderr to " + stderr_path); } } graphite_writer.reset(new GraphiteWriter("graphite")); } /// Заставляет демон завершаться, если хотя бы одна задача завершилась неудачно void Daemon::exitOnTaskError() { Observer obs(*this, &Daemon::handleNotification); getTaskManager().addObserver(obs); } /// Используется при exitOnTaskError() void Daemon::handleNotification(Poco::TaskFailedNotification *_tfn) { AutoPtr fn(_tfn); Logger *lg = &(logger()); LOG_ERROR(lg, "Task '" << fn->task()->name() << "' failed. Daemon is shutting down. Reason - " << fn->reason().displayText()); ServerApplication::terminate(); } void Daemon::defineOptions(Poco::Util::OptionSet& _options) { Poco::Util::ServerApplication::defineOptions (_options); _options.addOption( Poco::Util::Option ("config-file", "C", "load configuration from a given file") .required (false) .repeatable (false) .argument ("") .binding("config-file") ); _options.addOption( Poco::Util::Option ("log-file", "L", "use given log file") .required (false) .repeatable (false) .argument ("") .binding("logger.log") ); _options.addOption( Poco::Util::Option ("errorlog-file", "E", "use given log file for errors only") .required (false) .repeatable (false) .argument ("") .binding("logger.errorlog") ); _options.addOption( Poco::Util::Option ("pid-file", "P", "use given pidfile") .required (false) .repeatable (false) .argument ("") .binding("pid") ); } void Daemon::PID::seed(const std::string & file_) { /// переведём путь в абсолютный file = Poco::Path(file_).absolute().toString(); int fd = open(file.c_str(), O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); if (-1 == fd) { file.clear(); if (EEXIST == errno) throw Poco::Exception("Pid file exists, should not start daemon."); throw Poco::CreateFileException("Cannot create pid file."); } try { std::stringstream s; s << getpid(); if (static_cast(s.str().size()) != write(fd, s.str().c_str(), s.str().size())) throw Poco::Exception("Cannot write to pid file."); } catch (...) { close(fd); throw; } close(fd); } void Daemon::PID::clear() { if (!file.empty()) { Poco::File(file).remove(); file.clear(); } }