diff --git a/CHANGES b/CHANGES index 10b5f58753..322419d4c1 100644 --- a/CHANGES +++ b/CHANGES @@ -202,6 +202,9 @@ ARI deletion. Sorcery derived objects that are manipulated by this resource must have a sorcery wizard that supports the desired operations. + * A new feature has been added that allows for the rotation of log channels + through HTTP requests. + res_pjsip ------------------ diff --git a/include/asterisk/logger.h b/include/asterisk/logger.h index c701d17cea..636c744640 100644 --- a/include/asterisk/logger.h +++ b/include/asterisk/logger.h @@ -102,6 +102,13 @@ int logger_reload(void); /*! \brief Reload logger while rotating log files */ int ast_logger_rotate(void); +/*! + * \brief Rotate the specified log channel. + * + * \param log_channel The log channel to rotate + */ +int ast_logger_rotate_channel(const char *log_channel); + void __attribute__((format(printf, 5, 6))) ast_queue_log(const char *queuename, const char *callid, const char *agent, const char *event, const char *fmt, ...); /*! diff --git a/main/logger.c b/main/logger.c index bdcd6c4f58..607755b584 100644 --- a/main/logger.c +++ b/main/logger.c @@ -925,6 +925,46 @@ int ast_logger_rotate() return reload_logger(1, NULL); } +int ast_logger_rotate_channel(const char *log_channel) +{ + struct logchannel *f; + int success = 0; + + struct ast_str *filename = ast_str_create(64); + if (!filename) { + return -1; + } + + ast_str_append(&filename, 0, "%s/%s", ast_config_AST_LOG_DIR, log_channel); + + AST_RWLIST_WRLOCK(&logchannels); + + ast_mkdir(ast_config_AST_LOG_DIR, 0644); + + AST_RWLIST_TRAVERSE(&logchannels, f, list) { + if (f->disabled) { + f->disabled = 0; /* Re-enable logging at reload */ + manager_event(EVENT_FLAG_SYSTEM, "LogChannel", "Channel: %s\r\nEnabled: Yes\r\n", + f->filename); + } + if (f->fileptr && (f->fileptr != stdout) && (f->fileptr != stderr)) { + fclose(f->fileptr); /* Close file */ + f->fileptr = NULL; + if (strcmp(ast_str_buffer(filename), f->filename) == 0) { + rotate_file(f->filename); + success = 1; + } + } + } + + init_logger_chain(1 /* locked */, NULL); + + AST_RWLIST_UNLOCK(&logchannels); + ast_free(filename); + + return success; +} + static char *handle_logger_set_level(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int x; diff --git a/res/ari/ari_model_validators.c b/res/ari/ari_model_validators.c index fa16aea204..1be32de3d6 100644 --- a/res/ari/ari_model_validators.c +++ b/res/ari/ari_model_validators.c @@ -362,6 +362,61 @@ ari_validator ast_ari_validate_config_tuple_fn(void) return ast_ari_validate_config_tuple; } +int ast_ari_validate_log_channel(struct ast_json *json) +{ + int res = 1; + struct ast_json_iter *iter; + int has_logging_levels = 0; + int has_name = 0; + + for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) { + if (strcmp("logging_levels", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + has_logging_levels = 1; + prop_is_valid = ast_ari_validate_list( + ast_json_object_iter_value(iter), + ast_ari_validate_string); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI LogChannel field logging_levels failed validation\n"); + res = 0; + } + } else + if (strcmp("name", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + has_name = 1; + prop_is_valid = ast_ari_validate_string( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI LogChannel field name failed validation\n"); + res = 0; + } + } else + { + ast_log(LOG_ERROR, + "ARI LogChannel has undocumented field %s\n", + ast_json_object_iter_key(iter)); + res = 0; + } + } + + if (!has_logging_levels) { + ast_log(LOG_ERROR, "ARI LogChannel missing required field logging_levels\n"); + res = 0; + } + + if (!has_name) { + ast_log(LOG_ERROR, "ARI LogChannel missing required field name\n"); + res = 0; + } + + return res; +} + +ari_validator ast_ari_validate_log_channel_fn(void) +{ + return ast_ari_validate_log_channel; +} + int ast_ari_validate_module(struct ast_json *json) { int res = 1; diff --git a/res/ari/ari_model_validators.h b/res/ari/ari_model_validators.h index e122ded345..fa393448dc 100644 --- a/res/ari/ari_model_validators.h +++ b/res/ari/ari_model_validators.h @@ -224,6 +224,24 @@ int ast_ari_validate_config_tuple(struct ast_json *json); */ ari_validator ast_ari_validate_config_tuple_fn(void); +/*! + * \brief Validator for LogChannel. + * + * Details of an Asterisk log channel + * + * \param json JSON object to validate. + * \returns True (non-zero) if valid. + * \returns False (zero) if invalid. + */ +int ast_ari_validate_log_channel(struct ast_json *json); + +/*! + * \brief Function pointer to ast_ari_validate_log_channel(). + * + * See \ref ast_ari_model_validators.h for more details. + */ +ari_validator ast_ari_validate_log_channel_fn(void); + /*! * \brief Validator for Module. * @@ -1283,6 +1301,9 @@ ari_validator ast_ari_validate_application_fn(void); * ConfigTuple * - attribute: string (required) * - value: string (required) + * LogChannel + * - logging_levels: List[string] (required) + * - name: string (required) * Module * - description: string (required) * - name: string (required) diff --git a/res/ari/resource_asterisk.c b/res/ari/resource_asterisk.c index 8006862048..115adb3274 100644 --- a/res/ari/resource_asterisk.c +++ b/res/ari/resource_asterisk.c @@ -33,6 +33,7 @@ ASTERISK_REGISTER_FILE() #include "asterisk/ast_version.h" #include "asterisk/buildinfo.h" +#include "asterisk/logger.h" #include "asterisk/module.h" #include "asterisk/paths.h" #include "asterisk/pbx.h" @@ -627,6 +628,31 @@ void ast_ari_asterisk_reload_module(struct ast_variable *headers, ast_ari_response_no_content(response); } +void ast_ari_asterisk_rotate_log(struct ast_variable *headers, + struct ast_ari_asterisk_rotate_log_args *args, + struct ast_ari_response *response) +{ + int success; + + ast_assert(response != NULL); + + success = ast_logger_rotate_channel(args->log_channel_name); + + if (success == 0) { + ast_ari_response_error( + response, 404, "Not Found", + "Log channel does not exist"); + return; + } else if (success == -1) { + ast_ari_response_error( + response, 500, "Internal Server Error", + "Allocation failed"); + return; + } + + ast_ari_response_no_content(response); +} + void ast_ari_asterisk_get_global_var(struct ast_variable *headers, struct ast_ari_asterisk_get_global_var_args *args, struct ast_ari_response *response) diff --git a/res/ari/resource_asterisk.h b/res/ari/resource_asterisk.h index 1afc093176..e271b0547f 100644 --- a/res/ari/resource_asterisk.h +++ b/res/ari/resource_asterisk.h @@ -194,6 +194,19 @@ struct ast_ari_asterisk_reload_module_args { * \param[out] response HTTP response */ void ast_ari_asterisk_reload_module(struct ast_variable *headers, struct ast_ari_asterisk_reload_module_args *args, struct ast_ari_response *response); +/*! Argument struct for ast_ari_asterisk_rotate_log() */ +struct ast_ari_asterisk_rotate_log_args { + /*! Log channel's name */ + const char *log_channel_name; +}; +/*! + * \brief Rotates a log channel. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void ast_ari_asterisk_rotate_log(struct ast_variable *headers, struct ast_ari_asterisk_rotate_log_args *args, struct ast_ari_response *response); /*! Argument struct for ast_ari_asterisk_get_global_var() */ struct ast_ari_asterisk_get_global_var_args { /*! The variable to get */ diff --git a/res/res_ari_asterisk.c b/res/res_ari_asterisk.c index 671af59da4..25bffacf17 100644 --- a/res/res_ari_asterisk.c +++ b/res/res_ari_asterisk.c @@ -718,6 +718,65 @@ static void ast_ari_asterisk_reload_module_cb( } #endif /* AST_DEVMODE */ +fin: __attribute__((unused)) + return; +} +/*! + * \brief Parameter parsing callback for /asterisk/logging/{logChannelName}/rotate. + * \param get_params GET parameters in the HTTP request. + * \param path_vars Path variables extracted from the request. + * \param headers HTTP headers. + * \param[out] response Response to the HTTP request. + */ +static void ast_ari_asterisk_rotate_log_cb( + struct ast_tcptls_session_instance *ser, + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct ast_ari_response *response) +{ + struct ast_ari_asterisk_rotate_log_args args = {}; + struct ast_variable *i; + RAII_VAR(struct ast_json *, body, NULL, ast_json_unref); +#if defined(AST_DEVMODE) + int is_valid; + int code; +#endif /* AST_DEVMODE */ + + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "logChannelName") == 0) { + args.log_channel_name = (i->value); + } else + {} + } + ast_ari_asterisk_rotate_log(headers, &args, response); +#if defined(AST_DEVMODE) + code = response->response_code; + + switch (code) { + case 0: /* Implementation is still a stub, or the code wasn't set */ + is_valid = response->message == NULL; + break; + case 500: /* Internal Server Error */ + case 501: /* Not Implemented */ + case 404: /* Log channel does not exist. */ + is_valid = 1; + break; + default: + if (200 <= code && code <= 299) { + is_valid = ast_ari_validate_void( + response->message); + } else { + ast_log(LOG_ERROR, "Invalid error response %d for /asterisk/logging/{logChannelName}/rotate\n", code); + is_valid = 0; + } + } + + if (!is_valid) { + ast_log(LOG_ERROR, "Response validation failed for /asterisk/logging/{logChannelName}/rotate\n"); + ast_ari_response_error(response, 500, + "Internal Server Error", "Response validation failed"); + } +#endif /* AST_DEVMODE */ + fin: __attribute__((unused)) return; } @@ -989,6 +1048,32 @@ static struct stasis_rest_handlers asterisk_modules = { .children = { &asterisk_modules_moduleName, } }; /*! \brief REST handler for /api-docs/asterisk.{format} */ +static struct stasis_rest_handlers asterisk_logging_logChannelName_rotate = { + .path_segment = "rotate", + .callbacks = { + [AST_HTTP_PUT] = ast_ari_asterisk_rotate_log_cb, + }, + .num_children = 0, + .children = { } +}; +/*! \brief REST handler for /api-docs/asterisk.{format} */ +static struct stasis_rest_handlers asterisk_logging_logChannelName = { + .path_segment = "logChannelName", + .is_wildcard = 1, + .callbacks = { + }, + .num_children = 1, + .children = { &asterisk_logging_logChannelName_rotate, } +}; +/*! \brief REST handler for /api-docs/asterisk.{format} */ +static struct stasis_rest_handlers asterisk_logging = { + .path_segment = "logging", + .callbacks = { + }, + .num_children = 1, + .children = { &asterisk_logging_logChannelName, } +}; +/*! \brief REST handler for /api-docs/asterisk.{format} */ static struct stasis_rest_handlers asterisk_variable = { .path_segment = "variable", .callbacks = { @@ -1003,8 +1088,8 @@ static struct stasis_rest_handlers asterisk = { .path_segment = "asterisk", .callbacks = { }, - .num_children = 4, - .children = { &asterisk_config,&asterisk_info,&asterisk_modules,&asterisk_variable, } + .num_children = 5, + .children = { &asterisk_config,&asterisk_info,&asterisk_modules,&asterisk_logging,&asterisk_variable, } }; static int load_module(void) diff --git a/rest-api/api-docs/asterisk.json b/rest-api/api-docs/asterisk.json index 2705f45f65..5109419d74 100644 --- a/rest-api/api-docs/asterisk.json +++ b/rest-api/api-docs/asterisk.json @@ -296,6 +296,34 @@ } ] }, + { + "path": "/asterisk/logging/{logChannelName}/rotate", + "description": "Asterisk log channel", + "operations": [ + { + "httpMethod": "PUT", + "summary": "Rotates a log channel.", + "nickname": "rotateLog", + "responseClass": "void", + "parameters": [ + { + "name": "logChannelName", + "description": "Log channel's name", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + } + ], + "errorResponses": [ + { + "code": 404, + "reason": "Log channel does not exist." + } + ] + } + ] + }, { "path": "/asterisk/variable", "description": "Global variables", @@ -533,6 +561,32 @@ } } }, + "LogChannel": { + "id": "LogChannel", + "description": "Details of an Asterisk log channel", + "properties": { + "name": { + "type": "string", + "description": "The log channel path", + "required": true + }, + "type": { + "type": "string", + "description": "Types of logs for the log channel", + "required": true + }, + "status": { + "type": "string", + "description": "Whether or not a log type is enabled", + "required": true + }, + "configuration": { + "type": "string", + "description": "The various log levels", + "required": true + } + } + }, "Variable": { "id": "Variable", "description": "The value of a channel variable",