소스 검색

feat: 添加日志系统

SongZihuan 3 년 전
부모
커밋
949bff8ee0

+ 6 - 1
CMakeLists.txt

@@ -53,8 +53,10 @@ if (_print_dir)
 else()
     wi_set_install_dir_quiet(NAMES aFunlang)  # 设置安装路径
 endif()
+file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/${INSTALL_LOG})  # 安装log输出的目录
 
 include(info)
+include(filename)
 
 # CMake对象属性的初始化值的相关设定
 set(CMAKE_INSTALL_RPATH
@@ -70,7 +72,8 @@ set(base_compile_definitions
     compilerID="${CMAKE_C_COMPILER_ID}"
     aFunMajorVersion=${PROJECT_VERSION_MAJOR}
     aFunMinorVersion=${PROJECT_VERSION_MINOR}
-    aFunPatchVersion=${PROJECT_VERSION_PATCH})  # 默认的预定义宏
+    aFunPatchVersion=${PROJECT_VERSION_PATCH}
+    aFunLogDir="${INSTALL_LOG}")  # 默认的预定义宏
 
 if (WIN32 OR CYGWIN)
     list(APPEND base_compile_definitions aFunWIN32=1)
@@ -138,6 +141,8 @@ install(FILES
         ${CMAKE_BINARY_DIR}/cmake-tmp/aFunlangConfigVersion.cmake
         DESTINATION ${INSTALL_CMAKEDIR})
 
+install(CODE "file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/${INSTALL_LOG})")  # 创建log目录
+
 set(BUILD_TEST FALSE CACHE BOOL "Enable run test program")
 set(_build_test ${BUILD_TEST})
 if (_build_test)

+ 4 - 0
cmake/CMakeFindExternalProject/InstallDir.cmake

@@ -35,11 +35,13 @@ function(wi_set_install_dir_quiet)
         set(DEF_INSTALL_CMAKEDIR cmake)
         set(DEF_INSTALL_INCLUDEDIR ${CMAKE_INSTALL_INCLUDEDIR})
         set(DEF_INSTALL_RESOURCEDIR ${CMAKE_INSTALL_DATAROOTDIR})  # 关联文件
+        set(DEF_INSTALL_LOG ${CMAKE_INSTALL_LOCALSTATEDIR}/log)
     else()
         # unix类系统(Unix, Linux, MacOS, Cygwin等)把cmake文件安装到指定的系统的cmake文件夹中
         set(DEF_INSTALL_CMAKEDIR ${CMAKE_INSTALL_DATAROOTDIR}/cmake/${_names})
         set(DEF_INSTALL_INCLUDEDIR ${CMAKE_INSTALL_INCLUDEDIR}/${_names})
         set(DEF_INSTALL_RESOURCEDIR ${CMAKE_INSTALL_DATAROOTDIR}/${_names})  # 关联文件 CMAKE_INSTALL_DATAROOTDIR指: share
+        set(DEF_INSTALL_LOG ${CMAKE_INSTALL_LOCALSTATEDIR}/log/${_names})
     endif()
 
     # 设定安装的目录
@@ -48,10 +50,12 @@ function(wi_set_install_dir_quiet)
     set(INSTALL_CMAKEDIR ${DEF_INSTALL_CMAKEDIR} CACHE PATH "Installation directory for CMake files")
     set(INSTALL_INCLUDEDIR ${DEF_INSTALL_INCLUDEDIR} CACHE PATH "Installation directory for header files")
     set(INSTALL_RESOURCEDIR ${DEF_INSTALL_RESOURCEDIR} CACHE PATH "Installation directory for resource files")  # 关联文件
+    set(INSTALL_LOG ${DEF_INSTALL_LOG} CACHE PATH "Installation directory for log files")  # 关联文件
 
     unset(DEF_INSTALL_CMAKEDIR)
     unset(DEF_INSTALL_INCLUDEDIR)
     unset(DEF_INSTALL_RESOURCEDIR)
+    unset(DEF_INSTALL_LOG)
 endfunction()
 
 function(wi_set_install_dir)

+ 9 - 0
cmake/filename.cmake

@@ -0,0 +1,9 @@
+function(define_FILENAME targetname)
+    get_target_property(source_files "${targetname}" SOURCES)
+    foreach(sourcefile ${source_files})
+        get_filename_component(basename "${sourcefile}" NAME)
+        set_property(
+                SOURCE "${sourcefile}" APPEND
+                PROPERTY COMPILE_DEFINITIONS "__FILENAME__=\"${basename}\"")
+    endforeach()
+endfunction()

