Improve Swift API delegate log outputter

Capture whence information in all Serval logging API entry points.

Introduce the LogLevel enum type to avoid exposing internal C API
details in the Swift API.

Rename Swift API methods and types to use camelCase naming style, to
distinguish it from the C API, which uses under_score naming style.
This commit is contained in:
Andrew Bettison 2018-05-07 23:31:57 +09:30
parent 00804e9fc1
commit 81d4e696ca
10 changed files with 145 additions and 56 deletions

22
log.c
View File

@ -179,12 +179,19 @@ static void log_close(struct log_output_iterator *it)
(*it->output)->close(it);
}
static void log_capture_fd(struct log_output_iterator *it, int fd, bool_t *captured)
static bool_t log_capture_fd(struct log_output_iterator *it, int fd)
{
assert(it->output);
assert(*it->output);
if ((*it->output)->capture_fd)
(*it->output)->capture_fd(it, fd, captured);
return (*it->output)->capture_fd ? (*it->output)->capture_fd(it, fd) : 0;
}
static void log_suppress_fd(struct log_output_iterator *it, int fd)
{
assert(it->output);
assert(*it->output);
if ((*it->output)->suppress_fd)
(*it->output)->suppress_fd(it, fd);
}
/* Functions for use by log outputters. This is the "private" API of the logging system, as
@ -333,7 +340,12 @@ bool_t serval_log_capture_fd(int fd) {
struct log_output_iterator it;
log_iterator_start(&it);
bool_t captured = 0;
while (log_iterator_advance(&it))
log_capture_fd(&it, fd, &captured);
while (!captured && log_iterator_advance(&it))
captured = log_capture_fd(&it, fd);
if (captured) {
log_iterator_start(&it);
while (log_iterator_advance(&it))
log_suppress_fd(&it, fd);
}
return captured;
}

View File

@ -66,10 +66,15 @@ struct log_output {
// vlogMessage() primitive, before start_line() is called.
void (*open)(struct log_output_iterator *it);
// If *capture is 0 and the output is of a persistent nature (eg, a file or
// system log) and is able to redirect data written to the given file
// descriptor to its output, then do so and set *capture to 1.
void (*capture_fd)(struct log_output_iterator *it, int fd, bool_t *capture);
// If the output is of a persistent nature (eg, a file or system log) and
// is able to redirect data written to the given file descriptor to its
// output, then do so and return true, otherwise return false.
bool_t (*capture_fd)(struct log_output_iterator *it, int fd);
// Cease writing any output to the given file descriptor. This is called
// on all outputters immediately after any outputter's capture_fd(fd) call
// returns true, to prevent duplicate log messages being captured.
void (*suppress_fd)(struct log_output_iterator *it, int fd);
// Test whether output is able to handle messages; if it returns false then
// start_line() and end_line() will not be invoked.

View File

@ -82,8 +82,10 @@ static void open_log_console(struct log_output_iterator *it)
}
}
static void capture_fd_log_console(struct log_output_iterator *it, int fd, bool_t *UNUSED(capture))
static void suppress_fd_log_console(struct log_output_iterator *it, int fd)
{
// If another log outputter is capturing the console's output (eg, the logfile outputer), then
// cease logging to the console to avoid duplicate messages being sent to that output.
struct log_output_console_state *state = _state(*it->output);
if (state->fp && state->fp != DISABLED && fileno(state->fp) == fd) {
fflush(state->fp);
@ -137,7 +139,8 @@ static struct log_output static_log_output = {
.show_time = log_console_show_time,
.state = &static_state,
.open = open_log_console,
.capture_fd = capture_fd_log_console,
.capture_fd = NULL,
.suppress_fd = suppress_fd_log_console,
.is_available = is_log_console_available,
.start_line = log_console_start_line,
.end_line = log_console_end_line,

View File

@ -84,6 +84,17 @@ static void log_delegate_open(struct log_output_iterator *it)
}
}
static bool_t log_delegate_capture_fd(struct log_output_iterator *UNUSED(it), int fd)
{
return serval_log_delegate.capture_fd && serval_log_delegate.capture_fd(fd);
}
static void log_delegate_suppress_fd(struct log_output_iterator *UNUSED(it), int fd)
{
if (serval_log_delegate.suppress_fd)
serval_log_delegate.suppress_fd(fd);
}
static void log_delegate_start_line(struct log_output_iterator *it, int UNUSED(level))
{
struct log_output_delegate_state *state = _state(*it->output);
@ -113,7 +124,8 @@ static struct log_output static_log_output = {
.show_time = log_delegate_show_time,
.state = &static_state,
.open = log_delegate_open,
.capture_fd = NULL,
.capture_fd = log_delegate_capture_fd,
.suppress_fd = log_delegate_suppress_fd,
.is_available = is_log_delegate_available,
.start_line = log_delegate_start_line,
.end_line = log_delegate_end_line,
@ -130,5 +142,7 @@ struct log_delegate serval_log_delegate = {
.show_pid = 0,
.show_time = 1,
.print = NULL,
.flush = NULL
.flush = NULL,
.capture_fd = NULL,
.suppress_fd = NULL
};

View File

@ -29,6 +29,8 @@ struct log_delegate {
bool_t show_time;
void (*print)(int level, const char *message, bool_t overrun);
void (*flush)();
bool_t (*capture_fd)(int fd);
void (*suppress_fd)(int fd);
};
extern struct log_delegate serval_log_delegate;

View File

@ -310,26 +310,27 @@ static void open_log_file(struct log_output_iterator *it)
}
}
static void capture_fd_log_file(struct log_output_iterator *it, int fd, bool_t *capture)
static bool_t capture_fd_log_file(struct log_output_iterator *it, int fd)
{
if (!*capture) {
// This outputter does not connect the file descriptor to an output file until the next log
// message is output.
_state(*it->output)->capture_fd = fd;
// Ensure that the file descriptor is occupied, so that no other open() call can occupy it in
// the meantime.
int devnull;
if ((devnull = open("/dev/null", O_RDWR, 0)) == -1)
WHY_perror("open(\"/dev/null\")");
else {
if (devnull != fd && dup2(devnull, fd) == -1)
WHYF_perror("dup2(%d, %d)", devnull, fd);
else
*capture = 1;
if (devnull != fd)
close(devnull);
}
bool_t captured = 0;
// This outputter does not connect the file descriptor to an output file until the next log
// message is output.
_state(*it->output)->capture_fd = fd;
// Ensure that the file descriptor is occupied, so that no other open() call can occupy it in
// the meantime.
int devnull;
if ((devnull = open("/dev/null", O_RDWR, 0)) == -1)
WHY_perror("open(\"/dev/null\")");
else if (devnull == fd)
captured = 1;
else {
if (dup2(devnull, fd) == -1)
WHYF_perror("dup2(%d, %d)", devnull, fd);
else
captured = 1;
close(devnull);
}
return captured;
}
static bool_t is_log_file_available(const struct log_output_iterator *it)
@ -377,6 +378,7 @@ static struct log_output static_log_output = {
.state = &static_state,
.open = open_log_file,
.capture_fd = capture_fd_log_file,
.suppress_fd = NULL,
.is_available = is_log_file_available,
.start_line = log_file_start_line,
.end_line = NULL,

View File

@ -22,7 +22,7 @@ import ServalDNA
// Logging
logSetup()
servalLogSetup(minimum_level: .debug)
// Output

View File

@ -19,40 +19,81 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import serval_dna.lib
private func serval_log(level: CInt, format: String, va_list: CVaListPointer) {
format.withCString { CString in
serval_vlogf(level, __whence, CString, va_list)
}
}
public enum LogLevel {
case debug
case info
case hint
case warn
case error
case fatal
public func serval_log(level: CInt, text: String) {
text.withCString { CString in
withVaList([CString]) { va_list in
serval_log(level: level, format: "%s", va_list: va_list)
var rawValue : CInt {
get {
switch (self) {
case .debug: return LOG_LEVEL_DEBUG
case .info: return LOG_LEVEL_INFO
case .hint: return LOG_LEVEL_HINT
case .warn: return LOG_LEVEL_WARN
case .error: return LOG_LEVEL_ERROR
case .fatal: return LOG_LEVEL_FATAL
}
}
}
}
public func serval_log_fatal(_ text: String) {
serval_log(level: LOG_LEVEL_FATAL, text: text)
internal var baseFilePath : String = #file
private func trimpath(_ path: String) -> String {
var i = path.startIndex
for (b, p) in zip(baseFilePath.indices, path.indices) {
if path[p] != baseFilePath[b] {
break;
}
if path[p] == "/" {
i = path.index(after: p)
}
}
return String(path[i..<path.endIndex])
}
public func serval_log_error(_ text: String) {
serval_log(level: LOG_LEVEL_ERROR, text: text)
private func servalLog(level: LogLevel, format: String, va_list: CVaListPointer, file: String = #file, line: Int = #line, function: String = #function) {
trimpath(file).withCString { c_file in
function.withCString { c_function in
format.withCString { c_format in
serval_vlogf(level.rawValue, __sourceloc(file: c_file, line: UInt32(exactly: line) ?? 0, function: c_function), c_format, va_list)
}
}
}
}
public func serval_log_warning(_ text: String) {
serval_log(level: LOG_LEVEL_WARN, text: text)
public func servalLog(level: LogLevel, text: String, file: String = #file, line: Int = #line, function: String = #function) {
text.withCString { c_text in
withVaList([c_text]) { va_list in
servalLog(level: level, format: "%s", va_list: va_list, file: file, line: line, function: function)
}
}
}
public func serval_log_hint(_ text: String) {
serval_log(level: LOG_LEVEL_HINT, text: text)
public func servalLogFatal(_ text: String, file: String = #file, line: Int = #line, function: String = #function) {
servalLog(level: .fatal, text: text, file: file, line: line, function: function)
}
public func serval_log_info(_ text: String) {
serval_log(level: LOG_LEVEL_INFO, text: text)
public func servalLogError(_ text: String, file: String = #file, line: Int = #line, function: String = #function) {
servalLog(level: .error, text: text, file: file, line: line, function: function)
}
public func serval_log_debug(_ text: String) {
serval_log(level: LOG_LEVEL_DEBUG, text: text)
public func servalLogWarning(_ text: String, file: String = #file, line: Int = #line, function: String = #function) {
servalLog(level: .warn, text: text, file: file, line: line, function: function)
}
public func servalLogHint(_ text: String, file: String = #file, line: Int = #line, function: String = #function) {
servalLog(level: .hint, text: text, file: file, line: line, function: function)
}
public func servalLogInfo(_ text: String, file: String = #file, line: Int = #line, function: String = #function) {
servalLog(level: .info, text: text, file: file, line: line, function: function)
}
public func servalLogDebug(_ text: String, file: String = #file, line: Int = #line, function: String = #function) {
servalLog(level: .debug, text: text, file: file, line: line, function: function)
}

View File

@ -67,9 +67,17 @@ private func logPrint(_ level: CInt, _ message: UnsafePointer<CChar>?, _ overrun
#endif
public func logSetup() {
private func logSuppress(_ fd: CInt) {
if (fd == FileHandle.standardError.fileDescriptor) {
serval_log_delegate.print = nil
}
}
public func servalLogSetup(minimum_level: LogLevel = .info, baseFilePath file: String = #file) {
baseFilePath = file
serval_log_delegate.print = logPrint
serval_log_delegate.minimum_level = LOG_LEVEL_DEBUG
serval_log_delegate.suppress_fd = logSuppress
serval_log_delegate.minimum_level = minimum_level.rawValue
serval_log_delegate.show_prolog = 1
#if os(iOS) || os(macOS)
// Apple's unified logging system (iOS) and syslog (macOS) both record the

View File

@ -64,7 +64,9 @@ void xprint_sourceloc(XPRINTF xpf, struct __sourceloc loc)
if (loc.function && loc.function[0]) {
if (flag)
xputc(':', xpf);
xprintf(xpf, "%s()", loc.function);
xprintf(xpf, "%s", loc.function);
if (loc.function[strlen(loc.function) - 1] != ')')
xputs("()", xpf);
++flag;
}
}