Make Logger class static and introduce LoggerContext

A thread local LoggerContext is now used to hold the output file stream instead of the `Logger` class. Before doing any logging operations, a LoggerContext must be initialized.
This commit will not build successfully on purpose.
This commit is contained in:
lynxnb 2021-10-27 23:27:44 +02:00
parent 69ef93bfa8
commit 769e6c933d
14 changed files with 265 additions and 203 deletions

View File

@ -106,6 +106,7 @@ add_library(skyline SHARED
${source_DIR}/emu_jni.cpp
${source_DIR}/loader_jni.cpp
${source_DIR}/skyline/common.cpp
${source_DIR}/skyline/common/logger.cpp
${source_DIR}/skyline/common/settings.cpp
${source_DIR}/skyline/common/signal.cpp
${source_DIR}/skyline/common/uuid.cpp

View File

@ -4,10 +4,10 @@
#include <csignal>
#include <pthread.h>
#include <unistd.h>
#include <android/log.h>
#include <android/asset_manager_jni.h>
#include <sys/system_properties.h>
#include "skyline/common.h"
#include "skyline/common/logger.h"
#include "skyline/common/language.h"
#include "skyline/common/signal.h"
#include "skyline/common/settings.h"
@ -55,6 +55,17 @@ static std::string GetTimeZoneName() {
return "GMT";
}
extern "C" JNIEXPORT void Java_emu_skyline_SkylineApplication_initializeLog(
JNIEnv *env,
jobject,
jstring appFilesPathJstring,
jint logLevel
) {
std::string appFilesPath{env->GetStringUTFChars(appFilesPathJstring, nullptr)};
skyline::Logger::configLevel = static_cast<skyline::Logger::LogLevel>(logLevel);
skyline::Logger::LoaderContext.Initialize(appFilesPath + "loader.log");
}
extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_executeApplication(
JNIEnv *env,
jobject instance,
@ -77,7 +88,7 @@ extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_executeApplication(
close(preferenceFd);
skyline::JniString appFilesPath(env, appFilesPathJstring);
auto logger{std::make_shared<skyline::Logger>(appFilesPath + "skyline.log", settings->logLevel)};
skyline::Logger::EmulationContext.Initialize(appFilesPath + "emulation.log");
auto start{std::chrono::steady_clock::now()};
@ -90,7 +101,6 @@ extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_executeApplication(
try {
auto os{std::make_shared<skyline::kernel::OS>(
jvmManager,
logger,
settings,
appFilesPath,
GetTimeZoneName(),
@ -121,6 +131,7 @@ extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_executeApplication(
auto end{std::chrono::steady_clock::now()};
logger->Write(skyline::Logger::LogLevel::Info, fmt::format("Emulation has ended in {}ms", std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()));
skyline::Logger::EmulationContext.Finalize();
close(romFd);
}

View File

@ -1,6 +1,7 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include "skyline/common/logger.h"
#include "skyline/crypto/key_store.h"
#include "skyline/vfs/nca.h"
#include "skyline/vfs/os_backing.h"
@ -14,6 +15,8 @@
extern "C" JNIEXPORT jint JNICALL Java_emu_skyline_loader_RomFile_populate(JNIEnv *env, jobject thiz, jint jformat, jint fd, jstring appFilesPathJstring, jint systemLanguage) {
skyline::loader::RomFormat format{static_cast<skyline::loader::RomFormat>(jformat)};
skyline::Logger::SetContext(&skyline::Logger::LoaderContext);
auto keyStore{std::make_shared<skyline::crypto::KeyStore>(skyline::JniString(env, appFilesPathJstring))};
std::unique_ptr<skyline::loader::Loader> loader;
try {

View File

@ -1,7 +1,6 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <android/log.h>
#include "common.h"
#include "nce.h"
#include "soc.h"
@ -11,45 +10,8 @@
#include "kernel/types/KProcess.h"
namespace skyline {
Logger::Logger(const std::string &path, LogLevel configLevel)
: configLevel(configLevel),
start(util::GetTimeNs() / constant::NsInMillisecond) {
logFile.open(path, std::ios::trunc);
UpdateTag();
Write(LogLevel::Info, "Logging started");
}
Logger::~Logger() {
Write(LogLevel::Info, "Logging ended");
logFile.flush();
}
thread_local static std::string logTag, threadName;
void Logger::UpdateTag() {
std::array<char, 16> name;
if (!pthread_getname_np(pthread_self(), name.data(), name.size()))
threadName = name.data();
else
threadName = "unk";
logTag = std::string("emu-cpp-") + threadName;
}
void Logger::Write(LogLevel level, const std::string &str) {
constexpr std::array<char, 5> levelCharacter{'E', 'W', 'I', 'D', 'V'}; // The LogLevel as written out to a file
constexpr std::array<int, 5> levelAlog{ANDROID_LOG_ERROR, ANDROID_LOG_WARN, ANDROID_LOG_INFO, ANDROID_LOG_DEBUG, ANDROID_LOG_VERBOSE}; // This corresponds to LogLevel and provides its equivalent for NDK Logging
if (logTag.empty())
UpdateTag();
__android_log_write(levelAlog[static_cast<u8>(level)], logTag.c_str(), str.c_str());
std::lock_guard guard(mutex);
logFile << '\036' << levelCharacter[static_cast<u8>(level)] << '\035' << std::dec << (util::GetTimeNs() / constant::NsInMillisecond) - start << '\035' << threadName << '\035' << str << '\n'; // We use RS (\036) and GS (\035) as our delimiters
}
DeviceState::DeviceState(kernel::OS *os, std::shared_ptr<JvmManager> jvmManager, std::shared_ptr<Settings> settings, std::shared_ptr<Logger> logger)
: os(os), jvm(std::move(jvmManager)), settings(std::move(settings)), logger(std::move(logger)) {
DeviceState::DeviceState(kernel::OS *os, std::shared_ptr<JvmManager> jvmManager, std::shared_ptr<Settings> settings)
: os(os), jvm(std::move(jvmManager)), settings(std::move(settings)) {
// We assign these later as they use the state in their constructor and we don't want null pointers
gpu = std::make_shared<gpu::GPU>(*this);
soc = std::make_shared<soc::SOC>(*this);

View File

@ -8,7 +8,6 @@
#include <unordered_map>
#include <list>
#include <vector>
#include <fstream>
#include <mutex>
#include <shared_mutex>
#include <functional>
@ -21,152 +20,6 @@
#include <common/result.h>
namespace skyline {
/**
* @brief A wrapper around writing logs into a log file and logcat using Android Log APIs
*/
class Logger {
private:
std::mutex mutex; //!< Synchronizes all output I/O to ensure there are no races
std::ofstream logFile; //!< An output stream to the log file
i64 start; //!< A timestamp in milliseconds for when the logger was started, this is used as the base for all log timestamps
public:
enum class LogLevel {
Error,
Warn,
Info,
Debug,
Verbose,
};
LogLevel configLevel; //!< The minimum level of logs to write
/**
* @param path The path of the log file
* @param configLevel The minimum level of logs to write
*/
Logger(const std::string &path, LogLevel configLevel);
/**
* @brief Writes the termination message to the log file
*/
~Logger();
/**
* @brief Update the tag in log messages with a new thread name
*/
static void UpdateTag();
void Write(LogLevel level, const std::string &str);
/**
* @brief A wrapper around a string which captures the calling function using Clang source location builtins
* @note A function needs to be declared for every argument template specialization as CTAD cannot work with implicit casting
* @url https://clang.llvm.org/docs/LanguageExtensions.html#source-location-builtins
*/
template<typename S>
struct FunctionString {
S string;
const char *function;
constexpr FunctionString(S string, const char *function = __builtin_FUNCTION()) : string(std::move(string)), function(function) {}
std::string operator*() {
return std::string(function) + ": " + std::string(string);
}
};
template<typename... Args>
void Error(FunctionString<const char*> formatString, Args &&... args) {
if (LogLevel::Error <= configLevel)
Write(LogLevel::Error, util::Format(*formatString, args...));
}
template<typename... Args>
void Error(FunctionString<std::string> formatString, Args &&... args) {
if (LogLevel::Error <= configLevel)
Write(LogLevel::Error, util::Format(*formatString, args...));
}
template<typename S, typename... Args>
void ErrorNoPrefix(S formatString, Args &&... args) {
if (LogLevel::Error <= configLevel)
Write(LogLevel::Error, util::Format(formatString, args...));
}
template<typename... Args>
void Warn(FunctionString<const char*> formatString, Args &&... args) {
if (LogLevel::Warn <= configLevel)
Write(LogLevel::Warn, util::Format(*formatString, args...));
}
template<typename... Args>
void Warn(FunctionString<std::string> formatString, Args &&... args) {
if (LogLevel::Warn <= configLevel)
Write(LogLevel::Warn, util::Format(*formatString, args...));
}
template<typename S, typename... Args>
void WarnNoPrefix(S formatString, Args &&... args) {
if (LogLevel::Warn <= configLevel)
Write(LogLevel::Warn, util::Format(formatString, args...));
}
template<typename... Args>
void Info(FunctionString<const char*> formatString, Args &&... args) {
if (LogLevel::Info <= configLevel)
Write(LogLevel::Info, util::Format(*formatString, args...));
}
template<typename... Args>
void Info(FunctionString<std::string> formatString, Args &&... args) {
if (LogLevel::Info <= configLevel)
Write(LogLevel::Info, util::Format(*formatString, args...));
}
template<typename S, typename... Args>
void InfoNoPrefix(S formatString, Args &&... args) {
if (LogLevel::Info <= configLevel)
Write(LogLevel::Info, util::Format(formatString, args...));
}
template<typename... Args>
void Debug(FunctionString<const char*> formatString, Args &&... args) {
if (LogLevel::Debug <= configLevel)
Write(LogLevel::Debug, util::Format(*formatString, args...));
}
template<typename... Args>
void Debug(FunctionString<std::string> formatString, Args &&... args) {
if (LogLevel::Debug <= configLevel)
Write(LogLevel::Debug, util::Format(*formatString, args...));
}
template<typename S, typename... Args>
void DebugNoPrefix(S formatString, Args &&... args) {
if (LogLevel::Debug <= configLevel)
Write(LogLevel::Debug, util::Format(formatString, args...));
}
template<typename... Args>
void Verbose(FunctionString<const char*> formatString, Args &&... args) {
if (LogLevel::Verbose <= configLevel)
Write(LogLevel::Verbose, util::Format(*formatString, args...));
}
template<typename... Args>
void Verbose(FunctionString<std::string> formatString, Args &&... args) {
if (LogLevel::Verbose <= configLevel)
Write(LogLevel::Verbose, util::Format(*formatString, args...));
}
template<typename S, typename... Args>
void VerboseNoPrefix(S formatString, Args &&... args) {
if (LogLevel::Verbose <= configLevel)
Write(LogLevel::Verbose, util::Format(formatString, args...));
}
};
class Settings;
namespace nce {
class NCE;
@ -201,14 +54,13 @@ namespace skyline {
* @brief The state of the entire emulator is contained within this class, all objects related to emulation are tied into it
*/
struct DeviceState {
DeviceState(kernel::OS *os, std::shared_ptr<JvmManager> jvmManager, std::shared_ptr<Settings> settings, std::shared_ptr<Logger> logger);
DeviceState(kernel::OS *os, std::shared_ptr<JvmManager> jvmManager, std::shared_ptr<Settings> settings);
~DeviceState();
kernel::OS *os;
std::shared_ptr<JvmManager> jvm;
std::shared_ptr<Settings> settings;
std::shared_ptr<Logger> logger;
std::shared_ptr<loader::Loader> loader;
std::shared_ptr<kernel::type::KProcess> process{};
static thread_local inline std::shared_ptr<kernel::type::KThread> thread{}; //!< The KThread of the thread which accesses this object

View File

@ -6,6 +6,7 @@
#include <cstdint>
#include <stdexcept>
#include <variant>
#include <bitset>
#include <fmt/format.h>
namespace fmt {

View File

@ -0,0 +1,62 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <android/log.h>
#include "logger.h"
namespace skyline {
void Logger::LoggerContext::Initialize(const std::string &path) {
start = util::GetTimeNs() / constant::NsInMillisecond;
logFile.open(path, std::ios::trunc);
}
void Logger::LoggerContext::Finalize() {
logFile.close();
}
void Logger::LoggerContext::Flush() {
logFile.flush();
}
thread_local static std::string logTag, threadName;
thread_local static Logger::LoggerContext *context{&Logger::EmulationContext};
void Logger::UpdateTag() {
std::array<char, 16> name;
if (!pthread_getname_np(pthread_self(), name.data(), name.size()))
threadName = name.data();
else
threadName = "unk";
logTag = std::string("emu-cpp-") + threadName;
}
Logger::LoggerContext *Logger::GetContext() {
return context;
}
void Logger::SetContext(LoggerContext *pContext) {
context = pContext;
}
void Logger::WriteAndroid(LogLevel level, const std::string &str) {
constexpr std::array<int, 5> levelAlog{ANDROID_LOG_ERROR, ANDROID_LOG_WARN, ANDROID_LOG_INFO, ANDROID_LOG_DEBUG, ANDROID_LOG_VERBOSE}; // This corresponds to LogLevel and provides its equivalent for NDK Logging
if (logTag.empty())
UpdateTag();
__android_log_write(levelAlog[static_cast<u8>(level)], logTag.c_str(), str.c_str());
}
void Logger::Write(LogLevel level, const std::string &str) {
constexpr std::array<char, 5> levelCharacter{'E', 'W', 'I', 'D', 'V'}; // The LogLevel as written out to a file
WriteAndroid(level, str);
if (context)
// We use RS (\036) and GS (\035) as our delimiters
context->Write(fmt::format("\036{}\035{}\035{}\035{}\n", levelCharacter[static_cast<u8>(level)], (util::GetTimeNs() / constant::NsInMillisecond) - context->start, threadName, str));
}
void Logger::LoggerContext::Write(const std::string &str) {
std::lock_guard guard(mutex);
logFile << str;
}
}

View File

@ -0,0 +1,169 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <fstream>
#include <mutex>
#include "common.h"
namespace skyline {
/**
* @brief A wrapper around writing logs into a log file and logcat using Android Log APIs
*/
class Logger {
private:
Logger() {}
public:
enum class LogLevel {
Error,
Warn,
Info,
Debug,
Verbose,
};
static inline LogLevel configLevel{LogLevel::Verbose}; //!< The minimum level of logs to write
/**
* @brief Holds logger variables that cannot be static
*/
struct LoggerContext {
std::mutex mutex; //!< Synchronizes all output I/O to ensure there are no races
std::ofstream logFile; //!< An output stream to the log file
i64 start; //!< A timestamp in milliseconds for when the logger was started, this is used as the base for all log timestamps
LoggerContext() {}
void Initialize(const std::string &path);
void Finalize();
void Flush();
void Write(const std::string &str);
};
static inline LoggerContext EmulationContext, LoaderContext;
/**
* @brief Update the tag in log messages with a new thread name
*/
static void UpdateTag();
static LoggerContext *GetContext();
static void SetContext(LoggerContext *context);
static void WriteAndroid(LogLevel level, const std::string &str);
static void Write(LogLevel level, const std::string &str);
/**
* @brief A wrapper around a string which captures the calling function using Clang source location builtins
* @note A function needs to be declared for every argument template specialization as CTAD cannot work with implicit casting
* @url https://clang.llvm.org/docs/LanguageExtensions.html#source-location-builtins
*/
template<typename S>
struct FunctionString {
S string;
const char *function;
FunctionString(S string, const char *function = __builtin_FUNCTION()) : string(std::move(string)), function(function) {}
std::string operator*() {
return std::string(function) + ": " + std::string(string);
}
};
template<typename... Args>
static void Error(FunctionString<const char *> formatString, Args &&... args) {
if (LogLevel::Error <= configLevel)
Write(LogLevel::Error, util::Format(*formatString, args...));
}
template<typename... Args>
static void Error(FunctionString<std::string> formatString, Args &&... args) {
if (LogLevel::Error <= configLevel)
Write(LogLevel::Error, util::Format(*formatString, args...));
}
template<typename S, typename... Args>
static void ErrorNoPrefix(S formatString, Args &&... args) {
if (LogLevel::Error <= configLevel)
Write(LogLevel::Error, util::Format(formatString, args...));
}
template<typename... Args>
static void Warn(FunctionString<const char *> formatString, Args &&... args) {
if (LogLevel::Warn <= configLevel)
Write(LogLevel::Warn, util::Format(*formatString, args...));
}
template<typename... Args>
static void Warn(FunctionString<std::string> formatString, Args &&... args) {
if (LogLevel::Warn <= configLevel)
Write(LogLevel::Warn, util::Format(*formatString, args...));
}
template<typename S, typename... Args>
static void WarnNoPrefix(S formatString, Args &&... args) {
if (LogLevel::Warn <= configLevel)
Write(LogLevel::Warn, util::Format(formatString, args...));
}
template<typename... Args>
static void Info(FunctionString<const char *> formatString, Args &&... args) {
if (LogLevel::Info <= configLevel)
Write(LogLevel::Info, util::Format(*formatString, args...));
}
template<typename... Args>
static void Info(FunctionString<std::string> formatString, Args &&... args) {
if (LogLevel::Info <= configLevel)
Write(LogLevel::Info, util::Format(*formatString, args...));
}
template<typename S, typename... Args>
static void InfoNoPrefix(S formatString, Args &&... args) {
if (LogLevel::Info <= configLevel)
Write(LogLevel::Info, util::Format(formatString, args...));
}
template<typename... Args>
static void Debug(FunctionString<const char *> formatString, Args &&... args) {
if (LogLevel::Debug <= configLevel)
Write(LogLevel::Debug, util::Format(*formatString, args...));
}
template<typename... Args>
static void Debug(FunctionString<std::string> formatString, Args &&... args) {
if (LogLevel::Debug <= configLevel)
Write(LogLevel::Debug, util::Format(*formatString, args...));
}
template<typename S, typename... Args>
static void DebugNoPrefix(S formatString, Args &&... args) {
if (LogLevel::Debug <= configLevel)
Write(LogLevel::Debug, util::Format(formatString, args...));
}
template<typename... Args>
static void Verbose(FunctionString<const char *> formatString, Args &&... args) {
if (LogLevel::Verbose <= configLevel)
Write(LogLevel::Verbose, util::Format(*formatString, args...));
}
template<typename... Args>
static void Verbose(FunctionString<std::string> formatString, Args &&... args) {
if (LogLevel::Verbose <= configLevel)
Write(LogLevel::Verbose, util::Format(*formatString, args...));
}
template<typename S, typename... Args>
static void VerboseNoPrefix(S formatString, Args &&... args) {
if (LogLevel::Verbose <= configLevel)
Write(LogLevel::Verbose, util::Format(formatString, args...));
}
};
}

View File

@ -4,6 +4,7 @@
#pragma once
#include <random>
#include <span>
#include <frozen/unordered_map.h>
#include <frozen/string.h>
#include "base.h"

View File

@ -15,13 +15,12 @@
namespace skyline::kernel {
OS::OS(
std::shared_ptr<JvmManager> &jvmManager,
std::shared_ptr<Logger> &logger,
std::shared_ptr<Settings> &settings,
std::string appFilesPath,
std::string deviceTimeZone,
language::SystemLanguage systemLanguage,
std::shared_ptr<vfs::FileSystem> assetFileSystem)
: state(this, jvmManager, settings, logger),
: state(this, jvmManager, settings),
appFilesPath(std::move(appFilesPath)),
deviceTimeZone(std::move(deviceTimeZone)),
assetFileSystem(std::move(assetFileSystem)),

View File

@ -22,13 +22,11 @@ namespace skyline::kernel {
language::SystemLanguage systemLanguage;
/**
* @param logger An instance of the Logger class
* @param settings An instance of the Settings class
* @param window The ANativeWindow object to draw the screen to
*/
OS(
std::shared_ptr<JvmManager> &jvmManager,
std::shared_ptr<Logger> &logger,
std::shared_ptr<Settings> &settings,
std::string appFilesPath,
std::string deviceTimeZone,

View File

@ -37,10 +37,6 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
private var emulationThread : Thread? = null
}
init {
System.loadLibrary("skyline") // libskyline.so
}
private val binding by lazy { EmuActivityBinding.inflate(layoutInflater) }
/**

View File

@ -7,6 +7,15 @@ package emu.skyline
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
import emu.skyline.di.getSettings
@HiltAndroidApp
class SkylineApplication : Application()
class SkylineApplication : Application() {
private external fun initializeLog(appFilesPath : String, logLevel : Int)
override fun onCreate() {
super.onCreate()
System.loadLibrary("skyline")
initializeLog(applicationContext.filesDir.canonicalPath + "/", getSettings().logLevel.toInt())
}
}

View File

@ -127,8 +127,6 @@ internal class RomFile(context : Context, format : RomFormat, uri : Uri, systemL
get() = result == LoaderResult.Success
init {
System.loadLibrary("skyline")
context.contentResolver.openFileDescriptor(uri, "r")!!.use {
result = LoaderResult.get(populate(format.ordinal, it.fd, context.filesDir.canonicalPath + "/", systemLanguage))
}