libghostty: add log callback configuration

In C ABI builds, the Zig std.log default writes to stderr which is
not appropriate for a library. Override std_options.logFn with a
custom sink that dispatches to an embedder-provided callback, or
silently discards when none is registered.

Add GHOSTTY_SYS_OPT_LOG to ghostty_sys_set() following the existing
decode_png pattern. The callback receives the log level as a
GhosttySysLogLevel enum, scope and message as separate byte slices,
giving embedders full control over formatting and routing.

Export ghostty_sys_log_stderr as a built-in convenience callback that
writes to stderr using std.debug.lockStderrWriter for thread-safe
output. Embedders who want the old behavior can install it at startup
with a single ghostty_sys_set call.
This commit is contained in:
Mitchell Hashimoto
2026-04-10 10:17:40 -07:00
parent 7127abfe28
commit aa6943da37
4 changed files with 377 additions and 3 deletions

View File

@@ -64,6 +64,45 @@ typedef struct {
size_t data_len;
} GhosttySysImage;
/**
* Log severity levels for the log callback.
*/
typedef enum GHOSTTY_ENUM_TYPED {
GHOSTTY_SYS_LOG_LEVEL_ERROR = 0,
GHOSTTY_SYS_LOG_LEVEL_WARNING = 1,
GHOSTTY_SYS_LOG_LEVEL_INFO = 2,
GHOSTTY_SYS_LOG_LEVEL_DEBUG = 3,
GHOSTTY_SYS_LOG_LEVEL_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
} GhosttySysLogLevel;
/**
* Callback type for logging.
*
* When installed, internal library log messages are delivered through
* this callback instead of being discarded. The embedder is responsible
* for formatting and routing log output.
*
* @p scope is the log scope name as UTF-8 bytes (e.g. "osc", "kitty").
* When the log is unscoped (default scope), @p scope_len is 0.
*
* All pointer arguments are only valid for the duration of the callback.
* The callback must be safe to call from any thread.
*
* @param userdata The userdata pointer set via GHOSTTY_SYS_OPT_USERDATA
* @param level The severity level of the log message
* @param scope Pointer to the scope name bytes
* @param scope_len Length of the scope name in bytes
* @param message Pointer to the log message bytes
* @param message_len Length of the log message in bytes
*/
typedef void (*GhosttySysLogFn)(
void* userdata,
GhosttySysLogLevel level,
const uint8_t* scope,
size_t scope_len,
const uint8_t* message,
size_t message_len);
/**
* Callback type for PNG decoding.
*
@@ -106,6 +145,26 @@ typedef enum GHOSTTY_ENUM_TYPED {
* Input type: GhosttySysDecodePngFn (function pointer, or NULL)
*/
GHOSTTY_SYS_OPT_DECODE_PNG = 1,
/**
* Set the log callback.
*
* When set, internal library log messages are delivered to this
* callback. When cleared (NULL value), log messages are silently
* discarded.
*
* Use ghostty_sys_log_stderr as a convenience callback that
* writes formatted messages to stderr.
*
* Which log levels are emitted depends on the build mode of the
* library and is not configurable at runtime. Debug builds emit
* all levels (debug and above). Release builds emit info and
* above; debug-level messages are compiled out entirely and will
* never reach the callback.
*
* Input type: GhosttySysLogFn (function pointer, or NULL)
*/
GHOSTTY_SYS_OPT_LOG = 2,
GHOSTTY_SYS_OPT_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
} GhosttySysOption;
@@ -125,6 +184,23 @@ typedef enum GHOSTTY_ENUM_TYPED {
GHOSTTY_API GhosttyResult ghostty_sys_set(GhosttySysOption option,
const void* value);
/**
* Built-in log callback that writes to stderr.
*
* Formats each message as "[level](scope): message\n".
* Can be passed directly to ghostty_sys_set():
*
* @code
* ghostty_sys_set(GHOSTTY_SYS_OPT_LOG, &ghostty_sys_log_stderr);
* @endcode
*/
GHOSTTY_API void ghostty_sys_log_stderr(void* userdata,
GhosttySysLogLevel level,
const uint8_t* scope,
size_t scope_len,
const uint8_t* message,
size_t message_len);
#ifdef __cplusplus
}
#endif