diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 3d224f38..96a91081 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -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 diff --git a/app/src/main/cpp/emu_jni.cpp b/app/src/main/cpp/emu_jni.cpp index 15ad4e86..a04c107b 100644 --- a/app/src/main/cpp/emu_jni.cpp +++ b/app/src/main/cpp/emu_jni.cpp @@ -4,10 +4,10 @@ #include #include #include -#include #include #include #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(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(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( 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(end - start).count())); + skyline::Logger::EmulationContext.Finalize(); close(romFd); } diff --git a/app/src/main/cpp/loader_jni.cpp b/app/src/main/cpp/loader_jni.cpp index 2d278c9a..321b93f1 100644 --- a/app/src/main/cpp/loader_jni.cpp +++ b/app/src/main/cpp/loader_jni.cpp @@ -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(jformat)}; + skyline::Logger::SetContext(&skyline::Logger::LoaderContext); + auto keyStore{std::make_shared(skyline::JniString(env, appFilesPathJstring))}; std::unique_ptr loader; try { diff --git a/app/src/main/cpp/skyline/common.cpp b/app/src/main/cpp/skyline/common.cpp index 58b6fa5c..76ba2276 100644 --- a/app/src/main/cpp/skyline/common.cpp +++ b/app/src/main/cpp/skyline/common.cpp @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) -#include #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 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 levelCharacter{'E', 'W', 'I', 'D', 'V'}; // The LogLevel as written out to a file - constexpr std::array 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(level)], logTag.c_str(), str.c_str()); - - std::lock_guard guard(mutex); - logFile << '\036' << levelCharacter[static_cast(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, std::shared_ptr settings, std::shared_ptr logger) - : os(os), jvm(std::move(jvmManager)), settings(std::move(settings)), logger(std::move(logger)) { + DeviceState::DeviceState(kernel::OS *os, std::shared_ptr jvmManager, std::shared_ptr 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(*this); soc = std::make_shared(*this); diff --git a/app/src/main/cpp/skyline/common.h b/app/src/main/cpp/skyline/common.h index 99ab9fb8..48c2726b 100644 --- a/app/src/main/cpp/skyline/common.h +++ b/app/src/main/cpp/skyline/common.h @@ -8,7 +8,6 @@ #include #include #include -#include #include #include #include @@ -21,152 +20,6 @@ #include 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 - 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 - void Error(FunctionString formatString, Args &&... args) { - if (LogLevel::Error <= configLevel) - Write(LogLevel::Error, util::Format(*formatString, args...)); - } - - template - void Error(FunctionString formatString, Args &&... args) { - if (LogLevel::Error <= configLevel) - Write(LogLevel::Error, util::Format(*formatString, args...)); - } - - template - void ErrorNoPrefix(S formatString, Args &&... args) { - if (LogLevel::Error <= configLevel) - Write(LogLevel::Error, util::Format(formatString, args...)); - } - - template - void Warn(FunctionString formatString, Args &&... args) { - if (LogLevel::Warn <= configLevel) - Write(LogLevel::Warn, util::Format(*formatString, args...)); - } - - template - void Warn(FunctionString formatString, Args &&... args) { - if (LogLevel::Warn <= configLevel) - Write(LogLevel::Warn, util::Format(*formatString, args...)); - } - - template - void WarnNoPrefix(S formatString, Args &&... args) { - if (LogLevel::Warn <= configLevel) - Write(LogLevel::Warn, util::Format(formatString, args...)); - } - - template - void Info(FunctionString formatString, Args &&... args) { - if (LogLevel::Info <= configLevel) - Write(LogLevel::Info, util::Format(*formatString, args...)); - } - - template - void Info(FunctionString formatString, Args &&... args) { - if (LogLevel::Info <= configLevel) - Write(LogLevel::Info, util::Format(*formatString, args...)); - } - - template - void InfoNoPrefix(S formatString, Args &&... args) { - if (LogLevel::Info <= configLevel) - Write(LogLevel::Info, util::Format(formatString, args...)); - } - - template - void Debug(FunctionString formatString, Args &&... args) { - if (LogLevel::Debug <= configLevel) - Write(LogLevel::Debug, util::Format(*formatString, args...)); - } - - template - void Debug(FunctionString formatString, Args &&... args) { - if (LogLevel::Debug <= configLevel) - Write(LogLevel::Debug, util::Format(*formatString, args...)); - } - - template - void DebugNoPrefix(S formatString, Args &&... args) { - if (LogLevel::Debug <= configLevel) - Write(LogLevel::Debug, util::Format(formatString, args...)); - } - - template - void Verbose(FunctionString formatString, Args &&... args) { - if (LogLevel::Verbose <= configLevel) - Write(LogLevel::Verbose, util::Format(*formatString, args...)); - } - - template - void Verbose(FunctionString formatString, Args &&... args) { - if (LogLevel::Verbose <= configLevel) - Write(LogLevel::Verbose, util::Format(*formatString, args...)); - } - - template - 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, std::shared_ptr settings, std::shared_ptr logger); + DeviceState(kernel::OS *os, std::shared_ptr jvmManager, std::shared_ptr settings); ~DeviceState(); kernel::OS *os; std::shared_ptr jvm; std::shared_ptr settings; - std::shared_ptr logger; std::shared_ptr loader; std::shared_ptr process{}; static thread_local inline std::shared_ptr thread{}; //!< The KThread of the thread which accesses this object diff --git a/app/src/main/cpp/skyline/common/base.h b/app/src/main/cpp/skyline/common/base.h index 50cce139..f89f0a80 100644 --- a/app/src/main/cpp/skyline/common/base.h +++ b/app/src/main/cpp/skyline/common/base.h @@ -6,6 +6,7 @@ #include #include #include +#include #include namespace fmt { diff --git a/app/src/main/cpp/skyline/common/logger.cpp b/app/src/main/cpp/skyline/common/logger.cpp new file mode 100644 index 00000000..69f68ebd --- /dev/null +++ b/app/src/main/cpp/skyline/common/logger.cpp @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#include +#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 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 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(level)], logTag.c_str(), str.c_str()); + } + + void Logger::Write(LogLevel level, const std::string &str) { + constexpr std::array 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(level)], (util::GetTimeNs() / constant::NsInMillisecond) - context->start, threadName, str)); + } + + void Logger::LoggerContext::Write(const std::string &str) { + std::lock_guard guard(mutex); + logFile << str; + } +} \ No newline at end of file diff --git a/app/src/main/cpp/skyline/common/logger.h b/app/src/main/cpp/skyline/common/logger.h new file mode 100644 index 00000000..ddf92598 --- /dev/null +++ b/app/src/main/cpp/skyline/common/logger.h @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include +#include +#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 + 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 + static void Error(FunctionString formatString, Args &&... args) { + if (LogLevel::Error <= configLevel) + Write(LogLevel::Error, util::Format(*formatString, args...)); + } + + template + static void Error(FunctionString formatString, Args &&... args) { + if (LogLevel::Error <= configLevel) + Write(LogLevel::Error, util::Format(*formatString, args...)); + } + + template + static void ErrorNoPrefix(S formatString, Args &&... args) { + if (LogLevel::Error <= configLevel) + Write(LogLevel::Error, util::Format(formatString, args...)); + } + + template + static void Warn(FunctionString formatString, Args &&... args) { + if (LogLevel::Warn <= configLevel) + Write(LogLevel::Warn, util::Format(*formatString, args...)); + } + + template + static void Warn(FunctionString formatString, Args &&... args) { + if (LogLevel::Warn <= configLevel) + Write(LogLevel::Warn, util::Format(*formatString, args...)); + } + + template + static void WarnNoPrefix(S formatString, Args &&... args) { + if (LogLevel::Warn <= configLevel) + Write(LogLevel::Warn, util::Format(formatString, args...)); + } + + template + static void Info(FunctionString formatString, Args &&... args) { + if (LogLevel::Info <= configLevel) + Write(LogLevel::Info, util::Format(*formatString, args...)); + } + + template + static void Info(FunctionString formatString, Args &&... args) { + if (LogLevel::Info <= configLevel) + Write(LogLevel::Info, util::Format(*formatString, args...)); + } + + template + static void InfoNoPrefix(S formatString, Args &&... args) { + if (LogLevel::Info <= configLevel) + Write(LogLevel::Info, util::Format(formatString, args...)); + } + + template + static void Debug(FunctionString formatString, Args &&... args) { + if (LogLevel::Debug <= configLevel) + Write(LogLevel::Debug, util::Format(*formatString, args...)); + } + + template + static void Debug(FunctionString formatString, Args &&... args) { + if (LogLevel::Debug <= configLevel) + Write(LogLevel::Debug, util::Format(*formatString, args...)); + } + + template + static void DebugNoPrefix(S formatString, Args &&... args) { + if (LogLevel::Debug <= configLevel) + Write(LogLevel::Debug, util::Format(formatString, args...)); + } + + template + static void Verbose(FunctionString formatString, Args &&... args) { + if (LogLevel::Verbose <= configLevel) + Write(LogLevel::Verbose, util::Format(*formatString, args...)); + } + + template + static void Verbose(FunctionString formatString, Args &&... args) { + if (LogLevel::Verbose <= configLevel) + Write(LogLevel::Verbose, util::Format(*formatString, args...)); + } + + template + static void VerboseNoPrefix(S formatString, Args &&... args) { + if (LogLevel::Verbose <= configLevel) + Write(LogLevel::Verbose, util::Format(formatString, args...)); + } + }; +} diff --git a/app/src/main/cpp/skyline/common/utils.h b/app/src/main/cpp/skyline/common/utils.h index 1d35b925..1e9c62e7 100644 --- a/app/src/main/cpp/skyline/common/utils.h +++ b/app/src/main/cpp/skyline/common/utils.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include #include #include "base.h" diff --git a/app/src/main/cpp/skyline/os.cpp b/app/src/main/cpp/skyline/os.cpp index 157b5221..e36b12ab 100644 --- a/app/src/main/cpp/skyline/os.cpp +++ b/app/src/main/cpp/skyline/os.cpp @@ -15,13 +15,12 @@ namespace skyline::kernel { OS::OS( std::shared_ptr &jvmManager, - std::shared_ptr &logger, std::shared_ptr &settings, std::string appFilesPath, std::string deviceTimeZone, language::SystemLanguage systemLanguage, std::shared_ptr assetFileSystem) - : state(this, jvmManager, settings, logger), + : state(this, jvmManager, settings), appFilesPath(std::move(appFilesPath)), deviceTimeZone(std::move(deviceTimeZone)), assetFileSystem(std::move(assetFileSystem)), diff --git a/app/src/main/cpp/skyline/os.h b/app/src/main/cpp/skyline/os.h index 824a756b..02c08bd2 100644 --- a/app/src/main/cpp/skyline/os.h +++ b/app/src/main/cpp/skyline/os.h @@ -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, - std::shared_ptr &logger, std::shared_ptr &settings, std::string appFilesPath, std::string deviceTimeZone, diff --git a/app/src/main/java/emu/skyline/EmulationActivity.kt b/app/src/main/java/emu/skyline/EmulationActivity.kt index be1c0bec..6aeed44c 100644 --- a/app/src/main/java/emu/skyline/EmulationActivity.kt +++ b/app/src/main/java/emu/skyline/EmulationActivity.kt @@ -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) } /** diff --git a/app/src/main/java/emu/skyline/SkylineApplication.kt b/app/src/main/java/emu/skyline/SkylineApplication.kt index d4373408..19533d15 100644 --- a/app/src/main/java/emu/skyline/SkylineApplication.kt +++ b/app/src/main/java/emu/skyline/SkylineApplication.kt @@ -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()) + } +} diff --git a/app/src/main/java/emu/skyline/loader/RomFile.kt b/app/src/main/java/emu/skyline/loader/RomFile.kt index 44aac9d3..c4c18e11 100644 --- a/app/src/main/java/emu/skyline/loader/RomFile.kt +++ b/app/src/main/java/emu/skyline/loader/RomFile.kt @@ -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)) }