libmoost
|
00001 /* vim:set ts=3 sw=3 sts=3 et: */ 00028 #ifndef FM_LAST_MOOST_PROCESS_SERVICE_H_ 00029 #define FM_LAST_MOOST_PROCESS_SERVICE_H_ 00030 00154 #include <stdexcept> 00155 #include <cassert> 00156 00157 #include <boost/filesystem/path.hpp> 00158 #include <boost/shared_ptr.hpp> 00159 #include <boost/scoped_ptr.hpp> 00160 #include <boost/noncopyable.hpp> 00161 #include <boost/function.hpp> 00162 00163 #include "daemon.hpp" 00164 #include "sleeper.hpp" 00165 #include "pidfile.hpp" 00166 #include "quit_handler.hpp" 00167 #include "../logging/class_logger.hpp" 00168 #include "../logging/standard_console.hpp" 00169 #include "../service/remote_shell.h" 00170 #include "../service/appender.h" 00171 00172 // XXX: This is just a workaround until hopefully some day show_help() and get_prompt() are const... 00173 // #define MPS_FM303_SHELL_CONST const 00174 #define MPS_FM303_SHELL_CONST 00175 00176 namespace moost { namespace process { 00177 00178 class NoConsoleLoggerPolicy 00179 { 00180 public: 00181 void create() 00182 { 00183 } 00184 00185 bool set_log_level(const std::string&) 00186 { 00187 return false; 00188 } 00189 00190 std::string show_help() const 00191 { 00192 return ""; 00193 } 00194 00195 bool handle_command(std::string&, const std::string&, const std::string&) 00196 { 00197 return false; 00198 } 00199 00200 void enable() 00201 { 00202 } 00203 00204 void disable() 00205 { 00206 } 00207 00208 void log(const std::string&) 00209 { 00210 } 00211 }; 00212 00213 class MoostStandardConsoleLoggerPolicy 00214 { 00215 public: 00216 MoostStandardConsoleLoggerPolicy() 00217 : m_disabled(false) 00218 , m_invalid_log_level(new log4cxx::Level(-1, LOG4CXX_STR("INVALID"), 7 )) 00219 { 00220 } 00221 00222 void create() 00223 { 00224 if (!m_console) 00225 { 00226 m_console.reset(new moost::logging::standard_console); 00227 set_log_level(m_log_level); 00228 } 00229 } 00230 00231 bool set_log_level(const std::string& level) 00232 { 00233 if (m_console) 00234 { 00235 if (level.empty()) 00236 { 00237 // default to 'info' level 00238 return m_console->setThreshold(log4cxx::Level::getInfo()); 00239 } 00240 00241 return m_console->setThreshold(level); 00242 } 00243 00244 // this shouldn't be here; but we're safe as long as 00245 // this code and the logging framework are both in moost 00246 00247 log4cxx::LevelPtr new_level = log4cxx::Level::toLevel(level, m_invalid_log_level); 00248 00249 if (new_level == m_invalid_log_level) 00250 { 00251 return false; 00252 } 00253 00254 m_log_level = level; 00255 00256 return true; 00257 } 00258 00259 std::string show_help() const 00260 { 00261 if (m_console && !m_disabled) 00262 { 00263 return "- level [off|fatal|error|warn|info|debug|trace|all]\n" 00264 " set console log level [default: info]\n"; 00265 } 00266 00267 return ""; 00268 } 00269 00270 bool handle_command(std::string& rv, const std::string& cmd, const std::string& args) 00271 { 00272 if (m_console && !m_disabled) 00273 { 00274 if (cmd == "level") 00275 { 00276 if (set_log_level(args)) 00277 { 00278 std::string level; 00279 m_console->getThreshold(level); 00280 rv = "log level set to [" + level + "]\n"; 00281 } 00282 else 00283 { 00284 rv = "unknown log level " + args + "\n"; 00285 } 00286 00287 return true; 00288 } 00289 } 00290 00291 return false; 00292 } 00293 00294 void enable() 00295 { 00296 if (m_console && m_disabled) 00297 { 00298 m_console->enable(); 00299 m_disabled = false; 00300 } 00301 } 00302 00303 void disable() 00304 { 00305 if (m_console && !m_disabled) 00306 { 00307 m_console->disable(); 00308 m_disabled = true; 00309 } 00310 } 00311 00312 void log(const std::string& msg) 00313 { 00314 MLOG_NAMED_INFO("moost::process::service", msg); 00315 } 00316 00317 private: 00318 bool m_disabled; 00319 boost::scoped_ptr<moost::logging::standard_console> m_console; 00320 std::string m_log_level; 00321 log4cxx::LevelPtr m_invalid_log_level; 00322 }; 00323 00324 template<class ServiceT, class ConsoleLoggerPolicy = MoostStandardConsoleLoggerPolicy> 00325 class service : public boost::noncopyable 00326 { 00327 private: 00328 struct noop_child_init_func 00329 { 00330 bool operator()() 00331 { 00332 return false; 00333 } 00334 }; 00335 00336 struct default_parent_exit_func 00337 { 00338 void operator()(pid_t) 00339 { 00340 exit(0); 00341 } 00342 }; 00343 00344 class service_wrapper : public boost::noncopyable 00345 { 00346 private: 00347 boost::shared_ptr<typename ServiceT::HandlerType> checked_handler() 00348 { 00349 boost::shared_ptr<typename ServiceT::HandlerType> hdl(m_service->handler()); 00350 00351 if (!hdl) 00352 { 00353 throw std::runtime_error("got null pointer for service handler"); 00354 } 00355 00356 return hdl; 00357 } 00358 00359 boost::shared_ptr<const typename ServiceT::HandlerType> checked_handler() const 00360 { 00361 boost::shared_ptr<const typename ServiceT::HandlerType> hdl(m_service->handler()); 00362 00363 if (!hdl) 00364 { 00365 throw std::runtime_error("got null pointer for service handler"); 00366 } 00367 00368 return hdl; 00369 } 00370 00371 public: 00372 service_wrapper(boost::shared_ptr<ServiceT> service) 00373 : m_service(service) 00374 { 00375 } 00376 00377 bool set_log_level(const std::string& level) 00378 { 00379 return m_logger.set_log_level(level); 00380 } 00381 00382 std::string get_prompt() MPS_FM303_SHELL_CONST 00383 { 00384 return checked_handler()->get_prompt(); 00385 } 00386 00387 std::string show_help() MPS_FM303_SHELL_CONST 00388 { 00389 std::string help; 00390 00391 help += m_logger.show_help(); 00392 help += checked_handler()->show_help(); 00393 00394 return help; 00395 } 00396 00397 bool handle_command(std::string& rv, const std::string& cmd, const std::string& args) 00398 { 00399 if (m_logger.handle_command(rv, cmd, args)) 00400 { 00401 return true; 00402 } 00403 00404 return checked_handler()->handle_command(rv, cmd, args); 00405 } 00406 00407 std::string name() const 00408 { 00409 return m_service->name(); 00410 } 00411 00412 void start() 00413 { 00414 m_logger.create(); 00415 00416 m_service->start(); 00417 } 00418 00419 void disable_logger() 00420 { 00421 m_logger.disable(); 00422 } 00423 00424 void enable_logger() 00425 { 00426 m_logger.enable(); 00427 } 00428 00429 void stop() 00430 { 00431 m_service->stop(); 00432 } 00433 00434 boost::shared_ptr<ServiceT> get_service() 00435 { 00436 return m_service; 00437 } 00438 00439 void log(const std::string& msg) 00440 { 00441 m_logger.log(msg); 00442 } 00443 00444 private: 00445 boost::shared_ptr<ServiceT> m_service; 00446 ConsoleLoggerPolicy m_logger; 00447 }; 00448 00449 struct enable_logger_func 00450 { 00451 public: 00452 enable_logger_func(service_wrapper& svc) 00453 : m_svc(svc) 00454 { 00455 } 00456 00457 void operator()() 00458 { 00459 m_svc.enable_logger(); 00460 } 00461 00462 private: 00463 service_wrapper& m_svc; 00464 }; 00465 00466 public: 00467 service(boost::shared_ptr<ServiceT> service) 00468 : m_svc(service) 00469 , m_started(false) 00470 , m_daemonised(false) 00471 , m_shell_port(0) 00472 , m_default_stdout_state(true) 00473 , m_default_stderr_state(true) 00474 , m_child_init_func(noop_child_init_func()) 00475 , m_parent_exit_func(default_parent_exit_func()) 00476 { 00477 } 00478 00479 ~service() 00480 { 00481 if (m_pidfile) 00482 { 00483 m_pidfile->remove(); 00484 m_pidfile.reset(); 00485 } 00486 } 00487 00488 bool set_log_level(const std::string& log_level) 00489 { 00490 return m_svc.set_log_level(log_level); 00491 } 00492 00493 std::string name() const 00494 { 00495 return m_svc.name(); 00496 } 00497 00498 void set_shell_port(unsigned short shell_port) 00499 { 00500 m_shell_port = shell_port; 00501 } 00502 00503 void set_appender_factory(moost::service::appender_factory_ptr factory) 00504 { 00505 m_app_factory = factory; 00506 } 00507 00508 void set_default_stdout_state(bool enabled) 00509 { 00510 m_default_stdout_state = enabled; 00511 } 00512 00513 void set_default_stderr_state(bool enabled) 00514 { 00515 m_default_stderr_state = enabled; 00516 } 00517 00518 void set_child_init_func(boost::function0<bool> child_init_func) 00519 { 00520 m_child_init_func = child_init_func; 00521 } 00522 00523 void set_parent_exit_func(boost::function1<void, pid_t> parent_exit_func) 00524 { 00525 m_parent_exit_func = parent_exit_func; 00526 } 00527 00528 void run(bool daemonise = false) 00529 { 00530 if (daemonise) 00531 { 00532 moost::process::daemon d(false, m_child_init_func); 00533 00534 m_pidfile.reset(m_pidfile_name.empty() ? new moost::process::pidfile(m_svc.name(), moost::process::pidfile::get_default_rundir().string()) 00535 : new moost::process::pidfile(m_pidfile_name)); 00536 00537 if (d.is_parent()) 00538 { 00539 pid_t child_pid = d.get_pid(); 00540 00541 if (!m_pidfile->create(child_pid)) 00542 { 00543 throw std::runtime_error("failed to create pid file"); 00544 } 00545 00546 m_parent_exit_func(child_pid); 00547 00548 return; 00549 } 00550 00551 m_daemonised = true; 00552 } 00553 else 00554 { 00555 m_child_init_func(); 00556 } 00557 00558 moost::process::quit_handler::set(boost::bind(&moost::process::service<ServiceT, ConsoleLoggerPolicy>::quit_handler, this)); 00559 00560 m_svc.start(); 00561 00562 m_started = true; 00563 00564 if (daemonise && !m_shell_port) 00565 { 00566 m_svc.disable_logger(); 00567 m_sleeper.sleep(); 00568 } 00569 else 00570 { 00571 // Create our io_service object and preload it with a function 00572 // object that will disable the standard_console logger. This 00573 // will be executed as soon as the io_service starts its main 00574 // event loop, i.e. when the local shell has taken over control. 00575 00576 boost::shared_ptr<boost::asio::io_service> ios(new boost::asio::io_service); 00577 00578 ios->post(boost::bind(&service_wrapper::disable_logger, &m_svc)); 00579 00580 m_remote_shell.reset(new remote_shell_t(m_svc, ios)); 00581 00582 if (m_app_factory) 00583 { 00584 m_remote_shell->set_appender_factory(m_app_factory); 00585 } 00586 00587 m_remote_shell->set_default_stdout_state(m_default_stdout_state); 00588 m_remote_shell->set_default_stderr_state(m_default_stderr_state); 00589 00590 if (m_shell_port) 00591 { 00592 m_remote_shell->set_listen_port(m_shell_port); 00593 } 00594 00595 if (!daemonise) 00596 { 00597 // Enable a local shell if we're not daemonising. The pre shutdown 00598 // function object ensures that the standard_console has taken over 00599 // logging from the local shell before the local shell quits. 00600 00601 m_remote_shell->enable_local_shell(); 00602 m_remote_shell->set_pre_shutdown_function(enable_logger_func(m_svc)); 00603 } 00604 00605 m_remote_shell->run(); 00606 } 00607 00608 m_started = false; 00609 00610 m_svc.stop(); 00611 } 00612 00613 void quit(const std::string& msg = "") 00614 { 00615 if (m_started) 00616 { 00617 if (m_remote_shell) 00618 { 00619 m_remote_shell->stop(msg); 00620 } 00621 00622 m_sleeper.awaken(); 00623 00624 m_started = false; 00625 } 00626 } 00627 00628 void set_pidfile(const std::string& pidfile) 00629 { 00630 boost::filesystem::path p(pidfile); 00631 00632 if (!p.has_root_directory()) 00633 { 00634 throw std::runtime_error("require absolute path for pid file"); 00635 } 00636 00637 m_pidfile_name = pidfile; 00638 } 00639 00640 boost::shared_ptr<ServiceT> get_service() 00641 { 00642 return m_svc.get_service(); 00643 } 00644 00645 private: 00646 void quit_handler() 00647 { 00648 m_svc.log("Received quit signal, shutting down."); 00649 quit("quit signal received, shutting down"); 00650 } 00651 00652 typedef moost::service::remote_shell<service_wrapper> remote_shell_t; 00653 00654 service_wrapper m_svc; 00655 std::string m_pidfile_name; 00656 bool m_started; 00657 bool m_daemonised; 00658 boost::scoped_ptr<moost::process::pidfile> m_pidfile; 00659 unsigned short m_shell_port; 00660 boost::scoped_ptr<remote_shell_t> m_remote_shell; 00661 moost::service::appender_factory_ptr m_app_factory; 00662 moost::process::sleeper m_sleeper; 00663 bool m_default_stdout_state; 00664 bool m_default_stderr_state; 00665 boost::function0<bool> m_child_init_func; 00666 boost::function1<void, pid_t> m_parent_exit_func; 00667 }; 00668 00669 } } 00670 00671 #endif