mirror of
https://github.com/JakeHillion/drgn.git
synced 2024-12-22 01:03:07 +00:00
libdrgn: add simple logging framework
Exceptions aren't enough to debug complicated code paths like debug info discovery or stack unwinding. We really need logs for that, so let's add a small logging framework. By default, we log to stderr, but we also provide a way to direct logs to a different file, or even an arbitrary callback so that logs can be directed to the application's logging library of choice. Signed-off-by: Omar Sandoval <osandov@osandov.com>
This commit is contained in:
parent
fa82071618
commit
c1a2792e6a
@ -74,6 +74,8 @@ libdrgnimpl_la_SOURCES = $(ARCH_DEFS_PYS:_defs.py=.c) \
|
||||
linux_kernel.c \
|
||||
linux_kernel.h \
|
||||
linux_kernel_helpers.c \
|
||||
log.c \
|
||||
log.h \
|
||||
memory_reader.c \
|
||||
memory_reader.h \
|
||||
minmax.h \
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
@ -237,6 +238,15 @@ struct drgn_error *drgn_error_format_fault(uint64_t address,
|
||||
struct drgn_error *drgn_error_copy(struct drgn_error *src)
|
||||
__attribute__((__returns_nonnull__));
|
||||
|
||||
/**
|
||||
* Return a string representation of a @ref drgn_error.
|
||||
*
|
||||
* @param[in] err Error to write.
|
||||
* @return Returned string, or @c NULL if memory could not be allocated. On
|
||||
* success, must be freed with @c free().
|
||||
*/
|
||||
char *drgn_error_string(struct drgn_error *err);
|
||||
|
||||
/**
|
||||
* Write a @ref drgn_error followed by a newline to a @c stdio stream.
|
||||
*
|
||||
@ -922,6 +932,91 @@ struct drgn_error *drgn_program_element_info(struct drgn_program *prog,
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @defgroup Logging Logging
|
||||
*
|
||||
* Logging configuration.
|
||||
*
|
||||
* drgn can log to a file (@ref drgn_program_set_log_file()) or an arbitrary
|
||||
* callback (@ref drgn_program_set_log_callback()). Messages can be filtered
|
||||
* based on the log level (@ref drgn_program_set_log_level()).
|
||||
*
|
||||
* By default, the log file is set to `stderr` and the log level is @ref
|
||||
* DRGN_LOG_NONE, so logging is disabled.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** Log levels. */
|
||||
enum drgn_log_level {
|
||||
DRGN_LOG_DEBUG = 0,
|
||||
DRGN_LOG_INFO = 1,
|
||||
DRGN_LOG_WARNING = 2,
|
||||
DRGN_LOG_ERROR = 3,
|
||||
DRGN_LOG_CRITICAL = 4,
|
||||
};
|
||||
/** Don't log anything. */
|
||||
#define DRGN_LOG_NONE (DRGN_LOG_CRITICAL + 1)
|
||||
|
||||
/**
|
||||
* Set the minimum log level.
|
||||
*
|
||||
* Messages below this level will not be logged.
|
||||
*
|
||||
* @param[in] level Minimum @ref drgn_log_level to log, or @ref DRGN_LOG_NONE to
|
||||
* disable logging.
|
||||
*/
|
||||
void drgn_program_set_log_level(struct drgn_program *prog, int level);
|
||||
|
||||
/**
|
||||
* Get the minimum log level.
|
||||
*
|
||||
* @return Minimum @ref drgn_log_level being logged, or @ref DRGN_LOG_NONE if
|
||||
* logging is disabled.
|
||||
*/
|
||||
int drgn_program_get_log_level(struct drgn_program *prog);
|
||||
|
||||
/** Write logs to the given file. */
|
||||
void drgn_program_set_log_file(struct drgn_program *prog, FILE *file);
|
||||
|
||||
/**
|
||||
* Log callback.
|
||||
*
|
||||
* @param[in] prog Program message was logged to.
|
||||
* @param[in] arg `callback_arg` passed to @ref drgn_program_set_log_callback().
|
||||
* @param[in] level Message level.
|
||||
* @param[in] format printf-style format of message.
|
||||
* @param[in] ap Arguments for @p format.
|
||||
* @param[in] err Error to append after formatted message if non-@c NULL. This
|
||||
* can be formatted with @ref drgn_error_string(), @ref drgn_error_fwrite(), or
|
||||
* @ref drgn_error_dwrite().
|
||||
*/
|
||||
typedef void drgn_log_fn(struct drgn_program *prog, void *arg,
|
||||
enum drgn_log_level level, const char *format,
|
||||
va_list ap, struct drgn_error *err);
|
||||
|
||||
/**
|
||||
* Set a callback to log to.
|
||||
*
|
||||
* @param[in] callback Callback to call for each log message. This is only
|
||||
* called if the message's level is at least the current log level.
|
||||
* @param[in] callback_arg Argument to pass to callback.
|
||||
*/
|
||||
void drgn_program_set_log_callback(struct drgn_program *prog,
|
||||
drgn_log_fn *callback, void *callback_arg);
|
||||
|
||||
/**
|
||||
* Get the current log callback.
|
||||
*
|
||||
* @param[out] callback_ret Returned callback.
|
||||
* @param[out] callback_arg_ret Returned callback argument.
|
||||
*/
|
||||
void drgn_program_get_log_callback(struct drgn_program *prog,
|
||||
drgn_log_fn **callback_ret,
|
||||
void **callback_arg_ret);
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @defgroup Embedding Embedding
|
||||
*
|
||||
|
@ -225,6 +225,16 @@ bool string_builder_append_error(struct string_builder *sb,
|
||||
#undef emit_error_format
|
||||
}
|
||||
|
||||
LIBDRGN_PUBLIC char *drgn_error_string(struct drgn_error *err)
|
||||
{
|
||||
char *tmp;
|
||||
#define emit_error_format(...) (asprintf(&tmp, __VA_ARGS__) < 0 ? NULL : tmp)
|
||||
#define emit_error_string(s) strdup(s)
|
||||
return emit_error(err);
|
||||
#undef emit_error_string
|
||||
#undef emit_error_format
|
||||
}
|
||||
|
||||
LIBDRGN_PUBLIC int drgn_error_fwrite(FILE *file, struct drgn_error *err)
|
||||
{
|
||||
#define emit_error_format(format, ...) fprintf(file, format "\n", __VA_ARGS__)
|
||||
|
84
libdrgn/log.c
Normal file
84
libdrgn/log.c
Normal file
@ -0,0 +1,84 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "log.h"
|
||||
#include "program.h"
|
||||
#include "util.h"
|
||||
|
||||
LIBDRGN_PUBLIC void drgn_program_set_log_level(struct drgn_program *prog,
|
||||
int level)
|
||||
{
|
||||
prog->log_level = level;
|
||||
}
|
||||
|
||||
LIBDRGN_PUBLIC int drgn_program_get_log_level(struct drgn_program *prog)
|
||||
{
|
||||
return prog->log_level;
|
||||
}
|
||||
|
||||
static void drgn_file_log_fn(struct drgn_program *prog, void *arg,
|
||||
enum drgn_log_level level, const char *format,
|
||||
va_list ap, struct drgn_error *err)
|
||||
{
|
||||
FILE *file = arg;
|
||||
flockfile(file);
|
||||
|
||||
static const char * const prefix[] = {
|
||||
[DRGN_LOG_DEBUG] = "debug: ",
|
||||
[DRGN_LOG_INFO] = "info: ",
|
||||
[DRGN_LOG_WARNING] = "warning: ",
|
||||
[DRGN_LOG_ERROR] = "error: ",
|
||||
[DRGN_LOG_CRITICAL] = "critical: ",
|
||||
};
|
||||
fputs(prefix[level], file);
|
||||
vfprintf(file, format, ap);
|
||||
if (err)
|
||||
drgn_error_fwrite(file, err);
|
||||
else
|
||||
putc('\n', file);
|
||||
|
||||
funlockfile(file);
|
||||
}
|
||||
|
||||
LIBDRGN_PUBLIC void drgn_program_set_log_file(struct drgn_program *prog,
|
||||
FILE *file)
|
||||
{
|
||||
drgn_program_set_log_callback(prog, drgn_file_log_fn, file);
|
||||
}
|
||||
|
||||
LIBDRGN_PUBLIC void drgn_program_set_log_callback(struct drgn_program *prog,
|
||||
drgn_log_fn *callback,
|
||||
void *callback_arg)
|
||||
{
|
||||
prog->log_fn = callback;
|
||||
prog->log_arg = callback_arg;
|
||||
}
|
||||
|
||||
LIBDRGN_PUBLIC void drgn_program_get_log_callback(struct drgn_program *prog,
|
||||
drgn_log_fn **callback_ret,
|
||||
void **callback_arg_ret)
|
||||
{
|
||||
*callback_ret = prog->log_fn;
|
||||
*callback_arg_ret = prog->log_arg;
|
||||
}
|
||||
|
||||
bool drgn_log_is_enabled(struct drgn_program *prog, enum drgn_log_level level)
|
||||
{
|
||||
return level >= prog->log_level;
|
||||
}
|
||||
|
||||
void drgn_error_log(enum drgn_log_level level, struct drgn_program *prog,
|
||||
struct drgn_error *err, const char *format, ...)
|
||||
{
|
||||
if (!drgn_log_is_enabled(prog, level))
|
||||
return;
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
prog->log_fn(prog, prog->log_arg, level, format, ap, err);
|
||||
va_end(ap);
|
||||
}
|
89
libdrgn/log.h
Normal file
89
libdrgn/log.h
Normal file
@ -0,0 +1,89 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
/**
|
||||
* @file
|
||||
*
|
||||
* Logging.
|
||||
*
|
||||
* See @ref LoggingInternals.
|
||||
*/
|
||||
|
||||
#ifndef DRGN_LOG_H
|
||||
#define DRGN_LOG_H
|
||||
|
||||
#include "drgn.h"
|
||||
|
||||
/**
|
||||
* @ingroup Internals
|
||||
*
|
||||
* @defgroup LoggingInternals Logging
|
||||
*
|
||||
* Logging functions.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Return whether the given log level is enabled.
|
||||
*
|
||||
* This can be used to avoid expensive computations that are only needed for
|
||||
* logging.
|
||||
*/
|
||||
bool drgn_log_is_enabled(struct drgn_program *prog, enum drgn_log_level level);
|
||||
|
||||
/**
|
||||
* @{
|
||||
*
|
||||
* @name Logging Functions
|
||||
*/
|
||||
|
||||
#ifdef DOXYGEN
|
||||
/** Log a printf-style message at the given level. */
|
||||
void drgn_log(enum drgn_log_level level, struct drgn_program *prog,
|
||||
const char *format, ...);
|
||||
#else
|
||||
#define drgn_log(level, prog, ...) drgn_error_log(level, prog, NULL, __VA_ARGS__)
|
||||
#endif
|
||||
/** Log a critical message. */
|
||||
#define drgn_log_critical(...) drgn_log(DRGN_LOG_CRITICAL, __VA_ARGS__)
|
||||
/** Log an error message. */
|
||||
#define drgn_log_error(...) drgn_log(DRGN_LOG_ERROR, __VA_ARGS__)
|
||||
/** Log a warning message. */
|
||||
#define drgn_log_warning(...) drgn_log(DRGN_LOG_WARNING, __VA_ARGS__)
|
||||
/** Log an informational message. */
|
||||
#define drgn_log_info(...) drgn_log(DRGN_LOG_INFO, __VA_ARGS__)
|
||||
/** Log a debug message. */
|
||||
#define drgn_log_debug(...) drgn_log(DRGN_LOG_DEBUG, __VA_ARGS__)
|
||||
|
||||
/**
|
||||
* @}
|
||||
*
|
||||
* @{
|
||||
*
|
||||
* @name Error Logging Functions
|
||||
*/
|
||||
|
||||
/**
|
||||
* Log a printf-style message followed by a @ref drgn_error at the given level.
|
||||
*/
|
||||
__attribute__((__format__(__printf__, 4, 5)))
|
||||
void drgn_error_log(enum drgn_log_level level, struct drgn_program *prog,
|
||||
struct drgn_error *err, const char *format, ...);
|
||||
/** Log a critical message followed by a @ref drgn_error. */
|
||||
#define drgn_error_log_critical(...) drgn_error_log(DRGN_LOG_CRIT, __VA_ARGS__)
|
||||
/** Log an error message followed by a @ref drgn_error. */
|
||||
#define drgn_error_log_error(...) drgn_error_log(DRGN_LOG_ERR, __VA_ARGS__)
|
||||
/** Log a warning message followed by a @ref drgn_error. */
|
||||
#define drgn_error_log_warning(...) drgn_error_log(DRGN_LOG_WARNING, __VA_ARGS__)
|
||||
/** Log an informational message followed by a @ref drgn_error. */
|
||||
#define drgn_error_log_info(...) drgn_error_log(DRGN_LOG_INFO, __VA_ARGS__)
|
||||
/** Log a debug message followed by a @ref drgn_error. */
|
||||
#define drgn_error_log_debug(...) drgn_error_log(DRGN_LOG_DEBUG, __VA_ARGS__)
|
||||
|
||||
/**
|
||||
* @}
|
||||
* @}
|
||||
*/
|
||||
|
||||
#endif /* DRGN_LOG_H */
|
@ -104,6 +104,8 @@ void drgn_program_init(struct drgn_program *prog,
|
||||
drgn_program_set_platform(prog, platform);
|
||||
char *env = getenv("DRGN_PREFER_ORC_UNWINDER");
|
||||
prog->prefer_orc_unwinder = env && atoi(env);
|
||||
drgn_program_set_log_level(prog, DRGN_LOG_NONE);
|
||||
drgn_program_set_log_file(prog, stderr);
|
||||
drgn_object_init(&prog->vmemmap, prog);
|
||||
}
|
||||
|
||||
|
@ -186,6 +186,13 @@ struct drgn_program {
|
||||
/* Whether @ref drgn_program::direct_mapping_offset has been cached. */
|
||||
bool direct_mapping_offset_cached;
|
||||
|
||||
/*
|
||||
* Logging.
|
||||
*/
|
||||
drgn_log_fn *log_fn;
|
||||
void *log_arg;
|
||||
enum drgn_log_level log_level;
|
||||
|
||||
/*
|
||||
* Blocking callbacks.
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user