OpenGauss线程管理-系统日志线程-syslogger

OpenGauss线程管理-系统日志线程-syslogger

系统日志(syslogger)出现在Postgres 8.0中。它通过重定向到管道来捕获postmaster、后端和其他子进程的所有stderr输出,并将其写入一组日志文件。可以在postgresql.conf中配置日志文件的大小和期限限制。如果达到或超过这些限制,则关闭当前日志文件并创建一个新的日志文件(循环)。日志文件存储在子目录中(可在postgresql.conf中配置),使用用户可选的命名方案。

术语解释

  • stderr:标准输出(设备)文件,对应终端的屏幕。进程将从标准输入文件中得到输入数据,将正常输出数据输出到标准输出文件,而将错误信息送到标准错误文件中.
  • 管道:管道是内核维护的一个缓存, 它提供两个 fd, 从一个fd写入数据, 从另一个fd读出数据. 所以它是半双工的. 写管道操作默认是阻塞的, 也就是把数据全部写入缓存后write函数才返回, 如果缓存满了, 就一直保持阻塞, 直到缓存里的数据被读出, 数据被全部写入.

syslogger线程

作用:捕捉并写所有错误日志
路径:openGauss-server/src/gausskernel/process/postmaster/syslogger.cpp

在主线程中的使用

在GaussDbThreadMain中通过thread_role进入syslogger线程
在这里插入图片描述

代码理解

SysLoggerMain

syslogger进程argc/argv参数的主入口点仅在EXEC_BACKEND情况下有效。

NON_EXEC_STATIC void SysLoggerMain(int fd)
{
#ifndef WIN32
    char logbuffer[READ_BUF_SIZE];
    int bytes_in_logbuffer = 0;
#endif
    LogControlData* logctl = NULL;
    char* currentLogDir = NULL;
    char* currentLogFilename = NULL;
    int currentLogRotationAge;
    pg_time_t now;

    DISABLE_MEMORY_PROTECT();

    IsUnderPostmaster = true; /* 我们现在是postmaster子流程 */

    t_thrd.proc_cxt.MyProcPid = gs_thread_self(); /* 重置t_thrd.proc_cxt.MyProcPid */

    t_thrd.proc_cxt.MyStartTime = time(NULL); /* 设置我们的开始时间,以防我们打电话给elog */
    now = t_thrd.proc_cxt.MyStartTime;

    t_thrd.proc_cxt.MyProgName = "syslogger";

    t_thrd.myLogicTid = noProcLogicTid + SYSLOGGER_LID;

    syslogger_setfd(fd);

    t_thrd.role = SYSLOGGER;

    init_ps_display("logger process", "", "", "");

    /*
     * Syslogger自己的stderr不能是syslogPipe,因此如果我们不关闭它,
     * 请将其设置回文本模式。(在SubPostmasterMain中设置为二进制).
     */
#ifdef WIN32
    else
        _setmode(_fileno(stderr), _O_TEXT);
#endif

        /*
         * 同时关闭管道写入端的副本。这是为了确保我们能够正确检测管道EOF。
         * (但请注意,在重启案例中,postmaster已经这样做了。)
         */
#ifndef WIN32
    t_thrd.postmaster_cxt.syslogPipe[1] = -1;
#else
    syslogPipe[1] = 0;
#endif

    InitializeLatchSupport(); /* 闩锁等待所需 */

    /* 初始化专用闩锁以供信号处理程序使用 */
    InitLatch(&t_thrd.logger.sysLoggerLatch);

    /*
     * 正确接受或忽略邮政局长可能发送给我们的信号
     *
     * 注意:我们忽略所有终止信号,而只在所有上游进程都结束时退出,
     * 以确保我们不会错过任何后端崩溃的喘息。。。
     */
    /*
     * 重置一些邮局主管接受但不在此接受的信号
     */
    (void)gspqsignal(SIGHUP, sigHupHandler); /* 设置标志以读取配置文件 */
    (void)gspqsignal(SIGINT, SIG_IGN);
    (void)gspqsignal(SIGTERM, SIG_IGN);
    (void)gspqsignal(SIGQUIT, SIG_IGN);
    (void)gspqsignal(SIGALRM, SIG_IGN);
    (void)gspqsignal(SIGPIPE, SIG_IGN);
    (void)gspqsignal(SIGUSR1, sigUsr1Handler); /* 请求日志轮换 */
    (void)gspqsignal(SIGUSR2, SIG_IGN);

    /*
     * 重置一些邮局主管接受但不在此接受的信号
     */
    (void)gspqsignal(SIGCHLD, SIG_DFL);
    (void)gspqsignal(SIGTTIN, SIG_DFL);
    (void)gspqsignal(SIGTTOU, SIG_DFL);
    (void)gspqsignal(SIGCONT, SIG_DFL);
    (void)gspqsignal(SIGWINCH, SIG_DFL);

    gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL);
    (void)gs_signal_unblock_sigusr2();

