Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -158,7 +158,7 @@ DraftMgr.removeDraft(File); Server.removeDocument(File); CDB.invalidate(File); - log(llvm::toString(Contents.takeError())); + elog("Failed to update {0}: {1}", File, Contents.takeError()); return; } Index: clangd/ClangdServer.cpp =================================================================== --- clangd/ClangdServer.cpp +++ clangd/ClangdServer.cpp @@ -118,12 +118,12 @@ auto FS = FSProvider.getFileSystem(); auto Status = FS->status(RootPath); if (!Status) - log("Failed to get status for RootPath " + RootPath + ": " + - Status.getError().message()); + elog("Failed to get status for RootPath {0}: {1}", RootPath, + Status.getError().message()); else if (Status->isDirectory()) this->RootPath = RootPath; else - log("The provided RootPath " + RootPath + " is not a directory."); + elog("The provided RootPath {0} is not a directory.", RootPath); } void ClangdServer::addDocument(PathRef File, StringRef Contents, Index: clangd/ClangdUnit.cpp =================================================================== --- clangd/ClangdUnit.cpp +++ clangd/ClangdUnit.cpp @@ -146,7 +146,7 @@ auto Action = llvm::make_unique(); const FrontendInputFile &MainInput = Clang->getFrontendOpts().Inputs[0]; if (!Action->BeginSourceFile(*Clang, MainInput)) { - log("BeginSourceFile() failed when building AST for " + + log("BeginSourceFile() failed when building AST for {0}", MainInput.getFile()); return llvm::None; } @@ -158,7 +158,7 @@ collectIncludeStructureCallback(Clang->getSourceManager(), &Includes)); if (!Action->Execute()) - log("Execute() failed when building AST for " + MainInput.getFile()); + log("Execute() failed when building AST for {0}", MainInput.getFile()); // UnitDiagsConsumer is local, we can not store it in CompilerInstance that // has a longer lifetime. @@ -306,11 +306,11 @@ compileCommandsAreEqual(Inputs.CompileCommand, OldCompileCommand) && OldPreamble->Preamble.CanReuse(CI, ContentsBuffer.get(), Bounds, Inputs.FS.get())) { - log("Reusing preamble for file " + Twine(FileName)); + vlog("Reusing preamble for file {0}", Twine(FileName)); return OldPreamble; } - log("Preamble for file " + Twine(FileName) + - " cannot be reused. Attempting to rebuild it."); + vlog("Preamble for file {0} cannot be reused. Attempting to rebuild it.", + FileName); trace::Span Tracer("BuildPreamble"); SPAN_ATTACH(Tracer, "File", FileName); @@ -339,13 +339,13 @@ CI.getFrontendOpts().SkipFunctionBodies = false; if (BuiltPreamble) { - log("Built preamble of size " + Twine(BuiltPreamble->getSize()) + - " for file " + Twine(FileName)); + vlog("Built preamble of size {0} for file {1}", BuiltPreamble->getSize(), + FileName); return std::make_shared( std::move(*BuiltPreamble), PreambleDiagnostics.take(), SerializedDeclsCollector.takeIncludes()); } else { - log("Could not build a preamble for file " + Twine(FileName)); + elog("Could not build a preamble for file {0}", FileName); return nullptr; } } @@ -375,7 +375,7 @@ const SourceManager &SourceMgr = AST.getSourceManager(); auto Offset = positionToOffset(SourceMgr.getBufferData(FID), Pos); if (!Offset) { - log("getBeginningOfIdentifier: " + toString(Offset.takeError())); + log("getBeginningOfIdentifier: {0}", Offset.takeError()); return SourceLocation(); } SourceLocation InputLoc = SourceMgr.getComposedLoc(FID, *Offset); Index: clangd/CodeComplete.cpp =================================================================== --- clangd/CodeComplete.cpp +++ clangd/CodeComplete.cpp @@ -309,11 +309,10 @@ if (Include->second) Completion.HeaderInsertion = Includes.insert(Include->first); } else - log(llvm::formatv( - "Failed to generate include insertion edits for adding header " + log("Failed to generate include insertion edits for adding header " "(FileURI='{0}', IncludeHeader='{1}') into {2}", C.IndexResult->CanonicalDeclaration.FileURI, - C.IndexResult->Detail->IncludeHeader, FileName)); + C.IndexResult->Detail->IncludeHeader, FileName); } } @@ -594,11 +593,10 @@ if (NumResults == 0 && !contextAllowsIndex(Context.getKind())) return; if (CCSema) { - log(llvm::formatv( - "Multiple code complete callbacks (parser backtracked?). " + log("Multiple code complete callbacks (parser backtracked?). " "Dropping results from context {0}, keeping results from {1}.", getCompletionKindString(Context.getKind()), - getCompletionKindString(this->CCContext.getKind()))); + getCompletionKindString(this->CCContext.getKind())); return; } // Record the completion context. @@ -799,7 +797,7 @@ &DummyDiagsConsumer, false), Input.VFS); if (!CI) { - log("Couldn't create CompilerInvocation"); + elog("Couldn't create CompilerInvocation"); return false; } auto &FrontendOpts = CI->getFrontendOpts(); @@ -813,8 +811,7 @@ FrontendOpts.CodeCompletionAt.FileName = Input.FileName; auto Offset = positionToOffset(Input.Contents, Input.Pos); if (!Offset) { - log("Code completion position was invalid " + - llvm::toString(Offset.takeError())); + elog("Code completion position was invalid {0}", Offset.takeError()); return false; } std::tie(FrontendOpts.CodeCompletionAt.Line, @@ -837,7 +834,7 @@ SyntaxOnlyAction Action; if (!Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0])) { - log("BeginSourceFile() failed when running codeComplete for " + + log("BeginSourceFile() failed when running codeComplete for {0}", Input.FileName); return false; } @@ -845,7 +842,7 @@ Clang->getPreprocessor().addPPCallbacks( collectIncludeStructureCallback(Clang->getSourceManager(), Includes)); if (!Action.Execute()) { - log("Execute() failed when running codeComplete for " + Input.FileName); + log("Execute() failed when running codeComplete for {0}", Input.FileName); return false; } Action.EndSourceFile(); @@ -965,8 +962,8 @@ format::DefaultFallbackStyle, SemaCCInput.Contents, SemaCCInput.VFS.get()); if (!Style) { - log("Failed to get FormatStyle for file" + SemaCCInput.FileName + ": " + - llvm::toString(Style.takeError()) + ". Fallback is LLVM style."); + log("getStyle() failed for file {0}: {1}. Fallback is LLVM style.", + SemaCCInput.FileName, Style.takeError()); Style = format::getLLVMStyle(); } // If preprocessor was run, inclusions from preprocessor callback should @@ -1012,10 +1009,10 @@ SPAN_ATTACH(Tracer, "merged_results", NBoth); SPAN_ATTACH(Tracer, "returned_results", Output.Completions.size()); SPAN_ATTACH(Tracer, "incomplete", Output.HasMore); - log(llvm::formatv("Code complete: {0} results from Sema, {1} from Index, " - "{2} matched, {3} returned{4}.", - NSema, NIndex, NBoth, Output.Completions.size(), - Output.HasMore ? " (incomplete)" : "")); + log("Code complete: {0} results from Sema, {1} from Index, " + "{2} matched, {3} returned{4}.", + NSema, NIndex, NBoth, Output.Completions.size(), + Output.HasMore ? " (incomplete)" : ""); assert(!Opts.Limit || Output.Completions.size() <= Opts.Limit); // We don't assert that isIncomplete means we hit a limit. // Indexes may choose to impose their own limits even if we don't have one. @@ -1063,9 +1060,8 @@ Recorder->CCSema->getSourceManager()); // FIXME: we should send multiple weighted paths here. Req.ProximityPaths.push_back(FileName); - log(llvm::formatv("Code complete: fuzzyFind(\"{0}\", scopes=[{1}])", - Req.Query, - llvm::join(Req.Scopes.begin(), Req.Scopes.end(), ","))); + vlog("Code complete: fuzzyFind(\"{0}\", scopes=[{1}])", Req.Query, + llvm::join(Req.Scopes.begin(), Req.Scopes.end(), ",")); // Run the query against the index. if (Opts.Index->fuzzyFind( Req, [&](const Symbol &Sym) { ResultsBuilder.insert(Sym); })) @@ -1172,9 +1168,9 @@ ? Scores.Total / Relevance.NameMatch : Scores.Quality; - LLVM_DEBUG(llvm::dbgs() << "CodeComplete: " << First.Name << " (" << Origin - << ") = " << Scores.Total << "\n" - << Quality << Relevance << "\n"); + dlog("CodeComplete: {0} ({1}) = {2}\n{3}{4}\n", First.Name, + llvm::to_string(Origin), Scores.Total, llvm::to_string(Quality), + llvm::to_string(Relevance)); NSema += bool(Origin & SymbolOrigin::AST); NIndex += bool(Origin & ~SymbolOrigin::AST); Index: clangd/Compiler.cpp =================================================================== --- clangd/Compiler.cpp +++ clangd/Compiler.cpp @@ -31,7 +31,7 @@ OS << ":"; } - clangd::log(llvm::formatv("Ignored diagnostic. {0}{1}", Location, Message)); + clangd::log("Ignored diagnostic. {0}{1}", Location, Message); } void IgnoreDiagnostics::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, Index: clangd/Diagnostics.cpp =================================================================== --- clangd/Diagnostics.cpp +++ clangd/Diagnostics.cpp @@ -375,7 +375,7 @@ if (mentionsMainFile(*LastDiag)) Output.push_back(std::move(*LastDiag)); else - log(Twine("Dropped diagnostic outside main file:") + LastDiag->File + ":" + + log("Dropped diagnostic outside main file: {0}: {1}", LastDiag->File, LastDiag->Message); LastDiag.reset(); } Index: clangd/FileDistance.cpp =================================================================== --- clangd/FileDistance.cpp +++ clangd/FileDistance.cpp @@ -35,7 +35,6 @@ #include "Logger.h" #include "llvm/ADT/STLExtras.h" #include -#define DEBUG_TYPE "FileDistance" namespace clang { namespace clangd { @@ -64,8 +63,8 @@ // Keep track of down edges, in case we can use them to improve on this. for (const auto &S : Sources) { auto Canonical = canonicalize(S.getKey()); - LLVM_DEBUG(dbgs() << "Source " << Canonical << " = " << S.second.Cost - << ", MaxUp=" << S.second.MaxUpTraversals << "\n"); + dlog("Source {0} = {1}, MaxUp = {2}", Canonical, S.second.Cost, + S.second.MaxUpTraversals); // Walk up to ancestors of this source, assigning cost. StringRef Rest = Canonical; llvm::hash_code Hash = hash_value(Rest); @@ -134,7 +133,7 @@ Cost += Opts.DownCost; Cache.try_emplace(Hash, Cost); } - LLVM_DEBUG(dbgs() << "distance(" << Path << ") = " << Cost << "\n"); + dlog("distance({0} = {1})", Path, Cost); return Cost; } @@ -143,12 +142,10 @@ if (!R.second) return R.first->getSecond(); if (auto U = clangd::URI::parse(URI)) { - LLVM_DEBUG(dbgs() << "distance(" << URI << ") = distance(" << U->body() - << ")\n"); + dlog("distance({0} = {1})", URI, U->body()); R.first->second = forScheme(U->scheme()).distance(U->body()); } else { - log("URIDistance::distance() of unparseable " + URI + ": " + - llvm::toString(U.takeError())); + log("URIDistance::distance() of unparseable {0}: {1}", URI, U.takeError()); } return R.first->second; } @@ -163,9 +160,8 @@ else consumeError(U.takeError()); } - LLVM_DEBUG(dbgs() << "FileDistance for scheme " << Scheme << ": " - << SchemeSources.size() << "/" << Sources.size() - << " sources\n"); + dlog("FileDistance for scheme {0}: {1}/{2} sources", Scheme, + SchemeSources.size(), Sources.size()); Delegate.reset(new FileDistance(std::move(SchemeSources), Opts)); } return *Delegate; Index: clangd/FindSymbols.cpp =================================================================== --- clangd/FindSymbols.cpp +++ clangd/FindSymbols.cpp @@ -121,16 +121,15 @@ auto &CD = Sym.Definition ? Sym.Definition : Sym.CanonicalDeclaration; auto Uri = URI::parse(CD.FileURI); if (!Uri) { - log(llvm::formatv( - "Workspace symbol: Could not parse URI '{0}' for symbol '{1}'.", - CD.FileURI, Sym.Name)); + log("Workspace symbol: Could not parse URI '{0}' for symbol '{1}'.", + CD.FileURI, Sym.Name); return; } auto Path = URI::resolve(*Uri, HintPath); if (!Path) { - log(llvm::formatv("Workspace symbol: Could not resolve path for URI " - "'{0}' for symbol '{1}'.", - (*Uri).toString(), Sym.Name.str())); + log("Workspace symbol: Could not resolve path for URI '{0}' for symbol " + "'{1}'.", + Uri->toString(), Sym.Name); return; } Location L; @@ -154,16 +153,15 @@ if (auto NameMatch = Filter.match(Sym.Name)) Relevance.NameMatch = *NameMatch; else { - log(llvm::formatv("Workspace symbol: {0} didn't match query {1}", - Sym.Name, Filter.pattern())); + log("Workspace symbol: {0} didn't match query {1}", Sym.Name, + Filter.pattern()); return; } Relevance.merge(Sym); auto Score = evaluateSymbolAndRelevance(Quality.evaluate(), Relevance.evaluate()); - LLVM_DEBUG(llvm::dbgs() << "FindSymbols: " << Sym.Scope << Sym.Name << " = " - << Score << "\n" - << Quality << Relevance << "\n"); + dlog("FindSymbols: {0}{1} = {2}\n{3}{4}\n", Sym.Scope, Sym.Name, Score, + Quality, Relevance); Top.push({Score, std::move(Info)}); }); Index: clangd/GlobalCompilationDatabase.cpp =================================================================== --- clangd/GlobalCompilationDatabase.cpp +++ clangd/GlobalCompilationDatabase.cpp @@ -47,7 +47,7 @@ return std::move(Candidates.front()); } } else { - log("Failed to find compilation database for " + Twine(File)); + log("Failed to find compilation database for {0}", File); } return llvm::None; } Index: clangd/JSONRPCDispatcher.h =================================================================== --- clangd/JSONRPCDispatcher.h +++ clangd/JSONRPCDispatcher.h @@ -30,14 +30,16 @@ // JSONOutput now that we pass Context everywhere. public: JSONOutput(llvm::raw_ostream &Outs, llvm::raw_ostream &Logs, - llvm::raw_ostream *InputMirror = nullptr, bool Pretty = false) - : Pretty(Pretty), Outs(Outs), Logs(Logs), InputMirror(InputMirror) {} + Logger::Level MinLevel, llvm::raw_ostream *InputMirror = nullptr, + bool Pretty = false) + : Pretty(Pretty), MinLevel(MinLevel), Outs(Outs), Logs(Logs), + InputMirror(InputMirror) {} /// Emit a JSONRPC message. void writeMessage(const json::Expr &Result); /// Write a line to the logging stream. - void log(const Twine &Message) override; + void log(Level, const llvm::formatv_object_base &Message) override; /// Mirror \p Message into InputMirror stream. Does nothing if InputMirror is /// null. @@ -48,6 +50,7 @@ const bool Pretty; private: + Logger::Level MinLevel; llvm::raw_ostream &Outs; llvm::raw_ostream &Logs; llvm::raw_ostream *InputMirror; Index: clangd/JSONRPCDispatcher.cpp =================================================================== --- clangd/JSONRPCDispatcher.cpp +++ clangd/JSONRPCDispatcher.cpp @@ -15,6 +15,7 @@ #include "llvm/ADT/StringExtras.h" #include "llvm/Support/Chrono.h" #include "llvm/Support/Errno.h" +#include "llvm/Support/FormatVariadic.h" #include "llvm/Support/SourceMgr.h" #include @@ -67,14 +68,18 @@ Outs << "Content-Length: " << S.size() << "\r\n\r\n" << S; Outs.flush(); } - log(llvm::Twine("--> ") + S + "\n"); + vlog("--> {0}\n", S); } -void JSONOutput::log(const Twine &Message) { +void JSONOutput::log(Logger::Level Level, + const llvm::formatv_object_base &Message) { + if (Level < MinLevel) + return; llvm::sys::TimePoint<> Timestamp = std::chrono::system_clock::now(); trace::log(Message); std::lock_guard Guard(StreamMutex); - Logs << llvm::formatv("[{0:%H:%M:%S.%L}] {1}\n", Timestamp, Message); + Logs << llvm::formatv("{0}[{1:%H:%M:%S.%L}] {2}\n", indicator(Level), + Timestamp, Message); Logs.flush(); } @@ -89,7 +94,7 @@ void clangd::reply(json::Expr &&Result) { auto ID = Context::current().get(RequestID); if (!ID) { - log("Attempted to reply to a notification!"); + elog("Attempted to reply to a notification!"); return; } RequestSpan::attach([&](json::obj &Args) { Args["Reply"] = Result; }); @@ -103,7 +108,7 @@ } void clangd::replyError(ErrorCode code, const llvm::StringRef &Message) { - log("Error " + Twine(static_cast(code)) + ": " + Message); + elog("Error {0}: {1}", static_cast(code), Message); RequestSpan::attach([&](json::obj &Args) { Args["Error"] = json::obj{{"code", static_cast(code)}, {"message", Message.str()}}; @@ -228,9 +233,9 @@ // Content-Length is a mandatory header, and the only one we handle. if (LineRef.consume_front("Content-Length: ")) { if (ContentLength != 0) { - log("Warning: Duplicate Content-Length header received. " - "The previous value for this message (" + - llvm::Twine(ContentLength) + ") was ignored."); + elog("Warning: Duplicate Content-Length header received. " + "The previous value for this message ({0}) was ignored.", + ContentLength); } llvm::getAsUnsignedInteger(LineRef.trim(), 0, ContentLength); continue; @@ -246,8 +251,9 @@ // The fuzzer likes crashing us by sending "Content-Length: 9999999999999999" if (ContentLength > 1 << 30) { // 1024M - log("Refusing to read message with long Content-Length: " + - Twine(ContentLength) + ". Expect protocol errors."); + elog("Refusing to read message with long Content-Length: {0}. " + "Expect protocol errors", + ContentLength); return llvm::None; } if (ContentLength == 0) { @@ -262,8 +268,8 @@ ContentLength - Pos, In); Out.mirrorInput(StringRef(&JSON[Pos], Read)); if (Read == 0) { - log("Input was aborted. Read only " + llvm::Twine(Pos) + - " bytes of expected " + llvm::Twine(ContentLength) + "."); + elog("Input was aborted. Read only {0} bytes of expected {1}.", Pos, + ContentLength); return llvm::None; } clearerr(In); // If we're done, the error was transient. If we're not done, @@ -295,7 +301,7 @@ } if (ferror(In)) { - log("Input error while reading message!"); + elog("Input error while reading message!"); return llvm::None; } else { // Including EOF Out.mirrorInput( @@ -319,21 +325,20 @@ (InputStyle == Delimited) ? readDelimitedMessage : readStandardMessage; while (!IsDone && !feof(In)) { if (ferror(In)) { - log("IO error: " + llvm::sys::StrError()); + elog("IO error: {0}", llvm::sys::StrError()); return; } if (auto JSON = ReadMessage(In, Out)) { if (auto Doc = json::parse(*JSON)) { // Log the formatted message. - log(llvm::formatv(Out.Pretty ? "<-- {0:2}\n" : "<-- {0}\n", *Doc)); + vlog(Out.Pretty ? "<-- {0:2}\n" : "<-- {0}\n", *Doc); // Finally, execute the action for this JSON message. if (!Dispatcher.call(*Doc, Out)) - log("JSON dispatch failed!"); + elog("JSON dispatch failed!"); } else { // Parse error. Log the raw message. - log(llvm::formatv("<-- {0}\n" , *JSON)); - log(llvm::Twine("JSON parse error: ") + - llvm::toString(Doc.takeError())); + vlog("<-- {0}\n", *JSON); + elog("JSON parse error: {0}", llvm::toString(Doc.takeError())); } } } Index: clangd/Logger.h =================================================================== --- clangd/Logger.h +++ clangd/Logger.h @@ -11,24 +11,56 @@ #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_LOGGER_H #include "llvm/ADT/Twine.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/FormatVariadic.h" namespace clang { namespace clangd { -/// Main logging function. -/// Logs messages to a global logger, which can be set up by LoggingSesssion. -/// If no logger is registered, writes to llvm::errs(). -void log(const llvm::Twine &Message); - /// Interface to allow custom logging in clangd. class Logger { public: virtual ~Logger() = default; + enum Level { Debug, Verbose, Info, Error }; + static char indicator(Level L) { return "DVIE"[L]; } + /// Implementations of this method must be thread-safe. - virtual void log(const llvm::Twine &Message) = 0; + virtual void log(Level, const llvm::formatv_object_base &Message) = 0; }; +namespace detail { +const char *debugType(const char *Filename); +void log(Logger::Level, const llvm::formatv_object_base &); +} // namespace detail + +// Clangd logging functions write to a global logger set by LoggingSession. +// If no logger is registered, writes to llvm::errs(). +// All accept llvm::formatv()-style arguments, e.g. log("Text={0}", Text). + +// elog() is used for "loud" errors and warnings. +// This level is often visible to users. +template void elog(const char *Fmt, Ts &&... Vals) { + detail::log(Logger::Error, llvm::formatv(Fmt, std::forward(Vals)...)); +} +// log() is used for information important to understanding a clangd session. +// e.g. the names of LSP messages sent are logged at this level. +// This level could be enabled in production builds to allow later inspection. +template void log(const char *Fmt, Ts &&... Vals) { + detail::log(Logger::Info, llvm::formatv(Fmt, std::forward(Vals)...)); +} +// vlog() is used for details often needed for debugging clangd sessions. +// This level would typically be enabled for clangd developers. +template void vlog(const char *Fmt, Ts &&... Vals) { + detail::log(Logger::Verbose, llvm::formatv(Fmt, std::forward(Vals)...)); +} +// dlog only logs if --debug was passed, or --debug_only=Basename. +// This level would be enabled in a targeted way when debugging. +#define dlog(...) \ + DEBUG_WITH_TYPE( \ + ::clang::clangd::detail::debugType(__FILE__), \ + ::clang::clangd::detail::log(Logger::Debug, llvm::formatv(__VA_ARGS__))) + /// Only one LoggingSession can be active at a time. class LoggingSession { public: Index: clangd/Logger.cpp =================================================================== --- clangd/Logger.cpp +++ clangd/Logger.cpp @@ -25,9 +25,10 @@ LoggingSession::~LoggingSession() { L = nullptr; } -void log(const llvm::Twine &Message) { +void detail::log(Logger::Level Level, + const llvm::formatv_object_base &Message) { if (L) - L->log(Message); + L->log(Level, Message); else { static std::mutex Mu; std::lock_guard Guard(Mu); @@ -35,5 +36,13 @@ } } +const char *detail::debugType(const char *Filename) { + if (const char *Slash = strrchr(Filename, '/')) + return Slash + 1; + if (const char *Backslash = strrchr(Filename, '\\')) + return Backslash + 1; + return Filename; +} + } // namespace clangd } // namespace clang Index: clangd/Protocol.cpp =================================================================== --- clangd/Protocol.cpp +++ clangd/Protocol.cpp @@ -33,16 +33,17 @@ if (auto S = E.asString()) { auto U = URI::parse(*S); if (!U) { - log("Failed to parse URI " + *S + ": " + llvm::toString(U.takeError())); + elog("Failed to parse URI {0}: {1}", *S, U.takeError()); return false; } if (U->scheme() != "file" && U->scheme() != "test") { - log("Clangd only supports 'file' URI scheme for workspace files: " + *S); + elog("Clangd only supports 'file' URI scheme for workspace files: {0}", + *S); return false; } auto Path = URI::resolve(*U); if (!Path) { - log(llvm::toString(Path.takeError())); + log("{0}", Path.takeError()); return false; } R = URIForFile(*Path); Index: clangd/ProtocolHandlers.cpp =================================================================== --- clangd/ProtocolHandlers.cpp +++ clangd/ProtocolHandlers.cpp @@ -32,7 +32,7 @@ if (fromJSON(RawParams, P)) { (Callbacks->*Handler)(P); } else { - log("Failed to decode " + Method + " request."); + elog("Failed to decode {0} request.", Method); } }); } Index: clangd/TUScheduler.cpp =================================================================== --- clangd/TUScheduler.cpp +++ clangd/TUScheduler.cpp @@ -321,14 +321,14 @@ // Remove the old AST if it's still in cache. IdleASTs.take(this); - log("Updating file " + FileName + " with command [" + - Inputs.CompileCommand.Directory + "] " + + log("Updating file {0} with command [{1}] {2}", FileName, + Inputs.CompileCommand.Directory, llvm::join(Inputs.CompileCommand.CommandLine, " ")); // Rebuild the preamble and the AST. std::unique_ptr Invocation = buildCompilerInvocation(Inputs); if (!Invocation) { - log("Could not build CompilerInvocation for file " + FileName); + elog("Could not build CompilerInvocation for file {0}", FileName); return; } @@ -611,8 +611,8 @@ void TUScheduler::remove(PathRef File) { bool Removed = Files.erase(File); if (!Removed) - log("Trying to remove file from TUScheduler that is not tracked. File:" + - File); + elog("Trying to remove file from TUScheduler that is not tracked: {0}", + File); } void TUScheduler::runWithAST( Index: clangd/XRefs.cpp =================================================================== --- clangd/XRefs.cpp +++ clangd/XRefs.cpp @@ -46,12 +46,12 @@ return llvm::None; auto Uri = URI::parse(Loc.FileURI); if (!Uri) { - log("Could not parse URI: " + Loc.FileURI); + log("Could not parse URI: {0}", Loc.FileURI); return llvm::None; } auto Path = URI::resolve(*Uri, HintPath); if (!Path) { - log("Could not resolve URI: " + Loc.FileURI); + log("Could not resolve URI: {0}", Loc.FileURI); return llvm::None; } Location LSPLoc; @@ -182,7 +182,7 @@ FilePath = F->getName(); if (!llvm::sys::path::is_absolute(FilePath)) { if (!SourceMgr.getFileManager().makeAbsolutePath(FilePath)) { - log("Could not turn relative path to absolute: " + FilePath); + log("Could not turn relative path to absolute: {0}", FilePath); return llvm::None; } } Index: clangd/index/SymbolCollector.cpp =================================================================== --- clangd/index/SymbolCollector.cpp +++ clangd/index/SymbolCollector.cpp @@ -52,7 +52,7 @@ if (std::error_code EC = SM.getFileManager().getVirtualFileSystem()->makeAbsolute( AbsolutePath)) - log("Warning: could not make absolute file: " + EC.message()); + log("Warning: could not make absolute file: {0}", EC.message()); if (llvm::sys::path::is_absolute(AbsolutePath)) { // Handle the symbolic link path case where the current working directory // (getCurrentWorkingDirectory) is a symlink./ We always want to the real @@ -86,8 +86,7 @@ return U->toString(); ErrMsg += llvm::toString(U.takeError()) + "\n"; } - log(llvm::Twine("Failed to create an URI for file ") + AbsolutePath + ": " + - ErrMsg); + log("Failed to create an URI for file {0}: {1}", AbsolutePath, ErrMsg); return llvm::None; } Index: clangd/tool/ClangdMain.cpp =================================================================== --- clangd/tool/ClangdMain.cpp +++ clangd/tool/ClangdMain.cpp @@ -98,6 +98,13 @@ PrettyPrint("pretty", llvm::cl::desc("Pretty-print JSON output"), llvm::cl::init(false)); +static llvm::cl::opt LogLevel( + "log", llvm::cl::desc("Verbosity of log messages written to stderr"), + llvm::cl::values(clEnumVal(Logger::Error, "Error messages only"), + clEnumVal(Logger::Info, "High level execution tracing"), + clEnumVal(Logger::Debug, "Low level details")), + llvm::cl::init(Logger::Info)); + static llvm::cl::opt Test( "lit-test", llvm::cl::desc( @@ -223,7 +230,7 @@ if (Tracer) TracingSession.emplace(*Tracer); - JSONOutput Out(llvm::outs(), llvm::errs(), + JSONOutput Out(llvm::outs(), llvm::errs(), LogLevel, InputMirrorStream ? InputMirrorStream.getPointer() : nullptr, PrettyPrint);