+ 4 - 1
include/core/core_init.h

@@ -2,11 +2,14 @@
 #define AFUN_INIT_H
 #include "aFunCoreExport.h"
 #include "stdbool.h"
+#include <setjmp.h>
+#include "tool.h"
 
 #ifdef aFunWIN32
 #include "Windows.h"
 #endif
 
-AFUN_CORE_EXPORT bool aFunCoreInit(void);
+AFUN_CORE_EXPORT extern Logger *aFunCoreLogger;
+AFUN_CORE_EXPORT bool aFunCoreInit(char *log_dir, LogFactoryPrintConsole print_console, bool fe, bool se, jmp_buf *buf, LogLevel level);
 
 #endif //AFUN_INIT_H

+ 7 - 0
include/main.h

@@ -0,0 +1,7 @@
+#ifndef AFUN_MAIN_H
+#define AFUN_MAIN_H
+
+extern char *base_name;
+extern Logger *aFunlangLogger;
+
+#endif //AFUN_MAIN_H

+ 1 - 1
include/runtime/aFunlang.h

@@ -5,7 +5,7 @@
 
 #include "runtime.h"
 
-AFUN_LANG_EXPORT bool aFunInit(void);
+AFUN_LANG_EXPORT bool aFunInit(char *log_dir, LogFactoryPrintConsole print_console, jmp_buf *buf, LogLevel level);
 
 AFUN_LANG_EXPORT af_Environment *creatAFunEnviroment(int argc, char **argv);
 AFUN_LANG_EXPORT void destructAFunEnvironment(af_Environment *env);

+ 2 - 1
include/tool/file.h

@@ -8,8 +8,9 @@ AFUN_TOOL_EXPORT time_t getFileMTime(char *path);
 AFUN_TOOL_EXPORT char *joinPath(char *path, char *name, char *suffix);
 AFUN_TOOL_EXPORT char *getFileName(char *path_1);
 AFUN_TOOL_EXPORT char *getFileNameWithPath(char *path_1);
+AFUN_TOOL_EXPORT char *getFilePath(char *path_1, int dep);
 AFUN_TOOL_EXPORT char *getFileSurfix(char *path);
 AFUN_TOOL_EXPORT char *fileNameToVar(char *name, bool need_free);
 AFUN_TOOL_EXPORT char *findPath(char *path, char *env, bool need_free);
-
+AFUN_TOOL_EXPORT char *getExedir(char *pgm, int dep);
 #endif //AFUN_FILE_H

+ 58 - 0
include/tool/log.h

@@ -0,0 +1,58 @@
+#ifndef AFUN_LOG_H
+#define AFUN_LOG_H
+#include "aFunToolExport.h"
+#include <setjmp.h>
+
+enum LogLevel {
+    log_debug = 0,
+    log_info = 1,
+    log_warning = 2,
+    log_error = 3,
+    log_send_error = 4,
+    log_fatal_error = 5,
+};
+typedef enum LogLevel LogLevel;
+
+enum LogFactoryPrintConsole {
+    log_pc_all = 1,
+    log_pc_w = 2,
+    log_pc_e = 3,
+    log_pc_quite = 4,
+};
+typedef enum LogFactoryPrintConsole LogFactoryPrintConsole;
+
+struct Logger {
+    char *id;
+    LogLevel level;
+    bool process_send_error;  // 若为false则send_error转换为error
+    bool process_fatal_error;  // 若为false则fatal_error转换为error
+    jmp_buf *buf;
+};
+typedef struct Logger Logger;
+
+AFUN_TOOL_EXPORT int initLogSystem(FilePath path, LogFactoryPrintConsole print_console);
+
+AFUN_TOOL_EXPORT void initLogger(Logger *logger, char *id, LogLevel level);
+AFUN_TOOL_EXPORT void destructLogger(Logger *logger);
+
+AFUN_TOOL_EXPORT int writeDebugLog_(Logger *logger, char *file, int line, char *func, char *format, ...);
+AFUN_TOOL_EXPORT int writeInfoLog_(Logger *logger, char *file, int line, char *func, char *format, ...);
+AFUN_TOOL_EXPORT int writeWarningLog_(Logger *logger, char *file, int line, char *func, char *format, ...);
+AFUN_TOOL_EXPORT int writeErrorLog_(Logger *logger, char *file, int line, char *func, char *format, ...);
+AFUN_TOOL_EXPORT int writeSendErrorLog_(Logger *logger, char *file, int line, char *func, char *format, ...);
+AFUN_TOOL_EXPORT int writeFatalErrorLog_(Logger *logger, char *file, int line, char *func, int exit_code, char *format, ...);
+
+#ifdef __FILENAME__
+#define file_line (char *)__FILENAME__ , (int)__LINE__
+#else
+#define file_line (char *)"xx", (int)__LINE__
+#endif
+
+#define writeDebugLog(logger, ...) writeDebugLog_(logger, file_line, (char *)__FUNCTION__, __VA_ARGS__)
+#define writeInfoLog(logger, ...) writeInfoLog_(logger, file_line, (char *)__FUNCTION__, __VA_ARGS__)
+#define writeWarningLog(logger, ...) writeWarningLog_(logger, file_line, (char *)__FUNCTION__, __VA_ARGS__)
+#define writeErrorLog(logger, ...) writeErrorLog_(logger, file_line, (char *)__FUNCTION__, __VA_ARGS__)
+#define writeSendErrorLog(logger, ...) writeSendErrorLog_(logger, file_line, (char *)__FUNCTION__, __VA_ARGS__)
+#define writeFatalErrorLog(logger, exit_code, ...) writeFatalErrorLog_(logger, file_line, (char *)__FUNCTION__, exit_code, __VA_ARGS__)
+
+#endif //AFUN_LOG_H

