Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions amd/comgr/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ set(SOURCES
src/comgr-hotswap-patch-wmma-scale16.cpp
src/comgr-hotswap-patch-wmma-split.cpp
src/comgr-libcxx-headers.cpp
src/comgr-logger.cpp
src/comgr-metadata.cpp
src/comgr-signal.cpp
src/comgr-spirv-command.cpp
Expand Down
26 changes: 21 additions & 5 deletions amd/comgr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,12 +212,28 @@ include:
* `AMD_COMGR_SAVE_LLVM_TEMPS`: If this is set, Comgr forwards `--save-temps=obj`
to Clang Driver invocations.
* `AMD_COMGR_REDIRECT_LOGS`: If this is not set, or is set to "0", logs are
returned to the caller as normal. If this is set to "stdout"/"-" or "stderr",
logs are instead redirected to the standard output or error stream,
respectively. If this is set to any other value, it is interpreted as a
filename which logs should be appended to.
only returned to the caller (via `AMD_COMGR_DATA_KIND_LOG`) as normal. If this
is set to "stdout"/"-" or "stderr", logs are additionally copied to the
standard output or error stream, respectively. If this is set to any other
value, it is interpreted as a filename which logs are additionally appended
to. In all cases the logs are still returned to the caller as well; setting
this variable copies them to the extra destination, it does not move them.
When a filename is used, it is opened once and kept open for the lifetime of
the process, so the destination should not be rotated or removed while Comgr
is in use.
* `AMD_COMGR_EMIT_VERBOSE_LOGS`: If this is set, and is not "0", logs will
include additional Comgr-specific informational messages.
include additional Comgr-specific informational messages. Equivalent to
setting `AMD_COMGR_LOG_LEVEL=debug`, but only when `AMD_COMGR_LOG_LEVEL` is
not set itself: an explicit `AMD_COMGR_LOG_LEVEL` always takes precedence (so
`AMD_COMGR_LOG_LEVEL=none` suppresses logs even if this is enabled).
* `AMD_COMGR_LOG_LEVEL`: Controls the severity threshold of the global Comgr
logger. Valid values, from least to most verbose, are "none", "error",
"warning", "info", and "debug" (case-insensitive). The chosen level enables
that severity and every less-verbose (more severe) one; for example,
"warning" emits error and warning messages but suppresses info and debug.
"none" suppresses all messages. When set, this takes precedence over
`AMD_COMGR_EMIT_VERBOSE_LOGS`. If unset, the level defaults to "debug" when
`AMD_COMGR_EMIT_VERBOSE_LOGS` is enabled, and to "error" otherwise.
Comment thread
MixedMatched marked this conversation as resolved.
Outdated
* `AMD_COMGR_TIME_STATISTICS`: If this is set, and is not "0", logs will
include additional Comgr-specific timing information for compilation actions.
* `AMD_COMGR_TIME_STATISTICS_GRANULARITY`: If this is set to "us" or "ns",
Expand Down
17 changes: 13 additions & 4 deletions amd/comgr/include/amd_comgr.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,21 @@ extern "C" {
* forwards "--save-temps=obj" to Clang Driver invocations
* - @p AMD_COMGR_REDIRECT_LOGS: If this is not set, or is set to "0", logs are
* returned to the caller as normal. If this is set to "stdout"/"-" or
* "stderr", logs are instead redirected to the standard output or error
* "stderr", logs are additionally copied to the standard output or error
* stream, respectively. If this is set to any other value, it is interpreted
* as a filename which logs should be appended to. Logs may be redirected
* irrespective of whether logging is enabled.
* as a filename which logs are appended to. In all cases logs are still
* returned to the caller; this variable copies them, it does not move them.
* Logs may be redirected irrespective of whether logging is enabled.
* - @p AMD_COMGR_EMIT_VERBOSE_LOGS: If this is set, and is not "0", logs will
* include additional Comgr-specific informational messages.
* include additional Comgr-specific informational messages. Equivalent to
* AMD_COMGR_LOG_LEVEL=debug, unless AMD_COMGR_LOG_LEVEL is set, which takes
* precedence.
* - @p AMD_COMGR_LOG_LEVEL: Sets the severity threshold of the Comgr logger.
* Values from least to most verbose: "none", "error", "warning", "info",
* "debug" (case-insensitive). A level enables that severity and all more
* severe ones. Takes precedence over AMD_COMGR_EMIT_VERBOSE_LOGS; if unset,
* defaults to "debug" when AMD_COMGR_EMIT_VERBOSE_LOGS is enabled, else
* "error".
*/

/** \defgroup symbol_versions_group Symbol Versions
Expand Down
5 changes: 5 additions & 0 deletions amd/comgr/src/comgr-env.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably want to be case-insensitive here ?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The 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?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The 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.

@chinmaydd chinmaydd Jun 30, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree. There's a few reasons:

  • The precedent has already been set by various other projects in the ROCm / HIP ecosystem. See 1, 2, 3 etc. Since Comgr is a member of the ROCm ecosystem we want to play along.

  • Its intuitive to think about "big number = more logging". This way you dont have to worry about the exact number assigned to the level of logging. You can just say COMGR_LOG_LEVEL = 100 and the user gets what they want. Very likely, they are not going to be choosing between these levels. They'd want the maximum possible logging when debugging an error. In fact, I have been using AMD_LOG_LEVEL=7 for runtime and I have no clue what the individual numbers map to.

  • Tangential to point 2, you cant really establish an intuitive relationship between the various levels. How are "info" and "warning" related ? Does "error" give me more or less logging than "debug" ? Its confusing.

@theSK2005 theSK2005 Jun 30, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We dont want to do 0-100 exactly. We still want to do 0-4 or 0-5 (depending on the available levels).

However, we do want to perform numeric comparison rather than equality when checking against the parsed value.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check how Sam is trying to do something similar at #2478

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The 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;
Expand Down
5 changes: 5 additions & 0 deletions amd/comgr/src/comgr-env.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ std::optional<llvm::StringRef> getRedirectLogs();
/// Return whether the environment requests verbose logging.
bool shouldEmitVerboseLogs();

/// Return the raw value of AMD_COMGR_LOG_LEVEL (e.g. "none", "error",
/// "warning", "info", "debug"), or an empty string if unset. The Logger parses
/// this into a COMGR::LogLevel.
llvm::StringRef getLogLevel();

/// Return whether the environment requests time statistics collection.
bool needTimeStatistics();

Expand Down
156 changes: 156 additions & 0 deletions amd/comgr/src/comgr-logger.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
//===- 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 {

// 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;

@chinmaydd chinmaydd Jun 29, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comgr doesnt support multithreaded execution yet. Do we need thread_local ?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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());
}

StringRef severityPrefix(LogLevel Severity) {
switch (Severity) {
Comment thread
MixedMatched marked this conversation as resolved.
Outdated
case LogLevel::Error:
return "comgr: error: ";
case LogLevel::Warning:
return "comgr: warning: ";
case LogLevel::Info:
return "comgr: info: ";
case LogLevel::Debug:
return "comgr: debug: ";
}
return "comgr: ";
}

} // namespace

LogLevel parseLogLevel(StringRef Requested, bool VerboseFallback) {
// When the variable is unset or unrecognized, default to Debug if verbose
// logs are requested for back-compat with AMD_COMGR_EMIT_VERBOSE_LOGS,
// otherwise to Error. An explicit, recognized value always wins (including
// "none", which silences logging even when verbose logs are requested).
LogLevel Fallback = VerboseFallback ? LogLevel::Debug : LogLevel::Error;
if (Requested.empty())
return Fallback;

if (Requested.equals_insensitive("none"))
Comment thread
MixedMatched marked this conversation as resolved.
Outdated
return LogLevel::None;
if (Requested.equals_insensitive("error"))
return LogLevel::Error;
if (Requested.equals_insensitive("warning"))
return LogLevel::Warning;
if (Requested.equals_insensitive("info"))
return LogLevel::Info;
if (Requested.equals_insensitive("debug"))
return LogLevel::Debug;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commented out code should be removed

return Fallback;
}

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shorten?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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);
Comment thread
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 = severityPrefix(Severity);

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
Loading
Loading