From 73377229bb33ab79682ce4b126a63602d13304ad Mon Sep 17 00:00:00 2001 From: Katerina Barone-Adesi Date: Thu, 21 Feb 2013 05:16:29 +0000 Subject: Added a ring buffer log target to store the last N log messages. The log target can be used via log alarms and show alarms. Why? This feature was proposed/requested at http://openbsc.osmocom.org/trac/wiki/Tasks/ErrorLogTarget All messages use the same amount of space, prioritizing simplicity. --- src/Makefile.am | 4 +- src/loggingrb.c | 98 +++++++++++++++++++++++++++++ src/strrb.c | 170 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/vty/logging_vty.c | 85 +++++++++++++++++++++++-- 4 files changed, 351 insertions(+), 6 deletions(-) create mode 100644 src/loggingrb.c create mode 100644 src/strrb.c (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am index b425ea19..081be966 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -11,8 +11,8 @@ libosmocore_la_SOURCES = timer.c select.c signal.c msgb.c bits.c \ write_queue.c utils.c socket.c \ logging.c logging_syslog.c rate_ctr.c \ gsmtap_util.c crc16.c panic.c backtrace.c \ - conv.c application.c rbtree.c \ - crc8gen.c crc16gen.c crc32gen.c crc64gen.c + conv.c application.c rbtree.c strrb.c \ + loggingrb.c crc8gen.c crc16gen.c crc32gen.c crc64gen.c BUILT_SOURCES = crc8gen.c crc16gen.c crc32gen.c crc64gen.c diff --git a/src/loggingrb.c b/src/loggingrb.c new file mode 100644 index 00000000..8faa5b11 --- /dev/null +++ b/src/loggingrb.c @@ -0,0 +1,98 @@ +/* Ringbuffer-backed logging support code */ + +/* (C) 2012-2013 by Katerina Barone-Adesi + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +/*! \addtogroup logging + * @{ + */ + +/*! \file loggingrb.c */ + +#include +#include +#include + +static void _rb_output(struct log_target *target, + unsigned int level, const char *log) +{ + osmo_strrb_add(target->tgt_rb.rb, log); +} + +/*! \brief Return the number of log strings in the osmo_strrb-backed target. + * \param[in] target The target to search. + * + * \return The number of log strings in the osmo_strrb-backed target. + */ +size_t log_target_rb_used_size(struct log_target const *target) +{ + return osmo_strrb_elements(target->tgt_rb.rb); +} + +/*! \brief Return the capacity of the osmo_strrb-backed target. + * \param[in] target The target to search. + * + * Note that this is the capacity (aka max number of messages). + * It is not the number of unused message slots. + * \return The number of log strings in the osmo_strrb-backed target. + */ +size_t log_target_rb_avail_size(struct log_target const *target) +{ + struct osmo_strrb *rb = target->tgt_rb.rb; + return rb->size - 1; +} + +/*! \brief Return the nth log entry in a target. + * \param[in] target The target to search. + * \param[in] logindex The index of the log entry/error message. + * + * \return A pointer to the nth message, or NULL if logindex is invalid. + */ +const char *log_target_rb_get(struct log_target const *target, size_t logindex) +{ + return osmo_strrb_get_nth(target->tgt_rb.rb, logindex); +} + +/*! \brief Create a new logging target for ringbuffer-backed logging. + * \param[in] size The size of the internal backing osmo_strrb (messages). + * \returns A log target in case of success, NULL in case of error. + */ +struct log_target *log_target_create_rb(size_t size) +{ + struct log_target *target; + struct osmo_strrb *rb; + + target = log_target_create(); + if (!target) + return NULL; + + rb = osmo_strrb_create(target, size + 1); + if (!rb) { + log_target_destroy(target); + return NULL; + } + + target->tgt_rb.rb = rb; + target->type = LOG_TGT_TYPE_STRRB; + target->output = _rb_output; + + return target; +} + +/* @} */ diff --git a/src/strrb.c b/src/strrb.c new file mode 100644 index 00000000..626ade69 --- /dev/null +++ b/src/strrb.c @@ -0,0 +1,170 @@ +/* Ringbuffer implementation, tailored for logging. + * This is a lossy ringbuffer. It keeps up to N of the newest messages, + * overwriting the oldest as newer ones come in. + * + * (C) 2012-2013, Katerina Barone-Adesi + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +/*! \file strrb.c + * \brief Lossy string ringbuffer for logging; keeps newest messages. + */ + +#include +#include +#include + +#include + +/* Ringbuffer assumptions, invarients, and notes: + * - start is the index of the first used index slot in the ring buffer. + * - end is the index of the next index slot in the ring buffer. + * - start == end => buffer is empty + * - Consequence: the buffer can hold at most size - 1 messages + * (if this were not the case, full and empty buffers would be indistinguishable + * given the conventions in this implementation). + * - Whenever the ringbuffer is full, start is advanced. The second oldest + * message becomes unreachable by valid indexes (end is not a valid index) + * and the oldest message is overwritten (if there was a message there, which + * is the case unless this is the first time the ringbuffer becomes full). +*/ + +/*! \brief Create an empty, initialized osmo_strrb. + * \param[in] ctx The talloc memory context which should own this. + * \param[in] rb_size The number of messages the osmo_strrb can hold. + * \returns A struct osmo_strrb* on success, NULL in case of error. + * + * This function creates and initializes a ringbuffer. + */ + +struct osmo_strrb *osmo_strrb_create(TALLOC_CTX * ctx, size_t rb_size) +{ + struct osmo_strrb *rb = NULL; + unsigned int i; + + rb = talloc_zero(ctx, struct osmo_strrb); + if (!rb) + goto alloc_error; + + /* start and end are zero already, which is correct */ + rb->size = rb_size; + + rb->buffer = talloc_array(rb, char *, rb->size); + if (!rb->buffer) + goto alloc_error; + for (i = 0; i < rb->size; i++) { + rb->buffer[i] = + talloc_zero_size(rb->buffer, RB_MAX_MESSAGE_SIZE); + if (!rb->buffer[i]) + goto alloc_error; + } + + return rb; + +alloc_error: /* talloc_free(NULL) is safe */ + talloc_free(rb); + return NULL; +} + +/*! \brief Check if an osmo_strrb is empty. + * \param[in] rb The osmo_strrb to check. + * \returns True if the osmo_strrb is empty, false otherwise. + */ +bool osmo_strrb_is_empty(const struct osmo_strrb *rb) +{ + return rb->end == rb->start; +} + +/*! \brief Return a pointer to the Nth string in the osmo_strrb. + * \param[in] rb The osmo_strrb to search. + * \param[in] string_index The index sought (N), zero-indexed. + * + * Return a pointer to the Nth string in the osmo_strrb. + * Return NULL if there is no Nth string. + * Note that N is zero-indexed. + * \returns A pointer to the target string on success, NULL in case of error. + */ +const char *osmo_strrb_get_nth(const struct osmo_strrb *rb, + unsigned int string_index) +{ + unsigned int offset = string_index + rb->start; + + if ((offset >= rb->size) && (rb->start > rb->end)) + offset -= rb->size; + if (_osmo_strrb_is_bufindex_valid(rb, offset)) + return rb->buffer[offset]; + + return NULL; +} + +bool _osmo_strrb_is_bufindex_valid(const struct osmo_strrb *rb, + unsigned int bufi) +{ + if (osmo_strrb_is_empty(rb)) + return 0; + if ((bufi >= rb->size) || (bufi < 0)) + return 0; + if (rb->start < rb->end) + return (bufi >= rb->start) && (bufi < rb->end); + return (bufi < rb->end) || (bufi >= rb->start); +} + +/*! \brief Count the number of log messages in an osmo_strrb. + * \param[in] rb The osmo_strrb to count the elements of. + * + * \returns The number of log messages in the osmo_strrb. + */ +size_t osmo_strrb_elements(const struct osmo_strrb *rb) +{ + if (rb->end < rb->start) + return rb->end + (rb->size - rb->start); + + return rb->end - rb->start; +} + +/*! \brief Add a string to the osmo_strrb. + * \param[in] rb The osmo_strrb to add to. + * \param[in] data The string to add. + * + * Add a message to the osmo_strrb. + * Older messages will be overwritten as necessary. + * \returns 0 normally, 1 as a warning (ie, if data was truncated). + */ +int osmo_strrb_add(struct osmo_strrb *rb, const char *data) +{ + size_t len = strlen(data); + int ret = 0; + + if (len >= RB_MAX_MESSAGE_SIZE) { + len = RB_MAX_MESSAGE_SIZE - 1; + ret = 1; + } + + memcpy(rb->buffer[rb->end], data, len); + rb->buffer[rb->end][len] = '\0'; + + rb->end += 1; + rb->end %= rb->size; + + /* The buffer is full; oldest message is forgotten - see notes above */ + if (rb->end == rb->start) { + rb->start += 1; + rb->start %= rb->size; + } + return ret; +} diff --git a/src/vty/logging_vty.c b/src/vty/logging_vty.c index d473f129..ace346a2 100644 --- a/src/vty/logging_vty.c +++ b/src/vty/logging_vty.c @@ -27,8 +27,8 @@ #include #include #include - -//#include +#include +#include #include #include @@ -252,8 +252,8 @@ static void vty_print_logtarget(struct vty *vty, const struct log_info *info, #define SHOW_LOG_STR "Show current logging configuration\n" DEFUN(show_logging_vty, - show_logging_vty_cmd, - "show logging vty", + show_logging_vty_cmd, + "show logging vty", SHOW_STR SHOW_LOG_STR "Show current logging configuration for this vty\n") { @@ -267,6 +267,33 @@ DEFUN(show_logging_vty, return CMD_SUCCESS; } +DEFUN(show_alarms, + show_alarms_cmd, + "show alarms", + SHOW_STR SHOW_LOG_STR + "Show the contents of the logging ringbuffer\n") +{ + int i, num_alarms; + struct osmo_strrb *rb; + struct log_target *tgt = log_target_find(LOG_TGT_TYPE_STRRB, NULL); + if (!tgt) { + vty_out(vty, "%% No alarms, run 'log alarms <2-32700>'%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + rb = tgt->tgt_rb.rb; + num_alarms = osmo_strrb_elements(rb); + + vty_out(vty, "%% Showing %i alarms%s", num_alarms, VTY_NEWLINE); + + for (i = 0; i < num_alarms; i++) + vty_out(vty, "%% %s%s", osmo_strrb_get_nth(rb, i), + VTY_NEWLINE); + + return CMD_SUCCESS; +} + gDEFUN(cfg_description, cfg_description_cmd, "description .TEXT", "Save human-readable decription of the object\n" @@ -510,6 +537,49 @@ DEFUN(cfg_no_log_file, cfg_no_log_file_cmd, return CMD_SUCCESS; } +DEFUN(cfg_log_alarms, cfg_log_alarms_cmd, + "log alarms <2-32700>", + LOG_STR "Logging alarms to osmo_strrb\n" + "Maximum number of messages to log\n") +{ + struct log_target *tgt; + unsigned int rbsize = atoi(argv[0]); + + tgt = log_target_find(LOG_TGT_TYPE_STRRB, NULL); + if (tgt) + log_target_destroy(tgt); + + tgt = log_target_create_rb(rbsize); + if (!tgt) { + vty_out(vty, "%% Unable to create osmo_strrb (size %u)%s", + rbsize, VTY_NEWLINE); + return CMD_WARNING; + } + log_add_target(tgt); + + vty->index = tgt; + vty->node = CFG_LOG_NODE; + + return CMD_SUCCESS; +} + +DEFUN(cfg_no_log_alarms, cfg_no_log_alarms_cmd, + "no log alarms", + NO_STR LOG_STR "Logging alarms to osmo_strrb\n") +{ + struct log_target *tgt; + + tgt = log_target_find(LOG_TGT_TYPE_STRRB, NULL); + if (!tgt) { + vty_out(vty, "%% No osmo_strrb target found%s", VTY_NEWLINE); + return CMD_WARNING; + } + + log_target_destroy(tgt); + + return CMD_SUCCESS; +} + static int config_write_log_single(struct vty *vty, struct log_target *tgt) { int i; @@ -533,6 +603,10 @@ static int config_write_log_single(struct vty *vty, struct log_target *tgt) case LOG_TGT_TYPE_FILE: vty_out(vty, "log file %s%s", tgt->tgt_file.fname, VTY_NEWLINE); break; + case LOG_TGT_TYPE_STRRB: + vty_out(vty, "log alarms %zu%s", + log_target_rb_avail_size(tgt), VTY_NEWLINE); + break; } vty_out(vty, " logging filter all %u%s", @@ -590,6 +664,7 @@ void logging_vty_add_cmds(const struct log_info *cat) logging_level_cmd.doc = log_vty_command_description(cat); install_element_ve(&logging_level_cmd); install_element_ve(&show_logging_vty_cmd); + install_element_ve(&show_alarms_cmd); install_node(&cfg_log_node, config_write_log); install_default(CFG_LOG_NODE); @@ -603,6 +678,8 @@ void logging_vty_add_cmds(const struct log_info *cat) install_element(CONFIG_NODE, &cfg_no_log_stderr_cmd); install_element(CONFIG_NODE, &cfg_log_file_cmd); install_element(CONFIG_NODE, &cfg_no_log_file_cmd); + install_element(CONFIG_NODE, &cfg_log_alarms_cmd); + install_element(CONFIG_NODE, &cfg_no_log_alarms_cmd); #ifdef HAVE_SYSLOG_H install_element(CONFIG_NODE, &cfg_log_syslog_cmd); install_element(CONFIG_NODE, &cfg_log_syslog_local_cmd); -- cgit v1.2.3