[orc-rt] Add os_log logging backend (Darwin only) (#209081)
Implement the os_log backend, selected with ORC_RT_LOG_BACKEND=os_log. With this backend selected, the ORC_RT_LOG macros expand at the call site to os_log_with_type. Each ORC runtime log level maps to an os_log_type_t (error -> ERROR, warning -> DEFAULT, info -> INFO, debug -> DEBUG). The compile-time ORC_RT_LOG_LEVEL floor still applies, but runtime filtering is left to the system (e.g. `log config`), so the printf backend's ORC_RT_LOG and ORC_RT_LOG_OUTPUT have no effect. Records go to the "org.llvm.orc-rt" subsystem with the category as the os_log category. (Per-category handles are created on first use and cached). Add regression tests under test/regression/logging/os_log: no-printf-output.test checks that the backend produces no console output, and an opt-in delivery test (llvm-lit --param run-os-log-tests=1) scrapes `log show` to confirm records reach the expected subsystem and category.
This commit is contained in:
parent
d8df9b5e92
commit
b1c56fb53a
8 changed files with 194 additions and 2 deletions
|
|
@ -27,7 +27,10 @@
|
|||
|* "none" backend emits no code (but still type-checks every call site). The *|
|
||||
|* "printf" backend writes each message to stderr, or to the file named by *|
|
||||
|* the ORC_RT_LOG_OUTPUT environment variable; the ORC_RT_LOG variable sets a *|
|
||||
|* runtime level threshold (error, warning, info, debug, or off). *|
|
||||
|* runtime level threshold (error, warning, info, debug, or off). The *|
|
||||
|* "os_log" backend (Apple) routes records to the unified logging system, *|
|
||||
|* where filtering is configured out of band (e.g. `log config`), so *|
|
||||
|* ORC_RT_LOG and ORC_RT_LOG_OUTPUT have no effect. *|
|
||||
|* *|
|
||||
|* IMPORTANT: log arguments should be free of observable side effects. *|
|
||||
|* Whether they are evaluated is backend- and level-dependent, so they must *|
|
||||
|
|
@ -41,6 +44,10 @@
|
|||
#include "orc-rt-c/Compiler.h"
|
||||
#include "orc-rt-c/config.h"
|
||||
|
||||
#if ORC_RT_LOG_BACKEND == ORC_RT_LOG_BACKEND_OS_LOG
|
||||
#include <os/log.h>
|
||||
#endif
|
||||
|
||||
ORC_RT_C_EXTERN_C_BEGIN
|
||||
|
||||
/**
|
||||
|
|
@ -175,7 +182,84 @@ void orc_rt_log_printf(orc_rt_log_Level Level, orc_rt_log_Category Category,
|
|||
#endif
|
||||
|
||||
#elif ORC_RT_LOG_BACKEND == ORC_RT_LOG_BACKEND_OS_LOG
|
||||
#error "The os_log logging backend is not yet implemented."
|
||||
|
||||
/*
|
||||
* Per-category os_log_t cache, homed in Logging_oslog.cpp. Slots start null and
|
||||
* are filled lazily (and read) atomically by orc_rt_log_osLogHandle. An
|
||||
* implementation detail of that accessor; do not use directly.
|
||||
*/
|
||||
extern os_log_t orc_rt_log_OSLogHandles[orc_rt_log_Category_Count];
|
||||
|
||||
/*
|
||||
* Cold path for orc_rt_log_osLogHandle: creates the handle for Category (in the
|
||||
* "org.llvm.orc-rt" subsystem), caches it, and returns it. Defined in
|
||||
* Logging_oslog.cpp.
|
||||
*/
|
||||
os_log_t
|
||||
orc_rt_log_osLogHandleSlow(orc_rt_log_Category Category) ORC_RT_C_NOTHROW;
|
||||
|
||||
/*
|
||||
* Returns the os_log_t handle for the given category, creating it on first use.
|
||||
* Inlined, with a per-category cache, so os_log_with_type's callsite enabled-
|
||||
* check stays cheap; the warm path is just an atomic load. Returns
|
||||
* OS_LOG_DEFAULT for an out-of-range category. Not called directly: use
|
||||
* ORC_RT_LOG. (__atomic_* builtins are used rather than <atomic>/<stdatomic.h>
|
||||
* so this compiles the same in C and C++; the os_log backend is Clang-only.)
|
||||
*/
|
||||
static inline os_log_t orc_rt_log_osLogHandle(orc_rt_log_Category Category) {
|
||||
if (Category < 0 || Category >= orc_rt_log_Category_Count)
|
||||
return OS_LOG_DEFAULT;
|
||||
os_log_t Handle =
|
||||
__atomic_load_n(&orc_rt_log_OSLogHandles[Category], __ATOMIC_ACQUIRE);
|
||||
if (!Handle)
|
||||
Handle = orc_rt_log_osLogHandleSlow(Category);
|
||||
return Handle;
|
||||
}
|
||||
|
||||
/*
|
||||
* Each level maps to an os_log_type_t and expands, at the call site, directly
|
||||
* to os_log_with_type -- os_log needs the format string as a literal and the
|
||||
* type as a compile-time constant, so it cannot be routed through a runtime
|
||||
* function. Runtime filtering is left to the system (e.g. `log config`);
|
||||
* ORC_RT_LOG has no effect here. A level below the ORC_RT_LOG_LEVEL floor still
|
||||
* compiles out to ORC_RT_LOG_DISABLED.
|
||||
*/
|
||||
#if ORC_RT_LOG_LEVEL_ERROR >= ORC_RT_LOG_LEVEL
|
||||
#define ORC_RT_LOG_Error(Category, ...) \
|
||||
os_log_with_type(orc_rt_log_osLogHandle(Category), OS_LOG_TYPE_ERROR, \
|
||||
__VA_ARGS__)
|
||||
#else
|
||||
#define ORC_RT_LOG_Error(Category, ...) \
|
||||
ORC_RT_LOG_DISABLED(Category, __VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#if ORC_RT_LOG_LEVEL_WARNING >= ORC_RT_LOG_LEVEL
|
||||
#define ORC_RT_LOG_Warning(Category, ...) \
|
||||
os_log_with_type(orc_rt_log_osLogHandle(Category), OS_LOG_TYPE_DEFAULT, \
|
||||
__VA_ARGS__)
|
||||
#else
|
||||
#define ORC_RT_LOG_Warning(Category, ...) \
|
||||
ORC_RT_LOG_DISABLED(Category, __VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#if ORC_RT_LOG_LEVEL_INFO >= ORC_RT_LOG_LEVEL
|
||||
#define ORC_RT_LOG_Info(Category, ...) \
|
||||
os_log_with_type(orc_rt_log_osLogHandle(Category), OS_LOG_TYPE_INFO, \
|
||||
__VA_ARGS__)
|
||||
#else
|
||||
#define ORC_RT_LOG_Info(Category, ...) \
|
||||
ORC_RT_LOG_DISABLED(Category, __VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#if ORC_RT_LOG_LEVEL_DEBUG >= ORC_RT_LOG_LEVEL
|
||||
#define ORC_RT_LOG_Debug(Category, ...) \
|
||||
os_log_with_type(orc_rt_log_osLogHandle(Category), OS_LOG_TYPE_DEBUG, \
|
||||
__VA_ARGS__)
|
||||
#else
|
||||
#define ORC_RT_LOG_Debug(Category, ...) \
|
||||
ORC_RT_LOG_DISABLED(Category, __VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#else
|
||||
#error "Unknown ORC_RT_LOG_BACKEND."
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ set(files
|
|||
# is header-only and the os_log backend is not yet implemented.
|
||||
if(ORC_RT_LOG_BACKEND STREQUAL "printf")
|
||||
list(APPEND files Logging_printf.cpp)
|
||||
elseif(ORC_RT_LOG_BACKEND STREQUAL "os_log")
|
||||
list(APPEND files Logging_oslog.cpp)
|
||||
endif()
|
||||
|
||||
add_library(orc-rt-executor STATIC ${files})
|
||||
|
|
|
|||
45
orc-rt/lib/executor/Logging_oslog.cpp
Normal file
45
orc-rt/lib/executor/Logging_oslog.cpp
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
//===- Logging_oslog.cpp - os_log logging backend ------------------------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Runtime support for the os_log logging backend declared in
|
||||
// orc-rt-c/Logging.h.
|
||||
//
|
||||
// The log records themselves are emitted at the call site by os_log_with_type
|
||||
// (see the ORC_RT_LOG_* macros). The only runtime support needed here is a
|
||||
// per-category os_log_t handle, which orc_rt_log_osLogHandle reads inline; this
|
||||
// file homes the cache and provides the cold path that fills it.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "orc-rt-c/Logging.h"
|
||||
|
||||
#include <os/log.h>
|
||||
|
||||
// Cache for the inline accessor (declared in Logging.h). Zero-initialized, so
|
||||
// no static constructor runs; slots are filled lazily by the cold path below.
|
||||
os_log_t orc_rt_log_OSLogHandles[orc_rt_log_Category_Count];
|
||||
|
||||
extern "C" os_log_t
|
||||
orc_rt_log_osLogHandleSlow(orc_rt_log_Category Category) noexcept {
|
||||
// Category is already range-checked by the inline caller.
|
||||
//
|
||||
// ORC rt logs to the "org.llvm.orc-rt" subsystem.
|
||||
//
|
||||
// Combined with the per-category name it lets records be filtered in Console
|
||||
// or `log` (e.g. `log stream --predicate 'subsystem == "org.llvm.orc-rt"'`).
|
||||
//
|
||||
// Note: os_log_create deduplicates by (subsystem, category), so if two
|
||||
// threads race here they get the same handle back; storing either one is
|
||||
// correct and no lock is needed. The store publishes it for future warm-path
|
||||
// loads.
|
||||
os_log_t Handle =
|
||||
os_log_create("org.llvm.orc-rt", orc_rt_log_Category_getName(Category));
|
||||
__atomic_store_n(&orc_rt_log_OSLogHandles[Category], Handle,
|
||||
__ATOMIC_RELEASE);
|
||||
return Handle;
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
# -*- Python -*-
|
||||
|
||||
import os
|
||||
import random
|
||||
|
||||
import lit.formats
|
||||
import lit.util
|
||||
|
|
@ -62,6 +63,25 @@ def add_logging_features():
|
|||
|
||||
add_logging_features()
|
||||
|
||||
# The os_log delivery tests scrape the unified log (via `log show`), which is
|
||||
# slow and timing-sensitive, so they are opt-in: pass --param run-os-log-tests=1
|
||||
# to enable them. They also need the `log` tool. Warn if the tests were
|
||||
# requested but `log` is unavailable, so the request doesn't silently no-op.
|
||||
if lit_config.params.get("run-os-log-tests"):
|
||||
if lit.util.which("log"):
|
||||
config.available_features.add("os-log-show-tests")
|
||||
# A per-invocation id (stable across ALLOW_RETRIES) that the delivery
|
||||
# test emits and matches, so it can't match a stale record from an
|
||||
# earlier run.
|
||||
config.substitutions.append(
|
||||
("%{orc-rt-log-uid}", str(random.randint(1, 2**31 - 1)))
|
||||
)
|
||||
else:
|
||||
lit_config.warning(
|
||||
"run-os-log-tests was requested, but the 'log' tool was not found; "
|
||||
"the os_log delivery tests will be skipped"
|
||||
)
|
||||
|
||||
# Give logging tests a deterministic baseline: clear any logging environment
|
||||
# inherited from the developer's shell. Tests opt in with `env ORC_RT_LOG=...`.
|
||||
for var in ("ORC_RT_LOG", "ORC_RT_LOG_OUTPUT"):
|
||||
|
|
|
|||
17
orc-rt/test/regression/logging/os_log/delivery.test
Normal file
17
orc-rt/test/regression/logging/os_log/delivery.test
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Verify that an emitted record reaches the unified log with the expected
|
||||
# subsystem and category. Opt-in (llvm-lit --param run-os-log-tests=1).
|
||||
#
|
||||
# A unique per-run id makes the match specific to this run's record, not a
|
||||
# stale one from an earlier run. It is passed both to the tool (which emits it)
|
||||
# and to FileCheck via -Duid (lit substitutes %{...} on RUN lines, but not in
|
||||
# the CHECK text, so the value reaches the pattern as [[uid]]). The record is
|
||||
# emitted at ERROR level, which os_log persists, so `log show` sees it;
|
||||
#
|
||||
# REQUIRES: os-log-show-tests
|
||||
# RUN: orc-rt-log-check --uid %{orc-rt-log-uid}
|
||||
# RUN: log show --predicate \
|
||||
# RUN: 'subsystem == "org.llvm.orc-rt" && category == "General"' \
|
||||
# RUN: --style compact --last 15s \
|
||||
# RUN: | FileCheck -Duid=%{orc-rt-log-uid} %s
|
||||
|
||||
# CHECK: delivery marker uid=[[uid]]
|
||||
5
orc-rt/test/regression/logging/os_log/lit.local.cfg
Normal file
5
orc-rt/test/regression/logging/os_log/lit.local.cfg
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# These tests exercise the os_log logging backend, which is selected at build
|
||||
# time. Skip the whole directory unless that backend is compiled in.
|
||||
|
||||
if "orc-rt-log-backend-os_log" not in config.available_features:
|
||||
config.unsupported = True
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
# Verify that the os_log backend does not generate stderr/stdout messages.
|
||||
#
|
||||
# REQUIRES: orc-rt-log-level-error
|
||||
# RUN: orc-rt-log-check 2>&1 | FileCheck %s --allow-empty
|
||||
|
||||
# CHECK-NOT: [orc-rt:General:
|
||||
|
|
@ -65,12 +65,17 @@ int main(int argc, char *argv[]) {
|
|||
bool PrintBackend = false;
|
||||
bool PrintEnabledLevels = false;
|
||||
bool PrintHelp = false;
|
||||
int UID = -1;
|
||||
|
||||
{
|
||||
orc_rt::CommandLineParser P;
|
||||
P.addFlag("print-backend", "Print log backend", false, PrintBackend)
|
||||
.addFlag("print-enabled-levels", "Print enabled log levels", false,
|
||||
PrintEnabledLevels)
|
||||
.addValue("uid",
|
||||
"Emit one marker record carrying this id (for os_log "
|
||||
"delivery tests)",
|
||||
-1, UID)
|
||||
.addFlag("help", "Print help", false, PrintHelp);
|
||||
|
||||
if (auto Err = P.parse(argc, argv)) {
|
||||
|
|
@ -99,6 +104,14 @@ int main(int argc, char *argv[]) {
|
|||
if (PrintBackend || PrintEnabledLevels)
|
||||
return 0;
|
||||
|
||||
if (UID != -1) {
|
||||
// Emit one record whose payload carries the caller-supplied id, so a
|
||||
// delivery test can match exactly its own record and not a stale one. The
|
||||
// id is an integer (a %d scalar), which os_log shows unredacted.
|
||||
ORC_RT_LOG(Error, General, "delivery marker uid=%d", UID);
|
||||
return 0;
|
||||
}
|
||||
|
||||
emitAllLevels();
|
||||
|
||||
return 0;
|
||||
|
|
|
|||
Loading…
Reference in a new issue