-
Notifications
You must be signed in to change notification settings - Fork 106
[Comgr] Create a Global Logger Class for Comgr #3116
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: amd-staging
Are you sure you want to change the base?
Changes from 9 commits
39d8e5e
678d708
cb37cdf
a72673a
a7623d9
983e6d5
ec8cb9f
c162e90
7a3a133
b426a0d
3daf40e
17af8a8
bcaa0fe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -84,6 +84,11 @@ bool shouldEmitVerboseLogs() { | |
| return VerboseLogs && StringRef(VerboseLogs) != "0"; | ||
| } | ||
|
|
||
| StringRef getLogLevel() { | ||
| static const char *LogLevel = getenv("AMD_COMGR_LOG_LEVEL"); | ||
| return LogLevel ? StringRef(LogLevel) : StringRef(); | ||
|
Comment on lines
+88
to
+89
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We probably want to be case-insensitive here ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, whats the rationale behind using strings over numbers to denote severity ? I would lean more to using numbers since thats what we currently do in runtime. Its also easier to think about IMO There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Internally Comgr can map an integer to a severity level. But externally I think we should just expose them as numbers There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the case insensitivity is handled in comgr-logger.cpp, are you saying to move that the case lower to here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suppose the sensitivity discussion is irrelevant if we're exposing numbers instead
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh sorry, when I posted that comment, I didn't see the more recent ones. Personally, I think that these labels make it more clear for both assigning LogLevels to a particular output and for toggling the threshold for what logs a user would like to see. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I disagree. There's a few reasons:
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That makes sense, thanks for the clarification. I'll do a 0-20 level logging and let the users have autonomy. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We dont want to do However, we do want to perform numeric comparison rather than equality when checking against the parsed value. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. LogLevel should be an enum as @lamb-j mentioned. This function should return that enum There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Check how Sam is trying to do something similar at #2478
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just to confirm, this means to revert LogLevel away from using numbers (suggested here #3116 (comment)) and back to using the original None, Error, Warning, Debug, Info enum, correct? |
||
| } | ||
|
|
||
| llvm::StringRef getLLVMPath() { | ||
| static const char *EnvLLVMPath = std::getenv("LLVM_PATH"); | ||
| return EnvLLVMPath; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,134 @@ | ||
| //===- comgr-logger.cpp - Global Comgr logging facility -------------------===// | ||
| // | ||
| // Part of Comgr, under the Apache License v2.0 with LLVM Exceptions. See | ||
| // amd/comgr/LICENSE.TXT in this repository for license information. | ||
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| /// | ||
| /// \file | ||
| /// This file implements COMGR::Logger. See comgr-logger.h for the design. | ||
| /// | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "comgr-logger.h" | ||
| #include "comgr-env.h" | ||
|
|
||
| #include "llvm/ADT/SmallString.h" | ||
| #include "llvm/ADT/StringRef.h" | ||
| #include "llvm/Support/FileSystem.h" | ||
|
|
||
| using namespace llvm; | ||
|
|
||
| namespace COMGR { | ||
|
|
||
| namespace { | ||
|
|
||
| // Maximum value on the severity/level scale; values above this are clamped. | ||
| constexpr LogLevel MaxLogLevel = 4; | ||
|
|
||
| // The capture stream is per-thread so that a captured Action on one thread does | ||
| // not collect log output emitted by an unrelated API on another thread. | ||
| thread_local raw_ostream *ThreadCaptureStream = nullptr; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comgr doesnt support multithreaded execution yet. Do we need
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comgr can be run in multiple threads and is thread safe, via a mutex around LLVM calls that cause issues |
||
|
|
||
| // Resolve the configured level from the environment. Delegates the mapping to | ||
| // the testable parseLogLevel(); kept separate so the Logger constructor reads | ||
| // the (cached) environment exactly once. | ||
| LogLevel resolveLevel() { | ||
| return parseLogLevel(env::getLogLevel(), env::shouldEmitVerboseLogs()); | ||
| } | ||
|
|
||
| } // namespace | ||
|
|
||
| LogLevel parseLogLevel(StringRef Requested, bool VerboseFallback) { | ||
| // When the variable is unset or not a valid integer, default to the most | ||
| // verbose level if verbose logs are requested for back-compat with | ||
| // AMD_COMGR_EMIT_VERBOSE_LOGS, otherwise to a low level that still shows | ||
| // errors. | ||
|
theSK2005 marked this conversation as resolved.
|
||
| unsigned Numeric; | ||
| if (Requested.getAsInteger(10, Numeric)) | ||
| return VerboseFallback ? MaxLogLevel : 1; | ||
|
|
||
| return Numeric > MaxLogLevel ? MaxLogLevel : static_cast<LogLevel>(Numeric); | ||
| } | ||
|
|
||
| Logger::Logger() : Level(resolveLevel()), Sink(nullptr) { | ||
| std::optional<StringRef> RedirectLogs = env::getRedirectLogs(); | ||
| if (!RedirectLogs) | ||
| return; | ||
|
|
||
| StringRef RedirectLog = *RedirectLogs; | ||
| if (RedirectLog == "stdout" || RedirectLog == "-") { | ||
| Sink = &outs(); | ||
| } else if (RedirectLog == "stderr") { | ||
| Sink = &errs(); | ||
| } else { | ||
| std::error_code EC; | ||
| SinkFile = std::make_unique<raw_fd_ostream>( | ||
| RedirectLog, EC, sys::fs::OF_Text | sys::fs::OF_Append); | ||
| if (EC) { | ||
| SinkFile.reset(); | ||
| // Record the failure rather than writing it to stderr here. The Logger is | ||
| // constructed before any action's log buffer exists; the action layer | ||
| // surfaces this message into the returned comgr.log via getSinkError(), | ||
| // restoring the pre-Logger behavior of reporting it to the caller. | ||
|
Comment on lines
+69
to
+72
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. shorten? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 need to shorten more |
||
| SinkError = (Twine("unable to redirect log to file '") + RedirectLog + | ||
| "': " + EC.message()) | ||
| .str(); | ||
| } else { | ||
| Sink = SinkFile.get(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Logger::Logger(LogLevel Level, raw_ostream *Sink) : Level(Level), Sink(Sink) {} | ||
|
|
||
| void Logger::writeToSink(StringRef Data) { | ||
| if (!Sink) | ||
| return; | ||
|
|
||
| // Share the same mutex as emit() so teed output and Logger messages never | ||
| // interleave mid-write on the shared sink. The sink is intentionally left | ||
| // unflushed here; the caller flushes once when the action completes. | ||
| std::scoped_lock<std::mutex> Lock(Mutex); | ||
|
theSK2005 marked this conversation as resolved.
|
||
| *Sink << Data; | ||
| } | ||
|
|
||
| void Logger::emit(LogLevel Severity, const Twine &Message) { | ||
| if (!isEnabled(Severity)) | ||
| return; | ||
|
|
||
| SmallString<256> Buffer; | ||
| StringRef Text = Message.toStringRef(Buffer); | ||
| StringRef Prefix = "comgr: "; | ||
|
|
||
| std::scoped_lock<std::mutex> Lock(Mutex); | ||
|
|
||
| raw_ostream *Capture = ThreadCaptureStream; | ||
| if (Sink) { | ||
| *Sink << Prefix << Text << '\n'; | ||
| Sink->flush(); | ||
| } | ||
| // Guard against double-emission if a capture stream happens to alias the | ||
| // sink. | ||
| if (Capture && Capture != Sink) { | ||
| *Capture << Prefix << Text << '\n'; | ||
| Capture->flush(); | ||
| } | ||
| } | ||
|
|
||
| Logger &getLogger() { | ||
| static Logger TheLogger; | ||
| return TheLogger; | ||
| } | ||
|
|
||
| raw_ostream *getThreadCaptureStream() { return ThreadCaptureStream; } | ||
|
|
||
| LogCaptureScope::LogCaptureScope(raw_ostream &OS) | ||
| : Previous(ThreadCaptureStream) { | ||
| ThreadCaptureStream = &OS; | ||
| } | ||
|
|
||
| LogCaptureScope::~LogCaptureScope() { ThreadCaptureStream = Previous; } | ||
|
|
||
| } // namespace COMGR | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,153 @@ | ||
| //===- comgr-logger.h - Global Comgr logging facility ---------------------===// | ||
| // | ||
| // Part of Comgr, under the Apache License v2.0 with LLVM Exceptions. See | ||
| // amd/comgr/LICENSE.TXT in this repository for license information. | ||
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| /// | ||
| /// \file | ||
| /// This file declares COMGR::Logger, a process-global, thread-safe logging | ||
| /// facility shared by every Comgr API. Any API can emit diagnostics at a | ||
| /// configurable severity through Logger::emit, passing a numeric severity on a | ||
| /// 0-to-4 scale. | ||
| /// | ||
| /// Output goes to two independent destinations: | ||
| /// - The global "sink": resolved once from AMD_COMGR_REDIRECT_LOGS (stdout, | ||
| /// stderr, or an appended file). | ||
| /// - An optional per-thread "capture" stream: installed via LogCaptureScope | ||
| /// so emitted messages are also collected into the AMD_COMGR_DATA_KIND_LOG | ||
| /// ("comgr.log") data object returned to the caller. | ||
| /// | ||
| /// All writes are guarded by a mutex, so concurrent callers share the sink | ||
| /// safely. The severity threshold (a value in [0, 4]) is configured via | ||
| /// AMD_COMGR_LOG_LEVEL; see COMGR::env::getLogLevel(). | ||
| /// | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #ifndef COMGR_LOGGER_H | ||
| #define COMGR_LOGGER_H | ||
|
|
||
| #include "llvm/ADT/StringRef.h" | ||
| #include "llvm/ADT/Twine.h" | ||
| #include "llvm/Support/raw_ostream.h" | ||
|
|
||
| #include <cstdint> | ||
| #include <memory> | ||
| #include <mutex> | ||
| #include <string> | ||
|
|
||
| namespace COMGR { | ||
|
|
||
| /// Severity of a log message, and the logger's configured threshold, expressed | ||
| /// on a 0-to-4 scale where 0 silences logging and higher values are more | ||
| /// verbose. A message is emitted only when its severity is non-zero and does | ||
| /// not exceed the configured level (see Logger::isEnabled). Callers choose the | ||
| /// numeric severity passed to Logger::emit; larger numbers are reserved for | ||
| /// more detailed diagnostics. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. shorten?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same for the other function definitions in this file. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 need to shorten more. I think this is mostly okay, but could use some concise language |
||
| using LogLevel = uint8_t; | ||
|
|
||
| /// Parse @p Requested (the value of AMD_COMGR_LOG_LEVEL, which may be empty) into | ||
| /// a threshold on the 0-to-4 scale. The value must be a bare integer; it is | ||
| /// clamped to [0, 4]. When @p Requested is empty or is not a valid integer, | ||
| /// returns 4 if @p VerboseFallback is set (back-compat with | ||
| /// AMD_COMGR_EMIT_VERBOSE_LOGS), otherwise 1. Exposed for testing. | ||
| LogLevel parseLogLevel(llvm::StringRef Requested, bool VerboseFallback); | ||
|
|
||
| /// Process-global, thread-safe logging facility. Obtain the shared instance | ||
| /// through getLogger(); do not construct directly except for tests. | ||
| class Logger { | ||
| public: | ||
| /// Construct a Logger configured from the environment (AMD_COMGR_LOG_LEVEL | ||
| /// and AMD_COMGR_REDIRECT_LOGS). Used for the process-global instance. | ||
| Logger(); | ||
|
|
||
| /// Construct a Logger with an explicit level and non-owning sink (which may | ||
| /// be null). Intended for tests. | ||
| Logger(LogLevel Level, llvm::raw_ostream *Sink); | ||
|
|
||
| Logger(const Logger &) = delete; | ||
| Logger &operator=(const Logger &) = delete; | ||
|
|
||
| /// Return whether a message of the given @p Severity would be emitted under | ||
| /// the current level. A severity of 0 is never emitted, and emission is | ||
| /// disabled entirely when the level is 0. Callers that build expensive | ||
| /// messages can guard their formatting with this. | ||
| bool isEnabled(LogLevel Severity) const { | ||
| return Severity != 0 && Level != 0 && Severity <= Level; | ||
| } | ||
|
|
||
| /// The currently configured maximum severity that will be emitted. | ||
| LogLevel getLevel() const { return Level; } | ||
|
|
||
| /// Return the global sink stream, resolved once from AMD_COMGR_REDIRECT_LOGS | ||
| /// (stdout, stderr, or an appended file), or null when logs are not | ||
| /// redirected. Callers that maintain their own per-action log stream can | ||
| /// reuse this to avoid opening the redirect destination a second time. | ||
| /// Direct writes to the returned stream are NOT serialized against emit(); | ||
| /// callers that tee output into the sink should go through writeToSink() so | ||
| /// they share the logger's mutex. | ||
| llvm::raw_ostream *getSink() const { return Sink; } | ||
|
|
||
| /// Return a diagnostic describing why the redirect sink could not be opened, | ||
| /// or an empty string when redirection was not requested or succeeded. The | ||
| /// Logger is constructed before any per-action log buffer exists, so the | ||
| /// action layer surfaces this into the returned comgr.log when getSink() is | ||
| /// null despite AMD_COMGR_REDIRECT_LOGS being set. | ||
| llvm::StringRef getSinkError() const { return SinkError; } | ||
|
|
||
| /// Write @p Data verbatim to the global sink while holding the logger's mutex, | ||
| /// so callers that tee their own output into the sink (see TeeStream in | ||
| /// comgr.cpp) serialize with emit() instead of racing on the shared stream. | ||
| /// No prefix or newline is added and the sink is not flushed (the caller is | ||
| /// expected to flush once it is done). A no-op when there is no sink. | ||
| void writeToSink(llvm::StringRef Data); | ||
|
|
||
| /// Emit @p Message at @p Severity, prefixed and newline-terminated. Writes to | ||
| /// the global sink and, when one is installed on the calling thread, the | ||
| /// capture stream. Thread-safe. | ||
| void emit(LogLevel Severity, const llvm::Twine &Message); | ||
|
|
||
| private: | ||
| LogLevel Level; | ||
|
|
||
| // The global sink, resolved once at construction. Null when logs are not | ||
| // redirected (AMD_COMGR_REDIRECT_LOGS unset). When pointing at a file, the | ||
| // stream is owned by SinkFile. | ||
| llvm::raw_ostream *Sink; | ||
| std::unique_ptr<llvm::raw_fd_ostream> SinkFile; | ||
|
|
||
| // Diagnostic recorded when AMD_COMGR_REDIRECT_LOGS named a file that could not | ||
| // be opened. Empty otherwise. Surfaced to the caller via getSinkError(). | ||
| std::string SinkError; | ||
|
|
||
| // Guards all writes to Sink and to the active capture stream. | ||
| std::mutex Mutex; | ||
| }; | ||
|
|
||
| /// Return the process-global Logger instance. | ||
| Logger &getLogger(); | ||
|
|
||
| /// Install a capture stream for the current thread for the duration of this | ||
| /// scope. While active, every Logger::emit on this thread also writes into | ||
| /// @p OS, in addition to the global sink. Nesting is supported: the previous | ||
| /// capture stream (if any) is restored on destruction. | ||
| class LogCaptureScope { | ||
| public: | ||
| explicit LogCaptureScope(llvm::raw_ostream &OS); | ||
| ~LogCaptureScope(); | ||
|
|
||
| LogCaptureScope(const LogCaptureScope &) = delete; | ||
| LogCaptureScope &operator=(const LogCaptureScope &) = delete; | ||
|
|
||
| private: | ||
| llvm::raw_ostream *Previous; | ||
| }; | ||
|
|
||
| /// Return the capture stream installed on the calling thread, or null. Exposed | ||
| /// for Logger::emit; callers should use LogCaptureScope to manage it. | ||
| llvm::raw_ostream *getThreadCaptureStream(); | ||
|
|
||
| } // namespace COMGR | ||
|
|
||
| #endif // COMGR_LOGGER_H | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should explain what the log levels denote