#ifdef WIN32
    /* 启动单独的数据传输线程*/
    InitializeCriticalSection(&sysloggerSection);
    EnterCriticalSection(&sysloggerSection);

    threadHandle = (HANDLE)_beginthreadex(NULL, 0, pipeThread, NULL, 0, NULL);
    if (threadHandle == 0)
        ereport(FATAL, (errmsg("could not create syslogger data transfer thread: %m")));
#endif /* WIN32 */

    PLogCtlInit();
    /* 创建慢速查询目录 */
    if (g_instance.attr.attr_common.query_log_directory == NULL) {
        init_instr_log_directory(true, SLOWQUERY_LOG_TAG);
    } else {
        (void)pg_mkdir_p(g_instance.attr.attr_common.query_log_directory, S_IRWXU);
    }

    /* 创建asp目录 */
    if (g_instance.attr.attr_common.asp_log_directory == NULL) {
        init_instr_log_directory(true, ASP_LOG_TAG);
    } else {
        (void)pg_mkdir_p(g_instance.attr.attr_common.asp_log_directory, S_IRWXU);
    }

    /* 初始化其他日志 */
    /*
     * 记住活动日志文件的名称。我们从引用时间重新计算,因为在EXEC_BACKEND情况下,
     * 只传递pg_time_t比传递整个文件路径要便宜得多。
     */
    t_thrd.logger.last_file_name = logfile_getname(t_thrd.logger.first_syslogger_file_time,
        NULL,
        u_sess->attr.attr_common.Log_directory,
        u_sess->attr.attr_common.Log_filename);
    t_thrd.logger.syslogFile = logfile_open(t_thrd.logger.last_file_name, "a", false);

    foreach_logctl(logctl) {
        /*
         * 如果发生异常,当postmaster线程重新启动syslogger线程时,可能会发生fd泄漏。
         * 可能包括切换案例。
         * 如果线程重新启动,则必须删除原始内存上下文,而无需释放内存
         */
        logctl->now_file_name =
            logfile_getname(t_thrd.logger.first_syslogger_file_time, NULL, logctl->log_dir, logctl->filename_pattern);

        if (logctl->now_file_fd != NULL) {
            (void)fclose(logctl->now_file_fd);
            logctl->now_file_fd = NULL;
        }
        logctl->now_file_fd = logfile_open(logctl->now_file_name, "a", false);
        LogCtlWriteFileHeader(logctl);
    }

    /* 记住活动日志文件参数 */
    currentLogDir = pstrdup(u_sess->attr.attr_common.Log_directory);
    currentLogFilename = pstrdup(u_sess->attr.attr_common.Log_filename);
    currentLogRotationAge = u_sess->attr.attr_common.Log_RotationAge;
    /* 设置下一个计划的轮换时间 */
    set_next_rotation_time();

    /* 主辅助回路 */
    for (;;) {
        bool time_based_rotation = false;
        int size_rotation_for = 0;
        long cur_timeout;
        int cur_flags;

#ifndef WIN32
        int rc;
#endif

        /* 清除所有已挂起的唤醒*/
        ResetLatch(&t_thrd.logger.sysLoggerLatch);

        /*
         * 处理最近收到的任何请求或信号。
         */
        if (t_thrd.logger.got_SIGHUP) {
            t_thrd.logger.got_SIGHUP = false;
            ProcessConfigFile(PGC_SIGHUP);

            /*
             * 检查postgresql.conf中的日志目录或文件名模式是否更改。
             * 如果更改,请强制旋转以确保将日志文件写入正确的位置。
             */
            if (strcmp(u_sess->attr.attr_common.Log_directory, currentLogDir) != 0) {
                pfree(currentLogDir);
                currentLogDir = pstrdup(u_sess->attr.attr_common.Log_directory);
                t_thrd.logger.rotation_requested = true;
                /* 不影响pLogCtl的Log_directory */
                /*
                 * 此外,如果不存在,则创建新目录;忽略错误
                 */
                mkdir(u_sess->attr.attr_common.Log_directory, S_IRWXU);
            }
            if (strcmp(u_sess->attr.attr_common.Log_filename, currentLogFilename) != 0) {
                pfree(currentLogFilename);
                currentLogFilename = pstrdup(u_sess->attr.attr_common.Log_filename);
                t_thrd.logger.rotation_requested = true;
                foreach_logctl(logctl) {
                    if (logctl->filename_pattern != NULL) {
                        pfree_ext(logctl->filename_pattern);
                    }
                    /* 重新计算日志文件模式 */
                    logctl->filename_pattern = LogCtlGetFilenamePattern(logctl->file_suffix);
                    logctl->rotation_requested = true;
                }
            }

            /*
             * 如果旋转时间参数发生更改,请重置下一个旋转时间,但不要立即强制旋转。
             */
            if (currentLogRotationAge != u_sess->attr.attr_common.Log_RotationAge) {
                currentLogRotationAge = u_sess->attr.attr_common.Log_RotationAge;
                set_next_rotation_time();
            }

            /*
             * 如果旋转禁用失败,请在SIGHUP后重新启用旋转尝试,并立即强制执行一次。
             */
            if (t_thrd.logger.rotation_disabled) {
                t_thrd.logger.rotation_disabled = false;
                t_thrd.logger.rotation_requested = true;
                foreach_logctl(logctl) {
                    /* 与错误日志保持相同的步骤,并旋转此日志文件*/
                    logctl->rotation_requested = true;
                }
            }
        }

        if (u_sess->attr.attr_common.Log_RotationAge > 0 && !t_thrd.logger.rotation_disabled) {
            /* 如果时间允许,请执行日志文件轮换 */
            now = (pg_time_t)time(NULL);
            if (now >= t_thrd.logger.next_rotation_time) {
                t_thrd.logger.rotation_requested = time_based_rotation = true;
                foreach_logctl(logctl) {
                    /* 拥有相同的轮换年龄 */
                    logctl->rotation_requested = true;
                }
            }
        }

        if (!t_thrd.logger.rotation_requested && u_sess->attr.attr_common.Log_RotationSize > 0 &&
            !t_thrd.logger.rotation_disabled) {
            
            /* 如果文件太大,请执行旋转 */
            if (ftell(t_thrd.logger.syslogFile) >= u_sess->attr.attr_common.Log_RotationSize * 1024L) {
                t_thrd.logger.rotation_requested = true;
                size_rotation_for |= LOG_DESTINATION_STDERR;
            }
            if (t_thrd.logger.csvlogFile != NULL &&
                ftell(t_thrd.logger.csvlogFile) >= u_sess->attr.attr_common.Log_RotationSize * 1024L) {
                t_thrd.logger.rotation_requested = true;
                size_rotation_for |= LOG_DESTINATION_CSVLOG;
            }
            if (t_thrd.logger.querylogFile != NULL &&
                ftell(t_thrd.logger.querylogFile) >= u_sess->attr.attr_common.Log_RotationSize * 1024L) {
                t_thrd.logger.rotation_requested = true;
                size_rotation_for |= LOG_DESTINATION_QUERYLOG;
            }
            if (t_thrd.logger.asplogFile != NULL &&
                ftell(t_thrd.logger.asplogFile) >= u_sess->attr.attr_common.Log_RotationSize * 1024L) {
                t_thrd.logger.rotation_requested = true;
                size_rotation_for |= LOG_DESTINATION_ASPLOG;
            }
        }

        /*
         * 在错误日志类型之前检查其他日志类型的文件旋转。logfile_rotate()将更改
         * t_thrd.logger。通过调用set_next_rotation_time()获得next_rotation.time值。
         */
        foreach_logctl(logctl) {
            LogCtlRotateLogFileIfNeeded(logctl, time_based_rotation);
        }
        if (t_thrd.logger.rotation_requested) {
            /*
             * 当两个值都为零时强制旋转。这意味着请求是由pg_rotate_logfile发送的。
             */
            if (!time_based_rotation && size_rotation_for == 0)
                size_rotation_for = LOG_DESTINATION_STDERR | LOG_DESTINATION_CSVLOG |
                    LOG_DESTINATION_QUERYLOG | LOG_DESTINATION_ASPLOG;
            asp_logfile_rotate(time_based_rotation, size_rotation_for);
            slow_query_logfile_rotate(time_based_rotation, size_rotation_for);
            
            /* 只有最后一个才能重新计算next_rotation_time */
            logfile_rotate(time_based_rotation, size_rotation_for);
        }

        /*
         * 计算下一次基于时间的旋转之前的时间,这样我们就不会睡得比这个时间长。
         * 我们假设上面得到的“现在”值仍然足够接近。注意,在调用logfile_rotate()之前,
         * 我们无法进行此计算,因为它将提前t_thrd.logger.next_rotation_time。
         *
         * 还要注意,在计算超时时,我们需要注意溢出:Log_RotationAge、t_thrd.logger
         * 的设置较大。未来,next_rotation_time可能大于INT_MAX毫秒。在这种情况下,
         * 我们将等待不超过INT_MAX毫秒,然后重试。
         */
        if (u_sess->attr.attr_common.Log_RotationAge > 0 && !t_thrd.logger.rotation_disabled) {
            pg_time_t delay;

            delay = t_thrd.logger.next_rotation_time - now;
            if (delay > 0) {
                if (delay > INT_MAX / 1000)
                    delay = INT_MAX / 1000;
                cur_timeout = delay * 1000L; /* msec */
            } else
                cur_timeout = 0;
            cur_flags = WL_TIMEOUT;
        } else {
            cur_timeout = -1L;
            cur_flags = 0;
        }

        /*
         * 睡觉,直到有事情可做
         */
#ifndef WIN32
        rc = WaitLatchOrSocket(&t_thrd.logger.sysLoggerLatch,
            WL_LATCH_SET | WL_SOCKET_READABLE | cur_flags,
            t_thrd.postmaster_cxt.syslogPipe[0],
            cur_timeout);

        if (rc & WL_SOCKET_READABLE) {
            int bytesRead;

            bytesRead = read(t_thrd.postmaster_cxt.syslogPipe[0],
                logbuffer + bytes_in_logbuffer,
                sizeof(logbuffer) - bytes_in_logbuffer);
            if (bytesRead < 0) {
                if (errno != EINTR)
                    ereport(LOG, (errcode_for_socket_access(), errmsg("could not read from logger pipe: %m")));
            } else if (bytesRead > 0) {
                bytes_in_logbuffer += bytesRead;
                process_pipe_input(logbuffer, &bytes_in_logbuffer);
                continue;
            } else {
                /*
                 * ELSE分支永远不会在多线程模式下执行
                 *
                 * select()表示read ready时读取的零字节数意味着管道上的EOF:也就是说,
                 * 不再有任何进程打开管道写端。因此,邮局主管和所有后端都已关闭,
                 * 我们已经完成了。
                 */
                t_thrd.logger.pipe_eof_seen = true;

                /* 如果还有数据,现在就强制输出 */
                flush_pipe_input(logbuffer, &bytes_in_logbuffer);
            }
        }
#else  /* WIN32 */

        /*
         * 在Windows上,我们将其留给一个单独的线程来传输数据并检测管道EOF。
         * 主线程刚刚唤醒以处理SIGHUP和旋转条件。
         *
         * 服务器代码通常不是线程安全的,所以只要我们不睡觉,
         * 就可以通过进入关键部分来确保一次只有一个线程处于活动状态。
         */
        LeaveCriticalSection(&sysloggerSection);

        (void)WaitLatch(&t_thrd.logger.sysLoggerLatch, WL_LATCH_SET | cur_flags, cur_timeout);

        EnterCriticalSection(&sysloggerSection);
#endif /* WIN32 */

        if (t_thrd.logger.pipe_eof_seen) {
            /*
             * 在真正的stderr上看到这条消息很烦人,
             * 所以我们将其设置为DEBUG1以在正常使用中禁止显示。
             */
            ereport(DEBUG1, (errmsg("logger shutting down")));

            /*
             * 这里是syslogger的正常退出。请注意,在退出之前,我们故意不关闭
             * t_thrd.logger.syslogFile;这是为了允许在procexit内生成elog消息。
             * 常规exit()将负责刷新和关闭stdio通道。
             */
            proc_exit(0);
        }
    }
}
SysLogger_Start

