diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..378eac2 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..76fbe3e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.13) + +project(medusa LANGUAGES C VERSION 0.1.1) + +set(ENABLE_TEST ON) + +# set(CMAKE_VERBOSE_MAKEFILE ON) + +set(CMAKE_C_FLAGS "-Wall -Werror -fsanitize=address") + +add_library(${CMAKE_PROJECT_NAME} medusalog.c) +add_library(${CMAKE_PROJECT_NAME}::${CMAKE_PROJECT_NAME} ALIAS ${CMAKE_PROJECT_NAME}) + +find_package(Threads) + +target_link_libraries(${CMAKE_PROJECT_NAME} Threads::Threads) + +set(NAMESPACE "${CMAKE_PROJECT_NAME}::") + +target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC + $ + $ +) + +if (ENABLE_TEST) + add_subdirectory(test) +endif() diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..de3b7ab --- /dev/null +++ b/LICENSE @@ -0,0 +1,25 @@ +BSD 2-Clause License + +Copyright (c) 2022, Gabriel Correia +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..8a89892 --- /dev/null +++ b/Readme.md @@ -0,0 +1,7 @@ +# libmedusa + +A fork of medusalog program as a library for independent projects + +## References + +- [medusalog](https://github.com/correiudo/medusalog.git) diff --git a/medusalog.c b/medusalog.c new file mode 100644 index 0000000..02e0356 --- /dev/null +++ b/medusalog.c @@ -0,0 +1,364 @@ +#include +#include +#include +#include + +#include + +#include + +#include + +#include + +#include + +#include +#include +#include + +#include + +static void do_message(struct medusa_dispatch_data *dispatch); + +medusalog_t* medusa_new(medusaattr_t *user_attr, const char **logfilenames, size_t logcount) +{ + medusalog_t *medusa = (medusalog_t*)calloc(sizeof(medusalog_t), 1); + + assert(medusa); + + medusaattr_t *attr = &medusa->attr; + + if (user_attr != NULL) + { + memcpy(attr, user_attr, sizeof(medusaattr_t)); + } + else + { + attr->program = "Medusa Log System"; + attr->maxfmt = 150; + attr->maxmsg = 200; + } + + if (user_attr->dolog == NULL) + { + assert(logcount && *logfilenames); + attr->dolog = user_attr->dolog = do_message; + } + + medusa->newfmt = malloc(attr->maxfmt); + + if (logcount > 0) + { + medusa->outfiles = (const FILE **)calloc(sizeof(const FILE*), logcount + 1); + assert(medusa->outfiles); + } + + const FILE **out = medusa->outfiles; + + char pathbuffer[40]; + + while (logfilenames && *logfilenames != NULL) + { + strncpy(pathbuffer, *logfilenames, sizeof(pathbuffer)); + char *bak; + + char *pathtree; + + /* strtok_r writes and corrut the address, we need to copy the file path to other location */ + pathtree = strtok_r(pathbuffer, "/", &bak); + + for (; pathtree ;) + { + char *save = strtok_r(NULL, "/", &bak); + if (save == NULL) + /* We're arived at end of the string */ + break; + + struct stat statbuf; + + if (stat(pathtree, &statbuf) != 0) + if (mkdir(pathtree, S_IRWXU | S_IRGRP | S_IXGRP | S_IWOTH) != 0) + exit(fprintf(stderr, "Can't create %s, because: %s\n", save, strerror(errno))); + + pathtree = save; + } + + /* Creating and opening all specified files */ + FILE *ptr = fopen(*logfilenames++, "a"); + assert(ptr); + *out++ = ptr; + } + + /* Initializing the mutex resource */ + pthread_mutex_init(&medusa->release_mutex, NULL); + + pthread_mutex_init(&medusa->mutex, NULL); + + pthread_attr_init(&medusa->thread_attr); + + pthread_attr_setdetachstate( + &medusa->thread_attr, PTHREAD_CREATE_DETACHED + ); + + return medusa; +} + +static void medusa_finish(medusalog_t *medusa) +{ + const FILE **out = medusa->outfiles; + + for (; out && *out != NULL; ) + fclose((FILE*)*out++); + +} + +/* When this function returns, every message has been delivery */ +static void medusa_wait(medusalog_t *medusa) +{ + bool quit = false; + + while (quit != true) + { + pthread_mutex_lock(&medusa->mutex); + if (medusa->wait == 0) + quit = true; + pthread_mutex_unlock(&medusa->mutex); + } +} + +bool medusa_destroy(medusalog_t *medusa) +{ + assert(medusa); + //free((char*)medusa->name); + + medusa_wait(medusa); + + medusa_finish(medusa); + + pthread_mutex_lock(&medusa->mutex); + /* Putting the lock in a invalid state to continue */ + pthread_mutex_lock(&medusa->release_mutex); + /* Never unlocks the mutex */ + + free(medusa->outfiles); + + free(medusa->newfmt); + + pthread_attr_destroy(&medusa->thread_attr); + + pthread_mutex_destroy(&medusa->release_mutex); + pthread_mutex_destroy(&medusa->mutex); + + memset(medusa, 0xff, sizeof(medusalog_t)); + + free(medusa); + + return true; +} + +struct medusa_thread +{ + medusalog_t *medusa; + + char *message; + + size_t sleep_for; + + pthread_t logthread; + +}; + +static void do_message(struct medusa_dispatch_data *dispatch) +{ + const FILE **out = dispatch->output_files; + const char *usermessage = dispatch->message; + + for (; *out != NULL; ) + fwrite(usermessage, strlen(usermessage), 1, (FILE*)*out++); +} + +static void* medusa_thread_produce(void *thread_data) +{ + struct medusa_thread *medusa_data = (struct medusa_thread*)thread_data; + + medusalog_t *medusa = medusa_data->medusa; + medusaattr_t *attr = &medusa->attr; + + struct timespec req = {.tv_sec = medusa_data->sleep_for / 1e+3 /*, .tv_nsec = medusa_data->sleep_for * 1e+6 */}; + + /* Going sleep, I need to do this right now either ;) */ + nanosleep(&req, NULL); + + pthread_mutex_lock(&medusa->mutex); + pthread_mutex_lock(&medusa->release_mutex); + + if (attr->usestdout) + fprintf(stdout, "%s", medusa_data->message); + + struct medusa_dispatch_data *thread_message_data = malloc(sizeof(struct medusa_dispatch_data)); + + assert(thread_message_data != NULL); + + thread_message_data->output_files = medusa->outfiles; + + thread_message_data->message = medusa_data->message; + + attr->dolog(thread_message_data); + + medusa->wait--; + + free(thread_message_data); + + free(medusa_data->message); + + free(medusa_data); + + pthread_mutex_unlock(&medusa->release_mutex); + pthread_mutex_unlock(&medusa->mutex); + + return NULL; +} + +static int medusa_do(size_t milliseconds, medusa_log_type_t type, medusalog_t *medusa, + const char *fmt, va_list va_format) +{ + + assert(medusa); + + pthread_mutex_lock(&medusa->mutex); + + char *str_type = NULL; + + char str_typebuffer[30]; + + switch (type) + { + case SUCCESS: str_type = "Success"; break; + case INFO: str_type = "Info"; break; + case WARNING: str_type = "Warning"; break; + case ERROR: str_type = "Error"; break; + case DEBUG: str_type = "Debug"; break; + default: str_type = "USER"; break; + } + + medusaattr_t *attr = &medusa->attr; + + snprintf(str_typebuffer, sizeof(str_typebuffer), "%s%s%s", + attr->colored ? ( + type == SUCCESS ? "\033[0;32m" : + type == INFO ? "\033[0;34m" : + type == WARNING ? "\033[0;33m" : + type == ERROR ? "\033[0;31m" : + type == DEBUG ? "\033[0;35m" : "") : "", + str_type, attr->colored ? "\033[0m" : "" + ); + + assert(str_type); + + const char *program = attr->program; + + char date_str[32]; + + time_t rawtime; + struct tm *timeinfo; + + time(&rawtime); + + rawtime += (milliseconds / 1000); + + timeinfo = localtime(&rawtime); + + strftime(date_str, sizeof(date_str), "%T", timeinfo); + + char auxbuffer[60]; + + const size_t maxlen = attr->maxfmt; + + snprintf(auxbuffer, sizeof(auxbuffer), "%s ", program); + snprintf(medusa->newfmt, maxlen, "%s?", + attr->printprogram == true ? auxbuffer : "" + ); + +#ifndef __ANDROID__ + /* The way that the backtrace system works on Androd is different */ + void *stack_buffer[4]; + char **stack_strings; + + int symbol_count = backtrace(stack_buffer, sizeof(stack_buffer)/sizeof(void*)); + + stack_strings = backtrace_symbols(stack_buffer, symbol_count); + + snprintf(auxbuffer, sizeof(auxbuffer), "\'%s\' - ", strstr(stack_strings[symbol_count - 2], "(")); + snprintf(strstr(medusa->newfmt, "?"), maxlen, "%s?", + attr->printdebug == true ? auxbuffer : "" + ); + + free(stack_strings); +#endif + + snprintf(auxbuffer, sizeof(auxbuffer), "[%s] ", date_str); + snprintf(strstr(medusa->newfmt, "?"), maxlen, "%s?", + attr->printdate == true ? auxbuffer : "" + ); + + snprintf(auxbuffer, sizeof(auxbuffer), "(%18s): ", str_typebuffer); + snprintf(strstr(medusa->newfmt, "?"), maxlen, "%s?", + attr->printtype == true ? auxbuffer : "" + ); + + snprintf(strstr(medusa->newfmt, "?"), maxlen, "%s\n", fmt); + + struct medusa_thread *medusa_data = malloc(sizeof(struct medusa_thread)); + + assert(medusa_data); + + medusa_data->medusa = medusa; + + medusa_data->sleep_for = milliseconds; + + medusa_data->message = (char*)malloc(attr->maxmsg); + + assert(medusa_data->message); + + const int ret = vsnprintf(medusa_data->message, attr->maxmsg, medusa->newfmt, va_format); + + medusa->wait++; + + pthread_t thread; + + medusa_data->logthread = pthread_self(); + + pthread_mutex_unlock(&medusa->mutex); + + if (milliseconds != 0) + pthread_create(&thread, &medusa->thread_attr, medusa_thread_produce, (void*)medusa_data); + else medusa_thread_produce(medusa_data); + + return ret; +} + +int medusa_log_await(size_t milliseconds, medusa_log_type_t type, medusalog_t *medusa, const char *fmt, ...) +{ + va_list va_format; + va_start(va_format, fmt); + + int ret = medusa_do(milliseconds, type, medusa, fmt, va_format); + + va_end(va_format); + + return ret; +} + +int medusa_log(medusa_log_type_t type, medusalog_t *medusa, const char *fmt, ...) +{ + va_list va_format; + va_start(va_format, fmt); + + int ret = medusa_do(0, type, medusa, fmt, va_format); + + va_end(va_format); + + return ret; +} + diff --git a/medusalog.h b/medusalog.h new file mode 100644 index 0000000..0ae9e06 --- /dev/null +++ b/medusalog.h @@ -0,0 +1,76 @@ +#ifndef MEDUSALOG_H +#define MEDUSALOG_H + +#if defined(__cplusplus) +extern "C" { +#endif + +#include + +#include + +#include + +struct medusa_dispatch_data +{ + const FILE **output_files; + const char *message; +}; + +typedef struct +{ + const char *program; + + bool usestdout, + printprogram, +#ifndef __ANDROID__ + printdebug, +#endif + printdate, + printtype, + colored; + + size_t maxfmt; + + size_t maxmsg; + + void (*dolog)(struct medusa_dispatch_data *dispatch); + +} medusaattr_t; + +typedef struct +{ + const FILE **outfiles; + + pthread_mutex_t release_mutex, mutex; + + medusaattr_t attr; + + size_t wait; + + /* Reusable buffer */ + char *newfmt; + + pthread_attr_t thread_attr; + +} medusalog_t; + +/* Logging level mechanism */ +typedef enum +{ + SUCCESS, INFO, WARNING, ERROR, DEBUG +} medusa_log_type_t; + +medusalog_t* medusa_new(medusaattr_t *user_attr, const char **logfilenames, size_t logcount); + +bool medusa_destroy(medusalog_t *medusa); + +int medusa_log_await(size_t milliseconds, medusa_log_type_t type, medusalog_t *medusa, const char *fmt, ...); + +int medusa_log(medusa_log_type_t type, medusalog_t *medusa, const char *fmt, ...); + +#if defined(__cplusplus) +} +#endif + +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..723792b --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,4 @@ + +add_executable(medusatest medusatest.c) + +target_link_libraries(medusatest medusa::medusa) diff --git a/test/medusatest.c b/test/medusatest.c new file mode 100644 index 0000000..a122bd4 --- /dev/null +++ b/test/medusatest.c @@ -0,0 +1,55 @@ +#include + +void test_do_message(struct medusa_dispatch_data *medusa_message) +{ + const char *message = medusa_message->message; + printf("%s", message); +} + +int main() +{ + medusaattr_t main_attr = { + /* Display to standard out (my terminal) */ + .usestdout = false, + + /* Display the program name */ + .program = "Medusa Test", + + .printprogram = true, + + .printdebug = true, + + .printdate = true, + + .printtype = true, + + /* Display colors :) */ + .colored = true, + + .maxfmt = 150, + + .maxmsg = 200, + + .dolog = test_do_message + }; + + medusalog_t *main_log = medusa_new(&main_attr, NULL, 0); + + medusa_log(INFO, main_log, "Main function is located at %p", main); + + medusa_log_await(3000, DEBUG, main_log, "Final message, the log system will be destroyed"); + + medusa_log_await(2000, INFO, main_log, "Hmmm this will be printed before the success message"); + + medusa_log(WARNING, main_log, "I will print a error message, but don't worry, isn't a real error :)"); + + medusa_log(ERROR, main_log, "Error message"); + + medusa_log(INFO, main_log, "Sleeping for 1 second..."); + + medusa_log_await(1000, SUCCESS, main_log, "Everything is ok"); + + medusa_destroy(main_log); + +} +