diff options
author | Sébastien Helleu <flashcode@flashtux.org> | 2022-09-28 20:32:12 +0200 |
---|---|---|
committer | Sébastien Helleu <flashcode@flashtux.org> | 2022-09-28 20:52:59 +0200 |
commit | 7711ed95c5959be5b4fa75b6366a653ce3308a9d (patch) | |
tree | e395b8da0487bea8b124b40a76ed4602f339451c /src/plugins/logger | |
parent | 26e6fdc64561d7575b3c937a9bb4f39cc8c0954f (diff) | |
download | weechat-7711ed95c5959be5b4fa75b6366a653ce3308a9d.zip |
logger: add options to rotate and compress log files (closes #314)
New options:
- logger.file.rotation_compression_level
- logger.file.rotation_compression_type
- logger.file.rotation_size_max
Diffstat (limited to 'src/plugins/logger')
-rw-r--r-- | src/plugins/logger/logger-backlog.c | 2 | ||||
-rw-r--r-- | src/plugins/logger/logger-buffer.c | 690 | ||||
-rw-r--r-- | src/plugins/logger/logger-buffer.h | 25 | ||||
-rw-r--r-- | src/plugins/logger/logger-command.c | 2 | ||||
-rw-r--r-- | src/plugins/logger/logger-config.c | 115 | ||||
-rw-r--r-- | src/plugins/logger/logger-config.h | 6 | ||||
-rw-r--r-- | src/plugins/logger/logger.c | 440 | ||||
-rw-r--r-- | src/plugins/logger/logger.h | 8 |
8 files changed, 844 insertions, 444 deletions
diff --git a/src/plugins/logger/logger-backlog.c b/src/plugins/logger/logger-backlog.c index 724725aa5..c4cc0af48 100644 --- a/src/plugins/logger/logger-backlog.c +++ b/src/plugins/logger/logger-backlog.c @@ -244,7 +244,7 @@ logger_backlog_signal_cb (const void *pointer, void *data, if (ptr_logger_buffer && ptr_logger_buffer->log_enabled) { if (!ptr_logger_buffer->log_filename) - logger_set_log_filename (ptr_logger_buffer); + logger_buffer_set_log_filename (ptr_logger_buffer); if (ptr_logger_buffer->log_filename) { ptr_logger_buffer->log_enabled = 0; diff --git a/src/plugins/logger/logger-buffer.c b/src/plugins/logger/logger-buffer.c index b1925033a..17cf08ce2 100644 --- a/src/plugins/logger/logger-buffer.c +++ b/src/plugins/logger/logger-buffer.c @@ -26,12 +26,18 @@ #include <stdlib.h> #include <string.h> #include <stdio.h> +#include <errno.h> +#include <sys/stat.h> #include "../weechat-plugin.h" #include "logger.h" #include "logger-buffer.h" +#include "logger-config.h" +char *logger_buffer_compression_extension[LOGGER_BUFFER_NUM_COMPRESSION_TYPES] = +{ "", ".gz", ".zst" }; + struct t_logger_buffer *logger_buffers = NULL; struct t_logger_buffer *last_logger_buffer = NULL; @@ -96,6 +102,7 @@ logger_buffer_add (struct t_gui_buffer *buffer, int log_level) new_logger_buffer->log_level = log_level; new_logger_buffer->write_start_info_line = 1; new_logger_buffer->flush_needed = 0; + new_logger_buffer->compressing = 0; new_logger_buffer->prev_buffer = last_logger_buffer; new_logger_buffer->next_buffer = NULL; @@ -160,6 +167,687 @@ logger_buffer_search_log_filename (const char *log_filename) } /* + * Sets log filename for a logger buffer. + */ + +void +logger_buffer_set_log_filename (struct t_logger_buffer *logger_buffer) +{ + char *log_filename, *pos_last_sep; + char *dir_separator; + struct t_logger_buffer *ptr_logger_buffer; + + /* get log filename for buffer */ + log_filename = logger_get_filename (logger_buffer->buffer); + if (!log_filename) + { + weechat_printf_date_tags (NULL, 0, "no_log", + _("%s%s: not enough memory"), + weechat_prefix ("error"), + LOGGER_PLUGIN_NAME); + return; + } + + /* log file is already used by another buffer? */ + ptr_logger_buffer = logger_buffer_search_log_filename (log_filename); + if (ptr_logger_buffer) + { + weechat_printf_date_tags ( + NULL, 0, "no_log", + _("%s%s: unable to start logging for buffer " + "\"%s\": filename \"%s\" is already used by " + "another buffer (check your log settings)"), + weechat_prefix ("error"), + LOGGER_PLUGIN_NAME, + weechat_buffer_get_string (logger_buffer->buffer, "name"), + log_filename); + free (log_filename); + return; + } + + /* create directory for path in "log_filename" */ + dir_separator = weechat_info_get ("dir_separator", ""); + if (dir_separator) + { + pos_last_sep = strrchr (log_filename, dir_separator[0]); + if (pos_last_sep) + { + pos_last_sep[0] = '\0'; + weechat_mkdir_parents (log_filename, 0700); + pos_last_sep[0] = dir_separator[0]; + } + free (dir_separator); + } + + /* set log filename */ + logger_buffer->log_filename = log_filename; +} + +/* + * Creates a log file. + * + * Returns: + * 1: OK + * 0: error + */ + +int +logger_buffer_create_log_file (struct t_logger_buffer *logger_buffer) +{ + char *charset, *message, buf_time[256], buf_beginning[1024]; + int log_level, rc; + time_t seconds; + struct tm *date_tmp; + struct stat statbuf; + + if (logger_buffer->log_file) + { + /* + * check that the inode has not changed, otherwise that means the file + * was deleted, and we must reopen it + */ + rc = stat (logger_buffer->log_filename, &statbuf); + if ((rc == 0) && (statbuf.st_ino == logger_buffer->log_file_inode)) + { + /* inode has not changed, we can write in this file */ + return 1; + } + fclose (logger_buffer->log_file); + logger_buffer->log_file = NULL; + logger_buffer->log_file_inode = 0; + } + + /* get log level */ + log_level = logger_get_level_for_buffer (logger_buffer->buffer); + if (log_level == 0) + return 0; + + /* create directory */ + if (!logger_create_directory ()) + { + weechat_printf_date_tags ( + NULL, 0, "no_log", + _("%s%s: unable to create directory for logs " + "(\"%s\")"), + weechat_prefix ("error"), LOGGER_PLUGIN_NAME, + weechat_config_string (logger_config_file_path)); + return 0; + } + if (!logger_buffer->log_filename) + logger_buffer_set_log_filename (logger_buffer); + if (!logger_buffer->log_filename) + return 0; + + /* create or append to log file */ + logger_buffer->log_file = + fopen (logger_buffer->log_filename, "a"); + if (!logger_buffer->log_file) + { + weechat_printf_date_tags ( + NULL, 0, "no_log", + _("%s%s: unable to write log file \"%s\": %s"), + weechat_prefix ("error"), LOGGER_PLUGIN_NAME, + logger_buffer->log_filename, strerror (errno)); + return 0; + } + + /* get file inode */ + rc = stat (logger_buffer->log_filename, &statbuf); + if (rc != 0) + { + weechat_printf_date_tags ( + NULL, 0, "no_log", + _("%s%s: unable to get file status of log file \"%s\": %s"), + weechat_prefix ("error"), LOGGER_PLUGIN_NAME, + logger_buffer->log_filename, strerror (errno)); + fclose (logger_buffer->log_file); + logger_buffer->log_file = NULL; + logger_buffer->log_file_inode = 0; + return 0; + } + logger_buffer->log_file_inode = statbuf.st_ino; + + /* write info line */ + if (weechat_config_boolean (logger_config_file_info_lines) + && logger_buffer->write_start_info_line) + { + buf_time[0] = '\0'; + seconds = time (NULL); + date_tmp = localtime (&seconds); + if (date_tmp) + { + if (strftime (buf_time, sizeof (buf_time) - 1, + weechat_config_string (logger_config_file_time_format), + date_tmp) == 0) + buf_time[0] = '\0'; + } + snprintf (buf_beginning, sizeof (buf_beginning), + _("%s\t**** Beginning of log ****"), + buf_time); + charset = weechat_info_get ("charset_terminal", ""); + message = (charset) ? + weechat_iconv_from_internal (charset, buf_beginning) : NULL; + fprintf (logger_buffer->log_file, + "%s\n", (message) ? message : buf_beginning); + if (charset) + free (charset); + if (message) + free (message); + logger_buffer->flush_needed = 1; + } + logger_buffer->write_start_info_line = 0; + + return 1; +} + +/* + * Compresses a log file, in the child process. + */ + +void +logger_buffer_compress_file (struct t_logger_buffer *logger_buffer) +{ + char filename[PATH_MAX], new_filename[PATH_MAX]; + const char *ptr_extension; + int compression_type, compression_level; + + compression_type = weechat_config_integer ( + logger_config_file_rotation_compression_type); + ptr_extension = logger_buffer_compression_extension[compression_type]; + + snprintf (filename, sizeof (filename), + "%s.1", + logger_buffer->log_filename); + snprintf (new_filename, sizeof (new_filename), + "%s.1%s", + logger_buffer->log_filename, + ptr_extension); + + compression_level = weechat_config_integer ( + logger_config_file_rotation_compression_level); + + switch (compression_type) + { + case LOGGER_BUFFER_COMPRESSION_GZIP: + if (weechat_file_compress (filename, new_filename, + "gzip", compression_level)) + { + unlink (filename); + } + break; + case LOGGER_BUFFER_COMPRESSION_ZSTD: + if (weechat_file_compress (filename, new_filename, + "zstd", compression_level)) + { + unlink (filename); + } + break; + default: + break; + } + + exit (0); +} + +/* + * Compression callback. + */ + +int +logger_buffer_compress_cb (const void *pointer, void *data, + const char *command, int return_code, + const char *out, const char *err) +{ + struct t_logger_buffer *logger_buffer; + + /* make C compiler happy */ + (void) data; + (void) command; + (void) return_code; + (void) out; + (void) err; + + logger_buffer = (struct t_logger_buffer *)pointer; + if (!logger_buffer_valid (logger_buffer)) + return WEECHAT_RC_OK; + + if (return_code == WEECHAT_HOOK_PROCESS_CHILD) + { + logger_buffer_compress_file (logger_buffer); + } + else if (return_code >= 0) + { + logger_buffer->compressing = 0; + } + + return WEECHAT_RC_OK; +} + +/* + * Rotates a log file if needed (rotation enabled and max size reached). + * + * For example if we have these log files: + * + * irc.libera.#test.weechatlog (current log file) + * irc.libera.#test.weechatlog.1 + * irc.libera.#test.weechatlog.2 + * + * The following renames are done in this order: + * + * irc.libera.#test.weechatlog.2 -> irc.libera.#test.weechatlog.3 + * irc.libera.#test.weechatlog.1 -> irc.libera.#test.weechatlog.2 + * irc.libera.#test.weechatlog -> irc.libera.#test.weechatlog.1 + * + * And in case of compressed log files: + * + * irc.libera.#test.weechatlog.2.gz -> irc.libera.#test.weechatlog.3.gz + * irc.libera.#test.weechatlog.1.gz -> irc.libera.#test.weechatlog.2.gz + * irc.libera.#test.weechatlog -> irc.libera.#test.weechatlog.1 + * + * Then the file irc.libera.#test.weechatlog is created again. + */ + +void +logger_buffer_rotate (struct t_logger_buffer *logger_buffer) +{ + int compression_type, extension_index, found_comp, found_not_comp, i; + char filename[PATH_MAX], new_filename[PATH_MAX]; + const char *ptr_extension; + struct stat st; + + /* do not rotate if compression of log file is running */ + if (logger_buffer->compressing) + return; + + /* do not rotate if rotation is disabled */ + if (logger_config_rotation_size_max == 0) + return; + + /* do not rotate if max size is not reached */ + if (fstat (fileno (logger_buffer->log_file), &st) != 0) + return; + if (st.st_size <= (long int)logger_config_rotation_size_max) + return; + + if (weechat_logger_plugin->debug) + { + weechat_log_printf ("logger: rotation for log: \"%s\"", + logger_buffer->log_filename); + } + + compression_type = weechat_config_integer ( + logger_config_file_rotation_compression_type); + ptr_extension = logger_buffer_compression_extension[compression_type]; + + /* find the highest existing extension index */ + extension_index = 1; + while (1) + { + found_comp = 0; + found_not_comp = 0; + if (ptr_extension[0]) + { + /* try compressed file */ + snprintf (filename, sizeof (filename), + "%s.%d%s", + logger_buffer->log_filename, + extension_index, + ptr_extension); + found_comp = (access (filename, F_OK) == 0); + } + if (!found_comp) + { + /* try non-compressed file */ + snprintf (filename, sizeof (filename), + "%s.%d", + logger_buffer->log_filename, + extension_index); + found_not_comp = (access (filename, F_OK) == 0); + } + if (!found_comp && !found_not_comp) + break; + + extension_index++; + } + extension_index--; + + /* close current log file */ + fclose (logger_buffer->log_file); + logger_buffer->log_file = NULL; + logger_buffer->log_file_inode = 0; + + /* + * rename all files with an extension, starting with the higher one + * + * example with no compression enabled: + * .2" -> ".3" then ".1" -> ".2" then "" -> ".1" + * + * example with gzip compression: + * ".2.gz" -> ".3.gz" then ".1.gz" -> ".2.gz" then "" -> ".1" + */ + for (i = extension_index; i >= 0; i--) + { + if (i == 0) + { + /* rename current log file to ".1" (no compression for now) */ + snprintf (filename, sizeof (filename), + "%s", + logger_buffer->log_filename); + snprintf (new_filename, sizeof (new_filename), + "%s.%d", + logger_buffer->log_filename, + i + 1); + } + else + { + /* rename ".N" to ".N+1" */ + filename[0] = '\0'; + + if (ptr_extension[0]) + { + /* try compressed file */ + snprintf (filename, sizeof (filename), + "%s.%d%s", + logger_buffer->log_filename, + i, + ptr_extension); + if (access (filename, F_OK) == 0) + { + /* compressed file found, go on */ + snprintf (new_filename, sizeof (new_filename), + "%s.%d%s", + logger_buffer->log_filename, + i + 1, + ptr_extension); + } + else + { + filename[0] = '\0'; + } + } + if (!filename[0]) + { + /* non-compressed file */ + snprintf (filename, sizeof (filename), + "%s.%d", + logger_buffer->log_filename, + i); + snprintf (new_filename, sizeof (filename), + "%s.%d", + logger_buffer->log_filename, + i + 1); + } + } + if (weechat_logger_plugin->debug) + { + weechat_log_printf ("logger: renaming \"%s\" to \"%s\"", + filename, + new_filename); + } + if (rename (filename, new_filename) != 0) + break; + } + + if (compression_type != LOGGER_BUFFER_COMPRESSION_NONE) + { + /* compress rotated log file */ + if (weechat_logger_plugin->debug) + { + weechat_log_printf ("logger: compressing \"%s.1\" => \"%s.1%s\"", + logger_buffer->log_filename, + logger_buffer->log_filename, + ptr_extension); + } + logger_buffer->compressing = 1; + (void) weechat_hook_process ("func:compress", + 0, + &logger_buffer_compress_cb, + logger_buffer, + NULL); + } +} + +/* + * Writes a line to log file. + */ + +void +logger_buffer_write_line (struct t_logger_buffer *logger_buffer, + const char *format, ...) +{ + char *charset, *message; + + if (!logger_buffer_create_log_file (logger_buffer)) + return; + + if (!logger_buffer->log_file) + return; + + weechat_va_format (format); + if (vbuffer) + { + charset = weechat_info_get ("charset_terminal", ""); + message = (charset) ? + weechat_iconv_from_internal (charset, vbuffer) : NULL; + fprintf (logger_buffer->log_file, + "%s\n", (message) ? message : vbuffer); + if (charset) + free (charset); + if (message) + free (message); + logger_buffer->flush_needed = 1; + if (!logger_hook_timer) + { + fflush (logger_buffer->log_file); + if (weechat_config_boolean (logger_config_file_fsync)) + fsync (fileno (logger_buffer->log_file)); + logger_buffer->flush_needed = 0; + logger_buffer_rotate (logger_buffer); + } + free (vbuffer); + } +} + +/* + * Stops log for a logger buffer. + */ + +void +logger_buffer_stop (struct t_logger_buffer *logger_buffer, int write_info_line) +{ + time_t seconds; + struct tm *date_tmp; + char buf_time[256]; + + if (!logger_buffer) + return; + + if (logger_buffer->log_enabled && logger_buffer->log_file) + { + if (write_info_line && weechat_config_boolean (logger_config_file_info_lines)) + { + buf_time[0] = '\0'; + seconds = time (NULL); + date_tmp = localtime (&seconds); + if (date_tmp) + { + if (strftime (buf_time, sizeof (buf_time) - 1, + weechat_config_string (logger_config_file_time_format), + date_tmp) == 0) + buf_time[0] = '\0'; + } + logger_buffer_write_line (logger_buffer, + _("%s\t**** End of log ****"), + buf_time); + } + } + + logger_buffer_free (logger_buffer); +} + +/* + * Ends log for all buffers. + */ + +void +logger_buffer_stop_all (int write_info_line) +{ + while (logger_buffers) + { + logger_buffer_stop (logger_buffers, write_info_line); + } +} + +/* + * Starts logging for a buffer. + */ + +void +logger_buffer_start (struct t_gui_buffer *buffer, int write_info_line) +{ + struct t_logger_buffer *ptr_logger_buffer; + int log_level, log_enabled; + + if (!buffer) + return; + + log_level = logger_get_level_for_buffer (buffer); + log_enabled = weechat_config_boolean (logger_config_file_auto_log) + && (log_level > 0); + + ptr_logger_buffer = logger_buffer_search_buffer (buffer); + + /* logging is disabled for buffer */ + if (!log_enabled) + { + /* stop logger if it is active */ + if (ptr_logger_buffer) + logger_buffer_stop (ptr_logger_buffer, 1); + } + else + { + /* logging is enabled for buffer */ + if (ptr_logger_buffer) + ptr_logger_buffer->log_level = log_level; + else + { + ptr_logger_buffer = logger_buffer_add (buffer, log_level); + + if (ptr_logger_buffer) + { + if (ptr_logger_buffer->log_filename) + { + if (ptr_logger_buffer->log_file) + { + fclose (ptr_logger_buffer->log_file); + ptr_logger_buffer->log_file = NULL; + ptr_logger_buffer->log_file_inode = 0; + } + } + } + } + if (ptr_logger_buffer) + ptr_logger_buffer->write_start_info_line = write_info_line; + } +} + +/* + * Starts logging for all buffers. + */ + +void +logger_buffer_start_all (int write_info_line) +{ + struct t_infolist *ptr_infolist; + + ptr_infolist = weechat_infolist_get ("buffer", NULL, NULL); + if (ptr_infolist) + { + while (weechat_infolist_next (ptr_infolist)) + { + logger_buffer_start (weechat_infolist_pointer (ptr_infolist, + "pointer"), + write_info_line); + } + weechat_infolist_free (ptr_infolist); + } +} + +/* + * Flushes all log files. + */ + +void +logger_buffer_flush () +{ + struct t_logger_buffer *ptr_logger_buffer; + + for (ptr_logger_buffer = logger_buffers; ptr_logger_buffer; + ptr_logger_buffer = ptr_logger_buffer->next_buffer) + { + if (ptr_logger_buffer->log_file && ptr_logger_buffer->flush_needed) + { + if (weechat_logger_plugin->debug >= 2) + { + weechat_printf_date_tags (NULL, 0, "no_log", + "%s: flush file %s", + LOGGER_PLUGIN_NAME, + ptr_logger_buffer->log_filename); + } + fflush (ptr_logger_buffer->log_file); + if (weechat_config_boolean (logger_config_file_fsync)) + fsync (fileno (ptr_logger_buffer->log_file)); + ptr_logger_buffer->flush_needed = 0; + logger_buffer_rotate (ptr_logger_buffer); + } + } +} + +/* + * Adjusts log filenames for all buffers. + * + * Filename can change if configuration option is changed, or if day of system + * date has changed. + */ + +void +logger_buffer_adjust_log_filenames () +{ + struct t_infolist *ptr_infolist; + struct t_logger_buffer *ptr_logger_buffer; + struct t_gui_buffer *ptr_buffer; + char *log_filename; + + ptr_infolist = weechat_infolist_get ("buffer", NULL, NULL); + if (ptr_infolist) + { + while (weechat_infolist_next (ptr_infolist)) + { + ptr_buffer = weechat_infolist_pointer (ptr_infolist, "pointer"); + ptr_logger_buffer = logger_buffer_search_buffer (ptr_buffer); + if (ptr_logger_buffer && ptr_logger_buffer->log_filename) + { + log_filename = logger_get_filename (ptr_logger_buffer->buffer); + if (log_filename) + { + if (strcmp (log_filename, ptr_logger_buffer->log_filename) != 0) + { + /* + * log filename has changed (probably due to day + * change),then we'll use new filename + */ + logger_buffer_stop (ptr_logger_buffer, 1); + logger_buffer_start (ptr_buffer, 1); + } + free (log_filename); + } + } + } + weechat_infolist_free (ptr_infolist); + } +} + +/* * Removes a logger buffer from list. */ @@ -245,6 +933,8 @@ logger_buffer_add_to_infolist (struct t_infolist *infolist, return 0; if (!weechat_infolist_new_var_integer (ptr_item, "flush_needed", logger_buffer->flush_needed)) return 0; + if (!weechat_infolist_new_var_integer (ptr_item, "compressing", logger_buffer->compressing)) + return 0; return 1; } diff --git a/src/plugins/logger/logger-buffer.h b/src/plugins/logger/logger-buffer.h index 52266fa8d..383de56a8 100644 --- a/src/plugins/logger/logger-buffer.h +++ b/src/plugins/logger/logger-buffer.h @@ -21,10 +21,20 @@ #define WEECHAT_PLUGIN_LOGGER_BUFFER_H #include <stdio.h> +#include <stdarg.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> +enum t_logger_buffer_compression +{ + LOGGER_BUFFER_COMPRESSION_NONE = 0, + LOGGER_BUFFER_COMPRESSION_GZIP, + LOGGER_BUFFER_COMPRESSION_ZSTD, + /* number of compression types */ + LOGGER_BUFFER_NUM_COMPRESSION_TYPES, +}; + struct t_infolist; struct t_logger_buffer @@ -38,6 +48,9 @@ struct t_logger_buffer int write_start_info_line; /* 1 if start info line must be */ /* written in file */ int flush_needed; /* flush needed? */ + int compressing; /* compressing rotated log, this */ + /* prevents any new rotation */ + /* before the end of compression */ struct t_logger_buffer *prev_buffer; /* link to previous buffer */ struct t_logger_buffer *next_buffer; /* link to next buffer */ }; @@ -50,6 +63,18 @@ extern struct t_logger_buffer *logger_buffer_add (struct t_gui_buffer *buffer, int log_level); extern struct t_logger_buffer *logger_buffer_search_buffer (struct t_gui_buffer *buffer); extern struct t_logger_buffer *logger_buffer_search_log_filename (const char *log_filename); +extern void logger_buffer_set_log_filename (struct t_logger_buffer *logger_buffer); +extern int logger_buffer_create_log_file (struct t_logger_buffer *logger_buffer); +extern void logger_buffer_rotate (struct t_logger_buffer *logger_buffer); +extern void logger_buffer_write_line (struct t_logger_buffer *logger_buffer, + const char *format, ...); +extern void logger_buffer_stop (struct t_logger_buffer *logger_buffer, + int write_info_line); +extern void logger_buffer_stop_all (int write_info_line); +extern void logger_buffer_start (struct t_gui_buffer *buffer, int write_info_line); +extern void logger_buffer_start_all (int write_info_line); +extern void logger_buffer_flush (); +extern void logger_buffer_adjust_log_filenames (); extern void logger_buffer_free (struct t_logger_buffer *logger_buffer); extern int logger_buffer_add_to_infolist (struct t_infolist *infolist, struct t_logger_buffer *logger_buffer); diff --git a/src/plugins/logger/logger-command.c b/src/plugins/logger/logger-command.c index be81e493b..bb753ed06 100644 --- a/src/plugins/logger/logger-command.c +++ b/src/plugins/logger/logger-command.c @@ -143,7 +143,7 @@ logger_command_cb (const void *pointer, void *data, if (weechat_strcasecmp (argv[1], "flush") == 0) { - logger_flush (); + logger_buffer_flush (); return WEECHAT_RC_OK; } diff --git a/src/plugins/logger/logger-config.c b/src/plugins/logger/logger-config.c index b91459346..1fb263a34 100644 --- a/src/plugins/logger/logger-config.c +++ b/src/plugins/logger/logger-config.c @@ -20,11 +20,13 @@ */ #include <stdlib.h> +#include <string.h> #include <limits.h> #include "../weechat-plugin.h" #include "logger.h" #include "logger-config.h" +#include "logger-buffer.h" struct t_config_file *logger_config_file = NULL; @@ -56,8 +58,15 @@ struct t_config_option *logger_config_file_nick_prefix; struct t_config_option *logger_config_file_nick_suffix; struct t_config_option *logger_config_file_path; struct t_config_option *logger_config_file_replacement_char; +struct t_config_option *logger_config_file_rotation_compression_level; +struct t_config_option *logger_config_file_rotation_compression_type; +struct t_config_option *logger_config_file_rotation_size_max; struct t_config_option *logger_config_file_time_format; +/* other */ + +unsigned long long logger_config_rotation_size_max = 0; + /* * Callback for changes on option that require a restart of logging for all @@ -74,7 +83,7 @@ logger_config_change_file_option_restart_log (const void *pointer, void *data, (void) option; if (!logger_config_loading) - logger_adjust_log_filenames (); + logger_buffer_adjust_log_filenames (); } /* @@ -148,6 +157,59 @@ logger_config_flush_delay_change (const void *pointer, void *data, } /* + * Callback called when option "logger.file.rotation_size_max" is changed, + * to check if value is valid. + * + * Returns: + * 1: value is OK + * 0: value is invalid + */ + +int +logger_config_rotation_size_max_check (const void *pointer, void *data, + struct t_config_option *option, + const char *value) +{ + unsigned long long size; + + /* make C compiler happy */ + (void) pointer; + (void) data; + (void) option; + + if (!value || !value[0]) + return 0; + + if (strcmp (value, "0") == 0) + return 1; + + size = weechat_string_parse_size (value); + + return (size > 0) ? 1 : 0; +} + +/* + * Callback called when option "logger.file.rotation_size_max" is changed. + * + * Returns: + * 1: value is OK + * 0: value is invalid + */ + +void +logger_config_rotation_size_max_change (const void *pointer, void *data, + struct t_config_option *option) +{ + /* make C compiler happy */ + (void) pointer; + (void) data; + (void) option; + + logger_config_rotation_size_max = weechat_string_parse_size ( + weechat_config_string (logger_config_file_rotation_size_max)); +} + +/* * Callback for changes on a level option. */ @@ -161,7 +223,7 @@ logger_config_level_change (const void *pointer, void *data, (void) option; if (!logger_config_loading) - logger_start_buffer_all (1); + logger_buffer_start_all (1); } /* @@ -182,7 +244,7 @@ logger_config_level_delete_option (const void *pointer, void *data, weechat_config_option_free (option); - logger_start_buffer_all (1); + logger_buffer_start_all (1); return WEECHAT_CONFIG_OPTION_UNSET_OK_REMOVED; } @@ -243,7 +305,7 @@ logger_config_level_create_option (const void *pointer, void *data, } if (!logger_config_loading) - logger_start_buffer_all (1); + logger_buffer_start_all (1); return rc; } @@ -288,7 +350,7 @@ logger_config_mask_change (const void *pointer, void *data, (void) option; if (!logger_config_loading) - logger_adjust_log_filenames (); + logger_buffer_adjust_log_filenames (); } /* @@ -309,7 +371,7 @@ logger_config_mask_delete_option (const void *pointer, void *data, weechat_config_option_free (option); - logger_adjust_log_filenames (); + logger_buffer_adjust_log_filenames (); return WEECHAT_CONFIG_OPTION_UNSET_OK_REMOVED; } @@ -370,7 +432,7 @@ logger_config_mask_create_option (const void *pointer, void *data, } if (!logger_config_loading) - logger_adjust_log_filenames (); + logger_buffer_adjust_log_filenames (); return rc; } @@ -577,6 +639,45 @@ logger_config_init () NULL, NULL, NULL, &logger_config_change_file_option_restart_log, NULL, NULL, NULL, NULL, NULL); + logger_config_file_rotation_compression_level = weechat_config_new_option ( + logger_config_file, ptr_section, + "rotation_compression_level", "integer", + N_("compression level for rotated log files (with extension \".1\", " + "\".2\", etc.), if option logger.file.rotation_compression_type is " + "enabled: 1 = low compression / fast ... 100 = best compression / " + "slow; the value is a percentage converted to 1-9 for gzip and " + "1-19 for zstd; the default value is recommended, it offers a good " + "compromise between compression and speed"), + NULL, 1, 100, "20", NULL, 0, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + logger_config_file_rotation_compression_type = weechat_config_new_option ( + logger_config_file, ptr_section, + "rotation_compression_type", "integer", + N_("compression type for rotated log files; if set to \"none\", " + "rotated log files are not compressed; WARNING: if rotation was " + "enabled with another type of compression (or no compression), " + "you must first unload the logger plugin, compress files with the " + "new type (or decompress files), then change the option in " + "logger.conf, then load the logger plugin"), + "none|gzip|zstd", 0, 0, "none", NULL, 0, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + logger_config_file_rotation_size_max = weechat_config_new_option ( + logger_config_file, ptr_section, + "rotation_size_max", "string", + N_("when this size is reached, a rotation of log files is performed: " + "the existing rotated log files are renamed (.1 becomes .2, .2 " + "becomes .3, etc.) and the current file is renamed with extension " + ".1; an integer number with a suffix is allowed: b = bytes " + "(default if no unit given), k = kilobytes, m = megabytes, " + "g = gigabytes, t = terabytes; example: \"2g\" causes a rotation " + "if the file size is > 2,000,000,000 bytes; if set to \"0\", " + "no rotation is performed (unlimited log size); WARNING: before " + "changing this option, you should first set the compression type " + "via option logger.file.rotation_compression_type"), + NULL, 0, 0, "0", NULL, 0, + &logger_config_rotation_size_max_check, NULL, NULL, + &logger_config_rotation_size_max_change, NULL, NULL, + NULL, NULL, NULL); logger_config_file_time_format = weechat_config_new_option ( logger_config_file, ptr_section, "time_format", "string", diff --git a/src/plugins/logger/logger-config.h b/src/plugins/logger/logger-config.h index 46fb55a03..406fc7d87 100644 --- a/src/plugins/logger/logger-config.h +++ b/src/plugins/logger/logger-config.h @@ -22,7 +22,6 @@ #define LOGGER_CONFIG_NAME "logger" - extern struct t_config_option *logger_config_look_backlog; extern struct t_config_option *logger_config_look_backlog_conditions; @@ -40,8 +39,13 @@ extern struct t_config_option *logger_config_file_nick_prefix; extern struct t_config_option *logger_config_file_nick_suffix; extern struct t_config_option *logger_config_file_path; extern struct t_config_option *logger_config_file_replacement_char; +extern struct t_config_option *logger_config_file_rotation_compression_level; +extern struct t_config_option *logger_config_file_rotation_compression_type; +extern struct t_config_option *logger_config_file_rotation_size_max; extern struct t_config_option *logger_config_file_time_format; +extern unsigned long long logger_config_rotation_size_max; + extern struct t_config_option *logger_config_get_level (const char *name); extern int logger_config_set_level (const char *name, const char *value); extern struct t_config_option *logger_config_get_mask (const char *name); diff --git a/src/plugins/logger/logger.c b/src/plugins/logger/logger.c index 87a15dd7f..014ada811 100644 --- a/src/plugins/logger/logger.c +++ b/src/plugins/logger/logger.c @@ -21,15 +21,9 @@ #include <stdlib.h> #include <unistd.h> -#include <errno.h> #include <stdio.h> -#include <stdarg.h> #include <string.h> #include <ctype.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <sys/time.h> -#include <fcntl.h> #include <time.h> #include "../weechat-plugin.h" @@ -460,374 +454,6 @@ end: } /* - * Sets log filename for a logger buffer. - */ - -void -logger_set_log_filename (struct t_logger_buffer *logger_buffer) -{ - char *log_filename, *pos_last_sep; - char *dir_separator; - struct t_logger_buffer *ptr_logger_buffer; - - /* get log filename for buffer */ - log_filename = logger_get_filename (logger_buffer->buffer); - if (!log_filename) - { - weechat_printf_date_tags (NULL, 0, "no_log", - _("%s%s: not enough memory"), - weechat_prefix ("error"), - LOGGER_PLUGIN_NAME); - return; - } - - /* log file is already used by another buffer? */ - ptr_logger_buffer = logger_buffer_search_log_filename (log_filename); - if (ptr_logger_buffer) - { - weechat_printf_date_tags ( - NULL, 0, "no_log", - _("%s%s: unable to start logging for buffer " - "\"%s\": filename \"%s\" is already used by " - "another buffer (check your log settings)"), - weechat_prefix ("error"), - LOGGER_PLUGIN_NAME, - weechat_buffer_get_string (logger_buffer->buffer, "name"), - log_filename); - free (log_filename); - return; - } - - /* create directory for path in "log_filename" */ - dir_separator = weechat_info_get ("dir_separator", ""); - if (dir_separator) - { - pos_last_sep = strrchr (log_filename, dir_separator[0]); - if (pos_last_sep) - { - pos_last_sep[0] = '\0'; - weechat_mkdir_parents (log_filename, 0700); - pos_last_sep[0] = dir_separator[0]; - } - free (dir_separator); - } - - /* set log filename */ - logger_buffer->log_filename = log_filename; -} - -/* - * Creates a log file. - * - * Returns: - * 1: OK - * 0: error - */ - -int -logger_create_log_file (struct t_logger_buffer *logger_buffer) -{ - char *charset, *message, buf_time[256], buf_beginning[1024]; - int log_level, rc; - time_t seconds; - struct tm *date_tmp; - struct stat statbuf; - - if (logger_buffer->log_file) - { - /* - * check that the inode has not changed, otherwise that means the file - * was deleted, and we must reopen it - */ - rc = stat (logger_buffer->log_filename, &statbuf); - if ((rc == 0) && (statbuf.st_ino == logger_buffer->log_file_inode)) - { - /* inode has not changed, we can write in this file */ - return 1; - } - fclose (logger_buffer->log_file); - logger_buffer->log_file = NULL; - logger_buffer->log_file_inode = 0; - } - - /* get log level */ - log_level = logger_get_level_for_buffer (logger_buffer->buffer); - if (log_level == 0) - return 0; - - /* create directory */ - if (!logger_create_directory ()) - { - weechat_printf_date_tags ( - NULL, 0, "no_log", - _("%s%s: unable to create directory for logs " - "(\"%s\")"), - weechat_prefix ("error"), LOGGER_PLUGIN_NAME, - weechat_config_string (logger_config_file_path)); - return 0; - } - if (!logger_buffer->log_filename) - logger_set_log_filename (logger_buffer); - if (!logger_buffer->log_filename) - return 0; - - /* create or append to log file */ - logger_buffer->log_file = - fopen (logger_buffer->log_filename, "a"); - if (!logger_buffer->log_file) - { - weechat_printf_date_tags ( - NULL, 0, "no_log", - _("%s%s: unable to write log file \"%s\": %s"), - weechat_prefix ("error"), LOGGER_PLUGIN_NAME, - logger_buffer->log_filename, strerror (errno)); - return 0; - } - - /* get file inode */ - rc = stat (logger_buffer->log_filename, &statbuf); - if (rc != 0) - { - weechat_printf_date_tags ( - NULL, 0, "no_log", - _("%s%s: unable to get file status of log file \"%s\": %s"), - weechat_prefix ("error"), LOGGER_PLUGIN_NAME, - logger_buffer->log_filename, strerror (errno)); - fclose (logger_buffer->log_file); - logger_buffer->log_file = NULL; - logger_buffer->log_file_inode = 0; - return 0; - } - logger_buffer->log_file_inode = statbuf.st_ino; - - /* write info line */ - if (weechat_config_boolean (logger_config_file_info_lines) - && logger_buffer->write_start_info_line) - { - buf_time[0] = '\0'; - seconds = time (NULL); - date_tmp = localtime (&seconds); - if (date_tmp) - { - if (strftime (buf_time, sizeof (buf_time) - 1, - weechat_config_string (logger_config_file_time_format), - date_tmp) == 0) - buf_time[0] = '\0'; - } - snprintf (buf_beginning, sizeof (buf_beginning), - _("%s\t**** Beginning of log ****"), - buf_time); - charset = weechat_info_get ("charset_terminal", ""); - message = (charset) ? - weechat_iconv_from_internal (charset, buf_beginning) : NULL; - fprintf (logger_buffer->log_file, - "%s\n", (message) ? message : buf_beginning); - if (charset) - free (charset); - if (message) - free (message); - logger_buffer->flush_needed = 1; - } - logger_buffer->write_start_info_line = 0; - - return 1; -} - -/* - * Writes a line to log file. - */ - -void -logger_write_line (struct t_logger_buffer *logger_buffer, - const char *format, ...) -{ - char *charset, *message; - - if (!logger_create_log_file (logger_buffer)) - return; - - if (!logger_buffer->log_file) - return; - - weechat_va_format (format); - if (vbuffer) - { - charset = weechat_info_get ("charset_terminal", ""); - message = (charset) ? - weechat_iconv_from_internal (charset, vbuffer) : NULL; - fprintf (logger_buffer->log_file, - "%s\n", (message) ? message : vbuffer); - if (charset) - free (charset); - if (message) - free (message); - logger_buffer->flush_needed = 1; - if (!logger_hook_timer) - { - fflush (logger_buffer->log_file); - if (weechat_config_boolean (logger_config_file_fsync)) - fsync (fileno (logger_buffer->log_file)); - logger_buffer->flush_needed = 0; - } - free (vbuffer); - } -} - -/* - * Stops log for a logger buffer. - */ - -void -logger_stop (struct t_logger_buffer *logger_buffer, int write_info_line) -{ - time_t seconds; - struct tm *date_tmp; - char buf_time[256]; - - if (!logger_buffer) - return; - - if (logger_buffer->log_enabled && logger_buffer->log_file) - { - if (write_info_line && weechat_config_boolean (logger_config_file_info_lines)) - { - buf_time[0] = '\0'; - seconds = time (NULL); - date_tmp = localtime (&seconds); - if (date_tmp) - { - if (strftime (buf_time, sizeof (buf_time) - 1, - weechat_config_string (logger_config_file_time_format), - date_tmp) == 0) - buf_time[0] = '\0'; - } - logger_write_line (logger_buffer, - _("%s\t**** End of log ****"), - buf_time); - } - } - - logger_buffer_free (logger_buffer); -} - -/* - * Ends log for all buffers. - */ - -void -logger_stop_all (int write_info_line) -{ - while (logger_buffers) - { - logger_stop (logger_buffers, write_info_line); - } -} - -/* - * Starts logging for a buffer. - */ - -void -logger_start_buffer (struct t_gui_buffer *buffer, int write_info_line) -{ - struct t_logger_buffer *ptr_logger_buffer; - int log_level, log_enabled; - - if (!buffer) - return; - - log_level = logger_get_level_for_buffer (buffer); - log_enabled = weechat_config_boolean (logger_config_file_auto_log) - && (log_level > 0); - - ptr_logger_buffer = logger_buffer_search_buffer (buffer); - - /* logging is disabled for buffer */ - if (!log_enabled) - { - /* stop logger if it is active */ - if (ptr_logger_buffer) - logger_stop (ptr_logger_buffer, 1); - } - else - { - /* logging is enabled for buffer */ - if (ptr_logger_buffer) - ptr_logger_buffer->log_level = log_level; - else - { - ptr_logger_buffer = logger_buffer_add (buffer, log_level); - - if (ptr_logger_buffer) - { - if (ptr_logger_buffer->log_filename) - { - if (ptr_logger_buffer->log_file) - { - fclose (ptr_logger_buffer->log_file); - ptr_logger_buffer->log_file = NULL; - ptr_logger_buffer->log_file_inode = 0; - } - } - } - } - if (ptr_logger_buffer) - ptr_logger_buffer->write_start_info_line = write_info_line; - } -} - -/* - * Starts logging for all buffers. - */ - -void -logger_start_buffer_all (int write_info_line) -{ - struct t_infolist *ptr_infolist; - - ptr_infolist = weechat_infolist_get ("buffer", NULL, NULL); - if (ptr_infolist) - { - while (weechat_infolist_next (ptr_infolist)) - { - logger_start_buffer (weechat_infolist_pointer (ptr_infolist, - "pointer"), - write_info_line); - } - weechat_infolist_free (ptr_infolist); - } -} - -/* - * Flushes all log files. - */ - -void -logger_flush () -{ - struct t_logger_buffer *ptr_logger_buffer; - - for (ptr_logger_buffer = logger_buffers; ptr_logger_buffer; - ptr_logger_buffer = ptr_logger_buffer->next_buffer) - { - if (ptr_logger_buffer->log_file && ptr_logger_buffer->flush_needed) - { - if (weechat_logger_plugin->debug >= 2) - { - weechat_printf_date_tags (NULL, 0, "no_log", - "%s: flush file %s", - LOGGER_PLUGIN_NAME, - ptr_logger_buffer->log_filename); - } - fflush (ptr_logger_buffer->log_file); - if (weechat_config_boolean (logger_config_file_fsync)) - fsync (fileno (ptr_logger_buffer->log_file)); - ptr_logger_buffer->flush_needed = 0; - } - } -} - -/* * Callback for signal "buffer_opened". */ @@ -842,7 +468,7 @@ logger_buffer_opened_signal_cb (const void *pointer, void *data, (void) signal; (void) type_data; - logger_start_buffer (signal_data, 1); + logger_buffer_start (signal_data, 1); return WEECHAT_RC_OK; } @@ -862,7 +488,7 @@ logger_buffer_closing_signal_cb (const void *pointer, void *data, (void) signal; (void) type_data; - logger_stop (logger_buffer_search_buffer (signal_data), 1); + logger_buffer_stop (logger_buffer_search_buffer (signal_data), 1); return WEECHAT_RC_OK; } @@ -882,8 +508,8 @@ logger_buffer_renamed_signal_cb (const void *pointer, void *data, (void) signal; (void) type_data; - logger_stop (logger_buffer_search_buffer (signal_data), 1); - logger_start_buffer (signal_data, 1); + logger_buffer_stop (logger_buffer_search_buffer (signal_data), 1); + logger_buffer_start (signal_data, 1); return WEECHAT_RC_OK; } @@ -903,7 +529,7 @@ logger_start_signal_cb (const void *pointer, void *data, (void) signal; (void) type_data; - logger_start_buffer (signal_data, 1); + logger_buffer_start (signal_data, 1); return WEECHAT_RC_OK; } @@ -927,56 +553,12 @@ logger_stop_signal_cb (const void *pointer, void *data, ptr_logger_buffer = logger_buffer_search_buffer (signal_data); if (ptr_logger_buffer) - logger_stop (ptr_logger_buffer, 0); + logger_buffer_stop (ptr_logger_buffer, 0); return WEECHAT_RC_OK; } /* - * Adjusts log filenames for all buffers. - * - * Filename can change if configuration option is changed, or if day of system - * date has changed. - */ - -void -logger_adjust_log_filenames () -{ - struct t_infolist *ptr_infolist; - struct t_logger_buffer *ptr_logger_buffer; - struct t_gui_buffer *ptr_buffer; - char *log_filename; - - ptr_infolist = weechat_infolist_get ("buffer", NULL, NULL); - if (ptr_infolist) - { - while (weechat_infolist_next (ptr_infolist)) - { - ptr_buffer = weechat_infolist_pointer (ptr_infolist, "pointer"); - ptr_logger_buffer = logger_buffer_search_buffer (ptr_buffer); - if (ptr_logger_buffer && ptr_logger_buffer->log_filename) - { - log_filename = logger_get_filename (ptr_logger_buffer->buffer); - if (log_filename) - { - if (strcmp (log_filename, ptr_logger_buffer->log_filename) != 0) - { - /* - * log filename has changed (probably due to day - * change),then we'll use new filename - */ - logger_stop (ptr_logger_buffer, 1); - logger_start_buffer (ptr_buffer, 1); - } - free (log_filename); - } - } - } - weechat_infolist_free (ptr_infolist); - } -} - -/* * Callback for signal "day_changed". */ @@ -992,7 +574,7 @@ logger_day_changed_signal_cb (const void *pointer, void *data, (void) type_data; (void) signal_data; - logger_adjust_log_filenames (); + logger_buffer_adjust_log_filenames (); return WEECHAT_RC_OK; } @@ -1107,7 +689,7 @@ logger_print_cb (const void *pointer, void *data, buf_time[0] = '\0'; } - logger_write_line ( + logger_buffer_write_line ( ptr_logger_buffer, "%s\t%s%s%s\t%s%s", buf_time, @@ -1138,7 +720,7 @@ logger_timer_cb (const void *pointer, void *data, int remaining_calls) (void) data; (void) remaining_calls; - logger_flush (); + logger_buffer_flush (); return WEECHAT_RC_OK; } @@ -1163,7 +745,7 @@ weechat_plugin_init (struct t_weechat_plugin *plugin, int argc, char *argv[]) logger_command_init (); - logger_start_buffer_all (1); + logger_buffer_start_all (1); weechat_hook_signal ("buffer_opened", &logger_buffer_opened_signal_cb, NULL, NULL); @@ -1211,7 +793,7 @@ weechat_plugin_end (struct t_weechat_plugin *plugin) logger_config_write (); - logger_stop_all (1); + logger_buffer_stop_all (1); logger_config_free (); diff --git a/src/plugins/logger/logger.h b/src/plugins/logger/logger.h index 3a79590e4..364f5c163 100644 --- a/src/plugins/logger/logger.h +++ b/src/plugins/logger/logger.h @@ -33,12 +33,10 @@ extern struct t_weechat_plugin *weechat_logger_plugin; extern struct t_hook *logger_hook_timer; extern struct t_hook *logger_hook_print; +extern int logger_create_directory (); extern char *logger_build_option_name (struct t_gui_buffer *buffer); -extern void logger_set_log_filename (struct t_logger_buffer *logger_buffer); -extern void logger_start_buffer_all (int write_info_line); -extern void logger_flush (); -extern void logger_stop_all (int write_info_line); -extern void logger_adjust_log_filenames (); +extern int logger_get_level_for_buffer (struct t_gui_buffer *buffer); +extern char *logger_get_filename (struct t_gui_buffer *buffer); extern int logger_print_cb (const void *pointer, void *data, struct t_gui_buffer *buffer, time_t date, int tags_count, const char **tags, |