From e0f2b5393943bc01ef3697b0ab443e318c0d1fa7 Mon Sep 17 00:00:00 2001 From: Xenofon Karamanos Date: Thu, 8 Feb 2024 14:31:13 +0000 Subject: [PATCH 1/2] file_out: Add support for multiple files, extensions and interval --- src/modules/file_out/file_out.c | 164 ++++++++++++++++++++++---------- 1 file changed, 115 insertions(+), 49 deletions(-) diff --git a/src/modules/file_out/file_out.c b/src/modules/file_out/file_out.c index dada154eb12..d64ffdec74e 100644 --- a/src/modules/file_out/file_out.c +++ b/src/modules/file_out/file_out.c @@ -38,6 +38,8 @@ MODULE_VERSION #define FO_MAX_PATH_LEN 2048 #define FO_MAX_FILES 10 /* Maximum number of files */ +#define FO_DEFAULT_INTERVAL 10 * 60 +#define FO_DEFAULT_EXTENSION ".out" static int mod_init(void); static int child_init(int rank); @@ -49,25 +51,26 @@ static FILE *fo_get_file_handle(const int index); static int fo_get_full_path(const int index, char *full_path); static int fo_init_file(const int index); static int fo_close_file(const int index); -static int fo_check_interval(); +static int fo_check_interval(int index); static int fo_fixup_int_pvar(void **param, int param_no); static int fo_fixup_str_index(void **param, int param_no); static int fo_count_assigned_files(); static void fo_log_writer_process(int rank); static int fo_add_filename(modparam_t type, void *val); +static int fo_parse_filename_params(str input); /* Default parameters */ char *fo_base_folder = "/var/log/kamailio/file_out/"; char *fo_base_filename[FO_MAX_FILES] = {""}; -char *fo_extension = ".out"; -int fo_interval_seconds = 10 * 60; +char *fo_extension[FO_MAX_FILES] = {".out"}; +int fo_interval_seconds[FO_MAX_FILES] = {10 * 60}; int fo_worker_usleep = 10000; /* Shared variables */ fo_queue_t *fo_queue = NULL; int *fo_number_of_files = NULL; -time_t fo_stored_timestamp = 0; +time_t fo_stored_timestamp[FO_MAX_FILES] = {0}; time_t fo_current_timestamp = 0; FILE *fo_file_output[FO_MAX_FILES]; @@ -77,9 +80,7 @@ static cmd_export_t cmds[] = {{"file_out", (cmd_function)fo_write_to_file, 2, static param_export_t params[] = { {"base_folder", PARAM_STRING, &fo_base_folder}, - {"base_filename", PARAM_STRING | PARAM_USE_FUNC, &fo_add_filename}, - {"interval_seconds", PARAM_INT, &fo_interval_seconds}, - {"extension", PARAM_STRING, &fo_extension}, + {"file", PARAM_STRING | PARAM_USE_FUNC, &fo_add_filename}, {"worker_usleep", PARAM_INT, &fo_worker_usleep}, {0, 0, 0}}; struct module_exports exports = { @@ -100,9 +101,6 @@ static int mod_init(void) { LM_DBG("initializing\n"); LM_DBG("base_folder = %s\n", fo_base_folder); - LM_DBG("base_filename_path= %s\n", fo_base_filename[0]); - LM_DBG("interval_seconds = %d\n", fo_interval_seconds); - LM_DBG("extension = %s\n", fo_extension); //* Create shared variables */ fo_queue = (fo_queue_t *)shm_malloc(sizeof(fo_queue_t)); @@ -122,7 +120,9 @@ static int mod_init(void) *fo_number_of_files = fo_count_assigned_files(); /* Initialize per process vars */ - fo_stored_timestamp = time(NULL); + for(int i = 0; i < *fo_number_of_files; i++) { + fo_stored_timestamp[i] = time(NULL); + } /* Register worker process */ register_procs(1); @@ -206,10 +206,14 @@ static void fo_log_writer_process(int rank) static int fo_fixup_str_index(void **param, int param_no) { - fparam_t *p; int index = 0; + if(strlen((char *)*param) == 0) { + LM_ERR("function param value is required\n"); + return -1; + } + p = (fparam_t *)pkg_malloc(sizeof(fparam_t)); if(!p) { PKG_MEM_ERROR; @@ -231,13 +235,16 @@ static int fo_fixup_str_index(void **param, int param_no) index++; } - LM_ERR("Couldn't find index for %s\n", (char *)*param); + LM_ERR("Couldn't find %s\n", (char *)*param); + LM_ERR("Make sure the file [%s] is defined as modparam and not exceeding " + "file limit\n", + (char *)*param); pkg_free(p); return -1; } /* * fixup function for two parameters -* 1st param: int +* 1st param: string assoicated with file (returning an index) * 2nd param: string containing PVs */ static int fo_fixup_int_pvar(void **param, int param_no) @@ -252,8 +259,15 @@ static int fo_fixup_int_pvar(void **param, int param_no) static int fo_add_filename(modparam_t type, void *val) { + str in; + + if(val != NULL && strlen((char *)val) == 0) { + LM_ERR("modparam value is empty\n"); + return -1; + } + if(fo_number_of_files == NULL) { - LM_ERR("fo_number_of_files is NULL\n"); + LM_DBG("fo_number_of_files is NULL\n"); fo_number_of_files = (int *)shm_malloc(sizeof(int)); if(!fo_number_of_files) { SHM_MEM_ERROR; @@ -270,19 +284,79 @@ static int fo_add_filename(modparam_t type, void *val) if(fo_number_of_files != NULL && *fo_number_of_files >= FO_MAX_FILES) { LM_ERR("Maximum number of files [%d] reached. The rest will not be " "processed \n", - FO_MAX_FILES); + *fo_number_of_files); return 0; } - fo_base_filename[*fo_number_of_files] = (char *)val; - LM_DBG("fo_base_filename[%d] = %s\n", *fo_number_of_files, - fo_base_filename[*fo_number_of_files]); + + /* parse: name=missed_calls;extension=.txt;interval=600 */ + in.s = (char *)val; + in.len = strlen(in.s); + + if(fo_parse_filename_params(in) < 0) + return -1; + (*fo_number_of_files)++; return 0; } /* -* Count the number of files that are assigned -* return the number of files (saved also in shared fo_number_of_files) +* Parse the filename parameters +* name, extension, interval +* return 1 if successful +* return -1 if failed +*/ +static int fo_parse_filename_params(str in) +{ + LM_DBG("Parsing filename params\n"); + char *name = NULL; + char *extension = NULL; + char *interval = NULL; + char *token = NULL; + char *saveptr = NULL; + char *input = in.s; + token = strtok_r(input, ";", &saveptr); + while(token != NULL) { + if(strstr(token, "name=") != NULL) { + name = token + 5; + } else if(strstr(token, "extension=") != NULL) { + extension = token + 10; + } else if(strstr(token, "interval=") != NULL) { + interval = token + 9; + } + token = strtok_r(NULL, ";", &saveptr); + } + if(name != NULL) { + LM_ERR("name = %s\n", name); + fo_base_filename[*fo_number_of_files] = name; + } else { + LM_ERR("name is required. Make sure you provided name= in modparam " + "value\n"); + return -1; + } + + if(extension != NULL) { + LM_DBG("extension = %s\n", extension); + fo_extension[*fo_number_of_files] = extension; + } else { + LM_DBG("no extension= provided. Using default %s\n", + FO_DEFAULT_EXTENSION); + fo_extension[*fo_number_of_files] = FO_DEFAULT_EXTENSION; + } + + if(interval != NULL) { + LM_DBG("interval = %s\n", interval); + fo_interval_seconds[*fo_number_of_files] = atoi(interval); + } else { + LM_DBG("no interval= provided. Using default %d\n", + FO_DEFAULT_INTERVAL); + fo_interval_seconds[*fo_number_of_files] = FO_DEFAULT_INTERVAL; + } + + return 1; +} + +/* +* return the number of files assigned */ static int fo_count_assigned_files() { @@ -319,49 +393,40 @@ static int fo_close_file(const int index) * return 1 if interval has passed * return 0 if interval has not passed */ -static int fo_check_interval() +static int fo_check_interval(int index) { fo_current_timestamp = time(NULL); // Calculate the difference between the current timestamp and the stored timestamp - int difference = difftime(fo_current_timestamp, fo_stored_timestamp); - if(difference >= fo_interval_seconds) { + int difference = difftime(fo_current_timestamp, fo_stored_timestamp[index]); + if(difference >= fo_interval_seconds[index]) { LM_ERR("interval has passed\n"); return 1; } - // LM_ERR("interval has not passed\n"); return 0; } + /** - * maintain file handle + * Get file handle for file at index */ - static FILE *fo_get_file_handle(const int index) { int result = 0; - if(fo_check_interval()) { + if(fo_check_interval(index)) { /* Interval passed. Close all files and open new ones */ - for(int i = 0; i < *fo_number_of_files; i++) { - result = fo_close_file(i); - if(result != 1) { - LM_ERR("Failed to close output file"); - return NULL; - } + result = fo_close_file(index); + if(result != 1) { + LM_ERR("Failed to close output file"); + return NULL; } - fo_stored_timestamp = fo_current_timestamp; + fo_stored_timestamp[index] = fo_current_timestamp; - LM_DBG("Opening new files due to interval passed\n"); - /* Make sure we know how many files we need */ - if(fo_number_of_files == NULL) { - *fo_number_of_files = fo_count_assigned_files(); - } + LM_DBG("Opening new file due to interval passed\n"); /* Initialize and open files */ - for(int i = 0; i < *fo_number_of_files; i++) { - result = fo_init_file(i); - if(result != 1) { - LM_ERR("Failed to initialize output file"); - return NULL; - } + result = fo_init_file(index); + if(result != 1) { + LM_ERR("Failed to initialize output file"); + return NULL; } return fo_file_output[index]; } else { @@ -377,8 +442,9 @@ static FILE *fo_get_file_handle(const int index) static int fo_get_full_path(const int index, char *full_path) { snprintf(full_path, FO_MAX_PATH_LEN, "%s/%s_%.f%s", fo_base_folder, - fo_base_filename[index], difftime(fo_stored_timestamp, (time_t)0), - fo_extension); + fo_base_filename[index], + difftime(fo_stored_timestamp[index], (time_t)0), + fo_extension[index]); LM_INFO("Path to write to: %s\n", full_path); return 1; } @@ -387,7 +453,7 @@ static int fo_write_to_file(sip_msg_t *msg, char *index, char *log_message) { int result, file_index; if(index == NULL || log_message == NULL) { - LM_ERR("index or log_messsage is NULL\n"); + LM_ERR("filename or log_messsage is NULL\n"); return -1; } From bcea7bdac6cbbcccaf80291d46cf453334577b05 Mon Sep 17 00:00:00 2001 From: Xenofon Karamanos Date: Thu, 8 Feb 2024 15:10:22 +0000 Subject: [PATCH 2/2] file_out: Update docs --- src/modules/file_out/doc/file_out_admin.xml | 117 +++++++++++--------- 1 file changed, 67 insertions(+), 50 deletions(-) diff --git a/src/modules/file_out/doc/file_out_admin.xml b/src/modules/file_out/doc/file_out_admin.xml index bcb3f5d14e9..bef7e88f949 100644 --- a/src/modules/file_out/doc/file_out_admin.xml +++ b/src/modules/file_out/doc/file_out_admin.xml @@ -28,6 +28,16 @@ the pseudo-variables with the actual values. The module will also rotate the log files at a specified interval. The interval is specified in seconds. + + Known limitations on the rotation interval are: + + + + If there is no messages coming, the rotation will not be done until the next message arrives. + + + +
Dependencies @@ -59,6 +69,7 @@
+
Parameters
@@ -66,7 +77,6 @@ base_folder (string) Absolute path to the folder where log files should be saved. - @@ -81,64 +91,74 @@ modparam("file_out", "base_folder", "/tmp/file_out/") ... +
+
- <varname>base_filename</varname> (string) + file (string) - The filename to be used for each file. Don't include the extension. Required. + The definition of a file and its properties. The value of the parameter may have the following format: - - - Default value is null. - - - - Set <varname>base_filename</varname> parameter - -... -modparam("file_out", "base_filename", "accounting") -... - - + + + + "name=accounting;extension=.out;interval=20" + + + + + + The parameter can be set multiple times to define more files in same configuration file. + + - - <varname>extension</varname> (string) - - The extension to be used for each file. + + + + name (Required) - the name of the file + + + + No default value. This parameter is required. + - - + + + + + extension (Optional) - the extension of the file + + + Default value is .out. - + - - Set <varname>extension</varname> parameter - -... -modparam("file_out", "extension", ".txt") -... - - + - - <varname>interval_seconds</varname> (int) - - The interval in seconds between file rotation. - - - - Default value is 600 (10 minutes). - + + + interval (Optional) - the interval in seconds of the file rotation + + + + Default value is 600 (10min). + + + + - Set <varname>interval_seconds</varname> parameter + Set <varname>file</varname> parameter ... -modparam("file_out", "interval_seconds", "300") +modparam("file_out", "file", "name=missed_calls;interval=30;extension=.json") +modparam("file_out", "file", "name=accounting;extension=.txt") ... +
+
<varname>worker_usleep</varname> (int) @@ -157,7 +177,6 @@ modparam("file_out", "worker_usleep", "1000") ... -
@@ -166,23 +185,21 @@ modparam("file_out", "worker_usleep", "1000") Functions
- <function moreinfo="none">file_out(index, string)</function> + <function moreinfo="none">file_out(filename, string)</function> This function is used to write a string to a file. The file is - determined by the index parameter. The string parameter is the - string to be written to the file. + determined by the filename parameter. The string parameter is the + string to be written to the file. Filename is the name of the file defined in the configuration file as name=filename. - Index order is the same as the order in which the log files are - defined in the configuration file starting from 0. <function>file_out</function> usage ... -modparam("file_out", "base_filename", "accounting") -modparam("file_out", "base_filename", "missed_calls") +modparam("file_out", "file", "name=accounting;interval=200") +modparam("file_out", "file", "name=missed_calls;extension=.json;interval=300") request_route { file_out("accounting", "Writing to accounting.out file $rm from $fu");