commit
af1cd769e7
@ -0,0 +1,164 @@
|
|||||||
|
// Copyright 2014 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <mutex>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include "common/common.h" // for NonCopyable
|
||||||
|
#include "common/log.h" // for _dbg_assert_
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A MPMC (Multiple-Producer Multiple-Consumer) concurrent ring buffer. This data structure permits
|
||||||
|
* multiple threads to push and pop from a queue of bounded size.
|
||||||
|
*/
|
||||||
|
template <typename T, size_t ArraySize>
|
||||||
|
class ConcurrentRingBuffer : private NonCopyable {
|
||||||
|
public:
|
||||||
|
/// Value returned by the popping functions when the queue has been closed.
|
||||||
|
static const size_t QUEUE_CLOSED = -1;
|
||||||
|
|
||||||
|
ConcurrentRingBuffer() {}
|
||||||
|
|
||||||
|
~ConcurrentRingBuffer() {
|
||||||
|
// If for whatever reason the queue wasn't completely drained, destroy the left over items.
|
||||||
|
for (size_t i = reader_index, end = writer_index; i != end; i = (i + 1) % ArraySize) {
|
||||||
|
Data()[i].~T();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pushes a value to the queue. If the queue is full, this method will block. Does nothing if
|
||||||
|
* the queue is closed.
|
||||||
|
*/
|
||||||
|
void Push(T val) {
|
||||||
|
std::unique_lock<std::mutex> lock(mutex);
|
||||||
|
if (closed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the buffer is full, wait
|
||||||
|
writer.wait(lock, [&]{
|
||||||
|
return (writer_index + 1) % ArraySize != reader_index;
|
||||||
|
});
|
||||||
|
|
||||||
|
T* item = &Data()[writer_index];
|
||||||
|
new (item) T(std::move(val));
|
||||||
|
|
||||||
|
writer_index = (writer_index + 1) % ArraySize;
|
||||||
|
|
||||||
|
// Wake up waiting readers
|
||||||
|
lock.unlock();
|
||||||
|
reader.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pops up to `dest_len` items from the queue, storing them in `dest`. This function will not
|
||||||
|
* block, and might return 0 values if there are no elements in the queue when it is called.
|
||||||
|
*
|
||||||
|
* @return The number of elements stored in `dest`. If the queue has been closed, returns
|
||||||
|
* `QUEUE_CLOSED`.
|
||||||
|
*/
|
||||||
|
size_t Pop(T* dest, size_t dest_len) {
|
||||||
|
std::unique_lock<std::mutex> lock(mutex);
|
||||||
|
if (closed && !CanRead()) {
|
||||||
|
return QUEUE_CLOSED;
|
||||||
|
}
|
||||||
|
return PopInternal(dest, dest_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pops up to `dest_len` items from the queue, storing them in `dest`. This function will block
|
||||||
|
* if there are no elements in the queue when it is called.
|
||||||
|
*
|
||||||
|
* @return The number of elements stored in `dest`. If the queue has been closed, returns
|
||||||
|
* `QUEUE_CLOSED`.
|
||||||
|
*/
|
||||||
|
size_t BlockingPop(T* dest, size_t dest_len) {
|
||||||
|
std::unique_lock<std::mutex> lock(mutex);
|
||||||
|
if (closed && !CanRead()) {
|
||||||
|
return QUEUE_CLOSED;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!CanRead()) {
|
||||||
|
reader.wait(lock);
|
||||||
|
if (closed && !CanRead()) {
|
||||||
|
return QUEUE_CLOSED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_dbg_assert_(Common, CanRead());
|
||||||
|
return PopInternal(dest, dest_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the queue. After calling this method, `Push` operations won't have any effect, and
|
||||||
|
* `PopMany` and `PopManyBlock` will start returning `QUEUE_CLOSED`. This is intended to allow
|
||||||
|
* a graceful shutdown of all consumers.
|
||||||
|
*/
|
||||||
|
void Close() {
|
||||||
|
std::unique_lock<std::mutex> lock(mutex);
|
||||||
|
closed = true;
|
||||||
|
// We need to wake up any reader that are waiting for an item that will never come.
|
||||||
|
lock.unlock();
|
||||||
|
reader.notify_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if `Close()` has been called.
|
||||||
|
bool IsClosed() const {
|
||||||
|
return closed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
size_t PopInternal(T* dest, size_t dest_len) {
|
||||||
|
size_t output_count = 0;
|
||||||
|
while (output_count < dest_len && CanRead()) {
|
||||||
|
_dbg_assert_(Common, CanRead());
|
||||||
|
|
||||||
|
T* item = &Data()[reader_index];
|
||||||
|
T out_val = std::move(*item);
|
||||||
|
item->~T();
|
||||||
|
|
||||||
|
size_t prev_index = (reader_index + ArraySize - 1) % ArraySize;
|
||||||
|
reader_index = (reader_index + 1) % ArraySize;
|
||||||
|
if (writer_index == prev_index) {
|
||||||
|
writer.notify_one();
|
||||||
|
}
|
||||||
|
dest[output_count++] = std::move(out_val);
|
||||||
|
}
|
||||||
|
return output_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CanRead() const {
|
||||||
|
return reader_index != writer_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
T* Data() {
|
||||||
|
return static_cast<T*>(static_cast<void*>(&storage));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Storage for entries
|
||||||
|
typename std::aligned_storage<ArraySize * sizeof(T),
|
||||||
|
std::alignment_of<T>::value>::type storage;
|
||||||
|
|
||||||
|
/// Data is valid in the half-open interval [reader, writer). If they are `QUEUE_CLOSED` then the
|
||||||
|
/// queue has been closed.
|
||||||
|
size_t writer_index = 0, reader_index = 0;
|
||||||
|
// True if the queue has been closed.
|
||||||
|
bool closed = false;
|
||||||
|
|
||||||
|
/// Mutex that protects the entire data structure.
|
||||||
|
std::mutex mutex;
|
||||||
|
/// Signaling wakes up reader which is waiting for storage to be non-empty.
|
||||||
|
std::condition_variable reader;
|
||||||
|
/// Signaling wakes up writer which is waiting for storage to be non-full.
|
||||||
|
std::condition_variable writer;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
@ -1,319 +0,0 @@
|
|||||||
// Copyright 2013 Dolphin Emulator Project
|
|
||||||
// Licensed under GPLv2
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <windows.h>
|
|
||||||
#include <array>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "common/common.h"
|
|
||||||
#include "common/log_manager.h" // Common
|
|
||||||
#include "common/console_listener.h" // Common
|
|
||||||
|
|
||||||
ConsoleListener::ConsoleListener()
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
hConsole = nullptr;
|
|
||||||
bUseColor = true;
|
|
||||||
#else
|
|
||||||
bUseColor = isatty(fileno(stdout));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
ConsoleListener::~ConsoleListener()
|
|
||||||
{
|
|
||||||
Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 100, 100, "Dolphin Log Console"
|
|
||||||
// Open console window - width and height is the size of console window
|
|
||||||
// Name is the window title
|
|
||||||
void ConsoleListener::Open(bool Hidden, int Width, int Height, const char *Title)
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
if (!GetConsoleWindow())
|
|
||||||
{
|
|
||||||
// Open the console window and create the window handle for GetStdHandle()
|
|
||||||
AllocConsole();
|
|
||||||
// Hide
|
|
||||||
if (Hidden) ShowWindow(GetConsoleWindow(), SW_HIDE);
|
|
||||||
// Save the window handle that AllocConsole() created
|
|
||||||
hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
||||||
// Set the console window title
|
|
||||||
SetConsoleTitle(Common::UTF8ToTStr(Title).c_str());
|
|
||||||
// Set letter space
|
|
||||||
LetterSpace(80, 4000);
|
|
||||||
//MoveWindow(GetConsoleWindow(), 200,200, 800,800, true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConsoleListener::UpdateHandle()
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the console window and close the eventual file handle
|
|
||||||
void ConsoleListener::Close()
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
if (hConsole == nullptr)
|
|
||||||
return;
|
|
||||||
FreeConsole();
|
|
||||||
hConsole = nullptr;
|
|
||||||
#else
|
|
||||||
fflush(nullptr);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ConsoleListener::IsOpen()
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
return (hConsole != nullptr);
|
|
||||||
#else
|
|
||||||
return true;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
LetterSpace: SetConsoleScreenBufferSize and SetConsoleWindowInfo are
|
|
||||||
dependent on each other, that's the reason for the additional checks.
|
|
||||||
*/
|
|
||||||
void ConsoleListener::BufferWidthHeight(int BufferWidth, int BufferHeight, int ScreenWidth, int ScreenHeight, bool BufferFirst)
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
BOOL SB, SW;
|
|
||||||
if (BufferFirst)
|
|
||||||
{
|
|
||||||
// Change screen buffer size
|
|
||||||
COORD Co = {BufferWidth, BufferHeight};
|
|
||||||
SB = SetConsoleScreenBufferSize(hConsole, Co);
|
|
||||||
// Change the screen buffer window size
|
|
||||||
SMALL_RECT coo = {0,0,ScreenWidth, ScreenHeight}; // top, left, right, bottom
|
|
||||||
SW = SetConsoleWindowInfo(hConsole, TRUE, &coo);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Change the screen buffer window size
|
|
||||||
SMALL_RECT coo = {0,0, ScreenWidth, ScreenHeight}; // top, left, right, bottom
|
|
||||||
SW = SetConsoleWindowInfo(hConsole, TRUE, &coo);
|
|
||||||
// Change screen buffer size
|
|
||||||
COORD Co = {BufferWidth, BufferHeight};
|
|
||||||
SB = SetConsoleScreenBufferSize(hConsole, Co);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
void ConsoleListener::LetterSpace(int Width, int Height)
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
// Get console info
|
|
||||||
CONSOLE_SCREEN_BUFFER_INFO ConInfo;
|
|
||||||
GetConsoleScreenBufferInfo(hConsole, &ConInfo);
|
|
||||||
|
|
||||||
//
|
|
||||||
int OldBufferWidth = ConInfo.dwSize.X;
|
|
||||||
int OldBufferHeight = ConInfo.dwSize.Y;
|
|
||||||
int OldScreenWidth = (ConInfo.srWindow.Right - ConInfo.srWindow.Left);
|
|
||||||
int OldScreenHeight = (ConInfo.srWindow.Bottom - ConInfo.srWindow.Top);
|
|
||||||
//
|
|
||||||
int NewBufferWidth = Width;
|
|
||||||
int NewBufferHeight = Height;
|
|
||||||
int NewScreenWidth = NewBufferWidth - 1;
|
|
||||||
int NewScreenHeight = OldScreenHeight;
|
|
||||||
|
|
||||||
// Width
|
|
||||||
BufferWidthHeight(NewBufferWidth, OldBufferHeight, NewScreenWidth, OldScreenHeight, (NewBufferWidth > OldScreenWidth-1));
|
|
||||||
// Height
|
|
||||||
BufferWidthHeight(NewBufferWidth, NewBufferHeight, NewScreenWidth, NewScreenHeight, (NewBufferHeight > OldScreenHeight-1));
|
|
||||||
|
|
||||||
// Resize the window too
|
|
||||||
//MoveWindow(GetConsoleWindow(), 200,200, (Width*8 + 50),(NewScreenHeight*12 + 200), true);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#ifdef _WIN32
|
|
||||||
COORD ConsoleListener::GetCoordinates(int BytesRead, int BufferWidth)
|
|
||||||
{
|
|
||||||
COORD Ret = {0, 0};
|
|
||||||
// Full rows
|
|
||||||
int Step = (int)floor((float)BytesRead / (float)BufferWidth);
|
|
||||||
Ret.Y += Step;
|
|
||||||
// Partial row
|
|
||||||
Ret.X = BytesRead - (BufferWidth * Step);
|
|
||||||
return Ret;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
void ConsoleListener::PixelSpace(int Left, int Top, int Width, int Height, bool Resize)
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
// Check size
|
|
||||||
if (Width < 8 || Height < 12) return;
|
|
||||||
|
|
||||||
bool DBef = true;
|
|
||||||
bool DAft = true;
|
|
||||||
std::string SLog = "";
|
|
||||||
|
|
||||||
const HWND hWnd = GetConsoleWindow();
|
|
||||||
const HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
||||||
|
|
||||||
// Get console info
|
|
||||||
CONSOLE_SCREEN_BUFFER_INFO ConInfo;
|
|
||||||
GetConsoleScreenBufferInfo(hConsole, &ConInfo);
|
|
||||||
DWORD BufferSize = ConInfo.dwSize.X * ConInfo.dwSize.Y;
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
// Save the current text
|
|
||||||
// ------------------------
|
|
||||||
DWORD cCharsRead = 0;
|
|
||||||
COORD coordScreen = { 0, 0 };
|
|
||||||
|
|
||||||
static const int MAX_BYTES = 1024 * 16;
|
|
||||||
|
|
||||||
std::vector<std::array<TCHAR, MAX_BYTES>> Str;
|
|
||||||
std::vector<std::array<WORD, MAX_BYTES>> Attr;
|
|
||||||
|
|
||||||
// ReadConsoleOutputAttribute seems to have a limit at this level
|
|
||||||
static const int ReadBufferSize = MAX_BYTES - 32;
|
|
||||||
|
|
||||||
DWORD cAttrRead = ReadBufferSize;
|
|
||||||
DWORD BytesRead = 0;
|
|
||||||
while (BytesRead < BufferSize)
|
|
||||||
{
|
|
||||||
Str.resize(Str.size() + 1);
|
|
||||||
if (!ReadConsoleOutputCharacter(hConsole, Str.back().data(), ReadBufferSize, coordScreen, &cCharsRead))
|
|
||||||
SLog += Common::StringFromFormat("WriteConsoleOutputCharacter error");
|
|
||||||
|
|
||||||
Attr.resize(Attr.size() + 1);
|
|
||||||
if (!ReadConsoleOutputAttribute(hConsole, Attr.back().data(), ReadBufferSize, coordScreen, &cAttrRead))
|
|
||||||
SLog += Common::StringFromFormat("WriteConsoleOutputAttribute error");
|
|
||||||
|
|
||||||
// Break on error
|
|
||||||
if (cAttrRead == 0) break;
|
|
||||||
BytesRead += cAttrRead;
|
|
||||||
coordScreen = GetCoordinates(BytesRead, ConInfo.dwSize.X);
|
|
||||||
}
|
|
||||||
// Letter space
|
|
||||||
int LWidth = (int)(floor((float)Width / 8.0f) - 1.0f);
|
|
||||||
int LHeight = (int)(floor((float)Height / 12.0f) - 1.0f);
|
|
||||||
int LBufWidth = LWidth + 1;
|
|
||||||
int LBufHeight = (int)floor((float)BufferSize / (float)LBufWidth);
|
|
||||||
// Change screen buffer size
|
|
||||||
LetterSpace(LBufWidth, LBufHeight);
|
|
||||||
|
|
||||||
|
|
||||||
ClearScreen(true);
|
|
||||||
coordScreen.Y = 0;
|
|
||||||
coordScreen.X = 0;
|
|
||||||
DWORD cCharsWritten = 0;
|
|
||||||
|
|
||||||
int BytesWritten = 0;
|
|
||||||
DWORD cAttrWritten = 0;
|
|
||||||
for (size_t i = 0; i < Attr.size(); i++)
|
|
||||||
{
|
|
||||||
if (!WriteConsoleOutputCharacter(hConsole, Str[i].data(), ReadBufferSize, coordScreen, &cCharsWritten))
|
|
||||||
SLog += Common::StringFromFormat("WriteConsoleOutputCharacter error");
|
|
||||||
if (!WriteConsoleOutputAttribute(hConsole, Attr[i].data(), ReadBufferSize, coordScreen, &cAttrWritten))
|
|
||||||
SLog += Common::StringFromFormat("WriteConsoleOutputAttribute error");
|
|
||||||
|
|
||||||
BytesWritten += cAttrWritten;
|
|
||||||
coordScreen = GetCoordinates(BytesWritten, LBufWidth);
|
|
||||||
}
|
|
||||||
|
|
||||||
const int OldCursor = ConInfo.dwCursorPosition.Y * ConInfo.dwSize.X + ConInfo.dwCursorPosition.X;
|
|
||||||
COORD Coo = GetCoordinates(OldCursor, LBufWidth);
|
|
||||||
SetConsoleCursorPosition(hConsole, Coo);
|
|
||||||
|
|
||||||
if (SLog.length() > 0) Log(LogTypes::LNOTICE, SLog.c_str());
|
|
||||||
|
|
||||||
// Resize the window too
|
|
||||||
if (Resize) MoveWindow(GetConsoleWindow(), Left,Top, (Width + 100),Height, true);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConsoleListener::Log(LogTypes::LOG_LEVELS Level, const char *Text)
|
|
||||||
{
|
|
||||||
#if defined(_WIN32)
|
|
||||||
WORD Color;
|
|
||||||
|
|
||||||
switch (Level)
|
|
||||||
{
|
|
||||||
case OS_LEVEL: // light yellow
|
|
||||||
Color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY;
|
|
||||||
break;
|
|
||||||
case NOTICE_LEVEL: // light green
|
|
||||||
Color = FOREGROUND_GREEN | FOREGROUND_INTENSITY;
|
|
||||||
break;
|
|
||||||
case ERROR_LEVEL: // light red
|
|
||||||
Color = FOREGROUND_RED | FOREGROUND_INTENSITY;
|
|
||||||
break;
|
|
||||||
case WARNING_LEVEL: // light purple
|
|
||||||
Color = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY;
|
|
||||||
break;
|
|
||||||
case INFO_LEVEL: // cyan
|
|
||||||
Color = FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY;
|
|
||||||
break;
|
|
||||||
case DEBUG_LEVEL: // gray
|
|
||||||
Color = FOREGROUND_INTENSITY;
|
|
||||||
break;
|
|
||||||
default: // off-white
|
|
||||||
Color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
SetConsoleTextAttribute(hConsole, Color);
|
|
||||||
printf(Text);
|
|
||||||
#else
|
|
||||||
char ColorAttr[16] = "";
|
|
||||||
char ResetAttr[16] = "";
|
|
||||||
|
|
||||||
if (bUseColor)
|
|
||||||
{
|
|
||||||
strcpy(ResetAttr, "\033[0m");
|
|
||||||
switch (Level)
|
|
||||||
{
|
|
||||||
case NOTICE_LEVEL: // light green
|
|
||||||
strcpy(ColorAttr, "\033[92m");
|
|
||||||
break;
|
|
||||||
case ERROR_LEVEL: // light red
|
|
||||||
strcpy(ColorAttr, "\033[91m");
|
|
||||||
break;
|
|
||||||
case WARNING_LEVEL: // light yellow
|
|
||||||
strcpy(ColorAttr, "\033[93m");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fprintf(stderr, "%s%s%s", ColorAttr, Text, ResetAttr);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
// Clear console screen
|
|
||||||
void ConsoleListener::ClearScreen(bool Cursor)
|
|
||||||
{
|
|
||||||
#if defined(_WIN32)
|
|
||||||
COORD coordScreen = { 0, 0 };
|
|
||||||
DWORD cCharsWritten;
|
|
||||||
CONSOLE_SCREEN_BUFFER_INFO csbi;
|
|
||||||
DWORD dwConSize;
|
|
||||||
|
|
||||||
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
||||||
|
|
||||||
GetConsoleScreenBufferInfo(hConsole, &csbi);
|
|
||||||
dwConSize = csbi.dwSize.X * csbi.dwSize.Y;
|
|
||||||
// Write space to the entire console
|
|
||||||
FillConsoleOutputCharacter(hConsole, TEXT(' '), dwConSize, coordScreen, &cCharsWritten);
|
|
||||||
GetConsoleScreenBufferInfo(hConsole, &csbi);
|
|
||||||
FillConsoleOutputAttribute(hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten);
|
|
||||||
// Reset cursor
|
|
||||||
if (Cursor) SetConsoleCursorPosition(hConsole, coordScreen);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
|||||||
// Copyright 2013 Dolphin Emulator Project
|
|
||||||
// Licensed under GPLv2
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "common/log_manager.h"
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <windows.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class ConsoleListener : public LogListener
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ConsoleListener();
|
|
||||||
~ConsoleListener();
|
|
||||||
|
|
||||||
void Open(bool Hidden = false, int Width = 100, int Height = 100, const char * Name = "Console");
|
|
||||||
void UpdateHandle();
|
|
||||||
void Close();
|
|
||||||
bool IsOpen();
|
|
||||||
void LetterSpace(int Width, int Height);
|
|
||||||
void BufferWidthHeight(int BufferWidth, int BufferHeight, int ScreenWidth, int ScreenHeight, bool BufferFirst);
|
|
||||||
void PixelSpace(int Left, int Top, int Width, int Height, bool);
|
|
||||||
#ifdef _WIN32
|
|
||||||
COORD GetCoordinates(int BytesRead, int BufferWidth);
|
|
||||||
#endif
|
|
||||||
void Log(LogTypes::LOG_LEVELS, const char *Text) override;
|
|
||||||
void ClearScreen(bool Cursor = true);
|
|
||||||
|
|
||||||
private:
|
|
||||||
#ifdef _WIN32
|
|
||||||
HWND GetHwnd(void);
|
|
||||||
HANDLE hConsole;
|
|
||||||
#endif
|
|
||||||
bool bUseColor;
|
|
||||||
};
|
|
@ -1,198 +0,0 @@
|
|||||||
// Copyright 2013 Dolphin Emulator Project
|
|
||||||
// Licensed under GPLv2
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
#include "common/log_manager.h"
|
|
||||||
#include "common/console_listener.h"
|
|
||||||
#include "common/timer.h"
|
|
||||||
|
|
||||||
void GenericLog(LogTypes::LOG_LEVELS level, LogTypes::LOG_TYPE type, const char* file, int line,
|
|
||||||
const char* function, const char* fmt, ...)
|
|
||||||
{
|
|
||||||
va_list args;
|
|
||||||
va_start(args, fmt);
|
|
||||||
|
|
||||||
if (LogManager::GetInstance()) {
|
|
||||||
LogManager::GetInstance()->Log(level, type,
|
|
||||||
file, line, function, fmt, args);
|
|
||||||
}
|
|
||||||
va_end(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
LogManager *LogManager::m_logManager = nullptr;
|
|
||||||
|
|
||||||
LogManager::LogManager()
|
|
||||||
{
|
|
||||||
// create log files
|
|
||||||
m_Log[LogTypes::MASTER_LOG] = new LogContainer("*", "Master Log");
|
|
||||||
m_Log[LogTypes::BOOT] = new LogContainer("BOOT", "Boot");
|
|
||||||
m_Log[LogTypes::COMMON] = new LogContainer("COMMON", "Common");
|
|
||||||
m_Log[LogTypes::CONFIG] = new LogContainer("CONFIG", "Configuration");
|
|
||||||
m_Log[LogTypes::DISCIO] = new LogContainer("DIO", "Disc IO");
|
|
||||||
m_Log[LogTypes::FILEMON] = new LogContainer("FileMon", "File Monitor");
|
|
||||||
m_Log[LogTypes::PAD] = new LogContainer("PAD", "Pad");
|
|
||||||
m_Log[LogTypes::PIXELENGINE] = new LogContainer("PE", "PixelEngine");
|
|
||||||
m_Log[LogTypes::COMMANDPROCESSOR] = new LogContainer("CP", "CommandProc");
|
|
||||||
m_Log[LogTypes::VIDEOINTERFACE] = new LogContainer("VI", "VideoInt");
|
|
||||||
m_Log[LogTypes::SERIALINTERFACE] = new LogContainer("SI", "SerialInt");
|
|
||||||
m_Log[LogTypes::PROCESSORINTERFACE] = new LogContainer("PI", "ProcessorInt");
|
|
||||||
m_Log[LogTypes::MEMMAP] = new LogContainer("MI", "MI & memmap");
|
|
||||||
m_Log[LogTypes::SP1] = new LogContainer("SP1", "Serial Port 1");
|
|
||||||
m_Log[LogTypes::STREAMINGINTERFACE] = new LogContainer("Stream", "StreamingInt");
|
|
||||||
m_Log[LogTypes::DSPINTERFACE] = new LogContainer("DSP", "DSPInterface");
|
|
||||||
m_Log[LogTypes::DVDINTERFACE] = new LogContainer("DVD", "DVDInterface");
|
|
||||||
m_Log[LogTypes::GSP] = new LogContainer("GSP", "GSP");
|
|
||||||
m_Log[LogTypes::EXPANSIONINTERFACE] = new LogContainer("EXI", "ExpansionInt");
|
|
||||||
m_Log[LogTypes::GDB_STUB] = new LogContainer("GDB_STUB", "GDB Stub");
|
|
||||||
m_Log[LogTypes::AUDIO_INTERFACE] = new LogContainer("AI", "AudioInt");
|
|
||||||
m_Log[LogTypes::ARM11] = new LogContainer("ARM11", "ARM11");
|
|
||||||
m_Log[LogTypes::OSHLE] = new LogContainer("HLE", "HLE");
|
|
||||||
m_Log[LogTypes::DSPHLE] = new LogContainer("DSPHLE", "DSP HLE");
|
|
||||||
m_Log[LogTypes::DSPLLE] = new LogContainer("DSPLLE", "DSP LLE");
|
|
||||||
m_Log[LogTypes::DSP_MAIL] = new LogContainer("DSPMails", "DSP Mails");
|
|
||||||
m_Log[LogTypes::VIDEO] = new LogContainer("Video", "Video Backend");
|
|
||||||
m_Log[LogTypes::AUDIO] = new LogContainer("Audio", "Audio Emulator");
|
|
||||||
m_Log[LogTypes::DYNA_REC] = new LogContainer("JIT", "JIT");
|
|
||||||
m_Log[LogTypes::CONSOLE] = new LogContainer("CONSOLE", "Dolphin Console");
|
|
||||||
m_Log[LogTypes::OSREPORT] = new LogContainer("OSREPORT", "OSReport");
|
|
||||||
m_Log[LogTypes::TIME] = new LogContainer("Time", "Core Timing");
|
|
||||||
m_Log[LogTypes::LOADER] = new LogContainer("Loader", "Loader");
|
|
||||||
m_Log[LogTypes::FILESYS] = new LogContainer("FileSys", "File System");
|
|
||||||
m_Log[LogTypes::WII_IPC_HID] = new LogContainer("WII_IPC_HID", "WII IPC HID");
|
|
||||||
m_Log[LogTypes::KERNEL] = new LogContainer("KERNEL", "KERNEL HLE");
|
|
||||||
m_Log[LogTypes::WII_IPC_DVD] = new LogContainer("WII_IPC_DVD", "WII IPC DVD");
|
|
||||||
m_Log[LogTypes::WII_IPC_ES] = new LogContainer("WII_IPC_ES", "WII IPC ES");
|
|
||||||
m_Log[LogTypes::WII_IPC_FILEIO] = new LogContainer("WII_IPC_FILEIO", "WII IPC FILEIO");
|
|
||||||
m_Log[LogTypes::RENDER] = new LogContainer("RENDER", "RENDER");
|
|
||||||
m_Log[LogTypes::GPU] = new LogContainer("GPU", "GPU");
|
|
||||||
m_Log[LogTypes::SVC] = new LogContainer("SVC", "Supervisor Call HLE");
|
|
||||||
m_Log[LogTypes::HLE] = new LogContainer("HLE", "High Level Emulation");
|
|
||||||
m_Log[LogTypes::HW] = new LogContainer("HW", "Hardware");
|
|
||||||
m_Log[LogTypes::ACTIONREPLAY] = new LogContainer("ActionReplay", "ActionReplay");
|
|
||||||
m_Log[LogTypes::MEMCARD_MANAGER] = new LogContainer("MemCard Manager", "MemCard Manager");
|
|
||||||
m_Log[LogTypes::NETPLAY] = new LogContainer("NETPLAY", "Netplay");
|
|
||||||
m_Log[LogTypes::GUI] = new LogContainer("GUI", "GUI");
|
|
||||||
|
|
||||||
m_fileLog = new FileLogListener(FileUtil::GetUserPath(F_MAINLOG_IDX).c_str());
|
|
||||||
m_consoleLog = new ConsoleListener();
|
|
||||||
m_debuggerLog = new DebuggerLogListener();
|
|
||||||
|
|
||||||
for (int i = 0; i < LogTypes::NUMBER_OF_LOGS; ++i)
|
|
||||||
{
|
|
||||||
m_Log[i]->SetEnable(true);
|
|
||||||
m_Log[i]->AddListener(m_fileLog);
|
|
||||||
m_Log[i]->AddListener(m_consoleLog);
|
|
||||||
#ifdef _MSC_VER
|
|
||||||
if (IsDebuggerPresent())
|
|
||||||
m_Log[i]->AddListener(m_debuggerLog);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
m_consoleLog->Open();
|
|
||||||
}
|
|
||||||
|
|
||||||
LogManager::~LogManager()
|
|
||||||
{
|
|
||||||
for (int i = 0; i < LogTypes::NUMBER_OF_LOGS; ++i)
|
|
||||||
{
|
|
||||||
m_logManager->RemoveListener((LogTypes::LOG_TYPE)i, m_fileLog);
|
|
||||||
m_logManager->RemoveListener((LogTypes::LOG_TYPE)i, m_consoleLog);
|
|
||||||
m_logManager->RemoveListener((LogTypes::LOG_TYPE)i, m_debuggerLog);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < LogTypes::NUMBER_OF_LOGS; ++i)
|
|
||||||
delete m_Log[i];
|
|
||||||
|
|
||||||
delete m_fileLog;
|
|
||||||
delete m_consoleLog;
|
|
||||||
delete m_debuggerLog;
|
|
||||||
}
|
|
||||||
|
|
||||||
void LogManager::Log(LogTypes::LOG_LEVELS level, LogTypes::LOG_TYPE type, const char* file,
|
|
||||||
int line, const char* function, const char *fmt, va_list args)
|
|
||||||
{
|
|
||||||
char temp[MAX_MSGLEN];
|
|
||||||
char msg[MAX_MSGLEN * 2];
|
|
||||||
LogContainer *log = m_Log[type];
|
|
||||||
|
|
||||||
if (!log->IsEnabled() || level > log->GetLevel() || ! log->HasListeners())
|
|
||||||
return;
|
|
||||||
|
|
||||||
Common::CharArrayFromFormatV(temp, MAX_MSGLEN, fmt, args);
|
|
||||||
|
|
||||||
static const char level_to_char[7] = "ONEWID";
|
|
||||||
sprintf(msg, "%s %s:%u %c[%s] %s: %s\n", Common::Timer::GetTimeFormatted().c_str(), file, line,
|
|
||||||
level_to_char[(int)level], log->GetShortName(), function, temp);
|
|
||||||
|
|
||||||
#ifdef ANDROID
|
|
||||||
Host_SysMessage(msg);
|
|
||||||
#endif
|
|
||||||
log->Trigger(level, msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
void LogManager::Init()
|
|
||||||
{
|
|
||||||
m_logManager = new LogManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
void LogManager::Shutdown()
|
|
||||||
{
|
|
||||||
delete m_logManager;
|
|
||||||
m_logManager = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
LogContainer::LogContainer(const char* shortName, const char* fullName, bool enable)
|
|
||||||
: m_enable(enable)
|
|
||||||
{
|
|
||||||
strncpy(m_fullName, fullName, 128);
|
|
||||||
strncpy(m_shortName, shortName, 32);
|
|
||||||
m_level = LogTypes::MAX_LOGLEVEL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// LogContainer
|
|
||||||
void LogContainer::AddListener(LogListener *listener)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lk(m_listeners_lock);
|
|
||||||
m_listeners.insert(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
void LogContainer::RemoveListener(LogListener *listener)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lk(m_listeners_lock);
|
|
||||||
m_listeners.erase(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
void LogContainer::Trigger(LogTypes::LOG_LEVELS level, const char *msg)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lk(m_listeners_lock);
|
|
||||||
|
|
||||||
std::set<LogListener*>::const_iterator i;
|
|
||||||
for (i = m_listeners.begin(); i != m_listeners.end(); ++i)
|
|
||||||
{
|
|
||||||
(*i)->Log(level, msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FileLogListener::FileLogListener(const char *filename)
|
|
||||||
{
|
|
||||||
OpenFStream(m_logfile, filename, std::ios::app);
|
|
||||||
SetEnable(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void FileLogListener::Log(LogTypes::LOG_LEVELS, const char *msg)
|
|
||||||
{
|
|
||||||
if (!IsEnabled() || !IsValid())
|
|
||||||
return;
|
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lk(m_log_lock);
|
|
||||||
m_logfile << msg << std::flush;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DebuggerLogListener::Log(LogTypes::LOG_LEVELS, const char *msg)
|
|
||||||
{
|
|
||||||
#if _MSC_VER
|
|
||||||
::OutputDebugStringA(msg);
|
|
||||||
#endif
|
|
||||||
}
|
|
@ -1,166 +0,0 @@
|
|||||||
// Copyright 2013 Dolphin Emulator Project
|
|
||||||
// Licensed under GPLv2
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "common/log.h"
|
|
||||||
#include "common/string_util.h"
|
|
||||||
#include "common/file_util.h"
|
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
#include <set>
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
#define MAX_MESSAGES 8000
|
|
||||||
#define MAX_MSGLEN 1024
|
|
||||||
|
|
||||||
|
|
||||||
// pure virtual interface
|
|
||||||
class LogListener
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual ~LogListener() {}
|
|
||||||
|
|
||||||
virtual void Log(LogTypes::LOG_LEVELS, const char *msg) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class FileLogListener : public LogListener
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
FileLogListener(const char *filename);
|
|
||||||
|
|
||||||
void Log(LogTypes::LOG_LEVELS, const char *msg) override;
|
|
||||||
|
|
||||||
bool IsValid() { return !m_logfile.fail(); }
|
|
||||||
bool IsEnabled() const { return m_enable; }
|
|
||||||
void SetEnable(bool enable) { m_enable = enable; }
|
|
||||||
|
|
||||||
const char* GetName() const { return "file"; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::mutex m_log_lock;
|
|
||||||
std::ofstream m_logfile;
|
|
||||||
bool m_enable;
|
|
||||||
};
|
|
||||||
|
|
||||||
class DebuggerLogListener : public LogListener
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
void Log(LogTypes::LOG_LEVELS, const char *msg) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class LogContainer
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
LogContainer(const char* shortName, const char* fullName, bool enable = false);
|
|
||||||
|
|
||||||
const char* GetShortName() const { return m_shortName; }
|
|
||||||
const char* GetFullName() const { return m_fullName; }
|
|
||||||
|
|
||||||
void AddListener(LogListener* listener);
|
|
||||||
void RemoveListener(LogListener* listener);
|
|
||||||
|
|
||||||
void Trigger(LogTypes::LOG_LEVELS, const char *msg);
|
|
||||||
|
|
||||||
bool IsEnabled() const { return m_enable; }
|
|
||||||
void SetEnable(bool enable) { m_enable = enable; }
|
|
||||||
|
|
||||||
LogTypes::LOG_LEVELS GetLevel() const { return m_level; }
|
|
||||||
|
|
||||||
void SetLevel(LogTypes::LOG_LEVELS level) { m_level = level; }
|
|
||||||
|
|
||||||
bool HasListeners() const { return !m_listeners.empty(); }
|
|
||||||
|
|
||||||
private:
|
|
||||||
char m_fullName[128];
|
|
||||||
char m_shortName[32];
|
|
||||||
bool m_enable;
|
|
||||||
LogTypes::LOG_LEVELS m_level;
|
|
||||||
std::mutex m_listeners_lock;
|
|
||||||
std::set<LogListener*> m_listeners;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ConsoleListener;
|
|
||||||
|
|
||||||
class LogManager : NonCopyable
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
LogContainer* m_Log[LogTypes::NUMBER_OF_LOGS];
|
|
||||||
FileLogListener *m_fileLog;
|
|
||||||
ConsoleListener *m_consoleLog;
|
|
||||||
DebuggerLogListener *m_debuggerLog;
|
|
||||||
static LogManager *m_logManager; // Singleton. Ugh.
|
|
||||||
|
|
||||||
LogManager();
|
|
||||||
~LogManager();
|
|
||||||
public:
|
|
||||||
|
|
||||||
static u32 GetMaxLevel() { return LogTypes::MAX_LOGLEVEL; }
|
|
||||||
|
|
||||||
void Log(LogTypes::LOG_LEVELS level, LogTypes::LOG_TYPE type, const char* file, int line,
|
|
||||||
const char* function, const char *fmt, va_list args);
|
|
||||||
|
|
||||||
void SetLogLevel(LogTypes::LOG_TYPE type, LogTypes::LOG_LEVELS level)
|
|
||||||
{
|
|
||||||
m_Log[type]->SetLevel(level);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetEnable(LogTypes::LOG_TYPE type, bool enable)
|
|
||||||
{
|
|
||||||
m_Log[type]->SetEnable(enable);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsEnabled(LogTypes::LOG_TYPE type) const
|
|
||||||
{
|
|
||||||
return m_Log[type]->IsEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* GetShortName(LogTypes::LOG_TYPE type) const
|
|
||||||
{
|
|
||||||
return m_Log[type]->GetShortName();
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* GetFullName(LogTypes::LOG_TYPE type) const
|
|
||||||
{
|
|
||||||
return m_Log[type]->GetFullName();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddListener(LogTypes::LOG_TYPE type, LogListener *listener)
|
|
||||||
{
|
|
||||||
m_Log[type]->AddListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RemoveListener(LogTypes::LOG_TYPE type, LogListener *listener)
|
|
||||||
{
|
|
||||||
m_Log[type]->RemoveListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
FileLogListener *GetFileListener() const
|
|
||||||
{
|
|
||||||
return m_fileLog;
|
|
||||||
}
|
|
||||||
|
|
||||||
ConsoleListener *GetConsoleListener() const
|
|
||||||
{
|
|
||||||
return m_consoleLog;
|
|
||||||
}
|
|
||||||
|
|
||||||
DebuggerLogListener *GetDebuggerListener() const
|
|
||||||
{
|
|
||||||
return m_debuggerLog;
|
|
||||||
}
|
|
||||||
|
|
||||||
static LogManager* GetInstance()
|
|
||||||
{
|
|
||||||
return m_logManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void SetInstance(LogManager *logManager)
|
|
||||||
{
|
|
||||||
m_logManager = logManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void Init();
|
|
||||||
static void Shutdown();
|
|
||||||
};
|
|
@ -0,0 +1,151 @@
|
|||||||
|
// Copyright 2014 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "common/log.h" // For _dbg_assert_
|
||||||
|
|
||||||
|
#include "common/logging/backend.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "common/logging/text_formatter.h"
|
||||||
|
|
||||||
|
namespace Log {
|
||||||
|
|
||||||
|
static std::shared_ptr<Logger> global_logger;
|
||||||
|
|
||||||
|
/// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this.
|
||||||
|
#define ALL_LOG_CLASSES() \
|
||||||
|
CLS(Log) \
|
||||||
|
CLS(Common) \
|
||||||
|
SUB(Common, Filesystem) \
|
||||||
|
SUB(Common, Memory) \
|
||||||
|
CLS(Core) \
|
||||||
|
SUB(Core, ARM11) \
|
||||||
|
CLS(Config) \
|
||||||
|
CLS(Debug) \
|
||||||
|
SUB(Debug, Emulated) \
|
||||||
|
SUB(Debug, GPU) \
|
||||||
|
SUB(Debug, Breakpoint) \
|
||||||
|
CLS(Kernel) \
|
||||||
|
SUB(Kernel, SVC) \
|
||||||
|
CLS(Service) \
|
||||||
|
SUB(Service, SRV) \
|
||||||
|
SUB(Service, FS) \
|
||||||
|
SUB(Service, APT) \
|
||||||
|
SUB(Service, GSP) \
|
||||||
|
SUB(Service, AC) \
|
||||||
|
SUB(Service, PTM) \
|
||||||
|
SUB(Service, CFG) \
|
||||||
|
SUB(Service, DSP) \
|
||||||
|
SUB(Service, HID) \
|
||||||
|
CLS(HW) \
|
||||||
|
SUB(HW, Memory) \
|
||||||
|
SUB(HW, GPU) \
|
||||||
|
CLS(Frontend) \
|
||||||
|
CLS(Render) \
|
||||||
|
SUB(Render, Software) \
|
||||||
|
SUB(Render, OpenGL) \
|
||||||
|
CLS(Loader)
|
||||||
|
|
||||||
|
Logger::Logger() {
|
||||||
|
// Register logging classes so that they can be queried at runtime
|
||||||
|
size_t parent_class;
|
||||||
|
all_classes.reserve((size_t)Class::Count);
|
||||||
|
|
||||||
|
#define CLS(x) \
|
||||||
|
all_classes.push_back(Class::x); \
|
||||||
|
parent_class = all_classes.size() - 1;
|
||||||
|
#define SUB(x, y) \
|
||||||
|
all_classes.push_back(Class::x##_##y); \
|
||||||
|
all_classes[parent_class].num_children += 1;
|
||||||
|
|
||||||
|
ALL_LOG_CLASSES()
|
||||||
|
#undef CLS
|
||||||
|
#undef SUB
|
||||||
|
|
||||||
|
// Ensures that ALL_LOG_CLASSES isn't missing any entries.
|
||||||
|
_dbg_assert_(Log, all_classes.size() == (size_t)Class::Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetClassName is a macro defined by Windows.h, grrr...
|
||||||
|
const char* Logger::GetLogClassName(Class log_class) {
|
||||||
|
switch (log_class) {
|
||||||
|
#define CLS(x) case Class::x: return #x;
|
||||||
|
#define SUB(x, y) case Class::x##_##y: return #x "." #y;
|
||||||
|
ALL_LOG_CLASSES()
|
||||||
|
#undef CLS
|
||||||
|
#undef SUB
|
||||||
|
}
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* Logger::GetLevelName(Level log_level) {
|
||||||
|
#define LVL(x) case Level::x: return #x
|
||||||
|
switch (log_level) {
|
||||||
|
LVL(Trace);
|
||||||
|
LVL(Debug);
|
||||||
|
LVL(Info);
|
||||||
|
LVL(Warning);
|
||||||
|
LVL(Error);
|
||||||
|
LVL(Critical);
|
||||||
|
}
|
||||||
|
return "Unknown";
|
||||||
|
#undef LVL
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::LogMessage(Entry entry) {
|
||||||
|
ring_buffer.Push(std::move(entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Logger::GetEntries(Entry* out_buffer, size_t buffer_len) {
|
||||||
|
return ring_buffer.BlockingPop(out_buffer, buffer_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Logger> InitGlobalLogger() {
|
||||||
|
global_logger = std::make_shared<Logger>();
|
||||||
|
return global_logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
Entry CreateEntry(Class log_class, Level log_level,
|
||||||
|
const char* filename, unsigned int line_nr, const char* function,
|
||||||
|
const char* format, va_list args) {
|
||||||
|
using std::chrono::steady_clock;
|
||||||
|
using std::chrono::duration_cast;
|
||||||
|
|
||||||
|
static steady_clock::time_point time_origin = steady_clock::now();
|
||||||
|
|
||||||
|
std::array<char, 4 * 1024> formatting_buffer;
|
||||||
|
|
||||||
|
Entry entry;
|
||||||
|
entry.timestamp = duration_cast<std::chrono::microseconds>(steady_clock::now() - time_origin);
|
||||||
|
entry.log_class = log_class;
|
||||||
|
entry.log_level = log_level;
|
||||||
|
|
||||||
|
snprintf(formatting_buffer.data(), formatting_buffer.size(), "%s:%s:%u", filename, function, line_nr);
|
||||||
|
entry.location = std::string(formatting_buffer.data());
|
||||||
|
|
||||||
|
vsnprintf(formatting_buffer.data(), formatting_buffer.size(), format, args);
|
||||||
|
entry.message = std::string(formatting_buffer.data());
|
||||||
|
|
||||||
|
return std::move(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogMessage(Class log_class, Level log_level,
|
||||||
|
const char* filename, unsigned int line_nr, const char* function,
|
||||||
|
const char* format, ...) {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, format);
|
||||||
|
Entry entry = CreateEntry(log_class, log_level,
|
||||||
|
filename, line_nr, function, format, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
if (global_logger != nullptr && !global_logger->IsClosed()) {
|
||||||
|
global_logger->LogMessage(std::move(entry));
|
||||||
|
} else {
|
||||||
|
// Fall back to directly printing to stderr
|
||||||
|
PrintMessage(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,134 @@
|
|||||||
|
// Copyright 2014 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdarg>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "common/concurrent_ring_buffer.h"
|
||||||
|
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
|
||||||
|
namespace Log {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A log entry. Log entries are store in a structured format to permit more varied output
|
||||||
|
* formatting on different frontends, as well as facilitating filtering and aggregation.
|
||||||
|
*/
|
||||||
|
struct Entry {
|
||||||
|
std::chrono::microseconds timestamp;
|
||||||
|
Class log_class;
|
||||||
|
Level log_level;
|
||||||
|
std::string location;
|
||||||
|
std::string message;
|
||||||
|
|
||||||
|
Entry() = default;
|
||||||
|
|
||||||
|
// TODO(yuriks) Use defaulted move constructors once MSVC supports them
|
||||||
|
#define MOVE(member) member(std::move(o.member))
|
||||||
|
Entry(Entry&& o)
|
||||||
|
: MOVE(timestamp), MOVE(log_class), MOVE(log_level),
|
||||||
|
MOVE(location), MOVE(message)
|
||||||
|
{}
|
||||||
|
#undef MOVE
|
||||||
|
|
||||||
|
Entry& operator=(const Entry&& o) {
|
||||||
|
#define MOVE(member) member = std::move(o.member)
|
||||||
|
MOVE(timestamp);
|
||||||
|
MOVE(log_class);
|
||||||
|
MOVE(log_level);
|
||||||
|
MOVE(location);
|
||||||
|
MOVE(message);
|
||||||
|
#undef MOVE
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ClassInfo {
|
||||||
|
Class log_class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Total number of (direct or indirect) sub classes this class has. If any, they follow in
|
||||||
|
* sequence after this class in the class list.
|
||||||
|
*/
|
||||||
|
unsigned int num_children = 0;
|
||||||
|
|
||||||
|
ClassInfo(Class log_class) : log_class(log_class) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logging management class. This class has the dual purpose of acting as an exchange point between
|
||||||
|
* the logging clients and the log outputter, as well as containing reflection info about available
|
||||||
|
* log classes.
|
||||||
|
*/
|
||||||
|
class Logger {
|
||||||
|
private:
|
||||||
|
using Buffer = Common::ConcurrentRingBuffer<Entry, 16 * 1024 / sizeof(Entry)>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static const size_t QUEUE_CLOSED = Buffer::QUEUE_CLOSED;
|
||||||
|
|
||||||
|
Logger();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of all vector classes and subclasses. The sequence returned is a pre-order of
|
||||||
|
* classes and subclasses, which together with the `num_children` field in ClassInfo, allows
|
||||||
|
* you to recover the hierarchy.
|
||||||
|
*/
|
||||||
|
const std::vector<ClassInfo>& GetClasses() const { return all_classes; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the passed log class as a C-string. Subclasses are separated by periods
|
||||||
|
* instead of underscores as in the enumeration.
|
||||||
|
*/
|
||||||
|
static const char* GetLogClassName(Class log_class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the passed log level as a C-string.
|
||||||
|
*/
|
||||||
|
static const char* GetLevelName(Level log_level);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends a messages to the log buffer.
|
||||||
|
* @note This function is thread safe.
|
||||||
|
*/
|
||||||
|
void LogMessage(Entry entry);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a batch of messages from the log buffer, blocking until they are available.
|
||||||
|
* @note This function is thread safe.
|
||||||
|
*
|
||||||
|
* @param out_buffer Destination buffer that will receive the log entries.
|
||||||
|
* @param buffer_len The maximum size of `out_buffer`.
|
||||||
|
* @return The number of entries stored. In case the logger is shutting down, `QUEUE_CLOSED` is
|
||||||
|
* returned, no entries are stored and the logger should shutdown.
|
||||||
|
*/
|
||||||
|
size_t GetEntries(Entry* out_buffer, size_t buffer_len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiates a shutdown of the logger. This will indicate to log output clients that they
|
||||||
|
* should shutdown.
|
||||||
|
*/
|
||||||
|
void Close() { ring_buffer.Close(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if Close() has already been called on the Logger.
|
||||||
|
*/
|
||||||
|
bool IsClosed() const { return ring_buffer.IsClosed(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Buffer ring_buffer;
|
||||||
|
std::vector<ClassInfo> all_classes;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Creates a log entry by formatting the given source location, and message.
|
||||||
|
Entry CreateEntry(Class log_class, Level log_level,
|
||||||
|
const char* filename, unsigned int line_nr, const char* function,
|
||||||
|
const char* format, va_list args);
|
||||||
|
/// Initializes the default Logger.
|
||||||
|
std::shared_ptr<Logger> InitGlobalLogger();
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,132 @@
|
|||||||
|
// Copyright 2014 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "common/logging/filter.h"
|
||||||
|
#include "common/logging/backend.h"
|
||||||
|
#include "common/string_util.h"
|
||||||
|
|
||||||
|
namespace Log {
|
||||||
|
|
||||||
|
Filter::Filter(Level default_level) {
|
||||||
|
ResetAll(default_level);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Filter::ResetAll(Level level) {
|
||||||
|
class_levels.fill(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Filter::SetClassLevel(Class log_class, Level level) {
|
||||||
|
class_levels[static_cast<size_t>(log_class)] = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Filter::SetSubclassesLevel(const ClassInfo& log_class, Level level) {
|
||||||
|
const size_t log_class_i = static_cast<size_t>(log_class.log_class);
|
||||||
|
|
||||||
|
const size_t begin = log_class_i + 1;
|
||||||
|
const size_t end = begin + log_class.num_children;
|
||||||
|
for (size_t i = begin; begin < end; ++i) {
|
||||||
|
class_levels[i] = level;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Filter::ParseFilterString(const std::string& filter_str) {
|
||||||
|
auto clause_begin = filter_str.cbegin();
|
||||||
|
while (clause_begin != filter_str.cend()) {
|
||||||
|
auto clause_end = std::find(clause_begin, filter_str.cend(), ' ');
|
||||||
|
|
||||||
|
// If clause isn't empty
|
||||||
|
if (clause_end != clause_begin) {
|
||||||
|
ParseFilterRule(clause_begin, clause_end);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clause_end != filter_str.cend()) {
|
||||||
|
// Skip over the whitespace
|
||||||
|
++clause_end;
|
||||||
|
}
|
||||||
|
clause_begin = clause_end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename It>
|
||||||
|
static Level GetLevelByName(const It begin, const It end) {
|
||||||
|
for (u8 i = 0; i < static_cast<u8>(Level::Count); ++i) {
|
||||||
|
const char* level_name = Logger::GetLevelName(static_cast<Level>(i));
|
||||||
|
if (Common::ComparePartialString(begin, end, level_name)) {
|
||||||
|
return static_cast<Level>(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Level::Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename It>
|
||||||
|
static Class GetClassByName(const It begin, const It end) {
|
||||||
|
for (ClassType i = 0; i < static_cast<ClassType>(Class::Count); ++i) {
|
||||||
|
const char* level_name = Logger::GetLogClassName(static_cast<Class>(i));
|
||||||
|
if (Common::ComparePartialString(begin, end, level_name)) {
|
||||||
|
return static_cast<Class>(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Class::Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename InputIt, typename T>
|
||||||
|
static InputIt find_last(InputIt begin, const InputIt end, const T& value) {
|
||||||
|
auto match = end;
|
||||||
|
while (begin != end) {
|
||||||
|
auto new_match = std::find(begin, end, value);
|
||||||
|
if (new_match != end) {
|
||||||
|
match = new_match;
|
||||||
|
++new_match;
|
||||||
|
}
|
||||||
|
begin = new_match;
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Filter::ParseFilterRule(const std::string::const_iterator begin,
|
||||||
|
const std::string::const_iterator end) {
|
||||||
|
auto level_separator = std::find(begin, end, ':');
|
||||||
|
if (level_separator == end) {
|
||||||
|
LOG_ERROR(Log, "Invalid log filter. Must specify a log level after `:`: %s",
|
||||||
|
std::string(begin, end).c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Level level = GetLevelByName(level_separator + 1, end);
|
||||||
|
if (level == Level::Count) {
|
||||||
|
LOG_ERROR(Log, "Unknown log level in filter: %s", std::string(begin, end).c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Common::ComparePartialString(begin, level_separator, "*")) {
|
||||||
|
ResetAll(level);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto class_name_end = find_last(begin, level_separator, '.');
|
||||||
|
if (class_name_end != level_separator &&
|
||||||
|
!Common::ComparePartialString(class_name_end + 1, level_separator, "*")) {
|
||||||
|
class_name_end = level_separator;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Class log_class = GetClassByName(begin, class_name_end);
|
||||||
|
if (log_class == Class::Count) {
|
||||||
|
LOG_ERROR(Log, "Unknown log class in filter: %s", std::string(begin, end).c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (class_name_end == level_separator) {
|
||||||
|
SetClassLevel(log_class, level);
|
||||||
|
}
|
||||||
|
SetSubclassesLevel(log_class, level);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Filter::CheckMessage(Class log_class, Level level) const {
|
||||||
|
return static_cast<u8>(level) >= static_cast<u8>(class_levels[static_cast<size_t>(log_class)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
// Copyright 2014 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
|
||||||
|
namespace Log {
|
||||||
|
|
||||||
|
struct ClassInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements a log message filter which allows different log classes to have different minimum
|
||||||
|
* severity levels. The filter can be changed at runtime and can be parsed from a string to allow
|
||||||
|
* editing via the interface or loading from a configuration file.
|
||||||
|
*/
|
||||||
|
class Filter {
|
||||||
|
public:
|
||||||
|
/// Initializes the filter with all classes having `default_level` as the minimum level.
|
||||||
|
Filter(Level default_level);
|
||||||
|
|
||||||
|
/// Resets the filter so that all classes have `level` as the minimum displayed level.
|
||||||
|
void ResetAll(Level level);
|
||||||
|
/// Sets the minimum level of `log_class` (and not of its subclasses) to `level`.
|
||||||
|
void SetClassLevel(Class log_class, Level level);
|
||||||
|
/**
|
||||||
|
* Sets the minimum level of all of `log_class` subclasses to `level`. The level of `log_class`
|
||||||
|
* itself is not changed.
|
||||||
|
*/
|
||||||
|
void SetSubclassesLevel(const ClassInfo& log_class, Level level);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a filter string and applies it to this filter.
|
||||||
|
*
|
||||||
|
* A filter string consists of a space-separated list of filter rules, each of the format
|
||||||
|
* `<class>:<level>`. `<class>` is a log class name, with subclasses separated using periods.
|
||||||
|
* A rule for a given class also affects all of its subclasses. `*` wildcards are allowed and
|
||||||
|
* can be used to apply a rule to all classes or to all subclasses of a class without affecting
|
||||||
|
* the parent class. `<level>` a severity level name which will be set as the minimum logging
|
||||||
|
* level of the matched classes. Rules are applied left to right, with each rule overriding
|
||||||
|
* previous ones in the sequence.
|
||||||
|
*
|
||||||
|
* A few examples of filter rules:
|
||||||
|
* - `*:Info` -- Resets the level of all classes to Info.
|
||||||
|
* - `Service:Info` -- Sets the level of Service and all subclasses (Service.FS, Service.APT,
|
||||||
|
* etc.) to Info.
|
||||||
|
* - `Service.*:Debug` -- Sets the level of all Service subclasses to Debug, while leaving the
|
||||||
|
* level of Service unchanged.
|
||||||
|
* - `Service.FS:Trace` -- Sets the level of the Service.FS class to Trace.
|
||||||
|
*/
|
||||||
|
void ParseFilterString(const std::string& filter_str);
|
||||||
|
bool ParseFilterRule(const std::string::const_iterator start, const std::string::const_iterator end);
|
||||||
|
|
||||||
|
/// Matches class/level combination against the filter, returning true if it passed.
|
||||||
|
bool CheckMessage(Class log_class, Level level) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::array<Level, (size_t)Class::Count> class_levels;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,115 @@
|
|||||||
|
// Copyright 2014 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <chrono>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
namespace Log {
|
||||||
|
|
||||||
|
/// Specifies the severity or level of detail of the log message.
|
||||||
|
enum class Level : u8 {
|
||||||
|
Trace, ///< Extremely detailed and repetitive debugging information that is likely to
|
||||||
|
/// pollute logs.
|
||||||
|
Debug, ///< Less detailed debugging information.
|
||||||
|
Info, ///< Status information from important points during execution.
|
||||||
|
Warning, ///< Minor or potential problems found during execution of a task.
|
||||||
|
Error, ///< Major problems found during execution of a task that prevent it from being
|
||||||
|
/// completed.
|
||||||
|
Critical, ///< Major problems during execution that threathen the stability of the entire
|
||||||
|
/// application.
|
||||||
|
|
||||||
|
Count ///< Total number of logging levels
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef u8 ClassType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies the sub-system that generated the log message.
|
||||||
|
*
|
||||||
|
* @note If you add a new entry here, also add a corresponding one to `ALL_LOG_CLASSES` in log.cpp.
|
||||||
|
*/
|
||||||
|
enum class Class : ClassType {
|
||||||
|
Log, ///< Messages about the log system itself
|
||||||
|
Common, ///< Library routines
|
||||||
|
Common_Filesystem, ///< Filesystem interface library
|
||||||
|
Common_Memory, ///< Memory mapping and management functions
|
||||||
|
Core, ///< LLE emulation core
|
||||||
|
Core_ARM11, ///< ARM11 CPU core
|
||||||
|
Config, ///< Emulator configuration (including commandline)
|
||||||
|
Debug, ///< Debugging tools
|
||||||
|
Debug_Emulated, ///< Debug messages from the emulated programs
|
||||||
|
Debug_GPU, ///< GPU debugging tools
|
||||||
|
Debug_Breakpoint, ///< Logging breakpoints and watchpoints
|
||||||
|
Kernel, ///< The HLE implementation of the CTR kernel
|
||||||
|
Kernel_SVC, ///< Kernel system calls
|
||||||
|
Service, ///< HLE implementation of system services. Each major service
|
||||||
|
/// should have its own subclass.
|
||||||
|
Service_SRV, ///< The SRV (Service Directory) implementation
|
||||||
|
Service_FS, ///< The FS (Filesystem) service implementation
|
||||||
|
Service_APT, ///< The APT (Applets) service
|
||||||
|
Service_GSP, ///< The GSP (GPU control) service
|
||||||
|
Service_AC, ///< The AC (WiFi status) service
|
||||||
|
Service_PTM, ///< The PTM (Power status & misc.) service
|
||||||
|
Service_CFG, ///< The CFG (Configuration) service
|
||||||
|
Service_DSP, ///< The DSP (DSP control) service
|
||||||
|
Service_HID, ///< The HID (User input) service
|
||||||
|
HW, ///< Low-level hardware emulation
|
||||||
|
HW_Memory, ///< Memory-map and address translation
|
||||||
|
HW_GPU, ///< GPU control emulation
|
||||||
|
Frontend, ///< Emulator UI
|
||||||
|
Render, ///< Emulator video output and hardware acceleration
|
||||||
|
Render_Software, ///< Software renderer backend
|
||||||
|
Render_OpenGL, ///< OpenGL backend
|
||||||
|
Loader, ///< ROM loader
|
||||||
|
|
||||||
|
Count ///< Total number of logging classes
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Level below which messages are simply discarded without buffering regardless of the display
|
||||||
|
* settings.
|
||||||
|
*/
|
||||||
|
const Level MINIMUM_LEVEL =
|
||||||
|
#ifdef _DEBUG
|
||||||
|
Level::Trace;
|
||||||
|
#else
|
||||||
|
Level::Debug;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs a message to the global logger. This proxy exists to avoid exposing the details of the
|
||||||
|
* Logger class, including the ConcurrentRingBuffer template, to all files that desire to log
|
||||||
|
* messages, reducing unecessary recompilations.
|
||||||
|
*/
|
||||||
|
void LogMessage(Class log_class, Level log_level,
|
||||||
|
const char* filename, unsigned int line_nr, const char* function,
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
_Printf_format_string_
|
||||||
|
#endif
|
||||||
|
const char* format, ...)
|
||||||
|
#ifdef __GNUC__
|
||||||
|
__attribute__((format(printf, 6, 7)))
|
||||||
|
#endif
|
||||||
|
;
|
||||||
|
|
||||||
|
} // namespace Log
|
||||||
|
|
||||||
|
#define LOG_GENERIC(log_class, log_level, ...) \
|
||||||
|
do { \
|
||||||
|
if (::Log::Level::log_level >= ::Log::MINIMUM_LEVEL) \
|
||||||
|
::Log::LogMessage(::Log::Class::log_class, ::Log::Level::log_level, \
|
||||||
|
__FILE__, __LINE__, __func__, __VA_ARGS__); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define LOG_TRACE( log_class, ...) LOG_GENERIC(log_class, Trace, __VA_ARGS__)
|
||||||
|
#define LOG_DEBUG( log_class, ...) LOG_GENERIC(log_class, Debug, __VA_ARGS__)
|
||||||
|
#define LOG_INFO( log_class, ...) LOG_GENERIC(log_class, Info, __VA_ARGS__)
|
||||||
|
#define LOG_WARNING( log_class, ...) LOG_GENERIC(log_class, Warning, __VA_ARGS__)
|
||||||
|
#define LOG_ERROR( log_class, ...) LOG_GENERIC(log_class, Error, __VA_ARGS__)
|
||||||
|
#define LOG_CRITICAL(log_class, ...) LOG_GENERIC(log_class, Critical, __VA_ARGS__)
|
@ -0,0 +1,126 @@
|
|||||||
|
// Copyright 2014 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
# define WIN32_LEAN_AND_MEAN
|
||||||
|
# include <Windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "common/logging/backend.h"
|
||||||
|
#include "common/logging/filter.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "common/logging/text_formatter.h"
|
||||||
|
|
||||||
|
#include "common/string_util.h"
|
||||||
|
|
||||||
|
namespace Log {
|
||||||
|
|
||||||
|
// TODO(bunnei): This should be moved to a generic path manipulation library
|
||||||
|
const char* TrimSourcePath(const char* path, const char* root) {
|
||||||
|
const char* p = path;
|
||||||
|
|
||||||
|
while (*p != '\0') {
|
||||||
|
const char* next_slash = p;
|
||||||
|
while (*next_slash != '\0' && *next_slash != '/' && *next_slash != '\\') {
|
||||||
|
++next_slash;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_src = Common::ComparePartialString(p, next_slash, root);
|
||||||
|
p = next_slash;
|
||||||
|
|
||||||
|
if (*p != '\0') {
|
||||||
|
++p;
|
||||||
|
}
|
||||||
|
if (is_src) {
|
||||||
|
path = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FormatLogMessage(const Entry& entry, char* out_text, size_t text_len) {
|
||||||
|
unsigned int time_seconds = static_cast<unsigned int>(entry.timestamp.count() / 1000000);
|
||||||
|
unsigned int time_fractional = static_cast<unsigned int>(entry.timestamp.count() % 1000000);
|
||||||
|
|
||||||
|
const char* class_name = Logger::GetLogClassName(entry.log_class);
|
||||||
|
const char* level_name = Logger::GetLevelName(entry.log_level);
|
||||||
|
|
||||||
|
snprintf(out_text, text_len, "[%4u.%06u] %s <%s> %s: %s",
|
||||||
|
time_seconds, time_fractional, class_name, level_name,
|
||||||
|
TrimSourcePath(entry.location.c_str()), entry.message.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ChangeConsoleColor(Level level) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
static HANDLE console_handle = GetStdHandle(STD_ERROR_HANDLE);
|
||||||
|
|
||||||
|
WORD color = 0;
|
||||||
|
switch (level) {
|
||||||
|
case Level::Trace: // Grey
|
||||||
|
color = FOREGROUND_INTENSITY; break;
|
||||||
|
case Level::Debug: // Cyan
|
||||||
|
color = FOREGROUND_GREEN | FOREGROUND_BLUE; break;
|
||||||
|
case Level::Info: // Bright gray
|
||||||
|
color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; break;
|
||||||
|
case Level::Warning: // Bright yellow
|
||||||
|
color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY; break;
|
||||||
|
case Level::Error: // Bright red
|
||||||
|
color = FOREGROUND_RED | FOREGROUND_INTENSITY; break;
|
||||||
|
case Level::Critical: // Bright magenta
|
||||||
|
color = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetConsoleTextAttribute(console_handle, color);
|
||||||
|
#else
|
||||||
|
#define ESC "\x1b"
|
||||||
|
const char* color = "";
|
||||||
|
switch (level) {
|
||||||
|
case Level::Trace: // Grey
|
||||||
|
color = ESC "[1;30m"; break;
|
||||||
|
case Level::Debug: // Cyan
|
||||||
|
color = ESC "[0;36m"; break;
|
||||||
|
case Level::Info: // Bright gray
|
||||||
|
color = ESC "[0;37m"; break;
|
||||||
|
case Level::Warning: // Bright yellow
|
||||||
|
color = ESC "[1;33m"; break;
|
||||||
|
case Level::Error: // Bright red
|
||||||
|
color = ESC "[1;31m"; break;
|
||||||
|
case Level::Critical: // Bright magenta
|
||||||
|
color = ESC "[1;35m"; break;
|
||||||
|
}
|
||||||
|
#undef ESC
|
||||||
|
|
||||||
|
fputs(color, stderr);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintMessage(const Entry& entry) {
|
||||||
|
ChangeConsoleColor(entry.log_level);
|
||||||
|
std::array<char, 4 * 1024> format_buffer;
|
||||||
|
FormatLogMessage(entry, format_buffer.data(), format_buffer.size());
|
||||||
|
fputs(format_buffer.data(), stderr);
|
||||||
|
fputc('\n', stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextLoggingLoop(std::shared_ptr<Logger> logger, const Filter* filter) {
|
||||||
|
std::array<Entry, 256> entry_buffer;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
size_t num_entries = logger->GetEntries(entry_buffer.data(), entry_buffer.size());
|
||||||
|
if (num_entries == Logger::QUEUE_CLOSED) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < num_entries; ++i) {
|
||||||
|
const Entry& entry = entry_buffer[i];
|
||||||
|
if (filter->CheckMessage(entry.log_class, entry.log_level)) {
|
||||||
|
PrintMessage(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
// Copyright 2014 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace Log {
|
||||||
|
|
||||||
|
class Logger;
|
||||||
|
struct Entry;
|
||||||
|
class Filter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to trim an arbitrary prefix from `path`, leaving only the part starting at `root`. It's
|
||||||
|
* intended to be used to strip a system-specific build directory from the `__FILE__` macro,
|
||||||
|
* leaving only the path relative to the sources root.
|
||||||
|
*
|
||||||
|
* @param path The input file path as a null-terminated string
|
||||||
|
* @param root The name of the root source directory as a null-terminated string. Path up to and
|
||||||
|
* including the last occurence of this name will be stripped
|
||||||
|
* @return A pointer to the same string passed as `path`, but starting at the trimmed portion
|
||||||
|
*/
|
||||||
|
const char* TrimSourcePath(const char* path, const char* root = "src");
|
||||||
|
|
||||||
|
/// Formats a log entry into the provided text buffer.
|
||||||
|
void FormatLogMessage(const Entry& entry, char* out_text, size_t text_len);
|
||||||
|
/// Formats and prints a log entry to stderr.
|
||||||
|
void PrintMessage(const Entry& entry);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logging loop that repeatedly reads messages from the provided logger and prints them to the
|
||||||
|
* console. It is the baseline barebones log outputter.
|
||||||
|
*/
|
||||||
|
void TextLoggingLoop(std::shared_ptr<Logger> logger, const Filter* filter);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
// Copyright 2014 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
template <typename Func>
|
||||||
|
struct ScopeExitHelper {
|
||||||
|
explicit ScopeExitHelper(Func&& func) : func(std::move(func)) {}
|
||||||
|
~ScopeExitHelper() { func(); }
|
||||||
|
|
||||||
|
Func func;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Func>
|
||||||
|
ScopeExitHelper<Func> ScopeExit(Func&& func) { return ScopeExitHelper<Func>(std::move(func)); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This macro allows you to conveniently specify a block of code that will run on scope exit. Handy
|
||||||
|
* for doing ad-hoc clean-up tasks in a function with multiple returns.
|
||||||
|
*
|
||||||
|
* Example usage:
|
||||||
|
* \code
|
||||||
|
* const int saved_val = g_foo;
|
||||||
|
* g_foo = 55;
|
||||||
|
* SCOPE_EXIT({ g_foo = saved_val; });
|
||||||
|
*
|
||||||
|
* if (Bar()) {
|
||||||
|
* return 0;
|
||||||
|
* } else {
|
||||||
|
* return 20;
|
||||||
|
* }
|
||||||
|
* \endcode
|
||||||
|
*/
|
||||||
|
#define SCOPE_EXIT(body) auto scope_exit_helper_##__LINE__ = detail::ScopeExit([&]() body)
|
Loading…
Reference in New Issue