2016-02-01 21:55:43 +00:00
|
|
|
|
#include "Log.h"
|
2016-04-26 22:27:24 +00:00
|
|
|
|
#include "File.h"
|
|
|
|
|
#include "StrFmt.h"
|
2017-05-13 18:30:37 +00:00
|
|
|
|
#include "sema.h"
|
2016-04-26 22:27:24 +00:00
|
|
|
|
|
2017-08-13 15:32:48 +00:00
|
|
|
|
#include "Utilities/sysinfo.h"
|
2016-07-20 22:00:31 +00:00
|
|
|
|
#include "rpcs3_version.h"
|
2016-04-26 22:27:24 +00:00
|
|
|
|
#include <string>
|
2017-05-13 18:30:37 +00:00
|
|
|
|
#include <unordered_map>
|
2016-04-26 22:27:24 +00:00
|
|
|
|
|
2017-02-22 09:52:03 +00:00
|
|
|
|
#ifdef _WIN32
|
|
|
|
|
#include <Windows.h>
|
|
|
|
|
#else
|
|
|
|
|
#include <chrono>
|
|
|
|
|
#endif
|
|
|
|
|
|
2017-05-13 18:30:37 +00:00
|
|
|
|
static std::string empty_string()
|
|
|
|
|
{
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-26 22:27:24 +00:00
|
|
|
|
// Thread-specific log prefix provider
|
2017-05-13 18:30:37 +00:00
|
|
|
|
thread_local std::string(*g_tls_log_prefix)() = &empty_string;
|
2014-06-17 15:44:03 +00:00
|
|
|
|
|
2016-08-03 20:51:05 +00:00
|
|
|
|
template<>
|
|
|
|
|
void fmt_class_string<logs::level>::format(std::string& out, u64 arg)
|
|
|
|
|
{
|
|
|
|
|
format_enum(out, arg, [](auto lev)
|
|
|
|
|
{
|
|
|
|
|
switch (lev)
|
|
|
|
|
{
|
|
|
|
|
case logs::level::always: return "Nothing";
|
|
|
|
|
case logs::level::fatal: return "Fatal";
|
|
|
|
|
case logs::level::error: return "Error";
|
|
|
|
|
case logs::level::todo: return "TODO";
|
|
|
|
|
case logs::level::success: return "Success";
|
|
|
|
|
case logs::level::warning: return "Warning";
|
|
|
|
|
case logs::level::notice: return "Notice";
|
|
|
|
|
case logs::level::trace: return "Trace";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return unknown;
|
|
|
|
|
});
|
|
|
|
|
}
|
2016-05-13 14:01:48 +00:00
|
|
|
|
|
|
|
|
|
namespace logs
|
2014-06-17 15:44:03 +00:00
|
|
|
|
{
|
2016-04-26 22:27:24 +00:00
|
|
|
|
class file_writer
|
|
|
|
|
{
|
|
|
|
|
// Could be memory-mapped file
|
|
|
|
|
fs::file m_file;
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
file_writer(const std::string& name);
|
|
|
|
|
|
|
|
|
|
virtual ~file_writer() = default;
|
|
|
|
|
|
|
|
|
|
// Append raw data
|
2016-07-20 22:00:31 +00:00
|
|
|
|
void log(const char* text, std::size_t size);
|
2016-04-26 22:27:24 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct file_listener : public file_writer, public listener
|
|
|
|
|
{
|
2017-08-20 21:58:25 +00:00
|
|
|
|
file_listener(const std::string& name);
|
2016-04-26 22:27:24 +00:00
|
|
|
|
|
|
|
|
|
// Encode level, current thread name, channel name and write log message
|
2017-02-22 09:52:03 +00:00
|
|
|
|
virtual void log(u64 stamp, const message& msg, const std::string& prefix, const std::string& text) override;
|
2016-04-26 22:27:24 +00:00
|
|
|
|
};
|
|
|
|
|
|
2016-07-20 22:00:31 +00:00
|
|
|
|
static file_listener* get_logger()
|
2014-06-17 15:44:03 +00:00
|
|
|
|
{
|
2016-02-01 21:55:43 +00:00
|
|
|
|
// Use magic static
|
|
|
|
|
static file_listener logger("RPCS3.log");
|
2016-07-20 22:00:31 +00:00
|
|
|
|
return &logger;
|
2014-06-17 15:44:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-22 09:52:03 +00:00
|
|
|
|
static u64 get_stamp()
|
|
|
|
|
{
|
|
|
|
|
static struct time_initializer
|
|
|
|
|
{
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
|
LARGE_INTEGER freq;
|
|
|
|
|
LARGE_INTEGER start;
|
|
|
|
|
|
|
|
|
|
time_initializer()
|
|
|
|
|
{
|
|
|
|
|
QueryPerformanceFrequency(&freq);
|
|
|
|
|
QueryPerformanceCounter(&start);
|
|
|
|
|
}
|
|
|
|
|
#else
|
|
|
|
|
std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
u64 get() const
|
|
|
|
|
{
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
|
LARGE_INTEGER now;
|
|
|
|
|
QueryPerformanceCounter(&now);
|
|
|
|
|
const LONGLONG diff = now.QuadPart - start.QuadPart;
|
|
|
|
|
return diff / freq.QuadPart * 1'000'000 + diff % freq.QuadPart * 1'000'000 / freq.QuadPart;
|
|
|
|
|
#else
|
|
|
|
|
return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
} timebase{};
|
|
|
|
|
|
|
|
|
|
return timebase.get();
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-13 18:30:37 +00:00
|
|
|
|
channel GENERAL("");
|
|
|
|
|
channel LOADER("LDR");
|
|
|
|
|
channel MEMORY("MEM");
|
|
|
|
|
channel RSX("RSX");
|
|
|
|
|
channel HLE("HLE");
|
|
|
|
|
channel PPU("PPU");
|
|
|
|
|
channel SPU("SPU");
|
2016-01-12 21:57:16 +00:00
|
|
|
|
channel ARMv7("ARMv7");
|
2017-05-13 18:30:37 +00:00
|
|
|
|
|
|
|
|
|
struct channel_info
|
|
|
|
|
{
|
|
|
|
|
channel* pointer = nullptr;
|
|
|
|
|
level enabled = level::notice;
|
|
|
|
|
|
|
|
|
|
void set_level(level value)
|
|
|
|
|
{
|
|
|
|
|
enabled = value;
|
|
|
|
|
|
|
|
|
|
if (pointer)
|
|
|
|
|
{
|
|
|
|
|
pointer->enabled = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2017-08-20 21:58:25 +00:00
|
|
|
|
struct stored_message
|
|
|
|
|
{
|
|
|
|
|
message m;
|
|
|
|
|
u64 stamp;
|
|
|
|
|
std::string prefix;
|
|
|
|
|
std::string text;
|
|
|
|
|
};
|
|
|
|
|
|
2017-05-13 18:30:37 +00:00
|
|
|
|
// Channel registry mutex
|
|
|
|
|
semaphore<> g_mutex;
|
|
|
|
|
|
|
|
|
|
// Channel registry
|
|
|
|
|
std::unordered_map<std::string, channel_info> g_channels;
|
|
|
|
|
|
2017-08-20 21:58:25 +00:00
|
|
|
|
// Messages for delayed listener initialization
|
|
|
|
|
std::vector<stored_message> g_messages;
|
|
|
|
|
|
|
|
|
|
// Must be set to true in main()
|
|
|
|
|
atomic_t<bool> g_init{false};
|
|
|
|
|
|
2017-05-13 18:30:37 +00:00
|
|
|
|
void reset()
|
|
|
|
|
{
|
|
|
|
|
semaphore_lock lock(g_mutex);
|
|
|
|
|
|
|
|
|
|
for (auto&& pair : g_channels)
|
|
|
|
|
{
|
|
|
|
|
pair.second.set_level(level::notice);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void set_level(const std::string& ch_name, level value)
|
|
|
|
|
{
|
|
|
|
|
semaphore_lock lock(g_mutex);
|
|
|
|
|
|
|
|
|
|
g_channels[ch_name].set_level(value);
|
|
|
|
|
}
|
2017-08-20 21:58:25 +00:00
|
|
|
|
|
|
|
|
|
// Must be called in main() to stop accumulating messages in g_messages
|
|
|
|
|
void set_init()
|
|
|
|
|
{
|
|
|
|
|
if (!g_init)
|
|
|
|
|
{
|
|
|
|
|
semaphore_lock lock(g_mutex);
|
|
|
|
|
g_messages.clear();
|
|
|
|
|
g_init = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-06-17 15:44:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-01-24 23:22:19 +00:00
|
|
|
|
logs::listener::~listener()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2016-07-20 22:00:31 +00:00
|
|
|
|
void logs::listener::add(logs::listener* _new)
|
|
|
|
|
{
|
|
|
|
|
// Get first (main) listener
|
|
|
|
|
listener* lis = get_logger();
|
|
|
|
|
|
2017-08-20 21:58:25 +00:00
|
|
|
|
semaphore_lock lock(g_mutex);
|
|
|
|
|
|
2016-07-20 22:00:31 +00:00
|
|
|
|
// Install new listener at the end of linked list
|
|
|
|
|
while (lis->m_next || !lis->m_next.compare_and_swap_test(nullptr, _new))
|
|
|
|
|
{
|
|
|
|
|
lis = lis->m_next;
|
|
|
|
|
}
|
2017-08-20 21:58:25 +00:00
|
|
|
|
|
|
|
|
|
// Send initial messages
|
|
|
|
|
for (const auto& msg : g_messages)
|
|
|
|
|
{
|
|
|
|
|
_new->log(msg.stamp, msg.m, msg.prefix, msg.text);
|
|
|
|
|
}
|
2016-07-20 22:00:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-08-05 16:49:45 +00:00
|
|
|
|
void logs::message::broadcast(const char* fmt, const fmt_type_info* sup, const u64* args)
|
2014-06-17 15:44:03 +00:00
|
|
|
|
{
|
2017-02-22 09:52:03 +00:00
|
|
|
|
// Get timestamp
|
|
|
|
|
const u64 stamp = get_stamp();
|
|
|
|
|
|
2017-05-13 18:30:37 +00:00
|
|
|
|
// Register channel
|
|
|
|
|
if (ch->enabled == level::_uninit)
|
|
|
|
|
{
|
|
|
|
|
semaphore_lock lock(g_mutex);
|
|
|
|
|
|
|
|
|
|
auto& info = g_channels[ch->name];
|
|
|
|
|
|
|
|
|
|
if (info.pointer && info.pointer != ch)
|
|
|
|
|
{
|
|
|
|
|
fmt::throw_exception("logs::channel repetition: %s", ch->name);
|
|
|
|
|
}
|
|
|
|
|
else if (!info.pointer)
|
|
|
|
|
{
|
|
|
|
|
info.pointer = ch;
|
|
|
|
|
ch->enabled = info.enabled;
|
|
|
|
|
|
|
|
|
|
// Check level again
|
|
|
|
|
if (info.enabled < sev)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get text
|
|
|
|
|
std::string text;
|
|
|
|
|
fmt::raw_append(text, fmt, sup, args);
|
|
|
|
|
std::string prefix = g_tls_log_prefix();
|
2016-07-20 22:00:31 +00:00
|
|
|
|
|
|
|
|
|
// Get first (main) listener
|
|
|
|
|
listener* lis = get_logger();
|
2017-08-20 21:58:25 +00:00
|
|
|
|
|
|
|
|
|
if (!g_init)
|
|
|
|
|
{
|
|
|
|
|
semaphore_lock lock(g_mutex);
|
|
|
|
|
|
|
|
|
|
if (!g_init)
|
|
|
|
|
{
|
|
|
|
|
while (lis)
|
|
|
|
|
{
|
|
|
|
|
lis->log(stamp, *this, prefix, text);
|
|
|
|
|
lis = lis->m_next;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Store message additionally
|
|
|
|
|
g_messages.emplace_back(stored_message{*this, stamp, std::move(prefix), std::move(text)});
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-07-20 22:00:31 +00:00
|
|
|
|
|
|
|
|
|
// Send message to all listeners
|
|
|
|
|
while (lis)
|
|
|
|
|
{
|
2017-02-22 09:52:03 +00:00
|
|
|
|
lis->log(stamp, *this, prefix, text);
|
2016-07-20 22:00:31 +00:00
|
|
|
|
lis = lis->m_next;
|
|
|
|
|
}
|
2014-06-17 15:44:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-02-01 21:55:43 +00:00
|
|
|
|
[[noreturn]] extern void catch_all_exceptions();
|
|
|
|
|
|
2016-05-13 14:01:48 +00:00
|
|
|
|
logs::file_writer::file_writer(const std::string& name)
|
2014-06-17 15:44:03 +00:00
|
|
|
|
{
|
2016-01-12 21:57:16 +00:00
|
|
|
|
try
|
2014-06-17 15:44:03 +00:00
|
|
|
|
{
|
2016-02-01 21:55:43 +00:00
|
|
|
|
if (!m_file.open(fs::get_config_dir() + name, fs::rewrite + fs::append))
|
2014-06-17 15:44:03 +00:00
|
|
|
|
{
|
2016-08-08 16:01:06 +00:00
|
|
|
|
fmt::throw_exception("Can't create log file %s (error %s)", name, fs::g_tls_error);
|
2014-06-17 15:44:03 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2016-02-01 21:55:43 +00:00
|
|
|
|
catch (...)
|
2014-06-17 15:44:03 +00:00
|
|
|
|
{
|
2016-02-01 21:55:43 +00:00
|
|
|
|
catch_all_exceptions();
|
2016-01-12 21:57:16 +00:00
|
|
|
|
}
|
2014-06-17 15:44:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-07-20 22:00:31 +00:00
|
|
|
|
void logs::file_writer::log(const char* text, std::size_t size)
|
2014-06-27 07:22:00 +00:00
|
|
|
|
{
|
2016-07-20 22:00:31 +00:00
|
|
|
|
m_file.write(text, size);
|
2014-06-27 07:22:00 +00:00
|
|
|
|
}
|
2014-06-17 15:44:03 +00:00
|
|
|
|
|
2017-08-20 21:58:25 +00:00
|
|
|
|
logs::file_listener::file_listener(const std::string& name)
|
|
|
|
|
: file_writer(name)
|
|
|
|
|
, listener()
|
|
|
|
|
{
|
|
|
|
|
// Write UTF-8 BOM
|
|
|
|
|
file_writer::log("\xEF\xBB\xBF", 3);
|
|
|
|
|
|
|
|
|
|
// Write initial message
|
|
|
|
|
stored_message ver;
|
|
|
|
|
ver.m.ch = nullptr;
|
|
|
|
|
ver.m.sev = level::always;
|
|
|
|
|
ver.stamp = 0;
|
|
|
|
|
ver.text = fmt::format("RPCS3 v%s\n%s", rpcs3::version.to_string(), utils::get_system_info());
|
|
|
|
|
file_writer::log(ver.text.data(), ver.text.size());
|
|
|
|
|
file_writer::log("\n", 1);
|
|
|
|
|
g_messages.emplace_back(std::move(ver));
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-22 09:52:03 +00:00
|
|
|
|
void logs::file_listener::log(u64 stamp, const logs::message& msg, const std::string& prefix, const std::string& _text)
|
2014-06-17 15:44:03 +00:00
|
|
|
|
{
|
2016-08-05 16:49:45 +00:00
|
|
|
|
std::string text; text.reserve(prefix.size() + _text.size() + 200);
|
2016-01-12 21:57:16 +00:00
|
|
|
|
|
|
|
|
|
// Used character: U+00B7 (Middle Dot)
|
2016-07-20 22:00:31 +00:00
|
|
|
|
switch (msg.sev)
|
2014-06-17 15:44:03 +00:00
|
|
|
|
{
|
2016-07-20 22:00:31 +00:00
|
|
|
|
case level::always: text = u8"·A "; break;
|
|
|
|
|
case level::fatal: text = u8"·F "; break;
|
|
|
|
|
case level::error: text = u8"·E "; break;
|
|
|
|
|
case level::todo: text = u8"·U "; break;
|
|
|
|
|
case level::success: text = u8"·S "; break;
|
|
|
|
|
case level::warning: text = u8"·W "; break;
|
|
|
|
|
case level::notice: text = u8"·! "; break;
|
|
|
|
|
case level::trace: text = u8"·T "; break;
|
2014-06-17 15:44:03 +00:00
|
|
|
|
}
|
2015-04-25 13:29:05 +00:00
|
|
|
|
|
2017-02-22 09:52:03 +00:00
|
|
|
|
// Print miscosecond timestamp
|
|
|
|
|
const u64 hours = stamp / 3600'000'000;
|
|
|
|
|
const u64 mins = (stamp % 3600'000'000) / 60'000'000;
|
|
|
|
|
const u64 secs = (stamp % 60'000'000) / 1'000'000;
|
|
|
|
|
const u64 frac = (stamp % 1'000'000);
|
|
|
|
|
fmt::append(text, "%u:%02u:%02u.%06u ", hours, mins, secs, frac);
|
2015-04-25 13:29:05 +00:00
|
|
|
|
|
2016-08-05 16:49:45 +00:00
|
|
|
|
if (prefix.size() > 0)
|
2016-01-12 21:57:16 +00:00
|
|
|
|
{
|
2016-08-05 16:49:45 +00:00
|
|
|
|
text += "{";
|
|
|
|
|
text += prefix;
|
|
|
|
|
text += "} ";
|
2016-01-12 21:57:16 +00:00
|
|
|
|
}
|
2016-02-01 21:55:43 +00:00
|
|
|
|
|
2017-08-20 21:58:25 +00:00
|
|
|
|
if (msg.ch && '\0' != *msg.ch->name)
|
2016-01-12 21:57:16 +00:00
|
|
|
|
{
|
2016-07-20 22:00:31 +00:00
|
|
|
|
text += msg.ch->name;
|
|
|
|
|
text += msg.sev == level::todo ? " TODO: " : ": ";
|
2016-01-12 21:57:16 +00:00
|
|
|
|
}
|
2016-07-20 22:00:31 +00:00
|
|
|
|
else if (msg.sev == level::todo)
|
2016-01-12 21:57:16 +00:00
|
|
|
|
{
|
2016-07-20 22:00:31 +00:00
|
|
|
|
text += "TODO: ";
|
2016-01-12 21:57:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-08-05 16:49:45 +00:00
|
|
|
|
text += _text;
|
2016-07-20 22:00:31 +00:00
|
|
|
|
text += '\n';
|
2015-01-18 22:54:56 +00:00
|
|
|
|
|
2016-07-20 22:00:31 +00:00
|
|
|
|
file_writer::log(text.data(), text.size());
|
2015-01-18 22:54:56 +00:00
|
|
|
|
}
|