From e8f5f6ba64b77badcfb73e4128eae538056cbbb6 Mon Sep 17 00:00:00 2001 From: ykiko Date: Sun, 14 Sep 2025 16:45:07 +0800 Subject: [PATCH] Allow log output to a file (#252) Co-authored-by: star9029 --- CMakeLists.txt | 43 ++++++++++----- include/Async/Network.h | 3 - include/Async/libuv.h | 2 +- include/Support/Logger.h | 88 ----------------------------- include/Support/Logging.h | 80 +++++++++++++++++++++++++++ src/AST/Selection.cpp | 29 +++++----- src/Async/FileSystem.cpp | 4 +- src/Async/Network.cpp | 79 ++------------------------ src/Async/libuv.cpp | 7 ++- src/Compiler/Command.cpp | 21 +++---- src/Compiler/Preamble.cpp | 2 +- src/Driver/clice.cc | 110 +++++++++++++++++++++---------------- src/Driver/unit_tests.cc | 34 ++++++++---- src/Feature/Diagnostic.cpp | 8 +-- src/Feature/Formatting.cpp | 4 +- src/Server/Config.cpp | 5 +- src/Server/Document.cpp | 52 +++++++++--------- src/Server/Indexer.cpp | 4 +- src/Server/Lifecycle.cpp | 8 +-- src/Server/Server.cpp | 16 +++--- src/Support/Logging.cpp | 35 ++++++++++++ tests/unit/Index/USR.cpp | 6 +- xmake.lua | 5 +- 23 files changed, 323 insertions(+), 322 deletions(-) delete mode 100644 include/Support/Logger.h create mode 100644 include/Support/Logging.h create mode 100644 src/Support/Logging.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e093caeb..c4244e41 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,13 @@ endif() message(STATUS "CMake module path: ${CMAKE_MODULE_PATH}") message(STATUS "CMake prefix path: ${CMAKE_PREFIX_PATH}") +if(CLICE_USE_LIBCXX) + string(APPEND CMAKE_CXX_FLAGS "-stdlib=libc++") + string(APPEND CMAKE_EXE_LINKER_FLAGS "-stdlib=libc++") + string(APPEND CMAKE_SHARED_LINKER_FLAGS "-stdlib=libc++") + string(APPEND CMAKE_MODULE_LINKER_FLAGS "-stdlib=libc++") +endif() + message(STATUS "Installing other dependencies, please wait...") # install dependencies include(FetchContent) @@ -33,27 +40,40 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_BUILD_TYPE STREQUAL "Debug") set(ASAN ON CACHE BOOL "" FORCE) endif() -message(STATUS "Fetching tomlplusplus...") -FetchContent_Declare( - tomlplusplus - GIT_REPOSITORY https://github.com/marzer/tomlplusplus.git -) - message(STATUS "Fetching libuv...") FetchContent_Declare( libuv GIT_REPOSITORY https://github.com/libuv/libuv.git GIT_TAG v1.x ) -FetchContent_MakeAvailable(tomlplusplus libuv) + +set(BUILD_SHARED_LIBS OFF) +message(STATUS "Fetching spdlog...") +FetchContent_Declare( + spdlog + GIT_REPOSITORY https://github.com/gabime/spdlog.git + GIT_TAG v1.15.3 +) + +message(STATUS "Fetching tomlplusplus...") +FetchContent_Declare( + tomlplusplus + GIT_REPOSITORY https://github.com/marzer/tomlplusplus.git +) + +FetchContent_MakeAvailable(libuv spdlog tomlplusplus) message(STATUS "Found tomlplusplus: ${tomlplusplus_SOURCE_DIR}") message(STATUS "Found libuv: ${libuv_SOURCE_DIR}") +message(STATUS "Found spdlog: ${spdlog_SOURCE_DIR}") if(WIN32) set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL") + target_compile_definitions(uv_a PRIVATE _CRT_SECURE_NO_WARNINGS) endif() +target_compile_definitions(spdlog PUBLIC SPDLOG_USE_STD_FORMAT=1 SPDLOG_NO_EXCEPTIONS=1) + if (MSVC) # Remove any RTTI or exception enabling flags from CMAKE_CXX_FLAGS string(REPLACE "/EHsc" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") @@ -69,11 +89,6 @@ endif() set(CLICE_LINKER_FLAGS "") -if(CLICE_USE_LIBCXX) - list(APPEND CLICE_CXX_FLAGS "-stdlib=libc++") - list(APPEND CLICE_LINKER_FLAGS "-stdlib=libc++") -endif() - # Build-type specific flags if(CMAKE_BUILD_TYPE STREQUAL "Debug") message(STATUS "Debug build: Enabling -g -O0 -fsanitize=address") @@ -114,7 +129,6 @@ add_library(clice-core STATIC "${CLICE_SOURCES}") # set llvm include and lib path add_library(llvm-libs INTERFACE IMPORTED) - message(STATUS "LLVM include path: ${LLVM_INSTALL_PATH}/include") # add to include directories target_include_directories(llvm-libs INTERFACE "${LLVM_INSTALL_PATH}/include") @@ -188,7 +202,8 @@ target_compile_options(clice-core PUBLIC ${CLICE_CXX_FLAGS}) target_link_options(clice-core PUBLIC ${CLICE_LINKER_FLAGS}) target_include_directories(clice-core PUBLIC "${CMAKE_SOURCE_DIR}/include") -target_link_libraries(clice-core PUBLIC uv_a tomlplusplus::tomlplusplus llvm-libs) +target_link_libraries(clice-core PUBLIC uv_a spdlog::spdlog tomlplusplus::tomlplusplus llvm-libs) +target_compile_definitions(clice-core PUBLIC TOML_EXCEPTIONS=0) # clice executable add_executable(clice "${CMAKE_SOURCE_DIR}/src/Driver/clice.cc") diff --git a/include/Async/Network.h b/include/Async/Network.h index 09ce4098..1e3cc486 100644 --- a/include/Async/Network.h +++ b/include/Async/Network.h @@ -18,9 +18,6 @@ void listen(Callback callback); /// Listen on the given host and port, callback is called when there is a LSP message available. void listen(const char* host, unsigned int port, Callback callback); -/// FIXME: Spawn a new process and listen on its stdin/stdout. -void spawn(llvm::StringRef path, llvm::ArrayRef args, Callback callback); - /// Write a JSON value to the client. Task<> write(json::Value value); diff --git a/include/Async/libuv.h b/include/Async/libuv.h index 99871b1e..49949644 100644 --- a/include/Async/libuv.h +++ b/include/Async/libuv.h @@ -16,7 +16,7 @@ #include #include "Support/TypeTraits.h" -#include "Support/Logger.h" +#include "Support/Logging.h" namespace clice::async { diff --git a/include/Support/Logger.h b/include/Support/Logger.h deleted file mode 100644 index 328ec9da..00000000 --- a/include/Support/Logger.h +++ /dev/null @@ -1,88 +0,0 @@ -#pragma once - -#include "Format.h" -#include "FileSystem.h" - -#include "llvm/Support/CommandLine.h" - -namespace clice::log { - -enum class Level { - TRACE, - DEBUG, - INFO, - WARN, - FATAL, -}; - -struct LogOpt { - Level level = Level::INFO; - bool color = false; -}; - -inline LogOpt log_opt; - -template -void log(Level level, std::string_view fmt, Args&&... args) { -#define Green "\033[32m" -#define Yellow "\033[33m" -#define Cyan "\033[36m" -#define Magenta "\033[35m" -#define Red "\033[31m" -#define None "\033[0m" - namespace chrono = std::chrono; - auto now = chrono::floor(chrono::system_clock::now()); - auto time = now; // chrono::zoned_time(chrono::current_zone(), now); - auto tag = [&] { - switch(level) { - case Level::INFO: return log_opt.color ? Green "[INFO]" None : "[INFO]"; // Green - case Level::WARN: return log_opt.color ? Yellow "[WARN]" None : "[WARN]"; // Yellow - case Level::DEBUG: return log_opt.color ? Cyan "[DEBUG]" None : "[DEBUG]"; // Cyan - case Level::TRACE: - return log_opt.color ? Magenta "[TRACE]" None : "[TRACE]"; // Magenta - case Level::FATAL: - return log_opt.color ? Red "[FATAL ERROR]" None : "[FATAL ERROR]"; // Red - default: llvm::llvm_unreachable_internal("Illegal log level"); - } - }(); - if(level >= log_opt.level) { - llvm::errs() << std::format("[{0:%H:%M:%S}] {1} ", time, tag) - << std::vformat(fmt, std::make_format_args(args...)) << "\n"; - } -#undef Green -#undef Yellow -#undef Cyan -#undef Magenta -#undef Red -#undef None -} - -template -void info(std::format_string fmt, Args&&... args) { - log::log(Level::INFO, fmt.get(), std::forward(args)...); -} - -template -void warn(std::format_string fmt, Args&&... args) { - log::log(Level::WARN, fmt.get(), std::forward(args)...); -} - -template -void debug(std::format_string fmt, Args&&... args) { -#ifndef NDEBUG - log::log(Level::DEBUG, fmt.get(), std::forward(args)...); -#endif -} - -template -void trace(std::format_string fmt, Args&&... args) { - log::log(Level::TRACE, fmt.get(), std::forward(args)...); -} - -template -void fatal [[noreturn]] (std::format_string fmt, Args&&... args) { - log::log(Level::FATAL, fmt.get(), std::forward(args)...); - std::abort(); -} - -} // namespace clice::log diff --git a/include/Support/Logging.h b/include/Support/Logging.h new file mode 100644 index 00000000..1f664ea8 --- /dev/null +++ b/include/Support/Logging.h @@ -0,0 +1,80 @@ +#pragma once + +#include "Format.h" +#include "spdlog/spdlog.h" + +namespace clice::logging { + +using Level = spdlog::level::level_enum; + +struct Options { + Level level = Level::info; + bool color = false; +}; + +extern Options options; + +void create_stderr_logger(std::string_view name, const Options& options); + +void create_file_loggger(std::string_view name, std::string_view dir, const Options& options); + +template +struct logging_rformat { + template StrLike> + consteval logging_rformat(const StrLike& str, + std::source_location location = std::source_location::current()) : + str(str), location(location) {} + + std::format_string str; + std::source_location location; +}; + +template +using logging_format = logging_rformat...>; + +template +void log(spdlog::level::level_enum level, + std::source_location location, + std::format_string fmt, + Args&&... args) { + spdlog::source_loc loc{ + location.file_name(), + static_cast(location.line()), + location.function_name(), + }; + using spdlog_fmt = spdlog::format_string_t; + if constexpr(std::same_as) { + spdlog::log(loc, level, fmt.get(), std::forward(args)...); + } else { + spdlog::log(loc, level, fmt, std::forward(args)...); + } +} + +template +void trace(logging_format fmt, Args&&... args) { + logging::log(spdlog::level::trace, fmt.location, fmt.str, std::forward(args)...); +} + +template +void debug(logging_format fmt, Args&&... args) { + logging::log(spdlog::level::debug, fmt.location, fmt.str, std::forward(args)...); +} + +template +void info(logging_format fmt, Args&&... args) { + logging::log(spdlog::level::info, fmt.location, fmt.str, std::forward(args)...); +} + +template +void warn(logging_format fmt, Args&&... args) { + logging::log(spdlog::level::warn, fmt.location, fmt.str, std::forward(args)...); +} + +template +void fatal [[noreturn]] (logging_format fmt, Args&&... args) { + logging::log(spdlog::level::err, fmt.location, fmt.str, std::forward(args)...); + spdlog::shutdown(); + std::exit(1); +} + +} // namespace clice::logging diff --git a/src/AST/Selection.cpp b/src/AST/Selection.cpp index 8d106449..ed1e6c0e 100644 --- a/src/AST/Selection.cpp +++ b/src/AST/Selection.cpp @@ -4,9 +4,10 @@ #include #include "AST/Selection.h" #include "Compiler/CompilationUnit.h" -#include "Support/Logger.h" +#include "Support/Logging.h" #include "llvm/ADT/BitVector.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringExtras.h" #include "llvm/Support/raw_ostream.h" #include "clang/AST/ASTConcept.h" #include "clang/AST/ASTTypeTraits.h" @@ -947,10 +948,10 @@ private: } if(!checker.may_hit(S)) { - log::debug("{2}skip: {0} {1}", - print_node_to_string(N, print_policy), - S.printToString(SM), - indent()); + logging::debug("{2}skip: {0} {1}", + print_node_to_string(N, print_policy), + S.printToString(SM), + indent()); return true; } @@ -970,10 +971,10 @@ private: // Performs early hit detection for some nodes (on the earlySourceRange). void push(clang::DynTypedNode node) { clang::SourceRange Early = early_source_range(node); - log::debug("{2}push: {0} {1}", - print_node_to_string(node, print_policy), - node.getSourceRange().printToString(SM), - indent()); + logging::debug("{2}push: {0} {1}", + print_node_to_string(node, print_policy), + node.getSourceRange().printToString(SM), + indent()); nodes.emplace_back(); nodes.back().data = std::move(node); nodes.back().parent = stack.top(); @@ -986,7 +987,7 @@ private: // Performs primary hit detection. void pop() { Node& N = *stack.top(); - log::debug("{1}pop: {0}", print_node_to_string(N.data, print_policy), indent(-1)); + logging::debug("{1}pop: {0}", print_node_to_string(N.data, print_policy), indent(-1)); claim_tokens_for(N.data, N.selected); if(N.selected == no_tokens) { N.selected = SelectionTree::Unselected; @@ -1117,7 +1118,7 @@ private: } if(result && result != no_tokens) { - log::debug("{1}hit selection: {0}", S.printToString(SM), indent()); + logging::debug("{1}hit selection: {0}", S.printToString(SM), indent()); } } @@ -1242,9 +1243,9 @@ SelectionTree::SelectionTree(CompilationUnit& unit, LocalSourceRange range) : print_policy.IncludeNewlines = false; auto [begin, end] = range; - log::debug("Computing selection for {0}", - clang::SourceRange(SM.getComposedLoc(fid, begin), SM.getComposedLoc(fid, end)) - .printToString(SM)); + logging::debug("Computing selection for {0}", + clang::SourceRange(SM.getComposedLoc(fid, begin), SM.getComposedLoc(fid, end)) + .printToString(SM)); nodes = SelectionVisitor::collect(unit, print_policy, range, fid); m_root = nodes.empty() ? nullptr : &nodes.front(); diff --git a/src/Async/FileSystem.cpp b/src/Async/FileSystem.cpp index 62b2a3e9..03a37c07 100644 --- a/src/Async/FileSystem.cpp +++ b/src/Async/FileSystem.cpp @@ -1,5 +1,5 @@ #include "Async/FileSystem.h" -#include "Support/Logger.h" +#include "Support/Logging.h" namespace clice::async::awaiter {} @@ -122,7 +122,7 @@ handle::~handle() { uv_fs_t request; int error = uv_fs_close(async::loop, &request, file, nullptr); if(error < 0) { - log::warn("Failed to close file: {}", uv_strerror(error)); + logging::warn("Failed to close file: {}", uv_strerror(error)); } uv_fs_req_cleanup(&request); } diff --git a/src/Async/Network.cpp b/src/Async/Network.cpp index c8d3a962..8d30ac8e 100644 --- a/src/Async/Network.cpp +++ b/src/Async/Network.cpp @@ -1,5 +1,5 @@ #include "Async/Network.h" -#include "Support/Logger.h" +#include "Support/Logging.h" namespace clice::async::net { @@ -30,7 +30,7 @@ void on_read(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) { /// If an error occurred while reading, we can't continue. if(nread < 0) [[unlikely]] { - log::fatal("An error occurred while reading: {0}", uv_strerror(nread)); + logging::fatal("An error occurred while reading: {0}", uv_strerror(nread)); } /// We have at most one connection and use default event loop. So there is no data race @@ -57,7 +57,7 @@ void on_read(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) { task.dispose(); } else { /// If the message is invalid, we can't continue. - log::fatal("Unexpected JSON input: {0}", result); + logging::fatal("Unexpected JSON input: {0}", result); } /// Remove the processed message from the buffer. @@ -107,77 +107,6 @@ void listen(const char* host, unsigned int port, Callback callback) { uv_check_result(uv_listen(uv_cast(server), 1, on_connection)); } -void spawn(llvm::StringRef path, llvm::ArrayRef args, Callback callback) { - static uv_pipe_t in; - static uv_pipe_t out; - static uv_pipe_t err; - - net::callback = std::move(callback); - writer = reinterpret_cast(&in); - - uv_check_result(uv_pipe_init(async::loop, &in, 0)); - uv_check_result(uv_pipe_init(async::loop, &out, 0)); - uv_check_result(uv_pipe_init(async::loop, &err, 0)); - - static uv_process_t process; - static uv_process_options_t options; - - static uv_stdio_container_t stdio[3]; - stdio[0].flags = static_cast(UV_CREATE_PIPE | UV_READABLE_PIPE); - stdio[0].data.stream = (uv_stream_t*)∈ - - stdio[1].flags = static_cast(UV_CREATE_PIPE | UV_WRITABLE_PIPE); - stdio[1].data.stream = (uv_stream_t*)&out; - - stdio[2].flags = static_cast(UV_CREATE_PIPE | UV_WRITABLE_PIPE); - stdio[2].data.stream = (uv_stream_t*)&err; - - options = {[](uv_process_t* req, int64_t exit_status, int term_signal) { - printf("Child process exited with status %ld, signal %d\n", exit_status, term_signal); - uv_close((uv_handle_t*)req, NULL); - }}; - options.stdio = stdio; - options.stdio_count = 3; - - static llvm::SmallString<128> file = path; - options.file = file.c_str(); - - static llvm::SmallString<1024> buffer; - static llvm::SmallVector argv; - std::size_t size = 0; - size += path.size() + 1; - for(auto& arg: args) { - size += arg.size() + 1; - } - buffer.resize_for_overwrite(size); - argv.push_back(buffer.end()); - buffer.append(path); - buffer.push_back('\0'); - for(auto& arg: args) { - argv.push_back(buffer.end()); - buffer.append(arg); - buffer.push_back('\0'); - } - options.args = argv.data(); - - uv_check_result(uv_spawn(async::loop, &process, &options)); - uv_check_result(uv_read_start((uv_stream_t*)&out, net::on_alloc, net::on_read)); - - /// FIXME: This implementation is not correct. - auto on_read = [](uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) { - if(nread > 0) { - log::warn("{0}", llvm::StringRef{buf->base, static_cast(nread)}); - } else if(nread < 0) { - if(nread != UV_EOF) { - log::fatal("An error occurred while reading: {0}", uv_strerror(nread)); - } - uv_close((uv_handle_t*)stream, NULL); - } - }; - - uv_check_result(uv_read_start((uv_stream_t*)&err, net::on_alloc, on_read)); -} - namespace awaiter { struct write { @@ -201,7 +130,7 @@ struct write { uv_write(&req, writer, buf, 2, [](uv_write_t* req, int status) { if(status < 0) { - log::fatal("An error occurred while writing: {0}", uv_strerror(status)); + logging::fatal("An error occurred while writing: {0}", uv_strerror(status)); } auto& awaiter = uv_cast(req); diff --git a/src/Async/libuv.cpp b/src/Async/libuv.cpp index 59cea42b..ef623964 100644 --- a/src/Async/libuv.cpp +++ b/src/Async/libuv.cpp @@ -29,8 +29,11 @@ const std::error_category& category() { /// Use source_location to log the file, line, and function name where the error occurred. void uv_check_result(const int result, const std::source_location location) { if(result < 0) { - log::warn("libuv error: {}", uv_strerror(result)); - log::warn("At {}:{}:{}", location.file_name(), location.line(), location.function_name()); + logging::warn("libuv error: {}", uv_strerror(result)); + logging::warn("At {}:{}:{}", + location.file_name(), + location.line(), + location.function_name()); } } diff --git a/src/Compiler/Command.cpp b/src/Compiler/Command.cpp index 1ac56e51..208b69b6 100644 --- a/src/Compiler/Command.cpp +++ b/src/Compiler/Command.cpp @@ -5,7 +5,7 @@ #include "llvm/Support/CommandLine.h" #include "llvm/Support/Program.h" #include "clang/Driver/Driver.h" -#include "Support/Logger.h" +#include "Support/Logging.h" namespace clice { @@ -153,12 +153,12 @@ auto CompilationDatabase::query_driver(this Self& self, llvm::StringRef driver) bool keep_output_file = true; auto clean_up = llvm::make_scope_exit([&output_path, &keep_output_file]() { if(keep_output_file) { - log::warn("Query driver failed, output file:{}", output_path); + logging::warn("Query driver failed, output file:{}", output_path); return; } if(auto errc = llvm::sys::fs::remove(output_path)) { - log::warn("Fail to remove temporary file: {}", errc.message()); + logging::warn("Fail to remove temporary file: {}", errc.message()); } }); @@ -506,7 +506,7 @@ auto CompilationDatabase::get_command(this Self& self, llvm::StringRef file, Com record(system_header); } } else if(!options.suppress_log) { - log::warn("Failed to query driver:{}, error:{}", driver, driver_info.error()); + logging::warn("Failed to query driver:{}, error:{}", driver, driver_info.error()); } } @@ -532,7 +532,7 @@ auto CompilationDatabase::guess_or_fallback(this Self& self, llvm::StringRef fil // Filter case that dir is /path/to/foo and there's another directory /path/to/foobar if(other.starts_with(dir) && (other.size() == dir.size() || path::is_separator(other[dir.size()]))) { - log::info("Guess command for:{}, from existed file: {}", file, other_file); + logging::info("Guess command for:{}, from existed file: {}", file, other_file); return LookupInfo{info.directory, info.arguments}; } } @@ -557,17 +557,17 @@ auto CompilationDatabase::load_compile_database(this Self& self, std::string filepath = path::join(dir, "compile_commands.json"); auto content = fs::read(filepath); if(!content) { - log::warn("Failed to read CDB file: {}, {}", filepath, content.error()); + logging::warn("Failed to read CDB file: {}, {}", filepath, content.error()); return false; } auto load = self.load_commands(*content, workspace); if(!load) { - log::warn("Failed to load CDB file: {}. {}", filepath, load.error()); + logging::warn("Failed to load CDB file: {}. {}", filepath, load.error()); return false; } - log::info("Load CDB file: {} successfully, {} items loaded", filepath, load->size()); + logging::info("Load CDB file: {} successfully, {} items loaded", filepath, load->size()); return true; }; @@ -575,7 +575,7 @@ auto CompilationDatabase::load_compile_database(this Self& self, return; } - log::warn( + logging::warn( "Can not found any valid CDB file from given directories, search recursively from workspace: {} ...", workspace); @@ -602,7 +602,8 @@ auto CompilationDatabase::load_compile_database(this Self& self, } /// TODO: Add a default command in clice.toml. Or load commands from .clangd ? - log::warn("Can not found any valid CDB file in current workspace, fallback to default mode."); + logging::warn( + "Can not found any valid CDB file in current workspace, fallback to default mode."); } } // namespace clice diff --git a/src/Compiler/Preamble.cpp b/src/Compiler/Preamble.cpp index 086967ac..d6eb36f4 100644 --- a/src/Compiler/Preamble.cpp +++ b/src/Compiler/Preamble.cpp @@ -1,7 +1,7 @@ #include "Compiler/Preamble.h" #include "AST/SourceCode.h" #include "Support/Format.h" -#include "Support/Logger.h" +#include "Support/Logging.h" namespace clice { diff --git a/src/Driver/clice.cc b/src/Driver/clice.cc index 04641446..fb3636a7 100644 --- a/src/Driver/clice.cc +++ b/src/Driver/clice.cc @@ -1,5 +1,5 @@ #include "Server/Server.h" -#include "Support/Logger.h" +#include "Support/Logging.h" #include "Support/Format.h" #include "llvm/Support/InitLLVM.h" @@ -14,18 +14,27 @@ namespace { static cl::OptionCategory category("clice options"); -cl::opt mode{ +enum class Mode { + Pipe, + Socket, + Indexer, +}; + +cl::opt mode{ "mode", cl::cat(category), - cl::value_desc("pipe|socket|indexer"), - cl::init("pipe"), + cl::value_desc("string"), + cl::init(Mode::Pipe), + cl::values(clEnumValN(Mode::Pipe, "pipe", "pipe mode, clice will listen on stdio"), + clEnumValN(Mode::Socket, "socket", "socket mode, clice will listen on host:port")), + /// clEnumValN(Mode::Indexer, "indexer", "indexer mode, to implement") cl::desc("The mode of clice, default is pipe, socket is usually used for debugging"), }; cl::opt host{ "host", cl::cat(category), - cl::value_desc("str"), + cl::value_desc("string"), cl::init("127.0.0.1"), cl::desc("The host to connect to (default: 127.0.0.1)"), }; @@ -61,44 +70,45 @@ cl::opt log_color{ cl::desc("When to use terminal colors, default is auto"), }; -cl::opt log_level{ +cl::opt log_level{ "log-level", cl::cat(category), cl::value_desc("trace|debug|info|warn|fatal"), - cl::init("info"), + cl::init(logging::Level::info), + cl::values(clEnumValN(logging::Level::trace, "trace", ""), + clEnumValN(logging::Level::debug, "debug", ""), + clEnumValN(logging::Level::info, "info", ""), + clEnumValN(logging::Level::warn, "warn", ""), + clEnumValN(logging::Level::err, "fatal", "")), cl::desc("The log level, default is info"), }; -void printVersion(llvm::raw_ostream& os) { - os << std::format("clice version: {}\n", clice::config::version) - << std::format("llvm version: {}\n", clice::config::llvm_version); -} - void init_log() { - using namespace log; + using namespace logging; if(auto color_mode = llvm::StringRef{log_color}; !color_mode.compare("never")) { - log_opt.color = false; + options.color = false; } else if(!color_mode.compare("always")) { - log_opt.color = true; + options.color = true; } else { // Auto mode - log_opt.color = llvm::sys::Process::StandardErrIsDisplayed(); + options.color = llvm::sys::Process::StandardErrIsDisplayed(); } - log_opt.level = llvm::StringSwitch(log_level) - .Case("trace", Level::TRACE) - .Case("debug", Level::DEBUG) - .Case("warn", Level::WARN) - .Case("fatal", Level::FATAL) - .Default(Level::INFO); + options.level = log_level; + + logging::create_stderr_logger("clice", logging::options); } /// Check the command line arguments and initialize the clice. -bool checkArguments(int argc, const char** argv) { +bool check_arguments(int argc, const char** argv) { /// Hide unrelated options. cl::HideUnrelatedOptions(category); // Set version printer and parse command line options - cl::SetVersionPrinter(printVersion); + cl::SetVersionPrinter([](llvm::raw_ostream& os) { + os << std::format("clice version: {}\nllvm version: {}\n", + clice::config::version, + clice::config::llvm_version); + }); cl::ParseCommandLineOptions(argc, argv, "clice is a new generation of language server for C/C++"); @@ -106,40 +116,40 @@ bool checkArguments(int argc, const char** argv) { init_log(); for(int i = 0; i < argc; ++i) { - log::info("argv[{}] = {}", i, argv[i]); + logging::info("argv[{}] = {}", i, argv[i]); } // Handle configuration file loading if(config_path.empty()) { - log::info("No configuration file specified, using default settings"); + logging::info("No configuration file specified, using default settings"); } else { llvm::StringRef path = config_path; // Try to load the configuration file and check the result if(auto result = config::load(argv[0], path); result) { - log::info("Configuration file loaded successfully from: {}", path); + logging::info("Configuration file loaded successfully from: {}", path); } else { - log::warn("Failed to load configuration file from: {} because {}", - path, - result.error()); + logging::warn("Failed to load configuration file from: {} because {}", + path, + result.error()); return false; } } // Initialize resource directory if(resource_dir.empty()) { - log::info("No resource directory specified, using default resource directory"); + logging::info("No resource directory specified, using default resource directory"); // Try to initialize default resource directory if(auto result = fs::init_resource_dir(argv[0]); !result) { - log::warn("Cannot find default resource directory, because {}", result.error()); + logging::warn("Cannot find default resource directory, because {}", result.error()); return false; } } else { // Set and check the specified resource directory fs::resource_dir = resource_dir.getValue(); if(fs::exists(fs::resource_dir)) { - log::info("Resource directory found: {}", fs::resource_dir); + logging::info("Resource directory found: {}", fs::resource_dir); } else { - log::warn("Resource directory not found: {}", fs::resource_dir); + logging::warn("Resource directory not found: {}", fs::resource_dir); return false; } } @@ -154,7 +164,7 @@ int main(int argc, const char** argv) { llvm::setBugReportMsg( "Please report bugs to https://github.com/clice-io/clice/issues and include the crash backtrace"); - if(!checkArguments(argc, argv)) { + if(!check_arguments(argc, argv)) { return 1; } @@ -166,22 +176,28 @@ int main(int argc, const char** argv) { co_await instance.on_receive(value); }; - if(mode == "pipe") { - async::net::listen(loop); - log::info("Server starts listening on stdin/stdout"); - } else if(mode == "socket") { - async::net::listen(host.c_str(), port, loop); - log::info("Server starts listening on {}:{}", host.getValue(), port.getValue()); - } else if(mode == "indexer") { - /// TODO: - } else { - log::fatal("Invalid mode: {}", mode.getValue()); - return 1; + switch(mode) { + case Mode::Pipe: { + async::net::listen(loop); + logging::info("Server starts listening on stdin/stdout"); + break; + } + + case Mode::Socket: { + async::net::listen(host.c_str(), port, loop); + logging::info("Server starts listening on {}:{}", host.getValue(), port.getValue()); + break; + } + + case Mode::Indexer: { + /// TODO: + break; + } } async::run(); - log::info("clice exit normally!"); + logging::info("clice exit normally!"); return 0; } diff --git a/src/Driver/unit_tests.cc b/src/Driver/unit_tests.cc index cb1c701b..455ee6e2 100644 --- a/src/Driver/unit_tests.cc +++ b/src/Driver/unit_tests.cc @@ -19,21 +19,31 @@ namespace cl = llvm::cl; cl::OptionCategory unittest_category("Clice Unittest Options"); -cl::opt test_dir("test-dir", - cl::desc("Specify the test source directory path"), - cl::value_desc("path"), - cl::Required, - cl::cat(unittest_category)); +cl::opt test_dir{ + "test-dir", + cl::desc("Specify the test source directory path"), + cl::value_desc("path"), + cl::Required, + cl::cat(unittest_category), +}; -cl::opt resource_dir("resource-dir", - cl::desc("Resource dir path"), - cl::cat(unittest_category)); +cl::opt resource_dir{ + "resource-dir", + cl::desc("Resource dir path"), + cl::cat(unittest_category), +}; -cl::opt test_filter("test_filter", - cl::desc("A glob pattern to run subset of tests"), - cl::cat(unittest_category)); +cl::opt test_filter{ + "test-filter", + cl::desc("A glob pattern to run subset of tests"), + cl::cat(unittest_category), +}; -cl::opt enable_example("enable-example", cl::init(false), cl::cat(unittest_category)); +cl::opt enable_example{ + "enable-example", + cl::init(false), + cl::cat(unittest_category), +}; std::optional pattern; diff --git a/src/Feature/Diagnostic.cpp b/src/Feature/Diagnostic.cpp index 68e6966d..bd0c1d14 100644 --- a/src/Feature/Diagnostic.cpp +++ b/src/Feature/Diagnostic.cpp @@ -1,7 +1,7 @@ #include "Feature/Diagnostic.h" #include "Compiler/CompilationUnit.h" #include "Server/Convert.h" -#include "Support/Logger.h" +#include "Support/Logging.h" namespace clice::feature { @@ -32,9 +32,9 @@ json::Value diagnostics(PositionEncodingKind kind, PathMapping mapping, Compilat if(level == DiagnosticLevel::Note || level == DiagnosticLevel::Remark) { /// FIXME: figure out why it may be invalid. if(fid.isInvalid()) { - log::info("code: {}, message: {}", - raw_diagnostic.id.diagnostic_code(), - raw_diagnostic.message); + logging::info("code: {}, message: {}", + raw_diagnostic.id.diagnostic_code(), + raw_diagnostic.message); continue; } diff --git a/src/Feature/Formatting.cpp b/src/Feature/Formatting.cpp index 360d8ab6..ff051803 100644 --- a/src/Feature/Formatting.cpp +++ b/src/Feature/Formatting.cpp @@ -1,5 +1,5 @@ #include "Feature/Formatting.h" -#include "Support/Logger.h" +#include "Support/Logging.h" #include "Server/Convert.h" #include "clang/Format/Format.h" @@ -41,7 +41,7 @@ std::vector document_format(llvm::StringRef file, range ? tooling::Range(range->begin, range->length()) : tooling::Range(0, content.size()); auto replacements = format(file, content, selection); if(!replacements) { - log::info("Fail to format for {}\n{}", file, replacements.error()); + logging::info("Fail to format for {}\n{}", file, replacements.error()); return edits; } diff --git a/src/Server/Config.cpp b/src/Server/Config.cpp index 96de7048..1f050d84 100644 --- a/src/Server/Config.cpp +++ b/src/Server/Config.cpp @@ -2,7 +2,8 @@ #include "toml++/toml.hpp" #include "Server/Config.h" -#include "Support/Logger.h" +#include "Support/Logging.h" +#include "Support/FileSystem.h" #include "llvm/ADT/StringMap.h" namespace clice::config { @@ -136,7 +137,7 @@ void init(std::string_view workplace) { replace(config); - log::info("Config initialized successfully: {0}", json::serialize(config)); + logging::info("Config initialized successfully: {0}", json::serialize(config)); return; } diff --git a/src/Server/Document.cpp b/src/Server/Document.cpp index edf36c41..20b2260e 100644 --- a/src/Server/Document.cpp +++ b/src/Server/Document.cpp @@ -1,4 +1,4 @@ -#include "Support/Logger.h" +#include "Support/Logging.h" #include "Server/Server.h" #include "Compiler/Compilation.h" #include "Feature/Diagnostic.h" @@ -12,14 +12,14 @@ void Server::load_cache_info() { auto path = path::join(config::cache.dir, "cache.json"); auto file = llvm::MemoryBuffer::getFile(path); if(!file) { - log::warn("Fail to load cache info, because: {}", file.getError()); + logging::warn("Fail to load cache info, because: {}", file.getError()); return; } llvm::StringRef content = file.get()->getBuffer(); auto json = json::parse(content); if(!json) { - log::warn("Fail to load cache info, invalid json: {}", json.takeError()); + logging::warn("Fail to load cache info, invalid json: {}", json.takeError()); return; } @@ -30,7 +30,7 @@ void Server::load_cache_info() { auto version = object->getString("version"); if(!version) { - log::info("Fail to load cache info, the cache info is outdated"); + logging::info("Fail to load cache info, the cache info is outdated"); return; } @@ -75,7 +75,7 @@ void Server::load_cache_info() { } } - log::info("Load cache info successfully"); + logging::info("Load cache info successfully"); } void Server::save_cache_info() { @@ -105,20 +105,20 @@ void Server::save_cache_info() { llvm::SmallString<128> temp_path; if(auto error = llvm::sys::fs::createTemporaryFile("cache", "json", temp_path)) { - log::warn("Fail to create temporary file for cache info: {}", error.message()); + logging::warn("Fail to create temporary file for cache info: {}", error.message()); return; } auto clean_up = llvm::make_scope_exit([&temp_path]() { if(auto errc = llvm::sys::fs::remove(temp_path)) { - log::warn("Fail to remove temporary file: {}", errc.message()); + logging::warn("Fail to remove temporary file: {}", errc.message()); } }); std::error_code EC; llvm::raw_fd_ostream os(temp_path, EC, llvm::sys::fs::OF_None); if(EC) { - log::warn("Fail to open temporary file for writing: {}", EC.message()); + logging::warn("Fail to open temporary file for writing: {}", EC.message()); return; } @@ -127,18 +127,18 @@ void Server::save_cache_info() { os.close(); if(os.has_error()) { - log::warn("Fail to write cache info to temporary file"); + logging::warn("Fail to write cache info to temporary file"); return; } if(auto error = llvm::sys::fs::rename(temp_path, final_path)) { - log::warn("Fail to rename temporary file to final cache file: {}", error.message()); + logging::warn("Fail to rename temporary file to final cache file: {}", error.message()); return; } clean_up.release(); - log::info("Save cache info successfully"); + logging::info("Save cache info successfully"); } namespace { @@ -179,7 +179,7 @@ async::Task build_pch_task(CompilationDatabase::LookupInfo& info, if(!fs::exists(config::cache.dir)) { auto error = fs::create_directories(config::cache.dir); if(error) { - log::warn("Fail to create directory for PCH building: {}", config::cache.dir); + logging::warn("Fail to create directory for PCH building: {}", config::cache.dir); co_return false; } } @@ -200,7 +200,7 @@ async::Task build_pch_task(CompilationDatabase::LookupInfo& info, command += argument; } - log::info("Start building PCH for {}, command: [{}]", path, command); + logging::info("Start building PCH for {}, command: [{}]", path, command); command.clear(); PCHInfo pch; @@ -221,14 +221,14 @@ async::Task build_pch_task(CompilationDatabase::LookupInfo& info, }); if(!success) { - log::warn("Building PCH fails for {}, Because: {}", path, message); + logging::warn("Building PCH fails for {}, Because: {}", path, message); for(auto& diagnostic: *diagnostics) { - log::warn("{}", diagnostic.message); + logging::warn("{}", diagnostic.message); } co_return false; } - log::info("Building PCH successfully for {}", path); + logging::info("Building PCH successfully for {}", path); /// Update the built PCH info. open_file->pch = std::move(pch); @@ -255,7 +255,7 @@ async::Task Server::build_pch(std::string file, std::string content) { /// Check update ... if(open_file->pch && !check_pch_update(content, bound, info, *open_file->pch)) { /// If not need update, return directly. - log::info("PCH is already up-to-date for {}", file); + logging::info("PCH is already up-to-date for {}", file); co_return true; } @@ -264,12 +264,12 @@ async::Task Server::build_pch(std::string file, std::string content) { if(!task.empty()) { if(task.finished()) { task.release().destroy(); - log::info("Release old pch task!"); + logging::info("Release old pch task!"); } else { task.cancel(); task.dispose(); } - log::info("Cancel old PCH building task!"); + logging::info("Cancel old PCH building task!"); } /// Schedule the new building task. @@ -299,7 +299,7 @@ async::Task<> Server::build_ast(std::string path, std::string content) { auto pch = file->pch; if(!pch) { - log::fatal("Expected PCH built at this point"); + logging::fatal("Expected PCH built at this point"); } CommandOptions options; @@ -318,16 +318,16 @@ async::Task<> Server::build_ast(std::string path, std::string content) { auto ast = co_await async::submit([&] { return compile(params); }); if(!ast) { /// FIXME: Fails needs cancel waiting tasks. - log::warn("Building AST fails for {}, Beacuse: {}", path, ast.error()); + logging::warn("Building AST fails for {}, Beacuse: {}", path, ast.error()); for(auto& diagnostic: *file->diagnostics) { - log::warn("{}", diagnostic.message); + logging::warn("{}", diagnostic.message); } co_return; } /// Run Clang-Tidy if(config::server.clang_tidy) { - log::warn( + logging::warn( "clang-tidy is not fully supported yet. Tracked in https://github.com/clice-project/clice/issues/90."); } @@ -349,7 +349,7 @@ async::Task<> Server::build_ast(std::string path, std::string content) { /// Dispose the task so that it will destroyed when task complete. file->ast_build_task.dispose(); - log::info("Building AST successfully for {}", path); + logging::info("Building AST successfully for {}", path); } async::Task> Server::add_document(std::string path, std::string content) { @@ -363,12 +363,12 @@ async::Task> Server::add_document(std::string path, st if(!task.empty()) { if(task.finished()) { task.release().destroy(); - log::info("Release old AST building Task!"); + logging::info("Release old AST building Task!"); } else { task.cancel(); task.dispose(); } - log::info("Cancel old AST building Task!"); + logging::info("Cancel old AST building Task!"); } /// Create and schedule a new task. diff --git a/src/Server/Indexer.cpp b/src/Server/Indexer.cpp index 57c76dca..6e8eb8a9 100644 --- a/src/Server/Indexer.cpp +++ b/src/Server/Indexer.cpp @@ -2,7 +2,7 @@ #include "Compiler/Compilation.h" #include "Index/Index.h" #include "Server/Indexer.h" -#include "Support/Logger.h" +#include "Support/Logging.h" namespace clice { @@ -53,7 +53,7 @@ async::Task<> Indexer::index(llvm::StringRef file) { auto AST = co_await async::submit([&] { return compile(params); }); if(!AST) { - log::info("Fail to index background file {}", file); + logging::info("Fail to index background file {}", file); co_return; } diff --git a/src/Server/Lifecycle.cpp b/src/Server/Lifecycle.cpp index 3a4aaf0a..cbf851f2 100644 --- a/src/Server/Lifecycle.cpp +++ b/src/Server/Lifecycle.cpp @@ -3,9 +3,9 @@ namespace clice { async::Task Server::on_initialize(proto::InitializeParams params) { - log::info("Initialize from client: {}, version: {}", - params.clientInfo.name, - params.clientInfo.version); + logging::info("Initialize from client: {}, version: {}", + params.clientInfo.name, + params.clientInfo.version); /// FIXME: adjust position encoding. kind = PositionEncodingKind::UTF16; @@ -17,7 +17,7 @@ async::Task Server::on_initialize(proto::InitializeParams params) { return *params.rootUri; } - log::fatal("The client should provide one workspace folder or rootUri at least!"); + logging::fatal("The client should provide one workspace folder or rootUri at least!"); })()); /// Initialize configuration. diff --git a/src/Server/Server.cpp b/src/Server/Server.cpp index 7ec03a8d..ee6f4d70 100644 --- a/src/Server/Server.cpp +++ b/src/Server/Server.cpp @@ -1,4 +1,4 @@ -#include "Support/Logger.h" +#include "Support/Logging.h" #include "Server/Server.h" namespace clice { @@ -121,7 +121,7 @@ Server::Server() { async::Task<> Server::on_receive(json::Value value) { auto object = value.getAsObject(); if(!object) [[unlikely]] { - log::fatal("Invalid LSP message, not an object: {}", value); + logging::fatal("Invalid LSP message, not an object: {}", value); } /// If the json object has an `id`, it's a request, @@ -132,7 +132,7 @@ async::Task<> Server::on_receive(json::Value value) { if(auto result = object->getString("method")) { method = *result; } else [[unlikely]] { - log::warn("Invalid LSP message, method not found: {}", value); + logging::warn("Invalid LSP message, method not found: {}", value); if(id) { co_await response(std::move(*id), proto::ErrorCodes::InvalidRequest, @@ -149,7 +149,7 @@ async::Task<> Server::on_receive(json::Value value) { /// Handle request and notification separately. auto it = callbacks.find(method); if(it == callbacks.end()) { - log::info("Ignore unhandled method: {}", method); + logging::info("Ignore unhandled method: {}", method); co_return; } @@ -157,24 +157,24 @@ async::Task<> Server::on_receive(json::Value value) { auto current_id = client_request_id++; auto start_time = std::chrono::steady_clock::now(); - log::info("<-- Handling request: {}({})", method, current_id); + logging::info("<-- Handling request: {}({})", method, current_id); auto result = co_await it->second(*this, std::move(params)); co_await response(std::move(*id), std::move(result)); auto end_time = std::chrono::steady_clock::now(); auto duration = std::chrono::duration_cast(end_time - start_time); - log::info("--> Handled request: {}({}) {}ms", method, current_id, duration.count()); + logging::info("--> Handled request: {}({}) {}ms", method, current_id, duration.count()); } else { auto start_time = std::chrono::steady_clock::now(); - log::info("<-- Handling notification: {}", method); + logging::info("<-- Handling notification: {}", method); auto result = co_await it->second(*this, std::move(params)); auto end_time = std::chrono::steady_clock::now(); auto duration = std::chrono::duration_cast(end_time - start_time); - log::info("--> Handled notification: {} {}ms", method, duration.count()); + logging::info("--> Handled notification: {} {}ms", method, duration.count()); } co_return; diff --git a/src/Support/Logging.cpp b/src/Support/Logging.cpp new file mode 100644 index 00000000..b42d1ffe --- /dev/null +++ b/src/Support/Logging.cpp @@ -0,0 +1,35 @@ +#include "Support/Logging.h" +#include "Support/FileSystem.h" + +#include "spdlog/sinks/stdout_sinks.h" +#include "spdlog/sinks/stdout_color_sinks.h" +#include "spdlog/sinks/basic_file_sink.h" + +namespace clice::logging { + +Options options; + +void create_stderr_logger(std::string_view name, const Options& options) { + std::shared_ptr logger; + if(options.color) { + logger = spdlog::stderr_color_mt(std::string(name)); + } else { + logger = spdlog::stderr_logger_mt(std::string(name)); + } + logger->set_level(options.level); + logger->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%^%l%$] [thread %t] [%s:%#] %v"); + spdlog::set_default_logger(std::move(logger)); +} + +void create_file_loggger(std::string_view name, std::string_view dir, const Options& options) { + auto now = std::chrono::system_clock::now(); + auto filename = std::format("{:%Y-%m-%d_%H-%M-%S}.log", now); + auto path = path::join(dir, filename); + + auto logger = spdlog::basic_logger_mt(std::string(name), path); + logger->set_level(options.level); + logger->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%^%l%$] [thread %t] [%s:%#] %v"); + spdlog::set_default_logger(std::move(logger)); +} + +} // namespace clice::logging diff --git a/tests/unit/Index/USR.cpp b/tests/unit/Index/USR.cpp index 649860a5..9dafb1d8 100644 --- a/tests/unit/Index/USR.cpp +++ b/tests/unit/Index/USR.cpp @@ -1,4 +1,4 @@ -#include "Support/Logger.h" +#include "Support/Logging.h" #include "Test/Tester.h" #include "Index/USR.h" #include "clang/AST/DeclBase.h" @@ -36,7 +36,7 @@ struct GetUSRVisitor : public clang::RecursiveASTVisitor { if(offset.has_value() && USR.has_value()) { USRs[*offset] = USRInfo{*USR, *offset, decl}; - // log::info("USR: {} at {}:{}", USR->str(), pos->line, pos->character); + // logging::info("USR: {} at {}:{}", USR->str(), pos->line, pos->character); } return true; @@ -61,7 +61,7 @@ struct USRTester : public Tester { llvm::StringRef lookup(llvm::StringRef key) { auto iter = USRs.find((*this)["main.cpp", key]); if(iter == USRs.end()) { - log::fatal("USR not found for key: {}", key); + logging::fatal("USR not found for key: {}", key); } return iter->second.USR; } diff --git a/xmake.lua b/xmake.lua index 7e499515..c42503c6 100644 --- a/xmake.lua +++ b/xmake.lua @@ -40,7 +40,8 @@ if has_config("release") then includes("@builtin/xpack") end -add_requires(libuv_require, "toml++") +add_defines("TOML_EXCEPTIONS=0") +add_requires(libuv_require, "spdlog[header_only=n,std_format,noexcept]" ,"toml++") add_requires("llvm", {system = false}) add_rules("mode.release", "mode.debug", "mode.releasedbg") @@ -52,7 +53,7 @@ target("clice-core") add_files("src/**.cpp|Driver/*.cpp") add_includedirs("include", {public = true}) - add_packages("libuv", "toml++", {public = true}) + add_packages("libuv", "spdlog", "toml++", {public = true}) if is_mode("debug") then add_packages("llvm", {