diff --git a/src/android/app/src/main/cpp/CMakeLists.txt b/src/android/app/src/main/cpp/CMakeLists.txt index 08db8f4c7..f3a7e0131 100644 --- a/src/android/app/src/main/cpp/CMakeLists.txt +++ b/src/android/app/src/main/cpp/CMakeLists.txt @@ -1,6 +1,9 @@ cmake_minimum_required(VERSION 3.8) add_library(citra-android SHARED + logging/log.cpp + logging/logcat_backend.cpp + logging/logcat_backend.h native_interface.cpp native_interface.h ui/main/main_activity.cpp diff --git a/src/android/app/src/main/cpp/logging/log.cpp b/src/android/app/src/main/cpp/logging/log.cpp new file mode 100644 index 000000000..044f4eb4c --- /dev/null +++ b/src/android/app/src/main/cpp/logging/log.cpp @@ -0,0 +1,15 @@ +#include "common/logging/log.h" +#include "native_interface.h" + +namespace Log { +extern "C" { +JNICALL void Java_org_citra_1emu_citra_LOG_logEntry(JNIEnv* env, jclass type, jint level, + jstring file_name, jint line_number, + jstring function, jstring msg) { + using CitraJNI::GetJString; + FmtLogMessage(Class::Frontend, static_cast(level), GetJString(env, file_name).data(), + static_cast(line_number), GetJString(env, function).data(), + GetJString(env, msg).data()); +} +} +} // namespace Log diff --git a/src/android/app/src/main/cpp/logging/logcat_backend.cpp b/src/android/app/src/main/cpp/logging/logcat_backend.cpp new file mode 100644 index 000000000..17b6ae1a0 --- /dev/null +++ b/src/android/app/src/main/cpp/logging/logcat_backend.cpp @@ -0,0 +1,38 @@ +// Copyright 2019 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include "common/assert.h" +#include "common/logging/text_formatter.h" +#include "logcat_backend.h" + +namespace Log { +void LogcatBackend::Write(const Entry& entry) { + android_LogPriority priority; + switch (entry.log_level) { + case Level::Trace: + priority = ANDROID_LOG_VERBOSE; + break; + case Level::Debug: + priority = ANDROID_LOG_DEBUG; + break; + case Level::Info: + priority = ANDROID_LOG_INFO; + break; + case Level::Warning: + priority = ANDROID_LOG_WARN; + break; + case Level::Error: + priority = ANDROID_LOG_ERROR; + break; + case Level::Critical: + priority = ANDROID_LOG_FATAL; + break; + case Level::Count: + UNREACHABLE(); + } + + __android_log_print(priority, "citra", "%s\n", FormatLogMessage(entry).c_str()); +} +} // namespace Log \ No newline at end of file diff --git a/src/android/app/src/main/cpp/logging/logcat_backend.h b/src/android/app/src/main/cpp/logging/logcat_backend.h new file mode 100644 index 000000000..f3bac4762 --- /dev/null +++ b/src/android/app/src/main/cpp/logging/logcat_backend.h @@ -0,0 +1,22 @@ +// Copyright 2019 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/logging/backend.h" + +namespace Log { +class LogcatBackend : public Backend { +public: + static const char* Name() { + return "Logcat"; + } + + const char* GetName() const override { + return Name(); + } + + void Write(const Entry& entry) override; +}; +} // namespace Log diff --git a/src/android/app/src/main/cpp/ui/main/main_activity.cpp b/src/android/app/src/main/cpp/ui/main/main_activity.cpp index 412c358a0..b99ba1890 100644 --- a/src/android/app/src/main/cpp/ui/main/main_activity.cpp +++ b/src/android/app/src/main/cpp/ui/main/main_activity.cpp @@ -2,14 +2,30 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "common/common_paths.h" #include "common/file_util.h" +#include "common/logging/filter.h" +#include "common/logging/log.h" +#include "core/settings.h" +#include "logging/logcat_backend.h" #include "native_interface.h" namespace MainActivity { extern "C" { JNICALL void Java_org_citra_1emu_citra_ui_main_MainActivity_initUserPath(JNIEnv* env, jclass type, jstring path) { - FileUtil::SetUserPath(CitraJNI::GetJString(env, path)); + FileUtil::SetUserPath(CitraJNI::GetJString(env, path) + '/'); +} + +JNICALL void Java_org_citra_1emu_citra_ui_main_MainActivity_initLogging(JNIEnv* env, jclass type) { + Log::Filter log_filter(Log::Level::Debug); + log_filter.ParseFilterString(Settings::values.log_filter); + Log::SetGlobalFilter(log_filter); + + const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir); + FileUtil::CreateFullPath(log_dir); + Log::AddBackend(std::make_unique(log_dir + LOG_FILE)); + Log::AddBackend(std::make_unique()); } }; }; // namespace MainActivity diff --git a/src/android/app/src/main/java/org/citra_emu/citra/LOG.java b/src/android/app/src/main/java/org/citra_emu/citra/LOG.java new file mode 100644 index 000000000..c52f30b68 --- /dev/null +++ b/src/android/app/src/main/java/org/citra_emu/citra/LOG.java @@ -0,0 +1,41 @@ +package org.citra_emu.citra; + +public class LOG { + + private interface LOG_LEVEL { + int TRACE = 0, DEBUG = 1, INFO = 2, WARNING = 3, ERROR = 4, CRITICAL = 5; + } + + public static void TRACE(String msg, Object... args) { + LOG(LOG_LEVEL.TRACE, msg, args); + } + + public static void DEBUG(String msg, Object... args) { + LOG(LOG_LEVEL.DEBUG, msg, args); + } + + public static void INFO(String msg, Object... args) { + LOG(LOG_LEVEL.INFO, msg, args); + } + + public static void WARNING(String msg, Object... args) { + LOG(LOG_LEVEL.WARNING, msg, args); + } + + public static void ERROR(String msg, Object... args) { + LOG(LOG_LEVEL.ERROR, msg, args); + } + + public static void CRITICAL(String msg, Object... args) { + LOG(LOG_LEVEL.CRITICAL, msg, args); + } + + private static void LOG(int level, String msg, Object... args) { + StackTraceElement trace = Thread.currentThread().getStackTrace()[4]; + logEntry(level, trace.getFileName(), trace.getLineNumber(), trace.getMethodName(), + String.format(msg, args)); + } + + private static native void logEntry(int level, String file_name, int line_number, + String function, String message); +} diff --git a/src/android/app/src/main/java/org/citra_emu/citra/ui/main/MainActivity.java b/src/android/app/src/main/java/org/citra_emu/citra/ui/main/MainActivity.java index 0ae764798..5b4f3d3bc 100644 --- a/src/android/app/src/main/java/org/citra_emu/citra/ui/main/MainActivity.java +++ b/src/android/app/src/main/java/org/citra_emu/citra/ui/main/MainActivity.java @@ -18,7 +18,7 @@ import org.citra_emu.citra.utils.PermissionUtil; public final class MainActivity extends AppCompatActivity { // Java enums suck - private interface PermissionCodes { int INIT_USER_PATH = 0; } + private interface PermissionCodes { int INITIALIZE = 0; } @Override protected void onCreate(Bundle savedInstanceState) { @@ -26,16 +26,17 @@ public final class MainActivity extends AppCompatActivity { setContentView(R.layout.activity_main); PermissionUtil.verifyPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE, - PermissionCodes.INIT_USER_PATH); + PermissionCodes.INITIALIZE); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { switch (requestCode) { - case PermissionCodes.INIT_USER_PATH: + case PermissionCodes.INITIALIZE: if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { initUserPath(FileUtil.getUserPath().toString()); + initLogging(); } else { AlertDialog.Builder dialog = new AlertDialog.Builder(this) @@ -45,7 +46,7 @@ public final class MainActivity extends AppCompatActivity { .setPositiveButton("OK", (dialogInterface, which) -> { PermissionUtil.verifyPermission( MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE, - PermissionCodes.INIT_USER_PATH); + PermissionCodes.INITIALIZE); }); dialog.show(); } @@ -53,4 +54,5 @@ public final class MainActivity extends AppCompatActivity { } private static native void initUserPath(String path); + private static native void initLogging(); } diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index d93c5bbd7..f0062779b 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -259,7 +259,7 @@ Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsign entry.timestamp = duration_cast(steady_clock::now() - time_origin); entry.log_class = log_class; entry.log_level = log_level; - entry.filename = Common::TrimSourcePath(filename); + entry.filename = Common::TrimSourcePath(filename, {R"(\.\.)", "src"}).data(); entry.line_num = line_nr; entry.function = function; entry.message = std::move(message); diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp index 541a2bf4e..9306c8bed 100644 --- a/src/common/string_util.cpp +++ b/src/common/string_util.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include "common/common_paths.h" #include "common/logging/log.h" @@ -210,25 +211,17 @@ std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, std::size_t return std::string(buffer, len); } -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; - } +std::string TrimSourcePath(const std::string& file_path, const std::vector& roots) { + // match from beginning of path to dir sep + std::string regex_src = R"(.*([\/\\]|^)()"; + // plus the last occurrence of any root + for (auto root = roots.begin(); root < roots.end() - 1; ++root) { + regex_src += '(' + *root + ")|"; } - return path; + regex_src += '(' + roots.back() + ')'; + // plus dir sep + regex_src += R"()[\/\\])"; + std::regex regex(regex_src); + return std::regex_replace(file_path, regex, ""); } } // namespace Common diff --git a/src/common/string_util.h b/src/common/string_util.h index d8fa4053e..aa0d147a4 100644 --- a/src/common/string_util.h +++ b/src/common/string_util.h @@ -69,11 +69,11 @@ std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, std::size_t * 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 occurrence of this name will be stripped - * @return A pointer to the same string passed as `path`, but starting at the trimmed portion + * @param path The input file path as a string + * @param roots The name of the root source directorys as a vector of strings. Path up to and + * including the last occurrence of these names will be stripped + * @return The trimmed path as a string */ -const char* TrimSourcePath(const char* path, const char* root = "src"); +std::string TrimSourcePath(const std::string& file_path, const std::vector& roots); } // namespace Common