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

20
log.c
View File

@ -179,12 +179,19 @@ static void log_close(struct log_output_iterator *it)
(*it->output)->close(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);
assert(*it->output); assert(*it->output);
if ((*it->output)->capture_fd) return (*it->output)->capture_fd ? (*it->output)->capture_fd(it, fd) : 0;
(*it->output)->capture_fd(it, fd, captured); }
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 /* 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; struct log_output_iterator it;
log_iterator_start(&it); log_iterator_start(&it);
bool_t captured = 0; bool_t captured = 0;
while (!captured && log_iterator_advance(&it))
captured = log_capture_fd(&it, fd);
if (captured) {
log_iterator_start(&it);
while (log_iterator_advance(&it)) while (log_iterator_advance(&it))
log_capture_fd(&it, fd, &captured); log_suppress_fd(&it, fd);
}
return captured; return captured;
} }

View File

@ -66,10 +66,15 @@ struct log_output {
// vlogMessage() primitive, before start_line() is called. // vlogMessage() primitive, before start_line() is called.
void (*open)(struct log_output_iterator *it); void (*open)(struct log_output_iterator *it);
// If *capture is 0 and the output is of a persistent nature (eg, a file or // If the output is of a persistent nature (eg, a file or system log) and
// system log) and is able to redirect data written to the given file // is able to redirect data written to the given file descriptor to its
// descriptor to its output, then do so and set *capture to 1. // output, then do so and return true, otherwise return false.
void (*capture_fd)(struct log_output_iterator *it, int fd, bool_t *capture); 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 // Test whether output is able to handle messages; if it returns false then
// start_line() and end_line() will not be invoked. // 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); struct log_output_console_state *state = _state(*it->output);
if (state->fp && state->fp != DISABLED && fileno(state->fp) == fd) { if (state->fp && state->fp != DISABLED && fileno(state->fp) == fd) {
fflush(state->fp); fflush(state->fp);
@ -137,7 +139,8 @@ static struct log_output static_log_output = {
.show_time = log_console_show_time, .show_time = log_console_show_time,
.state = &static_state, .state = &static_state,
.open = open_log_console, .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, .is_available = is_log_console_available,
.start_line = log_console_start_line, .start_line = log_console_start_line,
.end_line = log_console_end_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)) static void log_delegate_start_line(struct log_output_iterator *it, int UNUSED(level))
{ {
struct log_output_delegate_state *state = _state(*it->output); 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, .show_time = log_delegate_show_time,
.state = &static_state, .state = &static_state,
.open = log_delegate_open, .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, .is_available = is_log_delegate_available,
.start_line = log_delegate_start_line, .start_line = log_delegate_start_line,
.end_line = log_delegate_end_line, .end_line = log_delegate_end_line,
@ -130,5 +142,7 @@ struct log_delegate serval_log_delegate = {
.show_pid = 0, .show_pid = 0,
.show_time = 1, .show_time = 1,
.print = NULL, .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; bool_t show_time;
void (*print)(int level, const char *message, bool_t overrun); void (*print)(int level, const char *message, bool_t overrun);
void (*flush)(); void (*flush)();
bool_t (*capture_fd)(int fd);
void (*suppress_fd)(int fd);
}; };
extern struct log_delegate serval_log_delegate; extern struct log_delegate serval_log_delegate;

View File

@ -310,9 +310,9 @@ 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) { bool_t captured = 0;
// This outputter does not connect the file descriptor to an output file until the next log // This outputter does not connect the file descriptor to an output file until the next log
// message is output. // message is output.
_state(*it->output)->capture_fd = fd; _state(*it->output)->capture_fd = fd;
@ -321,15 +321,16 @@ static void capture_fd_log_file(struct log_output_iterator *it, int fd, bool_t *
int devnull; int devnull;
if ((devnull = open("/dev/null", O_RDWR, 0)) == -1) if ((devnull = open("/dev/null", O_RDWR, 0)) == -1)
WHY_perror("open(\"/dev/null\")"); WHY_perror("open(\"/dev/null\")");
else if (devnull == fd)
captured = 1;
else { else {
if (devnull != fd && dup2(devnull, fd) == -1) if (dup2(devnull, fd) == -1)
WHYF_perror("dup2(%d, %d)", devnull, fd); WHYF_perror("dup2(%d, %d)", devnull, fd);
else else
*capture = 1; captured = 1;
if (devnull != fd)
close(devnull); close(devnull);
} }
} return captured;
} }
static bool_t is_log_file_available(const struct log_output_iterator *it) 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, .state = &static_state,
.open = open_log_file, .open = open_log_file,
.capture_fd = capture_fd_log_file, .capture_fd = capture_fd_log_file,
.suppress_fd = NULL,
.is_available = is_log_file_available, .is_available = is_log_file_available,
.start_line = log_file_start_line, .start_line = log_file_start_line,
.end_line = NULL, .end_line = NULL,

View File

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

View File

@ -19,40 +19,81 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import serval_dna.lib import serval_dna.lib
private func serval_log(level: CInt, format: String, va_list: CVaListPointer) { public enum LogLevel {
format.withCString { CString in case debug
serval_vlogf(level, __whence, CString, va_list) case info
} case hint
} case warn
case error
case fatal
public func serval_log(level: CInt, text: String) { var rawValue : CInt {
text.withCString { CString in get {
withVaList([CString]) { va_list in switch (self) {
serval_log(level: level, format: "%s", va_list: va_list) 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) { internal var baseFilePath : String = #file
serval_log(level: LOG_LEVEL_FATAL, text: text)
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) { private func servalLog(level: LogLevel, format: String, va_list: CVaListPointer, file: String = #file, line: Int = #line, function: String = #function) {
serval_log(level: LOG_LEVEL_ERROR, text: text) 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) { public func servalLog(level: LogLevel, text: String, file: String = #file, line: Int = #line, function: String = #function) {
serval_log(level: LOG_LEVEL_WARN, text: text) 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) { public func servalLogFatal(_ text: String, file: String = #file, line: Int = #line, function: String = #function) {
serval_log(level: LOG_LEVEL_HINT, text: text) servalLog(level: .fatal, text: text, file: file, line: line, function: function)
} }
public func serval_log_info(_ text: String) { public func servalLogError(_ text: String, file: String = #file, line: Int = #line, function: String = #function) {
serval_log(level: LOG_LEVEL_INFO, text: text) servalLog(level: .error, text: text, file: file, line: line, function: function)
} }
public func serval_log_debug(_ text: String) { public func servalLogWarning(_ text: String, file: String = #file, line: Int = #line, function: String = #function) {
serval_log(level: LOG_LEVEL_DEBUG, text: text) 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 #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.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 serval_log_delegate.show_prolog = 1
#if os(iOS) || os(macOS) #if os(iOS) || os(macOS)
// Apple's unified logging system (iOS) and syslog (macOS) both record the // 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 (loc.function && loc.function[0]) {
if (flag) if (flag)
xputc(':', xpf); xputc(':', xpf);
xprintf(xpf, "%s()", loc.function); xprintf(xpf, "%s", loc.function);
if (loc.function[strlen(loc.function) - 1] != ')')
xputs("()", xpf);
++flag; ++flag;
} }
} }