+ 10 - 6
include/tool/path.h

@@ -2,18 +2,22 @@
 #define AFUN_PATH_H
 
 /* 路径工具 */
-#ifdef __linux__
+#ifdef WIN32
+
+#define SEP "\\"
+#define SEP_CH '\\'
+
+#else
 
 #define SEP "/"
 #define SEP_CH '/'
-#define SHARED_MARK ".so"
 
-#else
+#endif
 
-#define SEP "\\"
-#define SEP_CH '\\'
+#ifdef aFunWIN32
 #define SHARED_MARK ".dll"
-
+#else
+#define SHARED_MARK ".so"
 #endif
 
 #endif //AFUN_PATH_H

+ 1 - 1
include/tool/time_s.h

@@ -4,5 +4,5 @@
 
 /* 时间工具 */
 AFUN_TOOL_EXPORT void safeSleep(double ms);
-
+AFUN_TOOL_EXPORT char *getTime(time_t *t);
 #endif //AFUN_TIME_H

+ 1 - 0
include/tool/tool.h

@@ -21,6 +21,7 @@
 #include "sig.h"
 #include "str.h"
 #include "time_s.h"
+#include "log.h"
 
 #include "fflags.h"
 

+ 1 - 0
src/CMakeLists.txt

@@ -38,6 +38,7 @@ foreach(tgt IN LISTS aFunList)
     target_include_directories(${tgt} PRIVATE ${PROJECT_SOURCE_DIR}/include)
     get_target_property(tmp ${tgt} COMPILE_DEFINITIONS)
     set_target_properties(${tgt} PROPERTIES PUBLIC_HEADER "${include_h}")
+    define_FILENAME(${tgt})
 endforeach()
 
 set(AFUN_LIBRARY "aFunCore" CACHE STRING "Assign core to aFun")

+ 1 - 0
src/core/CMakeLists.txt

@@ -47,6 +47,7 @@ foreach(tgt core-shared-t core-shared-s core-static-s)
     set_target_properties(${tgt} PROPERTIES
                           PUBLIC_HEADER "${public_h_build}"
                           PRIVATE_HEADER "${private_h}")  # PRIVATE_HEADER私有头文件, 可以用于高级开发
+    define_FILENAME(${tgt})
 endforeach()
 
 set_target_properties(core-shared-t PROPERTIES OUTPUT_NAME "aFunCore-t")

+ 18 - 2
src/core/core_init.c

@@ -7,7 +7,10 @@
 #include "tool.h"
 #include <locale.h>
 
