2016-01-21 21:11:47 +01:00
|
|
|
#include "log.h"
|
2017-09-12 06:56:45 +02:00
|
|
|
#include <backtrace.h>
|
2016-01-21 21:11:47 +01:00
|
|
|
#include <ccan/array_size/array_size.h>
|
2016-01-21 21:11:47 +01:00
|
|
|
#include <ccan/list/list.h>
|
2016-01-21 21:11:47 +01:00
|
|
|
#include <ccan/opt/opt.h>
|
2016-01-21 21:11:47 +01:00
|
|
|
#include <ccan/read_write_all/read_write_all.h>
|
|
|
|
#include <ccan/str/hex/hex.h>
|
|
|
|
#include <ccan/tal/str/str.h>
|
|
|
|
#include <ccan/time/time.h>
|
2017-12-15 11:29:03 +01:00
|
|
|
#include <common/memleak.h>
|
2017-08-28 18:04:01 +02:00
|
|
|
#include <common/pseudorand.h>
|
2017-08-28 18:02:01 +02:00
|
|
|
#include <common/utils.h>
|
2016-01-21 21:11:47 +01:00
|
|
|
#include <errno.h>
|
2016-01-21 21:11:47 +01:00
|
|
|
#include <fcntl.h>
|
2016-07-01 03:49:28 +02:00
|
|
|
#include <inttypes.h>
|
2018-02-05 05:09:28 +01:00
|
|
|
#include <lightningd/jsonrpc.h>
|
2017-12-15 11:18:54 +01:00
|
|
|
#include <lightningd/lightningd.h>
|
2018-02-05 05:09:28 +01:00
|
|
|
#include <lightningd/options.h>
|
2016-01-21 21:11:47 +01:00
|
|
|
#include <signal.h>
|
2016-01-21 21:11:47 +01:00
|
|
|
#include <stdio.h>
|
2016-01-21 21:11:47 +01:00
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/types.h>
|
2016-01-21 21:11:48 +01:00
|
|
|
#include <unistd.h>
|
2016-01-21 21:11:47 +01:00
|
|
|
|
|
|
|
struct log_entry {
|
|
|
|
struct list_node list;
|
|
|
|
struct timeabs time;
|
|
|
|
enum log_level level;
|
|
|
|
unsigned int skipped;
|
|
|
|
const char *prefix;
|
|
|
|
char *log;
|
|
|
|
};
|
|
|
|
|
2017-01-10 05:48:26 +01:00
|
|
|
struct log_book {
|
2016-01-21 21:11:47 +01:00
|
|
|
size_t mem_used;
|
|
|
|
size_t max_mem;
|
2016-01-21 21:11:47 +01:00
|
|
|
void (*print)(const char *prefix,
|
|
|
|
enum log_level level,
|
|
|
|
bool continued,
|
2018-02-02 01:05:41 +01:00
|
|
|
const struct timeabs *time,
|
2016-01-21 21:11:47 +01:00
|
|
|
const char *str, void *arg);
|
|
|
|
void *print_arg;
|
|
|
|
enum log_level print_level;
|
2016-01-21 21:11:47 +01:00
|
|
|
struct timeabs init_time;
|
|
|
|
|
|
|
|
struct list_head log;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct log {
|
2017-01-10 05:48:26 +01:00
|
|
|
struct log_book *lr;
|
2016-01-21 21:11:47 +01:00
|
|
|
const char *prefix;
|
|
|
|
};
|
|
|
|
|
2018-02-02 01:07:19 +01:00
|
|
|
static void log_to_file(const char *prefix,
|
|
|
|
enum log_level level,
|
|
|
|
bool continued,
|
|
|
|
const struct timeabs *time,
|
|
|
|
const char *str,
|
|
|
|
FILE *logf)
|
2016-01-21 21:11:47 +01:00
|
|
|
{
|
2018-02-02 01:05:41 +01:00
|
|
|
char iso8601_msec_fmt[sizeof("YYYY-mm-ddTHH:MM:SS.%03dZ")];
|
2018-02-02 01:05:41 +01:00
|
|
|
strftime(iso8601_msec_fmt, sizeof(iso8601_msec_fmt), "%FT%T.%%03dZ", gmtime(&time->ts.tv_sec));
|
2018-02-02 01:05:41 +01:00
|
|
|
char iso8601_s[sizeof("YYYY-mm-ddTHH:MM:SS.nnnZ")];
|
2018-02-02 01:05:41 +01:00
|
|
|
snprintf(iso8601_s, sizeof(iso8601_s), iso8601_msec_fmt, (int) time->ts.tv_nsec / 1000000);
|
2016-01-21 21:11:47 +01:00
|
|
|
if (!continued) {
|
2018-02-02 01:07:19 +01:00
|
|
|
fprintf(logf, "%s %s %s\n", iso8601_s, prefix, str);
|
2016-01-21 21:11:47 +01:00
|
|
|
} else {
|
2018-02-02 01:07:19 +01:00
|
|
|
fprintf(logf, "%s %s \t%s\n", iso8601_s, prefix, str);
|
2016-01-21 21:11:47 +01:00
|
|
|
}
|
2018-02-02 01:07:19 +01:00
|
|
|
fflush(logf);
|
|
|
|
}
|
|
|
|
|
2018-02-02 01:07:21 +01:00
|
|
|
static void log_to_stdout(const char *prefix,
|
|
|
|
enum log_level level,
|
|
|
|
bool continued,
|
|
|
|
const struct timeabs *time,
|
2018-02-02 07:02:57 +01:00
|
|
|
const char *str, void *unused UNUSED)
|
2018-02-02 01:07:19 +01:00
|
|
|
{
|
|
|
|
log_to_file(prefix, level, continued, time, str, stdout);
|
2016-01-21 21:11:47 +01:00
|
|
|
}
|
|
|
|
|
2016-01-21 21:11:47 +01:00
|
|
|
static size_t log_bufsize(const struct log_entry *e)
|
|
|
|
{
|
|
|
|
if (e->level == LOG_IO)
|
|
|
|
return tal_count(e->log);
|
|
|
|
else
|
|
|
|
return strlen(e->log) + 1;
|
|
|
|
}
|
|
|
|
|
2017-01-10 05:48:26 +01:00
|
|
|
static size_t prune_log(struct log_book *log)
|
2016-01-21 21:11:47 +01:00
|
|
|
{
|
|
|
|
struct log_entry *i, *next, *tail;
|
|
|
|
size_t skipped = 0, deleted = 0;
|
|
|
|
|
|
|
|
/* Never delete the last one. */
|
|
|
|
tail = list_tail(&log->log, struct log_entry, list);
|
|
|
|
|
|
|
|
list_for_each_safe(&log->log, i, next, list) {
|
|
|
|
/* 50% chance of deleting debug, 25% inform, 12.5% unusual. */
|
|
|
|
if (i == tail || !pseudorand(2 << i->level)) {
|
|
|
|
i->skipped += skipped;
|
|
|
|
skipped = 0;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
list_del_from(&log->log, &i->list);
|
|
|
|
log->mem_used -= sizeof(*i) + log_bufsize(i);
|
|
|
|
tal_free(i);
|
|
|
|
skipped++;
|
|
|
|
deleted++;
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(!skipped);
|
|
|
|
return deleted;
|
|
|
|
}
|
|
|
|
|
2017-01-10 05:48:26 +01:00
|
|
|
struct log_book *new_log_book(const tal_t *ctx,
|
|
|
|
size_t max_mem,
|
|
|
|
enum log_level printlevel)
|
2016-01-21 21:11:47 +01:00
|
|
|
{
|
2017-01-10 05:48:26 +01:00
|
|
|
struct log_book *lr = tal(ctx, struct log_book);
|
2016-01-21 21:11:47 +01:00
|
|
|
|
|
|
|
/* Give a reasonable size for memory limit! */
|
|
|
|
assert(max_mem > sizeof(struct log) * 2);
|
|
|
|
lr->mem_used = 0;
|
|
|
|
lr->max_mem = max_mem;
|
2018-02-02 01:07:21 +01:00
|
|
|
lr->print = log_to_stdout;
|
2016-01-21 21:11:47 +01:00
|
|
|
lr->print_level = printlevel;
|
2016-04-24 12:05:13 +02:00
|
|
|
lr->init_time = time_now();
|
2016-01-21 21:11:47 +01:00
|
|
|
list_head_init(&lr->log);
|
|
|
|
|
2017-09-12 13:49:37 +02:00
|
|
|
/* In case ltmp not initialized, do so now. */
|
|
|
|
if (!ltmp)
|
2017-12-15 11:29:03 +01:00
|
|
|
ltmp = notleak(tal(lr, char));
|
2017-09-12 13:49:37 +02:00
|
|
|
|
2016-01-21 21:11:47 +01:00
|
|
|
return lr;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* With different entry points */
|
|
|
|
struct log *PRINTF_FMT(3,4)
|
2017-01-10 05:48:26 +01:00
|
|
|
new_log(const tal_t *ctx, struct log_book *record, const char *fmt, ...)
|
2016-01-21 21:11:47 +01:00
|
|
|
{
|
|
|
|
struct log *log = tal(ctx, struct log);
|
|
|
|
va_list ap;
|
|
|
|
|
|
|
|
log->lr = record;
|
|
|
|
va_start(ap, fmt);
|
|
|
|
/* log->lr owns this, since its entries keep a pointer to it. */
|
log: block reporting on minor memleak.
Exception: Node /tmp/lightning-t5gxc6gs/test_closing_different_fees/lightning-2/ has memory leaks: [{'value': '0x55caa0a0b8d0', 'label': 'ccan/ccan/tal/str/str.c:90:char[]', 'backtrace': ['ccan/ccan/tal/tal.c:467 (tal_alloc_)', 'ccan/ccan/tal/tal.c:496 (tal_alloc_arr_)', 'ccan/ccan/tal/str/str.c:90 (tal_vfmt)', 'lightningd/log.c:131 (new_log)', 'lightningd/subd.c:632 (new_subd)', 'lightningd/subd.c:686 (new_peer_subd)', 'lightningd/peer_control.c:2487 (peer_accept_channel)', 'lightningd/peer_control.c:674 (peer_sent_nongossip)', 'lightningd/gossip_control.c:55 (peer_nongossip)', 'lightningd/gossip_control.c:142 (gossip_msg)', 'lightningd/subd.c:477 (sd_msg_read)', 'lightningd/subd.c:319 (read_fds)', 'ccan/ccan/io/io.c:59 (next_plan)', 'ccan/ccan/io/io.c:387 (do_plan)', 'ccan/ccan/io/io.c:397 (io_ready)', 'ccan/ccan/io/poll.c:305 (io_loop)', 'lightningd/lightningd.c:347 (main)', '(null):0 ((null))', '(null):0 ((null))', '(null):0 ((null))'], 'parents': ['lightningd/log.c:103:struct log_book', 'lightningd/lightningd.c:43:struct lightningd']}]
Technically, true, but we save more memory by sharing the prefix pointer
than we lose by leaking it.
However, we'd ideally refcount so it's freed if the log is freed and
all the entries using it are pruned from the log book.
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2018-01-30 07:29:02 +01:00
|
|
|
/* FIXME: Refcount this! */
|
|
|
|
log->prefix = notleak(tal_vfmt(log->lr, fmt, ap));
|
2016-01-21 21:11:47 +01:00
|
|
|
va_end(ap);
|
|
|
|
|
|
|
|
return log;
|
|
|
|
}
|
|
|
|
|
2017-02-24 06:52:56 +01:00
|
|
|
enum log_level get_log_level(struct log_book *lr)
|
|
|
|
{
|
|
|
|
return lr->print_level;
|
|
|
|
}
|
|
|
|
|
2017-01-10 05:48:26 +01:00
|
|
|
void set_log_level(struct log_book *lr, enum log_level level)
|
2016-01-21 21:11:47 +01:00
|
|
|
{
|
2016-01-21 21:11:47 +01:00
|
|
|
lr->print_level = level;
|
2016-01-21 21:11:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void set_log_prefix(struct log *log, const char *prefix)
|
|
|
|
{
|
|
|
|
/* log->lr owns this, since it keeps a pointer to it. */
|
|
|
|
log->prefix = tal_strdup(log->lr, prefix);
|
|
|
|
}
|
|
|
|
|
2017-01-10 05:48:26 +01:00
|
|
|
void set_log_outfn_(struct log_book *lr,
|
2016-01-21 21:11:47 +01:00
|
|
|
void (*print)(const char *prefix,
|
|
|
|
enum log_level level,
|
|
|
|
bool continued,
|
2018-02-02 01:05:41 +01:00
|
|
|
const struct timeabs *time,
|
2016-01-21 21:11:47 +01:00
|
|
|
const char *str, void *arg),
|
|
|
|
void *arg)
|
|
|
|
{
|
|
|
|
lr->print = print;
|
|
|
|
lr->print_arg = arg;
|
|
|
|
}
|
|
|
|
|
2016-01-21 21:11:47 +01:00
|
|
|
const char *log_prefix(const struct log *log)
|
|
|
|
{
|
|
|
|
return log->prefix;
|
|
|
|
}
|
|
|
|
|
2017-01-10 05:48:26 +01:00
|
|
|
size_t log_max_mem(const struct log_book *lr)
|
2016-01-21 21:11:47 +01:00
|
|
|
{
|
|
|
|
return lr->max_mem;
|
|
|
|
}
|
|
|
|
|
2017-01-10 05:48:26 +01:00
|
|
|
size_t log_used(const struct log_book *lr)
|
2016-01-21 21:11:47 +01:00
|
|
|
{
|
|
|
|
return lr->mem_used;
|
|
|
|
}
|
|
|
|
|
2017-01-10 05:48:26 +01:00
|
|
|
const struct timeabs *log_init_time(const struct log_book *lr)
|
2016-01-21 21:11:47 +01:00
|
|
|
{
|
|
|
|
return &lr->init_time;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void add_entry(struct log *log, struct log_entry *l)
|
|
|
|
{
|
|
|
|
log->lr->mem_used += sizeof(*l) + log_bufsize(l);
|
|
|
|
list_add_tail(&log->lr->log, &l->list);
|
|
|
|
|
|
|
|
if (log->lr->mem_used > log->lr->max_mem) {
|
|
|
|
size_t old_mem = log->lr->mem_used, deleted;
|
|
|
|
deleted = prune_log(log->lr);
|
|
|
|
log_debug(log, "Log pruned %zu entries (mem %zu -> %zu)",
|
|
|
|
deleted, old_mem, log->lr->mem_used);
|
|
|
|
}
|
2017-09-12 13:49:37 +02:00
|
|
|
|
|
|
|
/* Free up temporaries now if any */
|
|
|
|
if (tal_first(ltmp)) {
|
|
|
|
tal_free(ltmp);
|
2017-12-15 11:29:03 +01:00
|
|
|
ltmp = notleak(tal(log->lr, char));
|
2017-09-12 13:49:37 +02:00
|
|
|
}
|
2016-01-21 21:11:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct log_entry *new_log_entry(struct log *log, enum log_level level)
|
|
|
|
{
|
|
|
|
struct log_entry *l = tal(log->lr, struct log_entry);
|
|
|
|
|
2016-04-24 12:05:13 +02:00
|
|
|
l->time = time_now();
|
2016-01-21 21:11:47 +01:00
|
|
|
l->level = level;
|
|
|
|
l->skipped = 0;
|
|
|
|
l->prefix = log->prefix;
|
|
|
|
|
|
|
|
return l;
|
|
|
|
}
|
|
|
|
|
|
|
|
void logv(struct log *log, enum log_level level, const char *fmt, va_list ap)
|
|
|
|
{
|
2018-02-05 05:09:27 +01:00
|
|
|
int save_errno = errno;
|
2016-01-21 21:11:47 +01:00
|
|
|
struct log_entry *l = new_log_entry(log, level);
|
|
|
|
|
|
|
|
l->log = tal_vfmt(l, fmt, ap);
|
|
|
|
|
2016-01-21 21:11:47 +01:00
|
|
|
if (level >= log->lr->print_level)
|
2018-02-02 01:05:41 +01:00
|
|
|
log->lr->print(log->prefix, level, false, &l->time, l->log,
|
2016-01-21 21:11:47 +01:00
|
|
|
log->lr->print_arg);
|
2016-01-21 21:11:47 +01:00
|
|
|
|
|
|
|
add_entry(log, l);
|
2018-02-05 05:09:27 +01:00
|
|
|
errno = save_errno;
|
2016-01-21 21:11:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void log_io(struct log *log, bool in, const void *data, size_t len)
|
|
|
|
{
|
|
|
|
int save_errno = errno;
|
|
|
|
struct log_entry *l = new_log_entry(log, LOG_IO);
|
|
|
|
|
|
|
|
l->log = tal_arr(l, char, 1 + len);
|
|
|
|
l->log[0] = in;
|
|
|
|
memcpy(l->log + 1, data, len);
|
|
|
|
|
2016-01-21 21:11:47 +01:00
|
|
|
if (LOG_IO >= log->lr->print_level) {
|
|
|
|
const char *dir = in ? "[IN]" : "[OUT]";
|
|
|
|
char *hex = tal_arr(l, char, strlen(dir) + hex_str_size(len));
|
|
|
|
strcpy(hex, dir);
|
|
|
|
hex_encode(data, len, hex + strlen(dir), hex_str_size(len));
|
2018-02-02 01:05:41 +01:00
|
|
|
log->lr->print(log->prefix, LOG_IO, false, &l->time, l->log,
|
2016-01-21 21:11:47 +01:00
|
|
|
log->lr->print_arg);
|
2016-01-21 21:11:47 +01:00
|
|
|
tal_free(hex);
|
|
|
|
}
|
2016-01-21 21:11:47 +01:00
|
|
|
|
2016-01-21 21:11:47 +01:00
|
|
|
add_entry(log, l);
|
|
|
|
errno = save_errno;
|
|
|
|
}
|
|
|
|
|
2017-06-20 02:39:17 +02:00
|
|
|
void logv_add(struct log *log, const char *fmt, va_list ap)
|
2016-01-21 21:11:47 +01:00
|
|
|
{
|
|
|
|
struct log_entry *l = list_tail(&log->lr->log, struct log_entry, list);
|
|
|
|
size_t oldlen = strlen(l->log);
|
|
|
|
|
|
|
|
/* Remove from list, so it doesn't get pruned. */
|
|
|
|
log->lr->mem_used -= sizeof(*l) + oldlen + 1;
|
|
|
|
list_del_from(&log->lr->log, &l->list);
|
|
|
|
|
|
|
|
tal_append_vfmt(&l->log, fmt, ap);
|
|
|
|
add_entry(log, l);
|
|
|
|
|
2016-01-21 21:11:47 +01:00
|
|
|
if (l->level >= log->lr->print_level)
|
2018-02-02 01:05:41 +01:00
|
|
|
log->lr->print(log->prefix, l->level, true, &l->time, l->log + oldlen,
|
2016-01-21 21:11:47 +01:00
|
|
|
log->lr->print_arg);
|
2016-01-21 21:11:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void log_(struct log *log, enum log_level level, const char *fmt, ...)
|
|
|
|
{
|
|
|
|
va_list ap;
|
|
|
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
logv(log, level, fmt, ap);
|
|
|
|
va_end(ap);
|
|
|
|
}
|
|
|
|
|
|
|
|
void log_add(struct log *log, const char *fmt, ...)
|
|
|
|
{
|
|
|
|
va_list ap;
|
|
|
|
|
|
|
|
va_start(ap, fmt);
|
2017-06-20 02:39:17 +02:00
|
|
|
logv_add(log, fmt, ap);
|
2016-01-21 21:11:47 +01:00
|
|
|
va_end(ap);
|
|
|
|
}
|
|
|
|
|
2017-01-10 05:48:26 +01:00
|
|
|
void log_each_line_(const struct log_book *lr,
|
2016-01-21 21:11:47 +01:00
|
|
|
void (*func)(unsigned int skipped,
|
|
|
|
struct timerel time,
|
|
|
|
enum log_level level,
|
|
|
|
const char *prefix,
|
|
|
|
const char *log,
|
|
|
|
void *arg),
|
|
|
|
void *arg)
|
|
|
|
{
|
|
|
|
const struct log_entry *i;
|
|
|
|
|
|
|
|
list_for_each(&lr->log, i, list) {
|
|
|
|
func(i->skipped, time_between(i->time, lr->init_time),
|
|
|
|
i->level, i->prefix, i->log, arg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct log_data {
|
|
|
|
int fd;
|
|
|
|
const char *prefix;
|
|
|
|
};
|
|
|
|
|
|
|
|
static void log_one_line(unsigned int skipped,
|
|
|
|
struct timerel diff,
|
|
|
|
enum log_level level,
|
|
|
|
const char *prefix,
|
|
|
|
const char *log,
|
|
|
|
struct log_data *data)
|
|
|
|
{
|
|
|
|
char buf[101];
|
|
|
|
|
|
|
|
if (skipped) {
|
|
|
|
sprintf(buf, "%s... %u skipped...", data->prefix, skipped);
|
|
|
|
write_all(data->fd, buf, strlen(buf));
|
|
|
|
data->prefix = "\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
sprintf(buf, "%s+%lu.%09u %s%s: ",
|
|
|
|
data->prefix,
|
|
|
|
(unsigned long)diff.ts.tv_sec,
|
|
|
|
(unsigned)diff.ts.tv_nsec,
|
|
|
|
prefix,
|
|
|
|
level == LOG_IO ? (log[0] ? "IO-IN" : "IO-OUT")
|
|
|
|
: level == LOG_DBG ? "DEBUG"
|
|
|
|
: level == LOG_INFORM ? "INFO"
|
|
|
|
: level == LOG_UNUSUAL ? "UNUSUAL"
|
|
|
|
: level == LOG_BROKEN ? "BROKEN"
|
|
|
|
: "**INVALID**");
|
|
|
|
|
|
|
|
write_all(data->fd, buf, strlen(buf));
|
|
|
|
if (level == LOG_IO) {
|
|
|
|
size_t off, used, len = tal_count(log)-1;
|
|
|
|
|
|
|
|
/* No allocations, may be in signal handler. */
|
|
|
|
for (off = 0; off < len; off += used) {
|
|
|
|
used = len - off;
|
|
|
|
if (hex_str_size(used) > sizeof(buf))
|
|
|
|
used = hex_data_size(sizeof(buf));
|
|
|
|
hex_encode(log + 1 + off, used, buf, hex_str_size(used));
|
|
|
|
write_all(data->fd, buf, strlen(buf));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
write_all(data->fd, log, strlen(log));
|
|
|
|
}
|
|
|
|
|
|
|
|
data->prefix = "\n";
|
|
|
|
}
|
|
|
|
|
2016-01-21 21:11:47 +01:00
|
|
|
static struct {
|
|
|
|
const char *name;
|
|
|
|
enum log_level level;
|
|
|
|
} log_levels[] = {
|
|
|
|
{ "IO", LOG_IO },
|
|
|
|
{ "DEBUG", LOG_DBG },
|
|
|
|
{ "INFO", LOG_INFORM },
|
|
|
|
{ "UNUSUAL", LOG_UNUSUAL },
|
|
|
|
{ "BROKEN", LOG_BROKEN }
|
|
|
|
};
|
|
|
|
|
|
|
|
static char *arg_log_level(const char *arg, struct log *log)
|
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(log_levels); i++) {
|
|
|
|
if (strcasecmp(arg, log_levels[i].name) == 0) {
|
|
|
|
set_log_level(log->lr, log_levels[i].level);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return tal_fmt(NULL, "unknown log level");
|
|
|
|
}
|
|
|
|
|
2018-01-29 01:30:15 +01:00
|
|
|
static void show_log_level(char buf[OPT_SHOW_LEN], const struct log *log)
|
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(log_levels); i++) {
|
|
|
|
if (log->lr->print_level == log_levels[i].level) {
|
|
|
|
strncpy(buf, log_levels[i].name, OPT_SHOW_LEN-1);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
abort();
|
|
|
|
}
|
|
|
|
|
2016-01-21 21:11:47 +01:00
|
|
|
static char *arg_log_prefix(const char *arg, struct log *log)
|
|
|
|
{
|
|
|
|
set_log_prefix(log, arg);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2018-01-29 01:30:15 +01:00
|
|
|
static void show_log_prefix(char buf[OPT_SHOW_LEN], const struct log *log)
|
|
|
|
{
|
|
|
|
strncpy(buf, log->prefix, OPT_SHOW_LEN);
|
|
|
|
}
|
|
|
|
|
2018-01-29 01:30:15 +01:00
|
|
|
char *arg_log_to_file(const char *arg, struct lightningd *ld)
|
2016-01-21 21:11:47 +01:00
|
|
|
{
|
2018-01-29 01:30:15 +01:00
|
|
|
FILE *logf;
|
|
|
|
|
|
|
|
if (ld->logfile) {
|
|
|
|
fclose(ld->log->lr->print_arg);
|
|
|
|
ld->logfile = tal_free(ld->logfile);
|
|
|
|
}
|
|
|
|
ld->logfile = tal_strdup(ld, arg);
|
|
|
|
logf = fopen(arg, "a");
|
2016-01-21 21:11:47 +01:00
|
|
|
if (!logf)
|
|
|
|
return tal_fmt(NULL, "Failed to open: %s", strerror(errno));
|
2018-01-29 01:30:15 +01:00
|
|
|
set_log_outfn(ld->log->lr, log_to_file, logf);
|
2016-01-21 21:11:47 +01:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2018-01-29 01:30:15 +01:00
|
|
|
void opt_register_logging(struct lightningd *ld)
|
2016-01-21 21:11:47 +01:00
|
|
|
{
|
2018-01-29 01:30:15 +01:00
|
|
|
opt_register_arg("--log-level", arg_log_level, show_log_level, ld->log,
|
2016-01-21 21:11:47 +01:00
|
|
|
"log level (debug, info, unusual, broken)");
|
2018-01-29 01:30:15 +01:00
|
|
|
opt_register_arg("--log-prefix", arg_log_prefix, show_log_prefix,
|
|
|
|
ld->log,
|
2016-01-21 21:11:47 +01:00
|
|
|
"log prefix");
|
2018-01-29 01:30:15 +01:00
|
|
|
opt_register_arg("--log-file=<file>", arg_log_to_file, NULL, ld,
|
2016-01-21 21:11:47 +01:00
|
|
|
"log to file instead of stdout");
|
|
|
|
}
|
|
|
|
|
2017-09-12 06:56:45 +02:00
|
|
|
static int log_backtrace(void *log, uintptr_t pc,
|
|
|
|
const char *filename, int lineno,
|
|
|
|
const char *function)
|
|
|
|
{
|
|
|
|
log_broken(log, "backtrace: %s:%u (%s) %p",
|
|
|
|
filename, lineno, function, (void *)pc);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-02-05 21:25:54 +01:00
|
|
|
static void log_backtrace_error(void *log, const char *msg,
|
|
|
|
int errnum)
|
|
|
|
{
|
|
|
|
log_broken(log, "error getting backtrace: %s (%d)",
|
|
|
|
msg, errnum);
|
|
|
|
}
|
|
|
|
|
2016-01-21 21:11:47 +01:00
|
|
|
static struct log *crashlog;
|
|
|
|
|
2017-05-22 13:25:01 +02:00
|
|
|
/* FIXME: Dump peer logs! */
|
2016-01-21 21:11:47 +01:00
|
|
|
static void log_crash(int sig)
|
|
|
|
{
|
2016-01-21 21:11:48 +01:00
|
|
|
const char *logfile = NULL;
|
|
|
|
|
|
|
|
if (sig) {
|
|
|
|
log_broken(crashlog, "FATAL SIGNAL %i RECEIVED", sig);
|
2017-10-31 01:04:59 +01:00
|
|
|
if (backtrace_state)
|
2018-02-05 21:25:54 +01:00
|
|
|
backtrace_full(backtrace_state, 0, log_backtrace, log_backtrace_error,
|
2017-10-31 01:04:59 +01:00
|
|
|
crashlog);
|
2016-01-21 21:11:48 +01:00
|
|
|
}
|
2016-01-21 21:11:47 +01:00
|
|
|
|
2018-02-02 01:07:21 +01:00
|
|
|
if (crashlog->lr->print == log_to_stdout) {
|
2016-01-21 21:11:47 +01:00
|
|
|
int fd;
|
|
|
|
|
|
|
|
/* We expect to be in config dir. */
|
|
|
|
logfile = "crash.log";
|
|
|
|
fd = open(logfile, O_WRONLY|O_CREAT, 0600);
|
|
|
|
if (fd < 0) {
|
2017-12-07 23:59:39 +01:00
|
|
|
logfile = "/tmp/lightning-crash.log";
|
2016-01-21 21:11:47 +01:00
|
|
|
fd = open(logfile, O_WRONLY|O_CREAT, 0600);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Dump entire log. */
|
2016-01-21 21:11:48 +01:00
|
|
|
if (fd >= 0) {
|
|
|
|
log_dump_to_file(fd, crashlog->lr);
|
|
|
|
close(fd);
|
|
|
|
} else
|
|
|
|
logfile = NULL;
|
|
|
|
}
|
|
|
|
|
2017-09-12 06:56:45 +02:00
|
|
|
if (sig) {
|
2016-01-21 21:11:48 +01:00
|
|
|
fprintf(stderr, "Fatal signal %u. ", sig);
|
2017-10-31 01:04:59 +01:00
|
|
|
if (backtrace_state)
|
|
|
|
backtrace_print(backtrace_state, 0, stderr);
|
2017-09-12 06:56:45 +02:00
|
|
|
}
|
2016-01-21 21:11:48 +01:00
|
|
|
if (logfile)
|
|
|
|
fprintf(stderr, "Log dumped in %s", logfile);
|
|
|
|
fprintf(stderr, "\n");
|
2016-01-21 21:11:47 +01:00
|
|
|
}
|
|
|
|
|
2017-09-12 06:56:45 +02:00
|
|
|
void crashlog_activate(const char *argv0, struct log *log)
|
2016-01-21 21:11:47 +01:00
|
|
|
{
|
2016-01-21 21:11:48 +01:00
|
|
|
struct sigaction sa;
|
2016-01-21 21:11:47 +01:00
|
|
|
crashlog = log;
|
2016-01-21 21:11:48 +01:00
|
|
|
|
|
|
|
sa.sa_handler = log_crash;
|
|
|
|
sigemptyset(&sa.sa_mask);
|
|
|
|
/* We want to fall through to default handler */
|
|
|
|
sa.sa_flags = SA_RESETHAND;
|
|
|
|
sigaction(SIGILL, &sa, NULL);
|
|
|
|
sigaction(SIGABRT, &sa, NULL);
|
|
|
|
sigaction(SIGFPE, &sa, NULL);
|
|
|
|
sigaction(SIGSEGV, &sa, NULL);
|
|
|
|
sigaction(SIGBUS, &sa, NULL);
|
2016-01-21 21:11:47 +01:00
|
|
|
}
|
|
|
|
|
2017-01-10 05:48:26 +01:00
|
|
|
void log_dump_to_file(int fd, const struct log_book *lr)
|
2016-01-21 21:11:47 +01:00
|
|
|
{
|
|
|
|
const struct log_entry *i;
|
|
|
|
char buf[100];
|
|
|
|
struct log_data data;
|
|
|
|
time_t start;
|
|
|
|
|
|
|
|
i = list_top(&lr->log, const struct log_entry, list);
|
|
|
|
if (!i) {
|
|
|
|
write_all(fd, "0 bytes:\n\n", strlen("0 bytes:\n\n"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
start = lr->init_time.ts.tv_sec;
|
|
|
|
sprintf(buf, "%zu bytes, %s", lr->mem_used, ctime(&start));
|
|
|
|
write_all(fd, buf, strlen(buf));
|
|
|
|
|
|
|
|
/* ctime includes \n... WTF? */
|
|
|
|
data.prefix = "";
|
|
|
|
data.fd = fd;
|
|
|
|
log_each_line(lr, log_one_line, &data);
|
|
|
|
write_all(fd, "\n\n", strlen("\n\n"));
|
|
|
|
}
|
2016-01-21 21:11:48 +01:00
|
|
|
|
|
|
|
void fatal(const char *fmt, ...)
|
|
|
|
{
|
|
|
|
va_list ap;
|
|
|
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
vfprintf(stderr, fmt, ap);
|
|
|
|
fprintf(stderr, "\n");
|
|
|
|
va_end(ap);
|
|
|
|
|
|
|
|
/* Early on, we just dump errors to stderr. */
|
|
|
|
if (crashlog) {
|
|
|
|
va_start(ap, fmt);
|
|
|
|
logv(crashlog, LOG_BROKEN, fmt, ap);
|
|
|
|
va_end(ap);
|
|
|
|
log_crash(0);
|
|
|
|
}
|
2017-10-29 12:18:13 +01:00
|
|
|
abort();
|
2016-01-21 21:11:48 +01:00
|
|
|
}
|
2018-02-05 05:09:28 +01:00
|
|
|
|
|
|
|
struct log_info {
|
|
|
|
enum log_level level;
|
|
|
|
struct json_result *response;
|
|
|
|
unsigned int num_skipped;
|
|
|
|
};
|
|
|
|
|
|
|
|
static void add_skipped(struct log_info *info)
|
|
|
|
{
|
|
|
|
if (info->num_skipped) {
|
|
|
|
json_object_start(info->response, NULL);
|
|
|
|
json_add_string(info->response, "type", "SKIPPED");
|
|
|
|
json_add_num(info->response, "num_skipped", info->num_skipped);
|
|
|
|
json_object_end(info->response);
|
|
|
|
info->num_skipped = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void json_add_time(struct json_result *result, const char *fieldname,
|
|
|
|
struct timespec ts)
|
|
|
|
{
|
|
|
|
char timebuf[100];
|
|
|
|
|
|
|
|
sprintf(timebuf, "%lu.%09u",
|
|
|
|
(unsigned long)ts.tv_sec,
|
|
|
|
(unsigned)ts.tv_nsec);
|
|
|
|
json_add_string(result, fieldname, timebuf);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void log_to_json(unsigned int skipped,
|
|
|
|
struct timerel diff,
|
|
|
|
enum log_level level,
|
|
|
|
const char *prefix,
|
|
|
|
const char *log,
|
|
|
|
struct log_info *info)
|
|
|
|
{
|
|
|
|
info->num_skipped += skipped;
|
|
|
|
|
|
|
|
if (level < info->level) {
|
|
|
|
info->num_skipped++;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
add_skipped(info);
|
|
|
|
|
|
|
|
json_object_start(info->response, NULL);
|
|
|
|
json_add_string(info->response, "type",
|
|
|
|
level == LOG_BROKEN ? "BROKEN"
|
|
|
|
: level == LOG_UNUSUAL ? "UNUSUAL"
|
|
|
|
: level == LOG_INFORM ? "INFO"
|
|
|
|
: level == LOG_DBG ? "DEBUG"
|
|
|
|
: level == LOG_IO ? "IO"
|
|
|
|
: "UNKNOWN");
|
|
|
|
json_add_time(info->response, "time", diff.ts);
|
|
|
|
json_add_string(info->response, "source", prefix);
|
|
|
|
if (level == LOG_IO) {
|
|
|
|
assert(tal_count(log) > 0);
|
|
|
|
if (log[0])
|
|
|
|
json_add_string(info->response, "direction", "IN");
|
|
|
|
else
|
|
|
|
json_add_string(info->response, "direction", "OUT");
|
|
|
|
|
|
|
|
json_add_hex(info->response, "data", log+1, tal_count(log)-1);
|
|
|
|
} else
|
|
|
|
json_add_string(info->response, "log", log);
|
|
|
|
|
|
|
|
json_object_end(info->response);
|
|
|
|
}
|
|
|
|
|
|
|
|
void json_add_log(struct json_result *response, const char *fieldname,
|
|
|
|
const struct log_book *lr, enum log_level minlevel)
|
|
|
|
{
|
|
|
|
struct log_info info;
|
|
|
|
|
|
|
|
info.level = minlevel;
|
|
|
|
info.response = response;
|
|
|
|
info.num_skipped = 0;
|
|
|
|
|
|
|
|
json_array_start(info.response, "log");
|
|
|
|
log_each_line(lr, log_to_json, &info);
|
|
|
|
add_skipped(&info);
|
|
|
|
json_array_end(info.response);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void json_getlog(struct command *cmd,
|
|
|
|
const char *buffer, const jsmntok_t *params)
|
|
|
|
{
|
|
|
|
struct json_result *response = new_json_result(cmd);
|
|
|
|
enum log_level minlevel;
|
|
|
|
struct log_book *lr = cmd->ld->log_book;
|
|
|
|
jsmntok_t *level;
|
|
|
|
|
|
|
|
if (!json_get_params(cmd, buffer, params, "?level", &level, NULL)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!level)
|
|
|
|
minlevel = LOG_INFORM;
|
|
|
|
else if (json_tok_streq(buffer, level, "io"))
|
|
|
|
minlevel = LOG_IO;
|
|
|
|
else if (json_tok_streq(buffer, level, "debug"))
|
|
|
|
minlevel = LOG_DBG;
|
|
|
|
else if (json_tok_streq(buffer, level, "info"))
|
|
|
|
minlevel = LOG_INFORM;
|
|
|
|
else if (json_tok_streq(buffer, level, "unusual"))
|
|
|
|
minlevel = LOG_UNUSUAL;
|
|
|
|
else {
|
|
|
|
command_fail(cmd, "Invalid level param");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
json_object_start(response, NULL);
|
|
|
|
if (deprecated_apis)
|
|
|
|
json_add_time(response, "creation_time", log_init_time(lr)->ts);
|
|
|
|
json_add_time(response, "created_at", log_init_time(lr)->ts);
|
|
|
|
json_add_num(response, "bytes_used", (unsigned int)log_used(lr));
|
|
|
|
json_add_num(response, "bytes_max", (unsigned int)log_max_mem(lr));
|
|
|
|
json_add_log(response, "log", lr, minlevel);
|
|
|
|
json_object_end(response);
|
|
|
|
command_success(cmd, response);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct json_command getlog_command = {
|
|
|
|
"getlog",
|
|
|
|
json_getlog,
|
|
|
|
"Show logs, with optional log {level} (info|unusual|debug|io)"
|
|
|
|
};
|
|
|
|
AUTODATA(json_command, &getlog_command);
|