From 39bbdf882db9b0fb00d4ba6983721175be1e47cf Mon Sep 17 00:00:00 2001 From: gc87 Date: Tue, 10 Dec 2024 15:23:36 +0800 Subject: [PATCH] feat: tongtech log api --- plugins/restful/handle.c | 18 +++++ plugins/restful/log_handle.c | 123 ++++++++++++++++++++++++++++++++++- plugins/restful/log_handle.h | 2 + src/parser/neu_json_log.c | 45 ++++++++++++- src/parser/neu_json_log.h | 6 ++ tests/ft/log/test_log.py | 14 +++- tests/ft/neuron/api.py | 6 ++ 7 files changed, 211 insertions(+), 3 deletions(-) diff --git a/plugins/restful/handle.c b/plugins/restful/handle.c index 482ce779..52415213 100644 --- a/plugins/restful/handle.c +++ b/plugins/restful/handle.c @@ -118,6 +118,12 @@ static struct neu_http_handler cors_handler[] = { { .url = "/api/v2/log/level", }, + { + .url = "/api/v2/log/list", + }, + { + .url = "/api/v2/log/file", + }, { .url = "/api/v2/global/config", }, @@ -383,6 +389,18 @@ static struct neu_http_handler rest_handlers[] = { .url = "/api/v2/log/level", .value.handler = handle_log_level, }, + { + .method = NEU_HTTP_METHOD_GET, + .type = NEU_HTTP_HANDLER_FUNCTION, + .url = "/api/v2/log/list", + .value.handler = handle_log_list, + }, + { + .method = NEU_HTTP_METHOD_GET, + .type = NEU_HTTP_HANDLER_FUNCTION, + .url = "/api/v2/log/file", + .value.handler = handle_log_file, + }, { .method = NEU_HTTP_METHOD_GET, .type = NEU_HTTP_HANDLER_FUNCTION, diff --git a/plugins/restful/log_handle.c b/plugins/restful/log_handle.c index b606957a..9ba8dd30 100644 --- a/plugins/restful/log_handle.c +++ b/plugins/restful/log_handle.c @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -42,6 +43,8 @@ #include "log_handle.h" #include "utils/utarray.h" +#define LOG_DIR "logs" + void handle_log_level(nng_aio *aio) { neu_plugin_t *plugin = neu_rest_get_plugin(); @@ -99,4 +102,122 @@ void handle_log_level(nng_aio *aio) } } }) -} \ No newline at end of file +} + +void handle_log_list(nng_aio *aio) +{ + NEU_VALIDATE_JWT(aio); + + char log_dir[PATH_MAX] = { 0 }; + getcwd(log_dir, PATH_MAX - 1); + strncat(log_dir, "/", sizeof(log_dir) - strlen(log_dir) - 1); + strncat(log_dir, LOG_DIR, sizeof(log_dir) - strlen(log_dir) - 1); + DIR *dir = opendir(log_dir); + if (NULL == dir) { + NEU_JSON_RESPONSE_ERROR(NEU_ERR_FILE_NOT_EXIST, { + neu_http_response(aio, NEU_ERR_FILE_NOT_EXIST, result_error); + }); + } + + UT_icd icd = { sizeof(neu_resp_log_file_t), NULL, NULL, NULL }; + UT_array *log_files = NULL; + utarray_new(log_files, &icd); + + struct dirent *entry = NULL; + while (NULL != (entry = readdir(dir))) { + if (NULL == strstr(entry->d_name, ".log")) { + continue; + } + + char full_path[PATH_MAX] = { 0 }; + strncpy(full_path, log_dir, sizeof(full_path) - 1); + strncat(full_path, "/", sizeof(full_path) - strlen(full_path) - 1); + strncat(full_path, entry->d_name, + sizeof(full_path) - strlen(full_path) - 1); + struct stat st = { 0 }; + if (0 != stat(full_path, &st)) { + continue; + } + + neu_resp_log_file_t log_file = { 0 }; + strncpy(log_file.file, entry->d_name, sizeof(log_file.file) - 1); + log_file.size = st.st_size; + utarray_push_back(log_files, &log_file); + } + closedir(dir); + + char *result = NULL; + neu_json_encode_by_fn(log_files, neu_json_encode_log_list_resp, &result); + neu_http_ok(aio, result); + free(result); + utarray_free(log_files); +} + +static int response(nng_aio *aio, char *content, enum nng_http_status status) +{ + nng_http_res *res = NULL; + + nng_http_res_alloc(&res); + + nng_http_res_set_header(res, "Content-Type", "text/plain"); + nng_http_res_set_header(res, "Access-Control-Allow-Origin", "*"); + nng_http_res_set_header(res, "Access-Control-Allow-Methods", + "POST,GET,PUT,DELETE,OPTIONS"); + nng_http_res_set_header(res, "Access-Control-Allow-Headers", "*"); + + if (content != NULL && strlen(content) > 0) { + nng_http_res_copy_data(res, content, strlen(content)); + } + + nng_http_res_set_status(res, status); + + nng_http_req *nng_req = nng_aio_get_input(aio, 0); + nlog_notice("<%p> %s %s [%d]", aio, nng_http_req_get_method(nng_req), + nng_http_req_get_uri(nng_req), status); + + nng_aio_set_output(aio, 0, res); + nng_aio_finish(aio, 0); + + return 0; +} + +void handle_log_file(nng_aio *aio) +{ + NEU_VALIDATE_JWT(aio); + + char file_name[NEU_NODE_NAME_LEN] = { 0 }; + if (neu_http_get_param_str(aio, "name", file_name, sizeof(file_name)) <= + 0) { + NEU_JSON_RESPONSE_ERROR(NEU_ERR_PARAM_IS_WRONG, { + neu_http_response(aio, error_code.error, result_error); + }) + return; + } + + char path[PATH_MAX] = { 0 }; + getcwd(path, PATH_MAX - 1); + strncat(path, "/", sizeof(path) - strlen(path) - 1); + strncat(path, LOG_DIR, sizeof(path) - strlen(path) - 1); + + strncat(path, "/", sizeof(path) - strlen(path) - 1); + strncat(path, file_name, sizeof(path) - strlen(path) - 1); + + FILE *file = fopen(path, "r"); + if (NULL == file) { + NEU_JSON_RESPONSE_ERROR(NEU_ERR_FILE_NOT_EXIST, { + neu_http_response(aio, NEU_ERR_FILE_NOT_EXIST, result_error); + }); + } + + fseek(file, 0, SEEK_END); + long file_size = ftell(file); + fseek(file, 0, SEEK_SET); + + char *file_content = (char *) malloc(file_size + 1); + fread(file_content, 1, file_size, file); + file_content[file_size] = '\0'; + fclose(file); + + response(aio, file_content, NNG_HTTP_STATUS_OK); + free(file_content); +} diff --git a/plugins/restful/log_handle.h b/plugins/restful/log_handle.h index dc991937..8d6ea49a 100644 --- a/plugins/restful/log_handle.h +++ b/plugins/restful/log_handle.h @@ -24,5 +24,7 @@ // void handle_get_log(nng_aio *aio); void handle_log_level(nng_aio *aio); +void handle_log_list(nng_aio *aio); +void handle_log_file(nng_aio *aio); #endif diff --git a/src/parser/neu_json_log.c b/src/parser/neu_json_log.c index 62916993..58b2f3d1 100644 --- a/src/parser/neu_json_log.c +++ b/src/parser/neu_json_log.c @@ -29,6 +29,8 @@ #include "neu_json_log.h" #include "utils/log.h" +#include "utils/utarray.h" +#include "utils/utextend.h" int neu_json_decode_update_log_level_req( char *buf, neu_json_update_log_level_req_t **result) @@ -99,4 +101,45 @@ void neu_json_decode_update_log_level_req_free( if (req != NULL) { free(req); } -} \ No newline at end of file +} + +int neu_json_encode_log_list_resp(void *json_object, void *param) +{ + int ret = 0; + UT_array *log_files = (UT_array *) param; + void * log_array = neu_json_array(); + + if (NULL != log_files) { + utarray_foreach(log_files, neu_resp_log_file_t *, p_log) + { + if (NULL != p_log) { + neu_json_elem_t log_elems[] = { + { + .name = "file", + .t = NEU_JSON_STR, + .v.val_str = p_log->file, + }, + { + .name = "size", + .t = NEU_JSON_INT, + .v.val_int = p_log->size, + }, + }; + + neu_json_encode_array(log_array, log_elems, + NEU_JSON_ELEM_SIZE(log_elems)); + } + } + } + + neu_json_elem_t resp_elems[] = { { + .name = "files", + .t = NEU_JSON_OBJECT, + .v.val_object = log_array, + } }; + + ret = neu_json_encode_field(json_object, resp_elems, + NEU_JSON_ELEM_SIZE(resp_elems)); + + return ret; +} diff --git a/src/parser/neu_json_log.h b/src/parser/neu_json_log.h index afec402b..e111f741 100644 --- a/src/parser/neu_json_log.h +++ b/src/parser/neu_json_log.h @@ -46,10 +46,16 @@ typedef struct { int64_t error; } neu_json_get_log_resp_t; +typedef struct { + char file[128]; + int size; +} neu_resp_log_file_t; + void neu_json_decode_update_log_level_req_free( neu_json_update_log_level_req_t *req); int neu_json_decode_update_log_level_req( char *buf, neu_json_update_log_level_req_t **result); +int neu_json_encode_log_list_resp(void *json_object, void *param); #ifdef __cplusplus } diff --git a/tests/ft/log/test_log.py b/tests/ft/log/test_log.py index 67a38ce4..3311667b 100644 --- a/tests/ft/log/test_log.py +++ b/tests/ft/log/test_log.py @@ -125,4 +125,16 @@ class TestLog: response = api.get_nodes_state(node='') assert 200 == response.status_code - assert "notice" == response.json()['neuron_core'] \ No newline at end of file + assert "notice" == response.json()['neuron_core'] + + @description(given="neuron started", when="get log list", then="success") + def test_get_log_list(self): + response = api.get_log_list() + assert 200 == response.status_code + print(response.json()) + assert "neuron.log" in [item['file'] for item in response.json()["files"]] + + @description(given="neuron started", when="get log file", then="success") + def test_get_log_file(self): + response = api.get_log_file(file_name='neuron.log') + assert 200 == response.status_code diff --git a/tests/ft/neuron/api.py b/tests/ft/neuron/api.py index 42e1dcc2..a1b363a5 100644 --- a/tests/ft/neuron/api.py +++ b/tests/ft/neuron/api.py @@ -408,3 +408,9 @@ def otel_stop(): def otel_get(): return requests.get(url=config.BASE_URL + "/api/v2/otel", headers={"Authorization": config.default_jwt}) + +def get_log_list(): + return requests.get(url=config.BASE_URL + "/api/v2/log/list", headers={"Authorization": config.default_jwt}) + +def get_log_file(file_name=""): + return requests.get(url=config.BASE_URL + "/api/v2/log/file?name=" + file_name, headers={"Authorization": config.default_jwt})