-bool aFunCoreInit(void) {
+static Logger aFunCoreLogger_;
+Logger *aFunCoreLogger = &aFunCoreLogger_;
+
+bool aFunCoreInit(char *log_dir, LogFactoryPrintConsole print_console, bool fe, bool se, jmp_buf *buf, LogLevel level) {
     getEndian();
     if (setlocale(LC_ALL, "") == NULL)
         return false;
@@ -15,6 +18,19 @@ bool aFunCoreInit(void) {
     if(!SetConsoleOutputCP(65001))  // 设置windows代码页为utf-8编码
         return false;
 #endif
-    printf("try 中文\n");
+    if (log_dir == NULL)
+        return false;
+    char *log = strJoin(log_dir, "aFunlang-", false, false);
+    bool re = initLogSystem(log, print_console);
+    free(log);
+    if (re != 1)
+        return false;
+
+    initLogger(aFunCoreLogger, "aFunlang-core", level);
+    aFunCoreLogger->process_send_error = fe;
+    aFunCoreLogger->process_fatal_error = se;
+    aFunCoreLogger->buf = buf;
+
+    writeInfoLog(aFunCoreLogger, "aFunCore init success");
     return true;
 }

+ 31 - 1
src/main.c

@@ -1,5 +1,7 @@
 #include <stdio.h>
+#include <stdlib.h>
 #include "aFun.h"
+#include "main.h"
 #include "main_run.h"
 #include "main_build.h"
 
@@ -40,12 +42,40 @@ static int mainCL(ff_FFlags *ff);
 static int mainBuild(ff_FFlags *ff);
 extern const char *help_info;
 
+char *base_name = NULL;
+static Logger aFunlangLogger_;
+Logger *aFunlangLogger = &aFunlangLogger_;
+
+void freeBaseName(void) {
+    free(base_name);
+}
+
 int main(int argc, char **argv) {
-    if (!aFunInit()) {
+    jmp_buf main_buf;
+    base_name = getExedir(*argv, 1);
+    if (base_name == NULL)
+        goto INIT_ERROR;
+    atexit(freeBaseName);
+
+    if (setjmp(main_buf) == 1)
+        return EXIT_FAILURE;
+
+    char *log = strJoin(base_name, SEP aFunLogDir SEP, false, false);
+    bool re = aFunInit(log, log_pc_w, &main_buf, log_debug);
+    free(log);
+
+    if (!re) {
+INIT_ERROR:
         fprintf(stderr, "aFunlang init error.");
         return EXIT_FAILURE;
     }
 
+    initLogger(aFunlangLogger, "aFunlang-exe", log_debug);
+    aFunlangLogger->process_send_error = true;
+    aFunlangLogger->process_fatal_error = true;
+    aFunlangLogger->buf = &main_buf;
+    writeInfoLog(aFunlangLogger, "aFunlang-exe init success.");
+
     int exit_code = EXIT_SUCCESS;
     ff_FFlags *ff = ff_initFFlags(argc, argv, true, false, stderr, aFunlang_exe);
     if (ff == NULL)

+ 1 - 0
src/main_build.h

@@ -1,5 +1,6 @@
 #ifndef AFUN_MAIN_BUILD_H_
 #define AFUN_MAIN_BUILD_H_
+#include "main.h"
 
 int buildFileOutput(FilePath out, FilePath in, bool force);
 int buildFileToPath(FilePath path, FilePath in, bool force);

+ 1 - 0
src/main_run.h

@@ -1,5 +1,6 @@
 #ifndef AFUN_MAIN_RUN_H_
 #define AFUN_MAIN_RUN_H_
+#include "main.h"
 
 typedef struct RunList RunList;
 struct RunList {

+ 1 - 0
src/runtime/CMakeLists.txt

@@ -52,6 +52,7 @@ foreach(tgt aFun-xx-libs aFun-cx-libs aFun-ct-libs)
                                    $<BUILD_INTERFACE:${build_include_runtime}>
                                    $<INSTALL_INTERFACE:${install_include_runtime}>)
     set_target_properties(${tgt} PROPERTIES PUBLIC_HEADER "${public_h_build}")
+    define_FILENAME(${tgt})
 endforeach()
 
 set_target_properties(aFun-xx-libs PROPERTIES OUTPUT_NAME "aFun-xx")

+ 22 - 10
src/runtime/aFunlang.c

@@ -2,14 +2,23 @@
 #include "__aFunlang.h"
 #include "__env.h"
 
-static int
-runCode_(FilePath name, af_Parser *parser, int mode, FilePath save_path, FILE *error_file, af_Environment *env);
+static int runCode_(FilePath name, af_Parser *parser, int mode, FilePath save_path, FILE *error_file, af_Environment *env);
+static bool aFunInit_mark = false;
 
-bool aFunInit(void) {
-    return aFunCoreInit();
+bool aFunInit(char *log_dir, LogFactoryPrintConsole print_console, jmp_buf *buf, LogLevel level) {
+    if (aFunInit_mark)
+        return false;
+
+    aFunInit_mark = aFunCoreInit(log_dir, print_console, true, true, buf, level);
+    if (aFunInit_mark)
+        writeInfoLog(aFunCoreLogger, "aFun-base-runtime Init success");
+    return aFunInit_mark;
 }
 
 af_Environment *creatAFunEnviroment(int argc, char **argv){
+    if (!aFunInit_mark)
+        return NULL;
+
     af_Environment *env = makeEnvironment(grt_count);
     af_Code *code = NULL;
 
@@ -71,7 +80,7 @@ static int runCode_(FilePath name, af_Parser *parser, int mode, FilePath save_pa
  * 目标: 运行字符串中的程序 (源码形式)
  */
 int runCodeFromString(char *code, char *string_name, FILE *error_file, int mode, af_Environment *env){
-    if (env == NULL || code == NULL)
+    if (env == NULL || code == NULL || !aFunInit_mark)
         return -1;
 
     if (string_name == NULL)
@@ -88,7 +97,7 @@ int runCodeFromString(char *code, char *string_name, FILE *error_file, int mode,
  * 目标: 运行文件中的程序 (源码形式)
  */
 int runCodeFromFileSource(FilePath file, FILE *error_file, bool save_afb, FilePath save_path, int mode, af_Environment *env){
-    if (env == NULL || file == NULL)
+    if (env == NULL || file == NULL || !aFunInit_mark)
         return -1;
 
     if (error_file == NULL)
@@ -121,7 +130,7 @@ int runCodeFromFileSource(FilePath file, FILE *error_file, bool save_afb, FilePa
  * 目标: 运行stdin的程序 (源码形式)
  */
 int runCodeFromStdin(char *name, FILE *error_file, af_Environment *env) {
-    if (env == NULL || feof(stdin) || ferror(stdin))
+    if (env == NULL || feof(stdin) || ferror(stdin) || !aFunInit_mark)
         return -1;
 
     if (name == NULL)
@@ -138,6 +147,9 @@ int runCodeFromStdin(char *name, FILE *error_file, af_Environment *env) {
  * 目标: 运行内存中的程序 (字节码形式)
  */
 int runCodeFromMemory(af_Code *code, int mode, af_Environment *env){
+    if (!aFunInit_mark)
+        return -1;
+
     bool res = iterCode(code, mode, env);
     if (!res)
         return env->core->exit_code_->num;
@@ -149,7 +161,7 @@ int runCodeFromMemory(af_Code *code, int mode, af_Environment *env){
  * 目标: 运行文件中的程序 (字节码形式)
  */
 int runCodeFromFileByte(FilePath file, FILE *error_file, int mode, af_Environment *env){
-    if (env == NULL || file == NULL)
+    if (env == NULL || file == NULL || !aFunInit_mark)
         return -1;
 
     if (error_file == NULL)
@@ -178,7 +190,7 @@ int runCodeFromFileByte(FilePath file, FILE *error_file, int mode, af_Environmen
  * 目标: 运行文件中的程序 (字节码/源码形式)
  */
 int runCodeFromFile(FilePath file, FILE *error_file, bool save_afb, int mode, af_Environment *env){
-    if (env == NULL || file == NULL)
+    if (env == NULL || file == NULL || !aFunInit_mark)
         return -1;
 
     if (error_file == NULL)
@@ -224,7 +236,7 @@ RUN_SOURCE_CODE:
  * 目标: 生成字节码文件
  */
 int buildFile(FilePath out, FilePath in, FILE *error_file) {
-    if (out == NULL || in == NULL)
+    if (out == NULL || in == NULL || !aFunInit_mark)
         return -1;
 
     if (error_file == NULL)

+ 1 - 0
src/tool/CMakeLists.txt

@@ -40,6 +40,7 @@ foreach(tgt tool-shared tool-static)
     if(_build_mem)
         target_compile_definitions(${tgt} PUBLIC BUILD_MEM=1)
     endif()
+    define_FILENAME(${tgt})
 endforeach()
 
 set_target_properties(tool-shared PROPERTIES OUTPUT_NAME "aFunTool")

+ 32 - 2
src/tool/file.c

@@ -7,6 +7,9 @@
 #include <string.h>
 #include <ctype.h>
 #include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
 #include "tool.h"
 
 #ifndef S_ISREG
@@ -98,6 +101,24 @@ char *getFileNameWithPath(char *path_1){
     return res;
 }
 
+char *getFilePath(char *path_1, int dep){
+    char *slash = NULL;  // 后缀名.所在的字符的指针
+    char *path = strCopy(path_1);  // 复制数组, 避免path_1是常量字符串导致无法修改其值
+    char *res;
+
+    if (path[STR_LEN(path) - 1] == SEP_CH)  // 若路径的最后一个字符为SEP, 则忽略此SEP
+        path[STR_LEN(path) - 1] = NUL;
+
+    for (NULL; dep > 0; dep--) {
+        if ((slash = strrchr(path, SEP_CH)) != NULL)
+            *slash = NUL;
+    }
+
+    res = strCopy(path);
+    free(path);
+    return res;
+}
+
 /*
  * 函数名: getFileSurfix
  * 目标: 获取文件后缀 (不会生成新字符串)
@@ -134,7 +155,6 @@ char *fileNameToVar(char *name, bool need_free){
  * 函数名: findPath
  * 目标: 转换路径为合法路径(相对路径->绝对路径, 绝对路径保持不变)
  */
-#include "stdio.h"
 char *findPath(char *path, char *env, bool need_free){
     assert(env[STR_LEN(env) - 1] == SEP_CH);  // env 必须以 SEP 结尾
 #ifdef __linux
@@ -148,4 +168,14 @@ char *findPath(char *path, char *env, bool need_free){
     } else {
         return path;
     }
-}
+}
+
+/*
+ * 函数名: 获取可执行程序目录
+ * dep表示从可执行程序往回跳出的层数
+ */
+char *getExedir(char *pgm, int dep) {
+    if (pgm == NULL)
+        return NULL;
+    return getFilePath(pgm, dep + 1);
+}

+ 258 - 0
src/tool/log.c

@@ -0,0 +1,258 @@
+/*
+ * 文件名: log.c
+ * 目标: 日志系统对aFun的API
+ * 注意: 因为tool模块需要使用log系统, 因此log系统尽量少点依赖tool模块, 避免造成死循环
+ * 仅依赖:
+ * time_s.h 中 getTime
+ * mem.h 中 free
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include "macro.h"
+#include "mem.h"
+#include "log.h"
+#include "time_s.h"
+
+#if aFunWIN32
+#include <windows.h>
+#define getpid() (long)GetCurrentProcessId()
+#define gettid() (long)GetCurrentThreadId()
+#else
+#include <unistd.h>
+#include "sys/syscall.h"
+#define gettid() (long)syscall(SYS_gettid)
+#define getpid() (long)getpid()
+#endif
+
+#undef calloc
+
+static struct LogFactory {
+    bool init;  // 是否已经初始化
+
+    FILE *log;  // 记录文件输出的位置
+    LogFactoryPrintConsole print_console;  // 输出到终端、
+
+    Logger sys_log;
+} log_factory = {.init=false};
+
+static void destructLogSystem_at_exit(void);
+
+/*
+ * 函数名: initLogSystem
+ * 目标: 初始化日志系统
+ * 返回值:
+ * 1 表示初始化成功
+ * 2 表示已经初始化
+ * 0 表示初始化失败
+ */
+int initLogSystem(FilePath path, LogFactoryPrintConsole print_console) {
+    if (log_factory.init)
+        return 2;
+    if (strlen(path) >= 218)  // 路径过长
+        return 0;
+
+    char log_path[1024] = {0};
+    char log_base[1024] = {0};
+    long pid = getpid();  // 获取进程ID
+
+    time_t t;
+    char *ti = getTime(&t);
+
+    snprintf(log_path, 1024, "%s%ld-%ld.log", path, pid, t);
+    snprintf(log_base, 1024, "%s-base.log", path);
+
+    FILE *base = fopen(log_base, "a");
+    if (base == NULL)
+        base = fopen(log_path, "w");
+    if (base == NULL) {
+        free(ti);
+        return 0;
+    }
+
+    fprintf(base, "%s  %s\n", ti, log_path);
+    fclose(base);
+    free(ti);
+
+    log_factory.log = fopen(log_path, "a");
+    if (log_factory.log == NULL)
+        log_factory.log = fopen(log_path, "w");
+    if (log_factory.log == NULL)
+        return 0;
+    log_factory.print_console = print_console;
+    log_factory.init = true;
+    atexit(destructLogSystem_at_exit);
+
+    initLogger(&(log_factory.sys_log), "SYSTEM", log_debug);  // 设置为 debug, 记录 success 信息
+    log_factory.sys_log.process_fatal_error = true;
+    log_factory.sys_log.process_send_error = false;
+    writeInfoLog(NULL, "Log system init success.");
+    log_factory.sys_log.level = log_error;
+    return 1;
+}
+
+static void destructLogSystem_at_exit(void) {
+    if (!log_factory.init)
+        return;
+    fclose(log_factory.log);
+    log_factory.log = NULL;
+    log_factory.init = false;
+}
+
+int destructLogSystem(void) {
+    if (!log_factory.init)
+        return 2;
+    fclose(log_factory.log);
+    log_factory.log = NULL;
+    log_factory.init = false;
+    return 1;
+}
+
+void initLogger(Logger *logger, char *id, LogLevel level) {
+    memset(logger, 0, sizeof(Logger));
+    logger->id = id;
+    logger->level = level;
+}
+
+/* LogLevel和字符串的转换 */
+static const char *LogLevelName[] = {
+        "DE",  // debug 0
+        "IN",  // info 1
+        "WA",  // warning 2
+        "ER",  // error 3
+        "SE",  // send_error 4
+        "FE",  // fatal_error 5
+};
+
+static const char *LogLevelNameLong[] = {
+        /* 内容输出到终端时使用*/
+        "Debug",  // debug 0
+        "Info",  // info 1
+        "Warning",  // warning 2
+        "Error",  // error 3
+        "Fatal Error",  // send_error 4
+        "*FATAL ERROR*",  // fatal_error 5
+};
+
+static int writeLog_(Logger *logger, LogLevel level, char *file, int line, char *func, char *format, va_list ap){
+    if (logger->level > level)
+        return 2;
+    if (!log_factory.init || log_factory.log == NULL)
+        return 1;
+    if (ferror(log_factory.log))
+        clearerr(log_factory.log);
+
+    // 输出 head 信息
+    time_t t = 0;
+    char *ti = getTime(&t);
+
+#define FORMAT "%s/[%s] %ld {%s %ld} (%s:%d at %s) : '%s'\n"
+#define FORMAT_SHORT "%s[%s] : %s\n"
+    long tid = gettid();
+
+    char tmp[2048] = {0};
+    vsnprintf(tmp, 1024, format, ap);  // ap只使用一次
+    va_end(ap);
+
+    /* 写入文件日志 */
+    if (log_factory.log != NULL) {
+        fprintf(log_factory.log, FORMAT, LogLevelName[level], logger->id, tid, ti, t, file, line, func, tmp);
+        fflush(log_factory.log);
+    }
+
+    switch (log_factory.print_console) {
+        case log_pc_all:
+            if (level < log_warning) {
+                fprintf(stdout, FORMAT, LogLevelNameLong[level], logger->id, tid, ti, t, file, line, func, tmp);
+                fflush(stdout);
+            } else if (log_factory.print_console) {
+                fprintf(stderr, FORMAT, LogLevelNameLong[level], logger->id, tid, ti, t, file, line, func, tmp);
+                fflush(stderr);
+            }
+            break;
+
+        case log_pc_w:
+            if (level >= log_warning) { // warning的内容一定会被打印
+                fprintf(stderr, FORMAT_SHORT, LogLevelNameLong[level], logger->id, tmp);
+                fflush(stderr);
+            }
+            break;
+
+        case log_pc_e:
+            if (level >= log_error) {  // warning的内容一定会被打印
+                fprintf(stderr, FORMAT_SHORT, LogLevelNameLong[level], logger->id, tmp);
+                fflush(stderr);
+            }
+            break;
+
+        case log_pc_quite:
+        default:
+            break;
+    }
+
+    free(ti);
+#undef FORMAT
+#undef FORMAT_SHORT
+    return 0;
+}
+
+#define CHECK_LOGGER() do {if (logger == NULL) {logger = &(log_factory.sys_log);} \
+                           if (logger == NULL || logger->id == NULL) return -1;} while(0)
+int writeDebugLog_(Logger *logger, char *file, int line, char *func, char *format, ...) {
+    CHECK_LOGGER();
+
+    va_list ap;
+    va_start(ap, format);
+    return writeLog_(logger, log_debug, file, line, func, format, ap);
+}
+
+int writeInfoLog_(Logger *logger, char *file, int line, char *func, char *format, ...) {
+    CHECK_LOGGER();
+
+    va_list ap;
+    va_start(ap, format);
+    return writeLog_(logger, log_info, file, line, func, format, ap);
+}
+
+int writeWarningLog_(Logger *logger, char *file, int line, char *func, char *format, ...) {
+    CHECK_LOGGER();
+
+    va_list ap;
+    va_start(ap, format);
+    return writeLog_(logger, log_warning, file, line, func, format, ap);
+}
+
+int writeErrorLog_(Logger *logger, char *file, int line, char *func, char *format, ...) {
+    CHECK_LOGGER();
+
+    va_list ap;
+    va_start(ap, format);
+    return writeLog_(logger, log_error, file, line, func, format, ap);
+}
+
+int writeSendErrorLog_(Logger *logger, char *file, int line, char *func, char *format, ...) {
+    CHECK_LOGGER();
+
+    va_list ap;
+    va_start(ap, format);
+    int re = writeLog_(logger, log_send_error, file, line, func, format, ap);
+    if (logger->process_send_error) {
+        jmp_buf *buf = logger->buf;
+        initLogger(logger, NULL, 0);  // 清零
+        longjmp(*buf, 1);
+    }
+    return re;
+}
+
+int writeFatalErrorLog_(Logger *logger, char *file, int line, char *func, int exit_code, char *format, ...) {
+    CHECK_LOGGER();
+
+    va_list ap;
+    va_start(ap, format);
+    int re = writeLog_(logger, log_fatal_error, file, line, func, format, ap);
+    if (logger->process_fatal_error)
+        exit(exit_code);
+    return re;
+}

+ 20 - 1
src/tool/time.c

@@ -19,4 +19,23 @@ void safeSleep(double ms) {
         now = clock();
         d_time = now - start;
     } while (d_time < ms_t);
-}
+}
+
+/*
+ * 函数名: getTime
+ * 目标: 格式化输出时间
+ * 注意: 该函数不可以使用log模块
+ */
+char *getTime(time_t *t) {
+    time_t tmp;
+    if (t == NULL)
+        t = &tmp;
+
+    struct tm *lt;
+    time (t);  // 获取时间戳
+    lt = localtime (t);  // 转为时间结构
+    char time_str[100];
+    snprintf(time_str, 100, "%d/%d/%d %d:%d:%d",
+             lt->tm_year+1900, lt->tm_mon, lt->tm_mday, lt->tm_hour, lt->tm_min, lt->tm_sec);
+    return strCopy(time_str);
+}

+ 1 - 0
test/lib/CMakeLists.txt

@@ -6,5 +6,6 @@ foreach(src IN LISTS src_list)
     add_library(${file_name} SHARED ${src})
     set_target_properties(${file_name}
                           PROPERTIES OUTPUT_NAME "testlib_${file_name}")
+    define_FILENAME(${file_name})
     unset(file_name)
 endforeach()

+ 1 - 0
test/src/CMakeLists.txt

@@ -8,6 +8,7 @@ foreach(src IN LISTS src_list)
     set_target_properties(${file_name}
                           PROPERTIES OUTPUT_NAME "test_${file_name}")
     target_compile_definitions(${file_name} PRIVATE IN_CTEST)
+    define_FILENAME(${file_name})
     unset(file_name)
 endforeach()
 

+ 1 - 1
test/src/env_init.c

@@ -2,7 +2,7 @@
 #include "aFun.h"
 
 int main() {
-    aFunCoreInit();
+    aFunCoreInit("env_init-", log_pc_all, false, false, NULL, log_debug);
 
     af_Environment *env = makeEnvironment(grt_always);
     enableEnvironment(env);

+ 10 - 3
test/src/run_code.c

@@ -369,9 +369,16 @@ bool infixFunc(char *id, af_Object *obj) {
     return true;
 }
 
-int main() {
-    aFunInit();
-    printf("Hello World\n");
+int main(int argc, char **argv) {
+    char *base_name_ = getExedir(*argv, 1);
+
+    char *log = strJoin(base_name_, SEP aFunLogDir SEP, false, false);
+    bool re = aFunInit(log, log_pc_all, NULL, log_debug);
+    free(log);
+    free(base_name_);
+
+    if (!re)
+        exit(EXIT_FAILURE);
 
     af_Environment *env = creatAFunEnviroment(0, NULL);
     if(!pushLiteralRegex("data.*", "func", true, env)) {