Postmaster子例程以启动系统记录器子进程。

ThreadId SysLogger_Start(void)
{
    ThreadId sysloggerPid;
    char* filename = NULL;

    if (!g_instance.attr.attr_common.Logging_collector)
        return 0;

        /*
         * 如果是第一次通过,请创建将接收stderr输出的管道。
         *
         * 如果系统记录器崩溃并需要重新启动,我们将继续使用相同的管道
         * (实际上必须这样做,因为现有的后端将写入该管道)。
         *
         * 这意味着邮局主管必须继续保持管道的读取端打开,
         * 以便我们可以将其传递给转世的系统记录器。这有点笨拙,但我们别无选择。
         */
#ifndef WIN32
    if (t_thrd.postmaster_cxt.syslogPipe[0] < 0) {
        if (pipe(t_thrd.postmaster_cxt.syslogPipe) < 0)
            ereport(FATAL, (errcode_for_socket_access(), (errmsg("could not create pipe for syslog: %m"))));
    }
#else
    if (!t_thrd.postmaster_cxt.syslogPipe[0]) {
        SECURITY_ATTRIBUTES sa;

        errno_t rc = memset_s(&sa, sizeof(SECURITY_ATTRIBUTES), 0, sizeof(SECURITY_ATTRIBUTES));
        securec_check_c(rc, "\0", "\0");

        sa.nLength = sizeof(SECURITY_ATTRIBUTES);
        sa.bInheritHandle = TRUE;

        if (!CreatePipe(&t_thrd.postmaster_cxt.syslogPipe[0], &t_thrd.postmaster_cxt.syslogPipe[1], &sa, 32768))
            ereport(FATAL, (errcode_for_file_access(), (errmsg("could not create pipe for syslog: %m"))));
    }
#endif

    /*
     * 如果不存在,则创建日志目录;忽略错误
     */
    (void)pg_mkdir_p(u_sess->attr.attr_common.Log_directory, S_IRWXU);
    /* 创建日志目录*/
    LogCtlCreateLogParentDirectory();
    /* 从邮局主管设置全局名称 */
    LogCtlSetGlobalNames();
    /* 从邮局主管设置时区 */
    LogCtlSetTimeZone();

    /*
     * 初始日志文件在postmaster中创建,以验证Log_directory是否可写。
     * 我们保存了引用时间,以便syslogger子进程可以重新计算此文件名。
     *
     * 在syslogger重启期间重新执行此操作可能有点奇怪,但我们必须这样做,
     * 因为postmaster在前一个fork之后关闭了t_thrd.logger.syslogFile
     * (并记住旧文件无论如何都不会正确)。注意,我们总是在这里附加,
     * 我们不会覆盖任何现有文件。这与正常规则一致,因为根据定义,这不是基于时间的旋转。
     */
    t_thrd.logger.first_syslogger_file_time = time(NULL);
    filename = logfile_getname(t_thrd.logger.first_syslogger_file_time,
        NULL,
        u_sess->attr.attr_common.Log_directory,
        u_sess->attr.attr_common.Log_filename);

    pfree(filename);

    sysloggerPid = initialize_util_thread(SYSLOGGER);

    /* success, in postmaster */
    if (sysloggerPid != 0) {
        /* 现在我们重定向stderr,如果还没有这样做的话 */
        if (!t_thrd.postmaster_cxt.redirection_done) {
#ifndef WIN32
            fflush(stdout);
            if (dup2(t_thrd.postmaster_cxt.syslogPipe[1], fileno(stdout)) < 0)
                ereport(FATAL, (errcode_for_file_access(), errmsg("could not redirect stdout: %m")));
            fflush(stderr);
            if (dup2(t_thrd.postmaster_cxt.syslogPipe[1], fileno(stderr)) < 0)
                ereport(FATAL, (errcode_for_file_access(), errmsg("could not redirect stderr: %m")));
            /* 现在我们完成了管道的写入端。 */
            close(t_thrd.postmaster_cxt.syslogPipe[1]);
            t_thrd.postmaster_cxt.syslogPipe[1] = -1;
#else
            int fd;

            /*
             * 以二进制模式打开管道,并确保stderr在被复制到后是二进制的,
             * 以避免干扰管道分块协议。
             */
            fflush(stderr);
            fd = _open_osfhandle((intptr_t)t_thrd.postmaster_cxt.syslogPipe[1], _O_APPEND | _O_BINARY);
            if (dup2(fd, _fileno(stderr)) < 0)
                ereport(FATAL, (errcode_for_file_access(), errmsg("could not redirect stderr: %m")));
            close(fd);
            _setmode(_fileno(stderr), _O_BINARY);

            /*
             * 现在我们完成了管道的写入端。不能调用CloseHandle(),
             * 因为前面的close()会关闭基础句柄。
             */
            t_thrd.postmaster_cxt.syslogPipe[1] = 0;
#endif
            t_thrd.postmaster_cxt.redirection_done = true;
        }

        t_thrd.logger.syslogFile = NULL;
        return sysloggerPid;
    }

    /* 我们永远不应该到这里 */
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值