14 changed files with 743 additions and 74 deletions
-
16src/citra/citra.cpp
-
18src/citra_qt/main.cpp
-
6src/common/CMakeLists.txt
-
2src/common/common_types.h
-
164src/common/concurrent_ring_buffer.h
-
78src/common/log.h
-
58src/common/log_manager.cpp
-
151src/common/logging/backend.cpp
-
134src/common/logging/backend.h
-
115src/common/logging/log.h
-
47src/common/logging/text_formatter.cpp
-
26src/common/logging/text_formatter.h
-
1src/common/thread.h
-
1src/core/hle/config_mem.cpp
@ -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 |
||||
@ -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,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,47 @@ |
|||||
|
// Copyright 2014 Citra Emulator Project
|
||||
|
// Licensed under GPLv2+
|
||||
|
// Refer to the license.txt file included.
|
||||
|
|
||||
|
#include <array>
|
||||
|
#include <cstdio>
|
||||
|
|
||||
|
#include "common/logging/backend.h"
|
||||
|
#include "common/logging/log.h"
|
||||
|
#include "common/logging/text_formatter.h"
|
||||
|
|
||||
|
namespace Log { |
||||
|
|
||||
|
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, |
||||
|
entry.location.c_str(), entry.message.c_str()); |
||||
|
} |
||||
|
|
||||
|
void PrintMessage(const Entry& entry) { |
||||
|
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) { |
||||
|
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) { |
||||
|
PrintMessage(entry_buffer[i]); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
// 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; |
||||
|
|
||||
|
/// 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); |
||||
|
|
||||
|
} |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue