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;
}