mirror of
https://github.com/wesnoth/wesnoth
synced 2025-05-16 02:15:19 +00:00
Adds the --log-to-file command line option.
This sets up writing both the cerr and cout streams to a log file, as well as pulls the log rotation logic out of log_windows.*pp into the general logger. Note that this is meant for usage on macOS and Linux, not Windows. log_windows.*pp has additional functionality in it, as well as a fair bit related to the --wconsole option.
This commit is contained in:
parent
178a09c2cc
commit
d1833b5cf7
@ -190,6 +190,9 @@ lists defined log domains (only the ones containing
|
||||
.I filter
|
||||
if used) and exits
|
||||
.TP
|
||||
.B --log-to-file
|
||||
redirects logged output to a file. Log files are created in the logs directory under the userdata folder.
|
||||
.TP
|
||||
.BI --max-fps \ fps
|
||||
the number of frames per second the game can show, the value should be between
|
||||
.B 1
|
||||
|
@ -257,6 +257,7 @@ commandline_options::commandline_options(const std::vector<std::string>& args)
|
||||
("log-debug", po::value<std::string>(), "sets the severity level of the specified log domain(s) to 'debug'. Similar to --log-error.")
|
||||
("log-none", po::value<std::string>(), "sets the severity level of the specified log domain(s) to 'none'. Similar to --log-error.")
|
||||
("log-precise", "shows the timestamps in log output with more precision.")
|
||||
("log-to-file", "log output is written to a file rather than to standard error.")
|
||||
;
|
||||
|
||||
po::options_description multiplayer_opts("Multiplayer options");
|
||||
@ -268,7 +269,7 @@ commandline_options::commandline_options(const std::vector<std::string>& args)
|
||||
("era", po::value<std::string>(), "selects the era to be played in by its id.")
|
||||
("exit-at-end", "exit Wesnoth at the end of the scenario.")
|
||||
("ignore-map-settings", "do not use map settings.")
|
||||
("label", po::value<std::string>(), "sets the label for AIs.") //TODO is the description precise? this option was undocumented before.
|
||||
("label", po::value<std::string>(), "sets the label for AIs.") // TODO: is the description precise? this option was undocumented before.
|
||||
("multiplayer-repeat", po::value<unsigned int>(), "repeats a multiplayer game after it is finished <arg> times.")
|
||||
("nogui", "runs the game without the GUI.")
|
||||
("parm", po::value<std::vector<std::string>>()->composing(), "sets additional parameters for this side. <arg> should have format side:name:value.")
|
||||
|
@ -583,9 +583,12 @@ static void setup_user_data_dir()
|
||||
create_directory_if_missing(user_data_dir / "data" / "add-ons");
|
||||
create_directory_if_missing(user_data_dir / "saves");
|
||||
create_directory_if_missing(user_data_dir / "persist");
|
||||
create_directory_if_missing(filesystem::get_logs_dir());
|
||||
|
||||
#ifdef _WIN32
|
||||
lg::finish_log_file_setup();
|
||||
#else
|
||||
lg::rotate_logs(filesystem::get_logs_dir());
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -792,6 +795,11 @@ std::string get_user_data_dir()
|
||||
return get_user_data_path().string();
|
||||
}
|
||||
|
||||
std::string get_logs_dir()
|
||||
{
|
||||
return filesystem::get_user_data_dir() + "/logs";
|
||||
}
|
||||
|
||||
std::string get_cache_dir()
|
||||
{
|
||||
if(cache_dir.empty()) {
|
||||
@ -1042,7 +1050,6 @@ filesystem::scoped_istream istream_file(const std::string& fname, bool treat_fai
|
||||
filesystem::scoped_ostream ostream_file(const std::string& fname, std::ios_base::openmode mode, bool create_directory)
|
||||
{
|
||||
LOG_FS << "streaming " << fname << " for writing.";
|
||||
#if 1
|
||||
try {
|
||||
boost::iostreams::file_descriptor_sink fd(bfs::path(fname), mode);
|
||||
return std::make_unique<boost::iostreams::stream<boost::iostreams::file_descriptor_sink>>(fd, 4096, 0);
|
||||
@ -1056,9 +1063,6 @@ filesystem::scoped_ostream ostream_file(const std::string& fname, std::ios_base:
|
||||
|
||||
throw filesystem::io_exception(e.what());
|
||||
}
|
||||
#else
|
||||
return new bfs::ofstream(bfs::path(fname), mode);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Throws io_exception if an error occurs
|
||||
|
@ -165,6 +165,7 @@ void set_user_data_dir(std::string path);
|
||||
|
||||
std::string get_user_config_dir();
|
||||
std::string get_user_data_dir();
|
||||
std::string get_logs_dir();
|
||||
std::string get_cache_dir();
|
||||
|
||||
struct other_version_dir
|
||||
|
94
src/log.cpp
94
src/log.cpp
@ -21,6 +21,10 @@
|
||||
*/
|
||||
|
||||
#include "log.hpp"
|
||||
|
||||
#include "filesystem.hpp"
|
||||
#include "mt_rng.hpp"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include <map>
|
||||
@ -30,6 +34,12 @@
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
|
||||
static lg::log_domain log_setup("logsetup");
|
||||
#define ERR_LS LOG_STREAM(err, log_setup)
|
||||
#define WRN_LS LOG_STREAM(warn, log_setup)
|
||||
#define LOG_LS LOG_STREAM(info, log_setup)
|
||||
#define DBG_LS LOG_STREAM(debug, log_setup)
|
||||
|
||||
namespace {
|
||||
|
||||
class null_streambuf : public std::streambuf
|
||||
@ -47,27 +57,97 @@ static bool timestamp = true;
|
||||
static bool precise_timestamp = false;
|
||||
static std::mutex log_mutex;
|
||||
|
||||
static std::ostream *output_stream = nullptr;
|
||||
static std::ostream *output_stream_ = nullptr;
|
||||
|
||||
static std::ostream& output()
|
||||
{
|
||||
if(output_stream) {
|
||||
return *output_stream;
|
||||
if(output_stream_) {
|
||||
return *output_stream_;
|
||||
}
|
||||
return std::cerr;
|
||||
}
|
||||
|
||||
// custom deleter needed to reset cerr and cout
|
||||
// otherwise wesnoth segfaults on closing (such as clicking the Quit button on the main menu)
|
||||
// seems to be that there's a final flush done outside of wesnoth's code just before exiting
|
||||
// but at that point the output_file_ has already been cleaned up
|
||||
static std::unique_ptr<std::ostream, void(*)(std::ostream*)> output_file_(nullptr, [](std::ostream*){
|
||||
std::cerr.rdbuf(nullptr);
|
||||
std::cout.rdbuf(nullptr);
|
||||
});
|
||||
|
||||
namespace lg {
|
||||
|
||||
redirect_output_setter::redirect_output_setter(std::ostream& stream)
|
||||
: old_stream_(output_stream)
|
||||
/** Helper function for rotate_logs. */
|
||||
bool is_not_log_file(const std::string& fn)
|
||||
{
|
||||
output_stream = &stream;
|
||||
return !(boost::algorithm::istarts_with(fn, lg::log_file_prefix) &&
|
||||
boost::algorithm::iends_with(fn, lg::log_file_suffix));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes old log files from the log directory.
|
||||
*/
|
||||
void rotate_logs(const std::string& log_dir)
|
||||
{
|
||||
std::vector<std::string> files;
|
||||
filesystem::get_files_in_dir(log_dir, &files);
|
||||
|
||||
files.erase(std::remove_if(files.begin(), files.end(), is_not_log_file), files.end());
|
||||
|
||||
if(files.size() <= lg::max_logs) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sorting the file list and deleting all but the last max_logs items
|
||||
// should hopefully be faster than stat'ing every single file for its
|
||||
// time attributes (which aren't very reliable to begin with).
|
||||
|
||||
std::sort(files.begin(), files.end());
|
||||
|
||||
for(std::size_t j = 0; j < files.size() - lg::max_logs; ++j) {
|
||||
const std::string path = log_dir + '/' + files[j];
|
||||
LOG_LS << "rotate_logs(): delete " << path;
|
||||
if(!filesystem::delete_file(path)) {
|
||||
ERR_LS << "rotate_logs(): failed to delete " << path << "!";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a unique log file name.
|
||||
*/
|
||||
std::string unique_log_filename()
|
||||
{
|
||||
std::ostringstream o;
|
||||
const std::time_t cur = std::time(nullptr);
|
||||
randomness::mt_rng rng;
|
||||
|
||||
o << lg::log_file_prefix
|
||||
<< std::put_time(std::localtime(&cur), "%Y%m%d-%H%M%S-")
|
||||
<< rng.get_next_random()
|
||||
<< lg::log_file_suffix;
|
||||
|
||||
return o.str();
|
||||
}
|
||||
|
||||
void set_log_to_file()
|
||||
{
|
||||
// get the log file stream and assign cerr+cout to it
|
||||
output_file_.reset(filesystem::ostream_file(filesystem::get_logs_dir()+"/"+unique_log_filename()).release());
|
||||
std::cerr.rdbuf(output_file_.get()->rdbuf());
|
||||
std::cout.rdbuf(output_file_.get()->rdbuf());
|
||||
}
|
||||
|
||||
redirect_output_setter::redirect_output_setter(std::ostream& stream)
|
||||
: old_stream_(output_stream_)
|
||||
{
|
||||
output_stream_ = &stream;
|
||||
}
|
||||
|
||||
redirect_output_setter::~redirect_output_setter()
|
||||
{
|
||||
output_stream = old_stream_;
|
||||
output_stream_ = old_stream_;
|
||||
}
|
||||
|
||||
typedef std::map<std::string, int> domain_map;
|
||||
|
14
src/log.hpp
14
src/log.hpp
@ -61,6 +61,15 @@
|
||||
|
||||
namespace lg {
|
||||
|
||||
// Prefix and extension for log files. This is used both to generate the unique
|
||||
// log file name during startup and to find old files to delete.
|
||||
const std::string log_file_prefix = "wesnoth-";
|
||||
const std::string log_file_suffix = ".log";
|
||||
|
||||
// Maximum number of older log files to keep intact. Other files are deleted.
|
||||
// Note that this count does not include the current log file!
|
||||
const unsigned max_logs = 8;
|
||||
|
||||
enum severity
|
||||
{
|
||||
LG_ERROR=0,
|
||||
@ -117,6 +126,11 @@ std::string list_logdomains(const std::string& filter);
|
||||
void set_strict_severity(int severity);
|
||||
void set_strict_severity(const logger &lg);
|
||||
bool broke_strict();
|
||||
void set_log_to_file();
|
||||
|
||||
bool is_not_log_file(const std::string& filename);
|
||||
void rotate_logs(const std::string& log_dir);
|
||||
std::string unique_log_filename();
|
||||
|
||||
// A little "magic" to surround the logging operation in a mutex.
|
||||
// This works by capturing the output first to a stringstream formatter, then
|
||||
|
@ -44,91 +44,12 @@ static lg::log_domain log_setup("logsetup");
|
||||
#define LOG_LS LOG_STREAM(info, log_setup)
|
||||
#define DBG_LS LOG_STREAM(debug, log_setup)
|
||||
|
||||
namespace filesystem
|
||||
{
|
||||
|
||||
std::string get_logs_dir()
|
||||
{
|
||||
return filesystem::get_user_data_dir() + "/logs";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace lg
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// Prefix and extension for log files. This is used both to generate the unique
|
||||
// log file name during startup and to find old files to delete.
|
||||
const std::string log_file_prefix = "wesnoth-";
|
||||
const std::string log_file_suffix = ".log";
|
||||
|
||||
// Maximum number of older log files to keep intact. Other files are deleted.
|
||||
// Note that this count does not include the current log file!
|
||||
const unsigned max_logs = 8;
|
||||
|
||||
/** Helper function for rotate_logs. */
|
||||
bool is_not_log_file(const std::string& fn)
|
||||
{
|
||||
return !(boost::algorithm::istarts_with(fn, log_file_prefix) &&
|
||||
boost::algorithm::iends_with(fn, log_file_suffix));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes old log files from the log directory.
|
||||
*/
|
||||
void rotate_logs(const std::string& log_dir)
|
||||
{
|
||||
std::vector<std::string> files;
|
||||
filesystem::get_files_in_dir(log_dir, &files);
|
||||
|
||||
files.erase(std::remove_if(files.begin(), files.end(), is_not_log_file), files.end());
|
||||
|
||||
if(files.size() <= max_logs) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sorting the file list and deleting all but the last max_logs items
|
||||
// should hopefully be faster than stat'ing every single file for its
|
||||
// time attributes (which aren't very reliable to begin with.
|
||||
|
||||
std::sort(files.begin(), files.end());
|
||||
|
||||
for(std::size_t j = 0; j < files.size() - max_logs; ++j) {
|
||||
const std::string path = log_dir + '/' + files[j];
|
||||
LOG_LS << "rotate_logs(): delete " << path;
|
||||
if(!filesystem::delete_file(path)) {
|
||||
WRN_LS << "rotate_logs(): failed to delete " << path << "!";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a "unique" log file name.
|
||||
*
|
||||
* This is really not guaranteed to be unique, but it's close enough, since
|
||||
* the odds of having multiple Wesnoth instances spawn with the same PID within
|
||||
* a second span are close to zero.
|
||||
*
|
||||
* The file name includes a timestamp in order to satisfy the requirements of
|
||||
* the rotate_logs logic.
|
||||
*/
|
||||
std::string unique_log_filename()
|
||||
{
|
||||
std::ostringstream o;
|
||||
|
||||
o << log_file_prefix;
|
||||
|
||||
const std::time_t cur = std::time(nullptr);
|
||||
o << std::put_time(std::localtime(&cur), "%Y%m%d-%H%M%S-");
|
||||
|
||||
o << GetCurrentProcessId() << log_file_suffix;
|
||||
|
||||
return o.str();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to a system-defined temporary files dir.
|
||||
*/
|
||||
@ -291,7 +212,7 @@ private:
|
||||
};
|
||||
|
||||
log_file_manager::log_file_manager(bool native_console)
|
||||
: fn_(unique_log_filename())
|
||||
: fn_(lg::unique_log_filename())
|
||||
, cur_path_()
|
||||
, use_wincon_(console_attached())
|
||||
, created_wincon_(false)
|
||||
@ -531,7 +452,7 @@ void finish_log_file_setup()
|
||||
log_init_panic(std::string("Could not create logs directory at ") +
|
||||
log_dir + ".");
|
||||
} else {
|
||||
rotate_logs(log_dir);
|
||||
lg::rotate_logs(log_dir);
|
||||
}
|
||||
|
||||
lfm->move_log_file(log_dir);
|
||||
|
@ -39,16 +39,6 @@
|
||||
* and later versions, requiring UAC virtualization to be enabled).
|
||||
*/
|
||||
|
||||
namespace filesystem
|
||||
{
|
||||
|
||||
/**
|
||||
* Returns the path to the permanent log storage directory.
|
||||
*/
|
||||
std::string get_logs_dir();
|
||||
|
||||
}
|
||||
|
||||
namespace lg
|
||||
{
|
||||
|
||||
|
@ -986,12 +986,18 @@ int main(int argc, char** argv)
|
||||
assert(!args.empty());
|
||||
|
||||
// --nobanner needs to be detected before the main command-line parsing happens
|
||||
// --log-to needs to be detected so the logging output location is set before any actual logging happens
|
||||
bool nobanner = false;
|
||||
for(const auto& arg : args) {
|
||||
if(arg == "--nobanner") {
|
||||
nobanner = true;
|
||||
break;
|
||||
}
|
||||
#ifndef _WIN32
|
||||
else if(arg == "--log-to-file") {
|
||||
lg::set_log_to_file();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
Loading…
x
Reference in New Issue
Block a user