Goby3 3.2.3
2025.05.13
Loading...
Searching...
No Matches
interface.h
Go to the documentation of this file.
1// Copyright 2011-2024:
2// GobySoft, LLC (2013-)
3// Massachusetts Institute of Technology (2007-2014)
4// Community contributors (see AUTHORS file)
5// File authors:
6// Toby Schneider <toby@gobysoft.org>
7//
8//
9// This file is part of the Goby Underwater Autonomy Project Libraries
10// ("The Goby Libraries").
11//
12// The Goby Libraries are free software: you can redistribute them and/or modify
13// them under the terms of the GNU Lesser General Public License as published by
14// the Free Software Foundation, either version 2.1 of the License, or
15// (at your option) any later version.
16//
17// The Goby Libraries are distributed in the hope that they will be useful,
18// but WITHOUT ANY WARRANTY; without even the implied warranty of
19// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20// GNU Lesser General Public License for more details.
21//
22// You should have received a copy of the GNU Lesser General Public License
23// along with Goby. If not, see <http://www.gnu.org/licenses/>.
24
25#ifndef GOBY_MIDDLEWARE_APPLICATION_INTERFACE_H
26#define GOBY_MIDDLEWARE_APPLICATION_INTERFACE_H
27
28#include <chrono>
29#include <csignal>
30#include <iostream>
31#include <sys/types.h>
32#include <unistd.h>
33
34#include <boost/format.hpp>
35
36#include "goby/exception.h"
40#include "goby/time.h"
42#include "goby/util/geodesy.h"
43
44namespace goby
45{
52template <typename App>
54
61template <typename App,
62 typename Configurator = middleware::ProtobufConfigurator<typename App::ConfigType>>
63int run(int argc, char* argv[])
64{
65 return run<App>(Configurator(argc, argv));
66}
67
68namespace middleware
69{
71template <typename Config> class Application
72{
73 public:
75 virtual ~Application()
76 {
77 goby::glog.is_debug2() && goby::glog << "Application: destructing cleanly" << std::endl;
78 }
79
80 using ConfigType = Config;
81
82 protected:
84 virtual void pre_initialize(){};
85
87 virtual void initialize(){};
88
90 virtual void post_initialize(){};
91
93 virtual void run() = 0;
94
96 virtual void pre_finalize(){};
97
99 virtual void finalize(){};
100
102 virtual void post_finalize(){};
103
107 void quit(int return_value = 0)
108 {
109 alive_ = false;
110 return_value_ = return_value;
111 }
112
114 const Config& app_cfg() { return *app_cfg_; }
115
118 {
119 if (geodesy_)
120 return *geodesy_;
121 else
122 throw(goby::Exception("No lat_origin and lon_origin defined for requested UTMGeodesy"));
123 }
124
126 bool has_geodesy() { return geodesy_ ? true : false; }
127
128 std::string app_name() { return app3_base_configuration_->name(); }
129
130 protected:
132
133 private:
134 template <typename App>
135 friend int ::goby::run(
137 // main loop that exits on quit(); returns the desired return value
138 int __run();
139
140 void configure_logger();
141 void configure_glog_file();
142 void check_rotate_glog_file();
143
144 private:
145 // sets configuration (before Application construction)
146 static std::unique_ptr<Config> app_cfg_;
147 static std::unique_ptr<protobuf::AppConfig> app3_base_configuration_;
148
149 bool alive_;
150 int return_value_;
151
152 // static here allows fout_ to live until program exit to log glog output
153 static std::unique_ptr<std::ofstream> fout_;
154
155 goby::time::SteadyClock::time_point next_log_rotate_time_;
156
157 std::unique_ptr<goby::util::UTMGeodesy> geodesy_;
158};
159} // namespace middleware
160
161} // namespace goby
162
163template <typename Config>
164std::unique_ptr<std::ofstream> goby::middleware::Application<Config>::fout_;
165
166template <typename Config> std::unique_ptr<Config> goby::middleware::Application<Config>::app_cfg_;
167
168template <typename Config>
169std::unique_ptr<goby::middleware::protobuf::AppConfig>
171
172template <typename Config> goby::middleware::Application<Config>::Application() : alive_(true)
173{
174 using goby::glog;
175
176 configure_logger();
177 if (app3_base_configuration_->has_geodesy())
178 configure_geodesy({app3_base_configuration_->geodesy().lat_origin_with_units(),
179 app3_base_configuration_->geodesy().lon_origin_with_units()});
180
181 if (!app3_base_configuration_->IsInitialized())
182 throw(middleware::ConfigException("Invalid base configuration"));
183
184 glog.is_debug2() && glog << "Application: constructed with PID: " << getpid() << std::endl;
185 glog.is_debug1() && glog << "App name is " << app3_base_configuration_->name() << std::endl;
186 glog.is_debug2() && glog << "Configuration is: " << app_cfg_->DebugString() << std::endl;
187}
188
189template <typename Config> void goby::middleware::Application<Config>::configure_logger()
190{
191 using goby::glog;
192
193 // set up the logger
194 glog.set_name(app3_base_configuration_->name());
196 app3_base_configuration_->glog_config().tty_verbosity()),
197 &std::cout);
198
199 if (app3_base_configuration_->glog_config().show_gui())
201
202 if (app3_base_configuration_->glog_config().has_file_log())
203 configure_glog_file();
204
205 if (app3_base_configuration_->glog_config().show_dccl_log())
207}
208
210{
211 using goby::glog;
212 if (app3_base_configuration_->glog_config().has_file_log() &&
213 app3_base_configuration_->glog_config().file_log().has_log_rotate_sec() &&
214 goby::time::SteadyClock::now() > next_log_rotate_time_)
215 {
216 glog.remove_stream(fout_.get());
217 configure_glog_file();
218 }
219}
220
222{
223 const auto& file_log = app3_base_configuration_->glog_config().file_log();
224 std::string file_format_str;
225
226 std::string file_name_format = file_log.file_name();
227
228 if (!file_log.has_file_name() && file_log.omit().file_timestamp())
229 file_name_format = "%2%.txt";
230
231 if (file_log.has_file_dir() && !file_log.file_dir().empty())
232 {
233 auto file_dir = file_log.file_dir();
234 if (file_dir.back() != '/')
235 file_dir += "/";
236 file_format_str = file_dir + file_name_format;
237 }
238 else
239 {
240 file_format_str = file_name_format;
241 }
242
243 boost::format file_format(file_format_str);
244
245 if (!file_log.omit().file_timestamp())
246 {
247 if (file_format_str.find("%1") == std::string::npos)
248 glog.is_die() &&
249 glog << "file_name string must contain \"%1%\" which is expanded to the current "
250 "application start time (e.g. 20190201T184925), unless omit.file_timestamp "
251 "== true. Erroneous file_name is: "
252 << file_format_str << std::endl;
253 }
254
255 file_format.exceptions(boost::io::all_error_bits ^
256 (boost::io::too_many_args_bit | boost::io::too_few_args_bit));
257
258 std::string file_name =
259 (file_format % goby::time::file_str() % app3_base_configuration_->name()).str();
260 glog.is_verbose() && glog << "logging output to file: " << file_name << std::endl;
261
262 fout_.reset(new std::ofstream(file_name.c_str()));
263
264 if (!fout_->is_open())
265 glog.is_die() && glog << "cannot write glog output to requested file: " << file_name
266 << std::endl;
267
268 if (!file_log.omit().latest_symlink())
269 {
270 std::string file_symlink =
271 (file_format % "latest" % app3_base_configuration_->name()).str();
272 remove(file_symlink.c_str());
273 int result = symlink(realpath(file_name.c_str(), NULL), file_symlink.c_str());
274 if (result != 0)
275 glog.is_warn() &&
276 glog << "Cannot create symlink to latest file. Continuing onwards anyway"
277 << std::endl;
278 }
279
280 glog.add_stream(file_log.verbosity(), fout_.get());
281
282 if (file_log.has_log_rotate_sec())
283 next_log_rotate_time_ =
284 goby::time::SteadyClock::now() + std::chrono::seconds(file_log.log_rotate_sec());
285}
286
287template <typename Config>
293
294template <typename Config> int goby::middleware::Application<Config>::__run()
295{
296 // block SIGWINCH (change window size) in all threads
297 sigset_t signal_mask;
298 sigemptyset(&signal_mask);
299 sigaddset(&signal_mask, SIGWINCH);
300 pthread_sigmask(SIG_BLOCK, &signal_mask, NULL);
301
302 this->pre_initialize();
303 this->initialize();
304 this->post_initialize();
305 // continue to run while we are alive (quit() has not been called)
306 while (alive_)
307 {
308 this->run();
309 this->check_rotate_glog_file();
310 }
311 this->pre_finalize();
312 this->finalize();
313 this->post_finalize();
314 return return_value_;
315}
316
317template <typename App>
319{
320 int return_value = 0;
321 try
322 {
323 try
324 {
325 cfgtor.validate();
326 }
328 {
329 cfgtor.handle_config_error(e);
330 return 1;
331 }
332
333 // simply print the configuration and exit
334 if (cfgtor.app_configuration().debug_cfg())
335 {
336 std::cout << cfgtor.str() << std::endl;
337 exit(EXIT_SUCCESS);
338 }
339
340 // set configuration
341 App::app_cfg_.reset(new typename App::ConfigType(cfgtor.cfg()));
342 App::app3_base_configuration_.reset(
344
345 // set up simulation time
346 if (App::app3_base_configuration_->simulation().time().use_sim_time())
347 {
350 App::app3_base_configuration_->simulation().time().warp_factor();
351 if (App::app3_base_configuration_->simulation().time().has_reference_microtime())
353 std::chrono::system_clock::time_point(std::chrono::microseconds(
354 App::app3_base_configuration_->simulation().time().reference_microtime()));
355 }
356
357 // instantiate the application (with the configuration already set)
358 App app;
359 return_value = app.__run();
360 }
361 catch (std::exception& e)
362 {
363 // some other exception
364 std::cerr << "Application:: uncaught exception: " << e.what() << std::endl;
365 throw;
366 }
367
368 goby::glog.is_debug2() && goby::glog << "goby::run: exiting cleanly with code: " << return_value
369 << std::endl;
370 return return_value;
371}
372
373#endif
simple exception class for goby applications
Definition exception.h:35
Base class for Goby applications. Generally you will want to use SingleThreadApplication or MultiThre...
Definition interface.h:72
const util::UTMGeodesy & geodesy()
Accesses the geodetic conversion tool if lat_origin and lon_origin were provided.
Definition interface.h:117
virtual void pre_finalize()
Called just before finalize.
Definition interface.h:96
virtual void post_initialize()
Called just after initialize.
Definition interface.h:90
virtual void initialize()
Perform any initialize tasks that couldn't be done in the constructor.
Definition interface.h:87
virtual void pre_initialize()
Called just before initialize.
Definition interface.h:84
virtual void run()=0
Runs continuously until quit() is called.
bool has_geodesy()
Returns if the geodesy tool is configured with a datum.
Definition interface.h:126
virtual void finalize()
Perform any final cleanup actions just before the destructor is called.
Definition interface.h:99
void quit(int return_value=0)
Requests a clean exit.
Definition interface.h:107
void configure_geodesy(goby::util::UTMGeodesy::LatLonPoint datum)
Definition interface.h:288
virtual void post_finalize()
Called just after finalize.
Definition interface.h:102
const Config & app_cfg()
Accesses configuration object passed at launch.
Definition interface.h:114
indicates a problem with the runtime command line or .cfg file configuration (or –help was given)
Defines the interface to a "configurator", a class that can read command line parameters (argc,...
virtual void validate() const
Override to validate the configuration.
virtual void handle_config_error(middleware::ConfigException &e) const
Override to customize how ConfigException errors are handled.
virtual const protobuf::AppConfig & app_configuration() const
Subset of the configuration used to configure the Application itself.
const Config & cfg() const
The configuration object produced from the command line parameters.
virtual std::string str() const =0
Override to output the configuration object as a string.
void remove_stream(std::ostream *os=nullptr)
void set_name(const std::string &s)
Set the name of the application that the logger is serving.
void add_stream(logger::Verbosity verbosity=logger::VERBOSE, std::ostream *os=nullptr)
Attach a stream object (e.g. std::cout, std::ofstream, ...) to the logger with desired verbosity.
std::string str(TimeType value=SystemClock::now< TimeType >())
Returns the provided time (or current time if omitted) as a human-readable string.
Definition convert.h:187
std::string file_str(TimeType value=SystemClock::now< TimeType >())
Returns the provided time (or current time if omitted) as an ISO string suitable for file names (no s...
Definition convert.h:200
The global namespace for the Goby project.
util::FlexOstream glog
Access the Goby logger through this object.
int run(const goby::middleware::ConfiguratorInterface< typename App::ConfigType > &cfgtor)
Run a Goby application using the provided Configurator.
Definition interface.h:318
static void setup_dlog()
Enable dlog output to glog using same verbosity settings as glog.
static bool using_sim_time
Enables simulation time if true (if false, none of the remaining parameters are used)
Definition simulation.h:38
static std::chrono::system_clock::time_point reference_time
Reference time when calculating SystemClock::now(). If this is unset, the default is 1 January of the...
Definition simulation.h:42
static int warp_factor
Warp factor to speed up (or slow time) the time values returned by SteadyClock::now() and SystemClock...
Definition simulation.h:40
std::chrono::time_point< SteadyClock > time_point
static time_point now() noexcept
Returns the current steady time unless SimulatorSettings::using_sim_time == true in which case a simu...