openGauss-server/src/gausskernel/process/postmaster/syslogger.cpp

2605 lines
110 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* -------------------------------------------------------------------------
*
* syslogger.cpp
*
* The system logger (syslogger) appeared in Postgres 8.0. It catches all
* stderr output from the postmaster, backends, and other subprocesses
* by redirecting to a pipe, and writes it to a set of logfiles.
* It's possible to have size and age limits for the logfile configured
* in postgresql.conf. If these limits are reached or passed, the
* current logfile is closed and a new one is created (rotated).
* The logfiles are stored in a subdirectory (configurable in
* postgresql.conf), using a user-selectable naming scheme.
*
* Author: Andreas Pflug <pgadmin@pse-consulting.de>
*
* Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd.
* Copyright (c) 2004-2012, PostgreSQL Global Development Group
*
*
* IDENTIFICATION
* src/gausskernel/process/postmaster/syslogger.cpp
*
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "knl/knl_variable.h"
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/time.h>
#include "lib/stringinfo.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "nodes/pg_list.h"
#include "pgtime.h"
#include "postmaster/fork_process.h"
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pg_shmem.h"
#include "utils/guc.h"
#include "utils/ps_status.h"
#include "utils/timestamp.h"
#include "gssignal/gs_signal.h"
// 定义日志轮换的大小阈值当日志文件大小达到或超过20MB时将触发日志轮换
#define PROFILE_LOG_ROTATE_SIZE ((long)20 * 1024 * 1024L)
/*
* We really want line-buffered mode for logfile output, but Windows does
* not have it, and interprets _IOLBF as _IOFBF (bozos). So use _IONBF
* instead on Windows.
*/
// 根据操作系统类型定义了用于日志文件输出的缓冲模式。
// 在Windows上使用_IONBF缓冲模式其他操作系统使用_IOLBF
#ifdef WIN32
#define LBF_MODE _IONBF
#else
#define LBF_MODE _IOLBF
#endif
/*
* We read() into a temp buffer twice as big as a chunk, so that any fragment
* left after processing can be moved down to the front and we'll still have
* room to read a full chunk.
*/
// 定义用于读取日志消息的缓冲区大小是LOGPIPE_CHUNK_SIZE[系统管道的最大原子写入大小512]的两倍
#define READ_BUF_SIZE (2 * LOGPIPE_CHUNK_SIZE)
// 定义用于保存错误消息的缓冲区大小为1024字节
#define ERROR_BUF_SIZE 1024
// 定义用于存储时区、主机名和节点名的字符数组
static char logCtlTimeZone[TZ_STRLEN_MAX + 1] = {0};
static char logCtlHostName[LOG_MAX_NODENAME_LEN] = {0};
static char logCtlNodeName[LOG_MAX_NODENAME_LEN] = {0};
/* put all LogControlData of all log types into this array */
// 定义数组用于存储不同日志类型的LogControlData结构
static LogControlData* allLogCtl[LOG_TYPE_MAXVALID + 1] = {
NULL, /* LOG_TYPE_ELOG */
NULL, /* LOG_TYPE_PLOG */
NULL, /* LOG_TYPE_PLAN_LOG */
NULL, /* LOG_TYPE_ASP_LOG */
NULL /* LOG_TYPE_MAXVALID */
};
/* exclude error log in this FOR loop */
// 用于循环处理日志控制数据的迭代器,排除了错误日志类型
#define foreach_logctl(_logctl) for (int i = LOG_TYPE_ELOG + 1; ((_logctl) = allLogCtl[i]) != NULL; ++i)
/*
* Buffers for saving partial messages from different backends.
*
* Keep NBUFFER_LISTS lists of these, with the entry for a given source pid
* being in the list numbered (pid % NBUFFER_LISTS), so as to cut down on
* the number of entries we have to examine for any one incoming message.
* There must never be more than one entry for the same source pid.
*
* An inactive buffer is not removed from its list, just held for re-use.
* An inactive buffer has pid == 0 and undefined contents of data.
*/
// 用于保存来自不同后端进程的部分消息
// 有效地管理来自多个后端进程的部分消息,减少处理每个传入消息时需要检查的条目数量,以便在适当的时候将它们组装成完整的消息并进行记录
typedef struct {
ThreadId pid; /* PID of source process */ // 源进程的进程ID对于相同的源进程PID不允许存在多个缓冲区条目
StringInfoData data; /* accumulated data, as a StringInfo */ // 用于存储累积的数据
} save_buffer;
/* These must be exported for EXEC_BACKEND case ... annoying */
#ifndef WIN32
#else
HANDLE syslogPipe[2] = {0, 0}; // 用于在Windows下处理系统日志的管道
#endif
// 用于处理多线程操作
#ifdef WIN32
static HANDLE threadHandle = 0;
static CRITICAL_SECTION sysloggerSection;
#endif
/*
* Flags set by interrupt handlers for later service in the main loop.
*/
/* Local subroutines */
// 用于设置syslogger的文件描述符以指定将日志写入的目标文件或管道
static void syslogger_setfd(int fd);
// 用于处理从管道中读取的日志输入,将其存储在缓冲区中
static void process_pipe_input(char* logbuffer, int* bytes_in_logbuffer);
// 用于刷新管道输入缓冲区中的内容
static void flush_pipe_input(char* logbuffer, int* bytes_in_logbuffer);
// 用于打开CSV格式的日志文件以准备记录日志消息
static void open_csvlogfile(void);
#ifdef ENABLE_UT
#define static
#endif
// 用于打开指定的日志文件,接受文件名、模式和是否允许错误作为参数
static FILE* logfile_open(const char* filename, const char* mode, bool allow_errors);
#if defined(ENABLE_UT) && defined(static)
#undef static
#endif
#ifdef WIN32
// 仅在Windows下定义用于处理管道输入的线程
static unsigned int __stdcall pipeThread(void* arg);
#endif
// 用于执行日志文件的轮换操作,可以基于时间或大小触发
static void logfile_rotate(bool time_based_rotation, int size_rotation_for);
// 用于生成日志文件的名称,接受时间戳、后缀、日志目录和文件名作为参数
static char* logfile_getname(pg_time_t timestamp, const char* suffix, const char* logdir, const char* filename);
// 用于设置下一次日志文件轮换的时间
static void set_next_rotation_time(void);
// 用于处理SIGHUP和SIGUSR1信号
static void sigHupHandler(SIGNAL_ARGS);
static void sigUsr1Handler(SIGNAL_ARGS);
// 用于管理日志控制数据的一组函数
// 设置全局名称、设置时区、获取日志目录、
// 创建日志目录、写入文件头、检查是否需要轮换日志文件、
// 刷新缓冲区、获取文件名模式以及处理输入消息
static void LogCtlSetGlobalNames(void);
static void LogCtlSetTimeZone(void);
static char* LogCtlGetLogDirectory(const char* logid, bool include_nodename);
static void LogCtlCreateLogParentDirectory(void);
static void LogCtlWriteFileHeader(LogControlData* logctl);
static void LogCtlRotateLogFileIfNeeded(LogControlData*, bool);
static void LogCtlFlushBuf(LogControlData* logctl);
static char* LogCtlGetFilenamePattern(const char* post_suffix);
static void LogCtlProcessInput(LogControlData* logctl, const char* msg, int len);
// 用于初始化日志控制数据以及特定日志类型的轮换操作的一组函数
static void PLogCtlInit(void);
static void slow_query_logfile_rotate(bool time_based_rotation, int size_rotation_for);
static void asp_logfile_rotate(bool time_based_rotation, int size_rotation_for);
/*
* Main entry point for syslogger process
* argc/argv parameters are valid only in EXEC_BACKEND case.
*/
/*
* 功能syslogger进程的主要入口点函数
*
* 参数:
* fd在 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; /* we are a postmaster subprocess now */
// 设置为当前线程的PID
t_thrd.proc_cxt.MyProcPid = gs_thread_self(); /* reset t_thrd.proc_cxt.MyProcPid */
// 记录syslogger启动时间
t_thrd.proc_cxt.MyStartTime = time(NULL); /* set our start time in case we call elog */
now = t_thrd.proc_cxt.MyStartTime;
// 标识syslogger的进程名
t_thrd.proc_cxt.MyProgName = "syslogger";
// 标识syslogger的逻辑线程ID
t_thrd.myLogicTid = noProcLogicTid + SYSLOGGER_LID;
// 设置syslogger的文件描述符指定将日志写入的目标文件或管道
syslogger_setfd(fd);
t_thrd.role = SYSLOGGER; // 表示syslogger的角色
// 初始化进程状态显示,设置进程的名称
init_ps_display("logger process", "", "", "");
/*
* Syslogger's own stderr can't be the syslogPipe, so set it back to text
* mode if we didn't just close it. (It was set to binary in
* SubPostmasterMain).
*/
#ifdef WIN32
else
// 在Windows环境下将标准错误的模式设置为文本模式
_setmode(_fileno(stderr), _O_TEXT);
#endif
/*
* Also close our copy of the write end of the pipe. This is needed to
* ensure we can detect pipe EOF correctly. (But note that in the restart
* case, the postmaster already did this.)
*/
#ifndef WIN32
// 关闭写入端的管道描述符用于确保可以正确检测管道的EOF
t_thrd.postmaster_cxt.syslogPipe[1] = -1;
#else
syslogPipe[1] = 0;
#endif
// 初始化Latch支持用于等待事件
InitializeLatchSupport(); /* needed for latch waits */
/* Initialize private latch for use by signal handlers */
// 初始化sysLoggerLatch用于等待信号处理程序
InitLatch(&t_thrd.logger.sysLoggerLatch);
/*
* Properly accept or ignore signals the postmaster might send us
*
* Note: we ignore all termination signals, and instead exit only when all
* upstream processes are gone, to ensure we don't miss any dying gasps of
* broken backends...
*/
/*
* Reset some signals that are accepted by postmaster but not here
*/
// 设置信号处理程序
// 其中 SIGHUP 用于重新加载配置文件SIGUSR1 用于请求日志轮换
// 其余信号忽略
(void)gspqsignal(SIGHUP, sigHupHandler); /* set flag to read config file */
(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); /* request log rotation */
(void)gspqsignal(SIGUSR2, SIG_IGN);
/*
* Reset some signals that are accepted by postmaster but not here
*/
// 设置对子进程状态改变等信号的处理方式,恢复默认行为
(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);
// 设置信号掩码确保SIGUSR2信号不被阻塞
gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL);
(void)gs_signal_unblock_sigusr2();
#ifdef WIN32
/* Fire up separate data transfer thread */
// 初始化一个临界区,用于确保多个线程不会同时访问关键部分的代码,以避免竞态条件
InitializeCriticalSection(&sysloggerSection);
// 进入临界区,标志着当前线程要执行关键部分的代码
EnterCriticalSection(&sysloggerSection);
// 在 Windows 下创建一个新的线程,用于数据传输
// pipeThread 是线程的入口点函数
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(); // 初始化日志控制数据,用于管理不同类型的日志
/* create slow query directory */
// 检查配置文件中是否指定了慢查询日志目录
if (g_instance.attr.attr_common.query_log_directory == NULL) {
// 如果没有指定,则调用 init_instr_log_directory 创建默认的目录
init_instr_log_directory(true, SLOWQUERY_LOG_TAG);
} else {
// 如果指定了目录,则使用 pg_mkdir_p 函数创建目录
// 设置其权限为 S_IRWXU用户可读、可写、可执行
(void)pg_mkdir_p(g_instance.attr.attr_common.query_log_directory, S_IRWXU);
}
/* create asp directory */
// 检查配置文件中是否指定了ASP 日志目录
if (g_instance.attr.attr_common.asp_log_directory == NULL) {
// 如果没有指定,则调用 init_instr_log_directory 创建默认的目录
init_instr_log_directory(true, ASP_LOG_TAG);
} else {
// 如果指定了目录,则使用 pg_mkdir_p 函数创建目录
// 设置其权限为 S_IRWXU用户可读、可写、可执行
(void)pg_mkdir_p(g_instance.attr.attr_common.asp_log_directory, S_IRWXU);
}
/* init the other logs */
/*
* Remember active logfile's name. We recompute this from the reference
* time because passing down just the pg_time_t is a lot cheaper than
* passing a whole file path in the EXEC_BACKEND case.
*/
// 记录当前活跃的日志文件的名称
// 节省在 EXEC_BACKEND 情况下传递整个文件路径的开销
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);
// 打开当前活跃的日志文件,使用 "a" 模式打开文件以追加写入
// false 表示不允许在发生错误时报告错误
t_thrd.logger.syslogFile = logfile_open(t_thrd.logger.last_file_name, "a", false);
// 循环处理其他类型的日志文件
foreach_logctl(logctl) {
/*
* fd leaking maybe happens when syslogger thread is
* restarted repeated by postmaster thread if exception occurs.
* maybe include switch over case.
* if the thread restarted, the original memory context must be deleted and need not free the memory
*/
// 计算当前日志文件的名称
logctl->now_file_name =
logfile_getname(t_thrd.logger.first_syslogger_file_time, NULL, logctl->log_dir, logctl->filename_pattern);
// 如果当前日志文件的文件描述符不为 NULL则关闭文件
if (logctl->now_file_fd != NULL) {
(void)fclose(logctl->now_file_fd);
logctl->now_file_fd = NULL;
}
// 打开当前日志文件,使用 "a" 模式追加写入
// false 表示不允许在发生错误时报告错误
logctl->now_file_fd = logfile_open(logctl->now_file_name, "a", false);
// 写入文件头部信息
LogCtlWriteFileHeader(logctl);
}
/* remember active logfile parameters */
// 记录当前活跃的日志文件的目录、文件名和轮换年龄
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 planned rotation time */
set_next_rotation_time(); // 设置下一次计划轮换的时间
/* main worker loop */
for (;;) {
bool time_based_rotation = false;
int size_rotation_for = 0;
long cur_timeout;
int cur_flags;
#ifndef WIN32
int rc;
#endif
/* Clear any already-pending wakeups */
ResetLatch(&t_thrd.logger.sysLoggerLatch); // 清除任何已经发生的等待事件
/*
* Process any requests or signals received recently.
*/
if (t_thrd.logger.got_SIGHUP) { // 如果接收到 SIGHUP 信号
t_thrd.logger.got_SIGHUP = false; // 清除标志 got_SIGHUP
ProcessConfigFile(PGC_SIGHUP); // 处理配置文件的变化
/*
* Check if the log directory or filename pattern changed in
* postgresql.conf. If so, force rotation to make sure we're
* writing the logfiles in the right place.
*/
// 如果配置文件中的日志目录 Log_directory 发生了变化
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; // 表示需要执行日志轮换
/* not affect pLogCtl's Log_directory */
/*
* Also, create new directory if not present; ignore errors
*/
// 如果新的目录不存在,则创建一个新目录,权限设置为 S_IRWXU用户可读、可写、可执行
mkdir(u_sess->attr.attr_common.Log_directory, S_IRWXU);
}
// 如果配置文件中的日志文件名 Log_filename 发生了变化
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) { // 遍历每个日志类型
// 如果其文件名模式 filename_pattern 不为空
if (logctl->filename_pattern != NULL) {
pfree_ext(logctl->filename_pattern); // 释放之前的模式字符串
}
/* recompute log file pattern */
// 重新计算新的模式
logctl->filename_pattern = LogCtlGetFilenamePattern(logctl->file_suffix);
logctl->rotation_requested = true;
}
}
/*
* If rotation time parameter changed, reset next rotation time,
* but don't immediately force a rotation.
*/
// 如果配置文件中的日志轮换时间参数 Log_RotationAge 发生了变化
if (currentLogRotationAge != u_sess->attr.attr_common.Log_RotationAge) {
currentLogRotationAge = u_sess->attr.attr_common.Log_RotationAge;
// 重新计算下一次计划轮换的时间,但不会立即强制执行日志轮换
set_next_rotation_time();
}
/*
* If we had a rotation-disabling failure, re-enable rotation
* attempts after SIGHUP, and force one immediately.
*/
// 如果之前因为某种原因禁用了日志轮换
if (t_thrd.logger.rotation_disabled) {
t_thrd.logger.rotation_disabled = false;
t_thrd.logger.rotation_requested = true; // 重新启用轮换
// 对每个日志类型标记为需要轮换
foreach_logctl(logctl) {
/* keep the same step with error log, and rotate this log file */
logctl->rotation_requested = true;
}
}
}
// 处理时间和大小导致的日志轮换
// 如果启用了基于时间的日志轮换且未被禁用,会检查是否应该进行时间导致的轮换
if (u_sess->attr.attr_common.Log_RotationAge > 0 && !t_thrd.logger.rotation_disabled) {
/* Do a logfile rotation if it's time */
now = (pg_time_t)time(NULL); // 获取当前时间
// 比较是否超过了下一次计划轮换的时间 next_rotation_time
if (now >= t_thrd.logger.next_rotation_time) {
// 如果超过了,则标记需要执行时间轮换
t_thrd.logger.rotation_requested = time_based_rotation = true;
// 对每个日志类型标记需要轮换
foreach_logctl(logctl) {
/* share the same rotation age */
logctl->rotation_requested = true;
}
}
}
// 如果没有任何轮换请求,且启用了基于大小的日志轮换且未被禁用,会检查是否应该进行大小导致的轮换
if (!t_thrd.logger.rotation_requested && u_sess->attr.attr_common.Log_RotationSize > 0 &&
!t_thrd.logger.rotation_disabled) {
/* Do a rotation if file is too big */
// ftell函数用于获取当前打开文件的位置指针也就是当前文件的大小以字节为单位
// u_sess->attr.attr_common.Log_RotationSize 是配置文件中设置的日志大小轮换阈值,以千字节为单位
// 检查当前日志文件的大小是否超过了配置文件中设置的轮换大小
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;
}
// 类似的检查针对其他日志类型CSV 日志、查询日志和 ASP 日志
// 检查当前日志文件的大小是否超过了配置文件中设置的轮换大小
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;
}
}
/*
* check the other log types' file rotation before error log type.
* logfile_rotate() will change t_thrd.logger.next_rotation_time value by calling
* set_next_rotation_time().
*/
// 遍历不同类型的日志
foreach_logctl(logctl) {
// 检查特定类型日志文件是否需要进行轮换操作
// logctl 是当前正在检查的日志类型的控制结构time_based_rotation 是一个标志,指示是否基于时间执行轮换
LogCtlRotateLogFileIfNeeded(logctl, time_based_rotation);
}
if (t_thrd.logger.rotation_requested) { // 检查是否需要执行日志轮换
/*
* Force rotation when both values are zero. It means the request
* was sent by 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日志的轮换操作
asp_logfile_rotate(time_based_rotation, size_rotation_for);
// 执行慢查询日志的轮换操作
slow_query_logfile_rotate(time_based_rotation, size_rotation_for);
/* only last one can recalculate next_rotation_time */
// 执行错误日志的轮换操作
logfile_rotate(time_based_rotation, size_rotation_for);
}
/*
* Calculate time till next time-based rotation, so that we don't
* sleep longer than that. We assume the value of "now" obtained
* above is still close enough. Note we can't make this calculation
* until after calling logfile_rotate(), since it will advance
* t_thrd.logger.next_rotation_time.
*
* Also note that we need to beware of overflow in calculation of the
* timeout: with large settings of Log_RotationAge, t_thrd.logger.next_rotation_time
* could be more than INT_MAX msec in the future. In that case we'll
* wait no more than INT_MAX msec, and try again.
*/
// 检查是否启用了时间轮换且未禁用轮换
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) // 如果剩余时间超过了 INT_MAX 毫秒
delay = INT_MAX / 1000; // 避免整数溢出
cur_timeout = delay * 1000L; /* msec */ // 将剩余时间转换为毫秒,以便在休眠时使用
} else // 表示下一次轮换已到期
cur_timeout = 0; // 以便立即唤醒进程
cur_flags = WL_TIMEOUT; // 根据等待超时时间设置标志,表示只等待超时事件
} else {
cur_timeout = -1L;
cur_flags = 0;
}
/*
* Sleep until there's something to do
*/
#ifndef WIN32
// 使用 WaitLatchOrSocket 函数等待多个事件
// 包括 WL_LATCH_SET等待事件、WL_SOCKET_READABLE套接字可读事件和 cur_flags超时事件
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;
// 从管道读取数据并调用 process_pipe_input 处理数据
bytesRead = read(t_thrd.postmaster_cxt.syslogPipe[0],
logbuffer + bytes_in_logbuffer,
sizeof(logbuffer) - bytes_in_logbuffer);
if (bytesRead < 0) { // 如果读取的字节数小于零
if (errno != EINTR) // 如果 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 branch is never executed forever in multi-thread mode
*
* Zero bytes read when select() is saying read-ready means
* EOF on the pipe: that is, there are no longer any processes
* with the pipe write end open. Therefore, the postmaster
* and all backends are shut down, and we are done.
*/
t_thrd.logger.pipe_eof_seen = true; // 表示日志子进程已退出
/* if there's any data left then force it out now */
// 强制刷新管道中的剩余数据
flush_pipe_input(logbuffer, &bytes_in_logbuffer);
}
}
#else /* WIN32 */
/*
* On Windows we leave it to a separate thread to transfer data and
* detect pipe EOF. The main thread just wakes up to handle SIGHUP
* and rotation conditions.
*
* Server code isn't generally thread-safe, so we ensure that only one
* of the threads is active at a time by entering the critical section
* whenever we're not sleeping.
*/
// 离开临界区,表示不再占用共享资源
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) { // 检查是否看到了管道已关闭的标志
/*
* seeing this message on the real stderr is annoying - so we make
* it DEBUG1 to suppress in normal use.
*/
// 如果是,记录一条 DEBUG1 级别的日志,表示日志子进程正在关闭
ereport(DEBUG1, (errmsg("logger shutting down")));
/*
* Normal exit from the syslogger is here. Note that we
* deliberately do not close t_thrd.logger.syslogFile before exiting; this is to
* allow for the possibility of elog messages being generated
* inside proc_exit. Regular exit() will take care of flushing
* and closing stdio channels.
*/
proc_exit(0);
}
}
}
/*
* Postmaster subroutine to start a syslogger subprocess.
*/
/*
* 功能:启动系统日志管理进程
*
* 参数:无
*
* 返回值ThreadId类型表示新创建的子进程的线程 ID
*/
ThreadId SysLogger_Start(void)
{
ThreadId sysloggerPid; // 用于存储新创建的子进程的线程 ID
char* filename = NULL; // 用于存储日志文件名
// 检查配置参数 Logging_collector 是否为假
if (!g_instance.attr.attr_common.Logging_collector)
// 如果为假,则直接返回 0表示不启动日志收集进程
return 0;
/*
* If first time through, create the pipe which will receive stderr
* output.
*
* If the syslogger crashes and needs to be restarted, we continue to use
* the same pipe (indeed must do so, since extant backends will be writing
* into that pipe).
*
* This means the postmaster must continue to hold the read end of the
* pipe open, so we can pass it down to the reincarnated syslogger. This
* is a bit klugy but we have little choice.
*/
#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
/*
* Create log directory if not present; ignore errors
*/
// 如果目录不存在,创建日志目录;如果目录已经存在或者创建失败,会忽略错误
(void)pg_mkdir_p(u_sess->attr.attr_common.Log_directory, S_IRWXU);
/* create log directory */
LogCtlCreateLogParentDirectory(); // 创建日志的父目录
/* set global names from postmaster */
LogCtlSetGlobalNames(); // 设置全局的日志文件名和文件后缀
/* set time zone from postmaster */
LogCtlSetTimeZone(); // 设置日志的时区
/*
* The initial logfile is created right in the postmaster, to verify that
* the Log_directory is writable. We save the reference time so that
* the syslogger child process can recompute this file name.
*
* It might look a bit strange to re-do this during a syslogger restart,
* but we must do so since the postmaster closed t_thrd.logger.syslogFile after the
* previous fork (and remembering that old file wouldn't be right anyway).
* Note we always append here, we won't overwrite any existing file. This
* is consistent with the normal rules, because by definition this is not
* a time-based rotation.
*/
// 创建初始的日志文件,以验证日志目录是否可写。
// 函数记录了创建这个文件的时间戳,以供后续的系统日志管理进程使用
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); // 启动syslogger线程
/* success, in postmaster */
if (sysloggerPid != 0) {
// 如果成功启动了系统日志管理进程
/* now we redirect stderr, if not done already */
if (!t_thrd.postmaster_cxt.redirection_done) {
#ifndef WIN32
// 在非 Windows 平台上重定向 stderr 输出
fflush(stdout); // 刷新标准输出缓冲区,以确保任何待输出的内容都被写入
// 使用 dup2 函数将管道的写入端复制到标准输出的文件描述符
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); // 刷新标准错误输出缓冲区,以确保任何待输出的错误信息都被写入
// 使用 dup2 函数将管道的写入端复制到标准错误输出的文件描述符
if (dup2(t_thrd.postmaster_cxt.syslogPipe[1], fileno(stderr)) < 0)
// 如果复制失败,会触发致命错误
ereport(FATAL, (errcode_for_file_access(), errmsg("could not redirect stderr: %m")));
/* Now we are done with the write end of the pipe. */
// 关闭管道的写入端,因为标准输出和标准错误输出已经重定向到了该管道
close(t_thrd.postmaster_cxt.syslogPipe[1]);
t_thrd.postmaster_cxt.syslogPipe[1] = -1;
#else
// 在 Windows 平台上重定向 stderr 输出
int fd;
/*
* open the pipe in binary mode and make sure stderr is binary
* after it's been dup'ed into, to avoid disturbing the pipe
* chunking protocol.
*/
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);
/*
* Now we are done with the write end of the pipe.
* CloseHandle() must not be called because the preceding
* close() closes the underlying handle.
*/
t_thrd.postmaster_cxt.syslogPipe[1] = 0;
#endif
t_thrd.postmaster_cxt.redirection_done = true;
}
// 表示该进程已经开始管理日志
t_thrd.logger.syslogFile = NULL;
return sysloggerPid; // 返回新创建的子进程的线程 ID
}
/* we should never reach here */
return 0;
}
/* close the t_thrd.logger.syslogFile */
/*
* 功能:关闭 syslogger 进程中的日志文件
*
* 参数:无
*
* 返回值:无
*/
void SysLoggerClose(void)
{
if (t_thrd.logger.syslogFile) { // 检查当前线程是否已经打开了一个日志文件
// 如果已经打开了日志文件,则关闭该文件。将文件缓冲区中的数据写入磁盘,并释放相关的资源
fclose(t_thrd.logger.syslogFile);
t_thrd.logger.syslogFile = NULL; // 表示日志文件已经关闭
}
}
#ifdef EXEC_BACKEND
/*
* syslogger_ereprint() -
*
* print error in syslogger thread
*/
/*
* 功能:将日志信息写入指定的文件
*
* 参数:
* file用于指定要写入的文件
* buffer用于指定要写入的内容
*
* 返回值:无
*/
static void syslogger_erewrite(FILE* file, const char* buffer)
{
int tryTimes = 0; // 用于记录重试的次数
for (;;) { // 无限循环,用于多次尝试写入内容
/* clear errno before calling IO write */
errno = 0; // 清除之前可能存在的错误信息
int buffer_len = (int)strlen(buffer); // 计算要写入的内容 buffer 的长度
int rc = fwrite(buffer, 1, buffer_len, file); // 将 buffer 中的内容写入到指定的文件
// 检查写入操作是否发生了错误或未完全写入
if ((errno != 0) || (rc != buffer_len)) {
tryTimes++; // 增加 tryTimes 计数器,表示尝试次数加一
if (tryTimes >= 3) // 如果尝试次数达到 3 次,表示已经尝试了多次写入但仍然失败
break; // 退出循环
/*
* if no disk space, we will retry,
* and we can not report a log, because there is not space to write.
*/
if (errno == ENOSPC) { // 如果错误代码 errno 表示磁盘空间不足
break; // 退出循环,因为在没有足够磁盘空间的情况下,无法继续写入日志信息
}
}
break;
}
}
/*
* syslogger_setfd() -
*
* Extract data from the arglist for exec'ed syslogger process
*/
/*
* 功能:从参数中提取文件描述符,并为其创建文件流
*
* 参数:
* fd表示文件描述符
*
* 返回值:无
*/
static void syslogger_setfd(int fd)
{
if (fd != -1) { // 检查是否存在有效的文件描述符
// 将文件描述符 fd 打开为文件流,并将文件流指针赋值
t_thrd.logger.syslogFile = fdopen(fd, "a");
if (t_thrd.logger.syslogFile == NULL) { // 检查文件流是否创建成功
// 如果为空表示创建失败,生成错误报告
ereport(ERROR, (errcode_for_file_access(), errmsg("syslogger could not open file %d: %m,exit\n", fd)));
proc_exit(1);
}
// 设置文件流缓冲方式为不使用缓冲_IONBF参数 0 表示不设置缓冲大小
setvbuf(t_thrd.logger.syslogFile, NULL, LBF_MODE, 0);
}
}
#endif /* EXEC_BACKEND */
/*
* CheckPipeProtoHeader() -
*
* check whether p has the features of a LogPipeProtoHeader
*/
/*
* 功能:检查给定的数据结构 LogPipeProtoHeader 是否符合一定的特征
*
* 参数:
* PLogPipeProtoHeader 结构体
*
* 返回值bool类型表示是否符合特定特征
* 如果符合特征,返回 true表示通过检查
* 否则返回 false表示未通过检查
*/
static bool CheckPipeProtoHeader(const LogPipeProtoHeader p)
{
/* 检查结构体 p 是否符合:
* 结构体的前两个字节是否都为 NULL 字符('\0'),表示字符串结束符
* 结构体的 len 字段是否大于 0 且不超过 LOGPIPE_MAX_PAYLOADlen 表示数据负载的大小
* 结构体的 pid 字段是否不等于 0pid 表示进程的 ID
* 结构体的 is_last 字段是否为指定的字符
* 结构体的 logtype 字段是否在指定范围内logtype 表示日志类型
* 结构体的 magic 字段是否等于指定的魔术数字 PROTO_HEADER_MAGICNUM
*/
if (p.nuls[0] == '\0' && p.nuls[1] == '\0' && p.len > 0 &&
p.len <= LOGPIPE_MAX_PAYLOAD && p.pid != 0 &&
(p.is_last == 't' || p.is_last == 'f' || p.is_last == 'T' || p.is_last == 'F') &&
p.logtype >= LOG_TYPE_ELOG && p.logtype < LOG_TYPE_MAXVALID &&
p.magic == PROTO_HEADER_MAGICNUM)
return true; // 符合上述特征,返回 true表示通过检查
return false; // 返回 false表示未通过检查
}
/* --------------------------------
* pipe protocol handling
* --------------------------------
*/
/*
* Process data received through the syslogger pipe.
*
* This routine interprets the log pipe protocol which sends log messages as
* (hopefully atomic) chunks - such chunks are detected and reassembled here.
*
* The protocol has a header that starts with two nul bytes, then has a 16 bit
* length, the pid of the sending process, and a flag to indicate if it is
* the last chunk in a message. Incomplete chunks are saved until we read some
* more, and non-final chunks are accumulated until we get the final chunk.
*
* All of this is to avoid 2 problems:
* . partial messages being written to logfiles (messes rotation), and
* . messages from different backends being interleaved (messages garbled).
*
* Any non-protocol messages are written out directly. These should only come
* from non-PostgreSQL sources, however (e.g. third party libraries writing to
* stderr).
*
* logbuffer is the data input buffer, and *bytes_in_logbuffer is the number
* of bytes present. On exit, any not-yet-eaten data is left-justified in
* logbuffer, and *bytes_in_logbuffer is updated.
*/
/*
* 功能:处理通过 syslogger 管道接收到的数据,并根据日志管道协议将日志消息组装起来
*
* 参数:
* logbuffer数据输入缓冲区
* bytes_in_logbuffer缓冲区中的字节数
*
* 返回值bool类型表示是否符合特定特征
* 如果符合特征,返回 true表示通过检查
* 否则返回 false表示未通过检查
*/
static void process_pipe_input(char* logbuffer, int* bytes_in_logbuffer)
{
char* cursor = logbuffer;
int count = *bytes_in_logbuffer; // 表示数据输入缓冲区中的字节数
int dest = LOG_DESTINATION_STDERR; // 表示日志的目标,初始值为标准错误输出
/* While we have enough for a header, process data... */
// 进入一个循环,只要数据输入缓冲区中的字节数足够解析一个日志管道协议的头部,就继续处理数据
while (count >= (int)sizeof(LogPipeProtoHeader)) {
LogPipeProtoHeader p; // 用于存储日志管道协议的头部
int chunklen;
/* Do we have a valid header? */
errno_t rcs = memcpy_s(&p, sizeof(LogPipeProtoHeader), cursor, sizeof(LogPipeProtoHeader));
securec_check(rcs, "\0", "\0");
// 检查 p 是否符合日志管道协议的头部特征
if (CheckPipeProtoHeader(p) == true) {
// 如果是,则进入处理协议消息的分支
List* buffer_list = NULL;
ListCell* cell = NULL;
save_buffer* existing_slot = NULL;
save_buffer* free_slot = NULL;
StringInfo str;
// 计算协议消息的总长度,包括头部大小和数据负载大小
chunklen = LOGPIPE_HEADER_SIZE + p.len;
/* Fall out of loop if we don't have the whole chunk yet */
// 如果数据输入缓冲区中的字节数不足以完整接收当前消息,则跳出循环等待更多数据
if (count < chunklen)
break;
// 如果消息的日志类型为错误日志、查询计划日志或慢查询日志,则进入处理这些日志的分支
if (p.logtype == LOG_TYPE_ELOG || p.logtype == LOG_TYPE_PLAN_LOG || p.logtype == LOG_TYPE_ASP_LOG) {
if (p.logtype == LOG_TYPE_PLAN_LOG)
dest = LOG_DESTINATION_QUERYLOG; // 表示将日志写入查询日志
else if (p.logtype == LOG_TYPE_ASP_LOG)
dest = LOG_DESTINATION_ASPLOG; // 表示将日志写入慢查询日志
else
// 根据 p.is_last 的值来确定 dest 的值
// 'T' 或 'F',则将 dest 设置为 LOG_DESTINATION_CSVLOG表示将日志写入 CSV 日志文件;
// 否则,将 dest 设置为 LOG_DESTINATION_STDERR表示将日志写入标准错误输出
dest = (p.is_last == 'T' || p.is_last == 'F') ? LOG_DESTINATION_CSVLOG : LOG_DESTINATION_STDERR;
/* Locate any existing buffer for this source pid */
// 查找是否存在已保存该来源进程的日志缓冲区
buffer_list = t_thrd.logger.buffer_lists[p.pid % NBUFFER_LISTS];
// 访问相应 pid 对应的缓冲区列表,并遍历列表中的每个缓冲区
foreach (cell, buffer_list) {
save_buffer* buf = (save_buffer*)lfirst(cell);
// 查找与当前消息来源进程 pid 匹配的缓冲区
if (buf->pid == p.pid) {
// 如果找到匹配的缓冲区,就会保存该缓冲区的引用
existing_slot = buf;
break;
}
// 如果没有找到匹配的缓冲区,并且还没有分配空闲的缓冲区
if (buf->pid == 0 && free_slot == NULL)
free_slot = buf; // 创建一个新的缓冲区
}
// 如果当前消息不是最后一个消息的分片,将消息的数据添加到相应的缓冲区
if (p.is_last == 'f' || p.is_last == 'F') {
/*
* Save a complete non-final chunk in a per-pid buffer
*/
// 如果消息块不是最后一个
// 如果已经存在与来源进程 pid 匹配的缓冲区
if (existing_slot != NULL) {
/* Add chunk to data from preceding chunks */
// 将当前消息块添加到之前已保存的消息数据中
str = &(existing_slot->data);
appendBinaryStringInfo(str, cursor + LOGPIPE_HEADER_SIZE, p.len);
} else {
/* First chunk of message, save in a new buffer */
// 如果没有已存在的缓冲区,则创建一个新的缓冲区
if (free_slot == NULL) {
/*
* Need a free slot, but there isn't one in the list,
* so create a new one and extend the list with it.
*/
// 将当前消息块保存到该缓冲区中,然后将该缓冲区添加到相应的缓冲区列表中
free_slot = (save_buffer*)palloc(sizeof(save_buffer));
buffer_list = lappend(buffer_list, free_slot);
t_thrd.logger.buffer_lists[p.pid % NBUFFER_LISTS] = buffer_list;
}
free_slot->pid = p.pid;
str = &(free_slot->data);
initStringInfo(str);
appendBinaryStringInfo(str, cursor + LOGPIPE_HEADER_SIZE, p.len);
}
} else {
// 如果消息块是最后一个
/*
* Final chunk --- add it to anything saved for that pid, and
* either way write the whole thing out.
*/
// 如果已经存在与来源进程 pid 匹配的缓冲区
if (existing_slot != NULL) {
// 将当前消息块添加到之前已保存的消息数据中
str = &(existing_slot->data);
appendBinaryStringInfo(str, cursor + LOGPIPE_HEADER_SIZE, p.len);
// 将整个消息写入日志文件
write_syslogger_file(str->data, str->len, dest);
/* Mark the buffer unused, and reclaim string storage */
existing_slot->pid = 0;
pfree(str->data);
} else {
// 如果没有已存在的缓冲区,说明整个消息就是一个块
/* The whole message was one chunk, evidently. */
// 直接将消息块写入日志文件
write_syslogger_file(cursor + LOGPIPE_HEADER_SIZE, p.len, dest);
}
}
} else if (p.logtype < LOG_TYPE_MAXVALID) { // 如果消息的日志类型小于 LOG_TYPE_MAXVALID
Assert(LOG_TYPE_MAXVALID <= LOG_TYPE_UPLIMIT);
// 处理消息块数据,根据消息的类型将消息传递到相应的日志文件
LogCtlProcessInput(allLogCtl[(int)p.logtype], cursor + LOGPIPE_HEADER_SIZE, p.len);
} else {
// 如果消息的日志类型超出了 LOG_TYPE_MAXVALID 的上限
// 触发断言失败,表示发现了不支持的日志类型
Assert(0);
}
/* Finished processing this chunk */
// 更新游标位置和剩余字节数
cursor += chunklen;
count -= chunklen;
} else {
// 如果消息头不符合日志管道协议的格式
/* Process non-protocol data */
/*
* Look for the start of a protocol header. If found, dump data
* up to there and repeat the loop. Otherwise, dump it all and
* fall out of the loop. (Note: we want to dump it all if at all
* possible, so as to avoid dividing non-protocol messages across
* logfiles. We expect that in many scenarios, a non-protocol
* message will arrive all in one read(), and we want to respect
* the read() boundary if possible.)
*/
// 遍历查找非协议数据的消息头起始位置
for (chunklen = 1; chunklen < count; chunklen++) {
// 如果找到了消息头起始位置,则截取到消息头前的数据并继续循环处理
// 否则将所有非协议数据写入标准错误日志文件
// 确保非协议消息在写入日志文件时不会被截断,尽量保持完整性
if (cursor[chunklen] == '\0')
break;
}
/* fall back on the stderr log as the destination */
write_syslogger_file(cursor, chunklen, LOG_DESTINATION_STDERR);
cursor += chunklen;
count -= chunklen;
}
}
/* We don't have a full chunk, so left-align what remains in the buffer */
// 如果剩余数据不足以组成一个完整的消息块
if (count > 0 && cursor != logbuffer) {
// 将剩余数据左移以覆盖前面已处理的数据,以便下一次处理
errno_t rc = memmove_s(logbuffer, count, cursor, count);
securec_check_c(rc, "\0", "\0");
}
*bytes_in_logbuffer = count; // 更新剩余字节数
}
/*
* Force out any buffered data
*
* This is currently used only at syslogger shutdown, but could perhaps be
* useful at other times, so it is careful to leave things in a clean state.
*/
/*
* 功能:强制将任何缓冲的数据输出到日志文件
*
* 参数:
* logbuffer用于存储待输出数据的缓冲区
* bytes_in_logbuffer缓冲区中的字节数
*
* 返回值bool类型表示是否符合特定特征
* 如果符合特征,返回 true表示通过检查
* 否则返回 false表示未通过检查
*/
static void flush_pipe_input(char* logbuffer, int* bytes_in_logbuffer)
{
int i;
/* Dump any incomplete protocol messages */
// 遍历不同来源进程的缓冲区列表,处理不完整的协议消息
for (i = 0; i < NBUFFER_LISTS; i++) {
// 获取当前来源进程的缓冲区列表,以及该列表的元素迭代器
List* list = t_thrd.logger.buffer_lists[i];
ListCell* cell = NULL;
foreach (cell, list) {
save_buffer* buf = (save_buffer*)lfirst(cell);
if (buf->pid != 0) { // 检查缓冲区是否包含数据
// 如果缓冲区中包含数据,将数据写入标准错误日志文件
// 获取缓冲区中的数据字符串 str并使用其中的数据和长度来写入日志
StringInfo str = &(buf->data);
write_syslogger_file(str->data, str->len, LOG_DESTINATION_STDERR);
/* Mark the buffer unused, and reclaim string storage */
buf->pid = 0; // 标记缓冲区未使用
pfree(str->data); // 释放缓冲区中的字符串存储空间,以便重用
}
}
}
/*
* Force out any remaining pipe data as-is; we don't bother trying to
* remove any protocol headers that may exist in it.
*/
if (*bytes_in_logbuffer > 0) // 检查缓冲区中是否有剩余的数据
// 如果有则将其写入标准错误日志文件
write_syslogger_file(logbuffer, *bytes_in_logbuffer, LOG_DESTINATION_STDERR);
*bytes_in_logbuffer = 0; // 表示缓冲区现在为空
}
/* --------------------------------
* logfile routines
* --------------------------------
*/
/*
* Write text to the currently open logfile
*
* This is exported so that elog.c can call it when am_syslogger is true.
* This allows the syslogger process to record elog messages of its own,
* even though its stderr does not point at the syslog pipe.
*/
/*
* 功能:将文本写入当前打开的日志文件
*
* 参数:
* buffer包含要写入日志文件的文本数据的缓冲区
* count要写入的字节数
* destination指示写入的日志文件的目标可以是以下选项之一
* LOG_DESTINATION_STDERR标准错误日志文件。
* LOG_DESTINATION_CSVLOGCSV 日志文件。
* LOG_DESTINATION_QUERYLOG查询日志文件。
* LOG_DESTINATION_ASPLOG活动会话分析日志文件
*
* 返回值:无
*/
void write_syslogger_file(char* buffer, int count, int destination)
{
int rc;
FILE* logfile = NULL; // 用于表示当前要写入的日志文件
bool doOpen = false; // 用于表示是否需要打开日志文件
// 检查目标是否为 CSV 日志,并且当前 CSV 日志文件未打开
if (destination == LOG_DESTINATION_CSVLOG && t_thrd.logger.csvlogFile == NULL)
open_csvlogfile(); // 如果是,则打开 CSV 日志文件
// 根据目标确定要写入的日志文件,并根据需要打开该文件
if (destination == LOG_DESTINATION_QUERYLOG)
logfile = (FILE *)SQMOpenLogFile(&doOpen);
else if (destination == LOG_DESTINATION_ASPLOG)
logfile = (FILE *)ASPOpenLogFile(&doOpen);
else
logfile = (destination == LOG_DESTINATION_CSVLOG) ? t_thrd.logger.csvlogFile : t_thrd.logger.syslogFile;
errno = 0;
rc = fwrite(buffer, 1, count, logfile); // 将指定数量的字节从 buffer 写入 logfile
/* can't use ereport here because of possible recursion */
if (rc != count) { // 检查写入的字节数是否与指定的字节数相等,以确定写入是否成功
/*
* if no disk space, we will retry,
* and we can not report a log, because there is not space to write.
*/
// 如果写入失败的原因是磁盘空间不足
if (errno == ENOSPC) {
return; // 函数返回,不进行错误处理
}
// 构造错误消息字符串 errorbuf包含错误信息和描述信息
char errorbuf[ERROR_BUF_SIZE] = {'\0'};
rc = sprintf_s(errorbuf, ERROR_BUF_SIZE, "ERROR: could not write to log file: %s\n", gs_strerror(errno));
securec_check_ss_c(rc, "\0", "\0");
// 将错误消息写入日志文件以记录错误信息
syslogger_erewrite(logfile, errorbuf);
}
}
#ifdef WIN32
/*
* Worker thread to transfer data from the pipe to the current logfile.
*
* We need this because on Windows, WaitforMultipleObjects does not work on
* unnamed pipes: it always reports "signaled", so the blocking ReadFile won't
* allow for SIGHUP; and select is for sockets only.
*/
/*
* 功能:强制刷新管道中的缓冲数据
* 不断从管道中读取数据,处理数据并写入日志文件,直到检测到管道已关闭
* 如果当前日志文件大小达到了日志轮换的大小限制,会触发主线程执行日志轮换操作
*
* 参数:
* char* logbuffer指向包含数据的缓冲区的指针
* int* bytes_in_logbuffer指向表示缓冲区中字节数的整数指针
*
* 返回值:无
*/
static unsigned int __stdcall pipeThread(void* arg)
{
char logbuffer[READ_BUF_SIZE]; // 定义一个缓冲区用于存储从管道中读取的数据
int bytes_in_logbuffer = 0; // 记录缓冲区中当前的字节数
for (;;) { // 循环遍历多个缓冲区列表 不断从管道中读取数据
DWORD bytesRead; // 用于记录从管道中实际读取的字节数
BOOL result = false; // 用于记录ReadFile函数的执行结果
// 从管道中读取数据到logbuffer中
result = ReadFile(t_thrd.postmaster_cxt.syslogPipe[0],
logbuffer + bytes_in_logbuffer, // // 将数据追加到缓冲区的末尾
sizeof(logbuffer) - bytes_in_logbuffer, // // 剩余缓冲区大小
&bytesRead,
0);
/*
* Enter critical section before doing anything that might touch
* global state shared by the main thread. Anything that uses
* palloc()/pfree() in particular are not safe outside the critical
* section.
*/
// 进入临界区,保护后续操作不被其他线程干扰
EnterCriticalSection(&sysloggerSection);
if (!result) {
DWORD error = GetLastError();
// 处理从管道中读取数据时出现的错误
if (error == ERROR_HANDLE_EOF || error == ERROR_BROKEN_PIPE)
break; // 管道已关闭,退出循环
_dosmaperr(error);
ereport(LOG, (errcode_for_file_access(), errmsg("could not read from logger pipe: %m")));
} else if (bytesRead > 0) {
bytes_in_logbuffer += bytesRead; // 更新缓冲区中的字节数
process_pipe_input(logbuffer, &bytes_in_logbuffer); // 处理从管道中读取的数据
}
/*
* If we've filled the current logfile, nudge the main thread to do a
* log rotation.
*/
// 如果当前日志文件达到了日志轮换的大小限制,则通知主线程执行日志轮换
if (u_sess->attr.attr_common.Log_RotationSize > 0) {
if (ftell(t_thrd.logger.syslogFile) >= u_sess->attr.attr_common.Log_RotationSize * 1024L ||
(t_thrd.logger.querylogFile != NULL &&
ftell(t_thrd.logger.querylogFile) >= u_sess->attr.attr_common.Log_RotationSize * 1024L) ||
(t_thrd.logger.asplogFile != NULL &&
ftell(t_thrd.logger.asplogFile) >= u_sess->attr.attr_common.Log_RotationSize * 1024L) ||
(t_thrd.logger.csvlogFile != NULL &&
ftell(t_thrd.logger.csvlogFile) >= u_sess->attr.attr_common.Log_RotationSize * 1024L))
SetLatch(&t_thrd.logger.sysLoggerLatch); // 设置信号量,通知主线程执行日志轮换
}
LeaveCriticalSection(&sysloggerSection); // 退出临界区
}
/* We exit the above loop only upon detecting pipe EOF */
t_thrd.logger.pipe_eof_seen = true; // 表示管道已关闭
/* if there's any data left then force it out now */
flush_pipe_input(logbuffer, &bytes_in_logbuffer);
/* set the latch to waken the main thread, which will quit */
SetLatch(&t_thrd.logger.sysLoggerLatch); // 设置信号量,通知主线程退出
LeaveCriticalSection(&sysloggerSection); // 退出线程
_endthread();
return 0;
}
#endif /* WIN32 */
/*
* Open the csv log file - we do this opportunistically, because
* we don't know if CSV logging will be wanted.
*
* This is only used the first time we open the csv log in a given syslogger
* process, not during rotations. As with opening the main log file, we
* always append in this situation.
*/
/*
* 功能打开CSV格式的日志文件但是只有在第一次打开该文件时才执行不会在日志轮换时执行
* 构建CSV日志文件的文件名然后以追加模式打开文件
*
* 参数:无
*
* 返回值:无
*/
static void open_csvlogfile(void)
{
char* filename = NULL; // 用于存储CSV日志文件的文件名
// 构建CSV日志文件的文件名
filename = logfile_getname(
time(NULL), ".csv", u_sess->attr.attr_common.Log_directory, u_sess->attr.attr_common.Log_filename);
// 打开CSV日志文件以追加模式打开
t_thrd.logger.csvlogFile = logfile_open(filename, "a", false);
// 如果上一次打开的CSV日志文件名不为空则释放其内存
if (t_thrd.logger.last_csv_file_name != NULL) /* probably shouldn't happen */
pfree(t_thrd.logger.last_csv_file_name);
// 记录当前打开的CSV日志文件名
t_thrd.logger.last_csv_file_name = filename;
}
#ifdef ENABLE_UT
#define static
#endif
/*
* Open a new logfile with proper permissions and buffering options.
*
* If allow_errors is true, we just log any open failure and return NULL
* (with errno still correct for the fopen failure).
* Otherwise, errors are treated as fatal.
*/
/*
* 功能:以指定的模式打开一个新的日志文件,并设置适当的权限和缓冲选项
*
* 参数:
* filename要打开的文件的路径和名称
* mode打开文件的模式如 "r"、"w"、"a" 等
* allow_errors是否允许出现错误如果为 true则只记录错误不会引发致命错误
*
* 返回值:
* 如果成功打开文件,则返回指向文件的指针(文件句柄),如果失败,则返回 NULL
*/
static FILE* logfile_open(const char* filename, const char* mode, bool allow_errors)
{
FILE* fh = NULL; // 文件句柄
struct stat checkdir; // 用于检查目录是否存在的结构体
bool dirIsExist = false; // 标志目录是否存在
/*
* Note we do not let Log_file_mode disable IWUSR, since we certainly want
* to be able to write the files ourselves.
*/
// 如果 filename 为 NULL报错否则继续执行
if (filename == NULL) {
ereport(ERROR, (errcode(ERRCODE_UNEXPECTED_NULL_VALUE), errmsg("group_name can not be NULL ")));
}
// 检查文件或目录是否存在
if (stat(filename, &checkdir) == 0) {
dirIsExist = true;
}
// 打开文件,使用指定的模式
fh = fopen(filename, mode);
if (fh != NULL) {
// 如果成功打开,设置文件的缓冲模式
setvbuf(fh, NULL, LBF_MODE, 0);
#ifdef WIN32
/* use CRLF line endings on Windows */
_setmode(_fileno(fh), _O_TEXT);
#endif
} else {
int save_errno = errno;
if (allow_errors) {
// 如果允许出现错误,记录打开文件失败的错误信息
ereport(LOG, (errcode_for_file_access(), errmsg("could not open log file \"%s\": %m", filename)));
errno = save_errno;
} else {
/*
* If open file failed, we can not write any log to file, make the
* system crash is safe.
*/
// 如果打开文件失败且不允许错误,则将系统置为崩溃状态,报错
ereport(WARNING, (errcode_for_file_access(), errmsg("failed to open log file \"%s\": %m", filename)));
// 禁止立即中断
t_thrd.int_cxt.ImmediateInterruptOK = false;
fflush(stdout); // 刷新标准输出流,确保任何在输出缓冲区中的数据都被写入到标准输出
fflush(stderr); // 刷新标准错误流,确保任何在错误缓冲区中的数据都被写入到标准错误
abort(); // 用于表示发生了严重错误,需要立即停止程序的执行
}
}
/*
* Note we do not let Log_file_mode disable IWUSR, since we certainly want
* to be able to write the files ourselves.
*/
// 如果目录不存在,则设置文件的权限
if (!dirIsExist) {
// 检查 chmod 调用是否成功,将日志文件的权限设置为允许文件的拥有者写入,并确保不允许执行文件
// chmod函数是一个系统调用用于更改文件的权限模式
// 参数:要修改权限的文件的路径 filename 和新的权限模式
if (chmod(filename,
(((mode_t)u_sess->attr.attr_common.Log_file_mode | S_IWUSR) & (~S_IXUSR) & (~S_IXOTH) & (~S_IXGRP))) < 0) {
int save_errno = errno; // 保存错误码
// 记录更改文件权限失败的错误信息
ereport(allow_errors ? LOG : FATAL,
(errcode_for_file_access(), errmsg("could not chmod log file \"%s\": %m", filename)));
errno = save_errno; // 恢复错误码
}
}
return fh; // 返回文件句柄
}
#if defined(ENABLE_UT) && defined(static)
#undef static
#endif
/*
* perform logfile rotation
*/
/*
* 功能:执行日志文件的轮换
*
* 参数:
* time_based_rotation一个布尔值指示是否基于时间进行轮换
* size_rotation_for一个整数指示是否根据文件大小进行轮换的标志
*
* 返回值:无
*/
static void logfile_rotate(bool time_based_rotation, int size_rotation_for)
{
char* filename = NULL; // 用于存储新日志文件的文件名
char* csvfilename = NULL; // 用于存储新的 CSV 日志文件的文件名
pg_time_t fntime; // 用于存储轮换的时间戳
FILE* fh = NULL; // 用于打开新的日志文件
t_thrd.logger.rotation_requested = false; // 表示不需要执行日志文件轮换
/*
* When doing a time-based rotation, invent the new logfile name based on
* the planned rotation time, not current time, to avoid "slippage" in the
* file name when we don't do the rotation immediately.
*/
// 如果time_based_rotation 为 true
if (time_based_rotation)
// 使用预定的轮换时间而不是当前时间,以避免文件名的“滑动”
fntime = t_thrd.logger.next_rotation_time;
else
fntime = time(NULL); // 否则,使用当前时间
// 构造新日志文件的文件名,包括目录、文件名前缀等信息
filename =
logfile_getname(fntime, NULL, u_sess->attr.attr_common.Log_directory, u_sess->attr.attr_common.Log_filename);
if (t_thrd.logger.csvlogFile != NULL) // 如果存在 CSV 日志文件
// 构造 CSV 文件的文件名
csvfilename = logfile_getname(
fntime, ".csv", u_sess->attr.attr_common.Log_directory, u_sess->attr.attr_common.Log_filename);
/*
* Decide whether to overwrite or append. We can overwrite if (a)
* Log_truncate_on_rotation is set, (b) the rotation was triggered by
* elapsed time and not something else, and (c) the computed file name is
* different from what we were previously logging into.
*
* Note: t_thrd.logger.last_file_name should never be NULL here, but if it is, append.
*/
// 如果是基于时间的轮换或需要轮换到标准错误日志
if (time_based_rotation || (size_rotation_for & LOG_DESTINATION_STDERR)) {
// 如果配置中允许在轮换时截断日志文件,并且轮换是基于时间触发的,且新的文件名与上次不同
if (u_sess->attr.attr_common.Log_truncate_on_rotation && time_based_rotation &&
t_thrd.logger.last_file_name != NULL && strcmp(filename, t_thrd.logger.last_file_name) != 0)
// 打开新的日志文件以进行写入操作,如果文件已存在,将其截断为空文件
fh = logfile_open(filename, "w", true);
else
// 打开新的日志文件以进行追加写入操作,不截断已存在的文件内容
fh = logfile_open(filename, "a", true);
if (fh == NULL) { // 如果打开新的日志文件失败
/*
* ENFILE/EMFILE are not too surprising on a busy system; just
* keep using the old file till we manage to get a new one.
* Otherwise, assume something's wrong with Log_directory and stop
* trying to create files.
*/
// 如果错误代码不是 ENFILE 或 EMFILE则表示可能存在更严重的问题
if (errno != ENFILE && errno != EMFILE) {
// 发出一个日志记录消息,禁用自动轮换,以防止继续尝试创建新文件
ereport(LOG, (errmsg("disabling automatic rotation (use SIGHUP to re-enable)")));
t_thrd.logger.rotation_disabled = true;
}
if (filename != NULL)
pfree(filename); // 释放分配的文件名内存,以避免内存泄漏
if (csvfilename != NULL)
pfree(csvfilename); // 释放分配的 CSV 文件名内存
return; // 不执行后续的日志轮换操作
}
fclose(t_thrd.logger.syslogFile); // 关闭先前的日志文件,确保将日志写入新文件
t_thrd.logger.syslogFile = fh;
/* instead of pfree'ing filename, remember it for next time */
if (t_thrd.logger.last_file_name != NULL)
pfree(t_thrd.logger.last_file_name); // 释放上一次的文件名内存,以避免内存泄漏
t_thrd.logger.last_file_name = filename;
filename = NULL; // 以避免重复释放内存
}
/* Same as above, but for csv file. */
// 如果 t_thrd.logger.csvlogFile 不为 NULL
// 且 轮换基于时间触发或轮换基于文件大小触发或当前日志目的地包括 CSV 日志
if (t_thrd.logger.csvlogFile != NULL && (time_based_rotation || (size_rotation_for & LOG_DESTINATION_CSVLOG)) &&
((unsigned int)t_thrd.log_cxt.Log_destination & LOG_DESTINATION_CSVLOG)) {
if (u_sess->attr.attr_common.Log_truncate_on_rotation && time_based_rotation &&
t_thrd.logger.last_csv_file_name != NULL && strcmp(csvfilename, t_thrd.logger.last_csv_file_name) != 0)
fh = logfile_open(csvfilename, "w", true); // 以覆盖模式或追加模式打开 CSV 日志文件
else
fh = logfile_open(csvfilename, "a", true);
if (fh == NULL) { // 如果文件打开失败
/*
* ENFILE/EMFILE are not too surprising on a busy system; just
* keep using the old file till we manage to get a new one.
* Otherwise, assume something's wrong with Log_directory and stop
* trying to create files.
*/
// 如果错误码不是 ENFILE 或 EMFILE
if (errno != ENFILE && errno != EMFILE) {
// 发出一条日志消息,通知禁用自动轮换
ereport(LOG, (errmsg("disabling automatic rotation (use SIGHUP to re-enable)")));
t_thrd.logger.rotation_disabled = true; // 表明自动轮换已禁用
}
if (filename != NULL)
pfree(filename); // 释放分配的文件名内存
if (csvfilename != NULL)
pfree(csvfilename);
return;
}
fclose(t_thrd.logger.csvlogFile); // 关闭之前的日志文件
t_thrd.logger.csvlogFile = fh;
/* instead of pfree'ing filename, remember it for next time */
if (t_thrd.logger.last_csv_file_name != NULL)
pfree(t_thrd.logger.last_csv_file_name); // 释放上一次的文件名内存
t_thrd.logger.last_csv_file_name = csvfilename; // 将新的文件名赋值,以备下一次轮换操作使用
csvfilename = NULL;
}
if (filename != NULL)
pfree(filename);
if (csvfilename != NULL)
pfree(csvfilename);
set_next_rotation_time(); // 设置下一次的日志轮换时间
}
/*
* construct logfile name using timestamp information
*
* If suffix isn't NULL, append it to the name, replacing any ".log"
* that may be in the pattern.
*
* Result is palloc'd.
*/
/*
* 功能:构建日志文件的文件名
*
* 参数:
* timestamp时间戳
* suffix文件名后缀
* logdir日志目录
* filename_pattern文件名模式
*
* 返回值:动态分配的字符串,包含构建的文件名
*/
static char* logfile_getname(pg_time_t timestamp, const char* suffix, const char* logdir, const char* filename_pattern)
{
char* filename = NULL;
int len = 0;
int ret = 0;
filename = (char*)palloc(MAXPGPATH); // 分配内存
// 将 logdir 的内容格式化并复制到 filename 中
ret = snprintf_s(filename, MAXPGPATH, MAXPGPATH - 1, "%s/", logdir);
securec_check_ss(ret, "", "");
len = strlen(filename); // 计算 filename 字符串的长度
/* treat Log_filename as a strftime pattern */
// 将格式化时间信息附加到 filename 的末尾
pg_strftime(filename + len, MAXPGPATH - len, filename_pattern, pg_localtime(&timestamp, log_timezone));
// 检查是否提供了文件名后缀
if (suffix != NULL) {
len = strlen(filename); // 计算 filename 字符串的长度
// 检查文件名是否以 ".log" 结尾
if (len > 4 && (strcmp(filename + (len - 4), ".log") == 0))
len -= 4; // 如果是,将从文件名中移除 ".log"
// 将后缀 suffix 复制到 filename 的末尾,确保不会超出文件名的最大长度 MAXPGPATH - len
strlcpy(filename + len, suffix, MAXPGPATH - len);
}
return filename; // 返回构建的文件名字符串
}
/*
* Determine the next planned rotation time, and store in t_thrd.logger.next_rotation_time.
*/
/*
* 功能:确定下一次计划的日志文件轮换时间
*
* 参数:无
*
* 返回值:无
*/
static void set_next_rotation_time(void)
{
pg_time_t now;
struct pg_tm* tm_t = NULL;
int rotinterval;
/* nothing to do if time-based rotation is disabled */
// 检查是否禁用了基于时间的日志轮换
if (u_sess->attr.attr_common.Log_RotationAge <= 0)
return; // 禁用z阿返回
/*
* The requirements here are to choose the next time > now that is a
* "multiple" of the log rotation interval. "Multiple" can be interpreted
* fairly loosely. In this version we align to log_timezone rather than
* GMT.
*/
// 计算日志轮换的时间间隔
rotinterval = u_sess->attr.attr_common.Log_RotationAge * SECS_PER_MINUTE; /* convert to seconds */
now = (pg_time_t)time(NULL); // 获取当前时间
tm_t = pg_localtime(&now, log_timezone); // 将当前时间 now 转换为本地时间
if (NULL == tm_t) { // 检查是否成功获取本地时间
return; // 如果获取失败,直接返回
}
// 将当前时间调整为 GMT 偏移量的时间,以便对齐到日志轮换的时间间隔
now += tm_t->tm_gmtoff;
// 计算 now 与轮换时间间隔的模,并将其减去,以得到下一个轮换时间的起点
now -= now % rotinterval;
// 将计划的轮换时间增加一个完整的轮换时间间隔,以得到下一次计划的轮换时间
now += rotinterval;
// 将计划的轮换时间调整回本地时间,以得到最终的计划轮换时间
now -= tm_t->tm_gmtoff;
// 将计划的轮换时间存储在全局变量中,以供日志轮换时使用
t_thrd.logger.next_rotation_time = now;
}
/* --------------------------------
* signal handler routines
* --------------------------------
*/
/* SIGHUP: set flag to reload config file */
/*
*功能:处理 SIGHUP 信号,用于重新读取配置文件
*
* 参数:
* SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息
*
* 返回值:
* 无
*/
static void sigHupHandler(SIGNAL_ARGS)
{
int save_errno = errno; // 保存当前的错误码
t_thrd.logger.got_SIGHUP = true; // 表示接收到 SIGHUP 信号
SetLatch(&t_thrd.logger.sysLoggerLatch); // 设置该进程的进程latch
errno = save_errno; // 恢复之前保存的错误码
}
/* SIGUSR1: set flag to rotate logfile */
/*
*功能: 处理 SIGUSR1 信号,用于刷新缓冲区请求,以及日志文件的轮换请求
*
* 参数:
* SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息
*
* 返回值:
* 无
*/
static void sigUsr1Handler(SIGNAL_ARGS)
{
int save_errno = errno; // 保存当前的错误码
LogControlData* logctl = NULL;
// 检查是否有刷新缓冲区的请求
if (g_instance.flush_buf_requested) {
// 如果有,遍历所有的 LogControlData 结构体
foreach_logctl(logctl) {
/* request to flush buffer data */
logctl->flush_requested = true; // 表示需要刷新缓冲区中的数据
}
/* reset this request */
g_instance.flush_buf_requested = false; // 重置标志,表示刷新缓冲区请求已经被处理
} else {
// 如果没有刷新缓冲区的请求
t_thrd.logger.rotation_requested = true; // 表示需要进行日志文件的轮换
/* all log should be rotation requested */
foreach_logctl(logctl) {
logctl->rotation_requested = true; // 表示需要进行日志文件的轮换
}
}
SetLatch(&t_thrd.logger.sysLoggerLatch); // 设置该进程的进程latch
errno = save_errno; // 恢复之前保存的错误码
}
/*
* 功能:设置一个全局标志,表示要求刷新缓冲区
*
* 参数:无
*
* 返回值:无
*/
void set_flag_to_flush_buffer(void)
{
g_instance.flush_buf_requested = true; // 表示要求刷新缓冲区
}
/*
* @Description: get log directory for different log type.
* @Param[IN] logid: log type tag
* @Param[IN] include_nodename: nodename appears in log directory info
* @Return: log directory
* @See also:
*/
static char* LogCtlGetLogDirectory(const char* logid, bool include_nodename)
{
char path[MAXPGPATH] = {0}; // 用于存储最终的日志目录路径
char* rootdir = gs_getenv_r("GAUSSLOG"); // 获取环境变量 $GAUSSLOG 的值
char log_rootdir[PATH_MAX + 1] = {'\0'};// 存储 $GAUSSLOG 环境变量的解析后的绝对路径
// 检查环境变量 $GAUSSLOG 是否有效
if (rootdir == NULL || realpath(rootdir, log_rootdir) == NULL) {
// 如果无效则发出警告消息
ereport(WARNING,
(errmodule(MOD_EXECUTOR), errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION),
errmsg("Failed to obtain environment value $GAUSSLOG!"),
errdetail("N/A"),
errcause("Incorrect environment value."),
erraction("Please refer to backend log for more details.")));
}
rootdir = NULL; // 释放 rootdir 内存
int rc = 0; // 存储函数调用的返回值或错误码
/*
* $GAUSSLOG env must not be an empty string.
* if so, log directory will be under root dir '/' and permition denied.
*/
if (*log_rootdir != '\0') {
check_backend_env(log_rootdir); // 检查指定路径是否可用
// 拼接解析后的 $GAUSSLOG 环境变量路径到 path 中
rc = strcat_s(path, MAXPGPATH, log_rootdir);
securec_check_c(rc, "\0", "\0");
// 在 path 后面添加斜杠,形成路径分隔符
rc = strcat_s(path, MAXPGPATH, "/");
securec_check_c(rc, "\0", "\0");
}
/* if GAUSSLOG not set, create directory under node rootdir */
// 将 logid 参数指定的日志类型添加到 path 中
rc = strcat_s(path, MAXPGPATH, logid);
securec_check_c(rc, "\0", "\0");
rc = strcat_s(path, MAXPGPATH, "/"); // 在 path 后面再次添加斜杠
securec_check_c(rc, "\0", "\0");
if (include_nodename) {
rc = strcat_s(path, MAXPGPATH, g_instance.attr.attr_common.PGXCNodeName);
securec_check_c(rc, "\0", "\0");
}
return pstrdup(path); // 返回最终的日志目录路径的复制
}
/* copy name of src to dst whose max capacity is LOG_MAX_NODENAME_LEN */
/*
* 功能:将源字符串 src 复制到目标字符串 dst 中
* 但要确保目标字符串 dst 的最大容量不超过 LOG_MAX_NODENAME_LEN。
* 如果源字符串 src 的长度超过了 LOG_MAX_NODENAME_LEN则会进行截断处理
*
* 参数:
* src源字符串
* dst目标字符串
*
* 返回值:无
*/
static void copy_name(const char* src, char* dst)
{
size_t len = strlen(src); // 计算源字符串 src 的长度
// 检查源字符串的长度是否超过了目标字符串的最大容量
if (len >= LOG_MAX_NODENAME_LEN) {
/* truncate this name */
// 如果长度超过了最大容量,将长度限制为 LOG_MAX_NODENAME_LEN - 1
// 以确保目标字符串末尾可以添加终止符\0
len = LOG_MAX_NODENAME_LEN - 1;
}
// 将源字符串的内容复制到目标字符串中
int rc = memcpy_s(dst, LOG_MAX_NODENAME_LEN, src, len + 1);
securec_check(rc, "\0", "\0");
dst[len] = '\0'; // 手动添加终止符\0以确保目标字符串的正确终止
}
/*
* logCtlNodeName && logCtlHostName will be inited by postmaster only once.
* it's a pity that PGXCNodeName is null in syslog thread,
* so we have to set this global information from postmaster thread.
*/
/*
* 功能:初始化全局变量 logCtlNodeName 和 logCtlHostName
*
* 参数:无
*
* 返回值:无
*/
static void LogCtlSetGlobalNames(void)
{
if (0 == logCtlNodeName[0]) { // 检查 logCtlNodeName 是否已经被初始化
copy_name(g_instance.attr.attr_common.PGXCNodeName, logCtlNodeName);
}
if (0 == logCtlHostName[0]) { // 检查 logCtlHostName 是否已经被初始化
const char* hostname = gs_getenv_r("HOSTNAME"); // 获取环境变量 HOSTNAME 的值,即主机名
if (NULL == hostname) { // 检查获取到的主机名是否为 NULL
/* just set a default hostname */
hostname = "UnknownHostname"; // 设置一个默认的主机名
}
check_backend_env(hostname); // 检查主机名是否符合一些要求
copy_name(hostname, logCtlHostName);
}
}
/*
* logCtlTimeZone will be inited by postmaster only once.`
* it's a pity that log_timezone is null in syslog thread,
* so we have to set this global information from postmaster thread.
*/
/*
* 功能:初始化全局变量 logCtlTimeZone
*
* 参数:无
*
* 返回值:无
*/
static void LogCtlSetTimeZone(void)
{
if (log_timezone) { // 检查 log_timezone 是否已经被初始化
// 复制时区名称并确保安全性
int rc = memcpy_s(logCtlTimeZone, TZ_STRLEN_MAX + 1, pg_get_timezone_name(log_timezone), TZ_STRLEN_MAX + 1);
securec_check(rc, "\0", "\0");
}
}
/*
* @Description: write head data for each new log file
* @Param[IN] logctl: log manager for different log types
* @Return: void
* @See also:
*/
static void LogCtlWriteFileHeader(LogControlData* logctl)
{
// 获取主机名、节点名和时区字符串的长度包括末尾的0
size_t hostname_len = strlen(logCtlHostName) + 1; /* including tail 0 */
size_t nodename_len = strlen(logCtlNodeName) + 1; /* including tail 0 */
size_t timezone_len = strlen(logCtlTimeZone) + 1; /* including tail 0 */
// 计算头部数据的总长度
size_t total_len = sizeof(LogFileHeader) + hostname_len + nodename_len + timezone_len;
int rc = 0;
total_len = MAXALIGN(total_len); // 对齐头部数据长度
Assert(total_len <= BLCKSZ); // 确保头部数据长度不超过块大小
char* buf = (char*)palloc0(total_len); // 分配内存缓冲区用于存储头部数据
int off = 0;
/* the first magic data */
*(unsigned long*)(buf + off) = LOG_MAGICNUM;
off += sizeof(unsigned long);
/* version */
*(uint16*)(buf + off) = logctl->ver;
off += sizeof(uint16);
/* host name */
*(uint8*)(buf + off) = (uint8)hostname_len;
off += sizeof(uint8);
/* node name */
*(uint8*)(buf + off) = (uint8)nodename_len;
off += sizeof(uint8);
/* time zone */
*(uint16*)(buf + off) = (uint16)timezone_len;
off += sizeof(uint16);
/* host name string */
rc = memcpy_s(buf + off, total_len - off, logCtlHostName, hostname_len);
securec_check(rc, "\0", "\0");
off += hostname_len;
/* node name string */
rc = memcpy_s(buf + off, total_len - off, logCtlNodeName, nodename_len);
securec_check(rc, "\0", "\0");
off += nodename_len;
/* time zone string */
rc = memcpy_s(buf + off, total_len - off, logCtlTimeZone, timezone_len);
securec_check(rc, "\0", "\0");
off += timezone_len;
/* the last magic data */
Assert(total_len - off >= sizeof(unsigned long));
*(unsigned long*)(buf + total_len - sizeof(unsigned long)) = LOG_MAGICNUM;
Assert(logctl->now_file_fd); // 确保文件描述符存在
errno = 0; /* clear errno before disk write */
// 写入头部数据到文件
size_t ret_len = fwrite(buf, 1, total_len, logctl->now_file_fd);
// 检查写入是否成功
if ((errno != 0) || (ret_len != total_len)) {
char errorbuf[ERROR_BUF_SIZE] = {'\0'};
rc = sprintf_s(
errorbuf, ERROR_BUF_SIZE, "ERROR: could not write file head for binary log: %s\n", gs_strerror(errno));
securec_check_ss_c(rc, "\0", "\0");
// 写入错误消息到系统日志
syslogger_erewrite(logctl->now_file_fd, errorbuf);
}
pfree(buf);
}
/*
* @Description: rotate and create a new log file
* @Param[IN] logctl: log manager for different log types
* @Param[IN] time_based_rotation: time based rotation
* @Return: void
* @See also:
*/
static void LogCtlRotateFile(LogControlData* logctl, bool time_based_rotation)
{
logctl->rotation_requested = false; // 标记为不需要轮换
// 获取旋转时的时间戳
pg_time_t fntime = time_based_rotation ? t_thrd.logger.next_rotation_time : time(NULL);
// 构建新日志文件的名称
char* filename = logfile_getname(fntime, NULL, logctl->log_dir, logctl->filename_pattern);
/*
* Decide whether to overwrite or append. We can overwrite if (a)
* Log_truncate_on_rotation is set, (b) the rotation was triggered by
* elapsed time and not something else, and (c) the computed file name is
* different from what we were previously logging into.
*
* Note: logctl->now_file_name should never be NULL here, but if it is, append.
*/
char* write_mode = NULL;
if (u_sess->attr.attr_common.Log_truncate_on_rotation && time_based_rotation && logctl->now_file_name &&
strcmp(filename, logctl->now_file_name) != 0) {
write_mode = "w"; // 覆盖文件
} else {
write_mode = "a"; // 追加内容
}
FILE* fh = logfile_open(filename, write_mode, true); // 打开新的日志文件
if (fh == NULL) {
if (errno != ENFILE && errno != EMFILE) {
ereport(LOG, (errmsg("disable automatic PLOG rotation (use SIGHUP to re-enable)")));
/* disable file rotation of all log types if IO error happens */
t_thrd.logger.rotation_disabled = true;
}
pfree(filename);
return;
}
fclose(logctl->now_file_fd); // 关闭当前的日志文件
logctl->now_file_fd = fh; // 更新当前的日志文件句柄为新的文件
/* instead of pfree'ing filename, remember it for next time */
if (logctl->now_file_name != NULL) {
pfree(logctl->now_file_name);
}
logctl->now_file_name = filename;
/* write file header */
LogCtlWriteFileHeader(logctl);
}
/*
* @Description: rotate log file if needed. we will flush
* the buffered data into previous log file.
* @Param[IN] logctl: log manager for different log types
* @Param[IN] time_based_rotation: time based rotation
* @Return: void
* @See also:
*/
static void LogCtlRotateLogFileIfNeeded(LogControlData* logctl, bool time_based_rotation)
{
/* just flush buffered log into file, not rotate file */
if (logctl->flush_requested) {
logctl->flush_requested = false;
LogCtlFlushBuf(logctl);
/* rotation will be handled by the following */
}
/*
* case 1: rotation requested, including rotation age request.
* case 2: single file size reaches PROFILE_LOG_ROTATE_SIZE amount.
*/
if (logctl->rotation_requested ||
(!t_thrd.logger.rotation_disabled &&
(ftell(logctl->now_file_fd) >= (PROFILE_LOG_ROTATE_SIZE - logctl->cur_len)))) {
LogCtlFlushBuf(logctl);
LogCtlRotateFile(logctl, time_based_rotation);
}
}
/*
* @Description: flush log buffer data into disk.
* if disk is full, process will enter waiting forever until this problem
* is solved.
* if disk writing error happens, stderr will be written. (may be some problems)
* @Return: void
* @See also:
*/
static void LogCtlFlushBuf(LogControlData* logctl)
{
/* flush log into disk file */
for (;;) {
/* clear errno before calling IO write */
errno = 0;
size_t rc = fwrite(logctl->log_buf, 1, logctl->cur_len, logctl->now_file_fd);
if ((errno != 0) || ((int)rc != logctl->cur_len)) {
/*
* if no disk space, we will retry,
* and we can not report a log, because there is not space to write.
*/
if (errno == ENOSPC) {
break;
}
/* disk IO error, print message and discard this logs */
char errorbuf[ERROR_BUF_SIZE] = {'\0'};
int rc_error = sprintf_s(errorbuf,
ERROR_BUF_SIZE,
"ERROR: could not write profile log: %s\n"
"WARNING: discard profile log because of disk IO error\n",
gs_strerror(errno));
securec_check_ss_c(rc_error, "\0", "\0");
syslogger_erewrite(logctl->now_file_fd, errorbuf);
}
break;
}
/* buffer is empty */
logctl->cur_len = 0;
}
/*
* @Description: append an input log message into log buffer.
* if buffer will be full, flush buffer first.
* @Param[IN] logctl: log manager for different log types
* @Param[IN] msg: input log message
* @Param[IN] len: length of input log message
* @Return: void
* @See also:
*/
static void LogCtlProcessInput(LogControlData* logctl, const char* msg, int len)
{
if (logctl->cur_len + len > logctl->max_len) {
LogCtlFlushBuf(logctl); // 如果缓冲区即将满,先刷新缓冲区
}
// 将输入的日志消息复制到缓冲区
int rc = memcpy_s(logctl->log_buf + logctl->cur_len, logctl->max_len, msg, len);
securec_check(rc, "\0", "\0");
logctl->cur_len += len; // 更新当前缓冲区长度
}
/*
* @Description: get its log file name pattern for different log type.
* @Param[IN] post_suffix: log file suffix
* @Return: log file name pattern
* @See also:
*/
static char* LogCtlGetFilenamePattern(const char* post_suffix)
{
char* pattern = NULL;
const size_t len = strlen(u_sess->attr.attr_common.Log_filename);
int maxlen = 0;
int rc = 0;
/*
* if there is a suffix within file pattern,
* replace it with my own suffix. otherwise,
* append with my own suffix.
*/
char* p = (char*)memrchr(u_sess->attr.attr_common.Log_filename, '.', len);
size_t filename_len = p ? (p - u_sess->attr.attr_common.Log_filename) : len;
/* 5 = strlen(PROFILE_LOG_SUFFIX) + sizeof('\0') */
maxlen = filename_len + 5;
pattern = (char*)palloc(maxlen);
rc = memcpy_s(pattern, maxlen, u_sess->attr.attr_common.Log_filename, filename_len);
securec_check(rc, "\0", "\0");
/* append filename post suffix */
rc = memcpy_s(pattern + filename_len, maxlen - filename_len, PROFILE_LOG_SUFFIX, 4);
securec_check(rc, "\0", "\0");
pattern[maxlen - 1] = '\0';
return pattern;
}
/*
* @Description: Init profile log control data.
* notice, this function is reentrant.
* @Return: void
* @See also:
*/
static void PLogCtlInit(void)
{
t_thrd.log_cxt.pLogCtl = (LogControlData*)palloc0(sizeof(LogControlData));
// 设置性能日志控制数据的各个字段
t_thrd.log_cxt.pLogCtl->ver = PROFILE_LOG_VERSION; // 设置性能日志版本号
t_thrd.log_cxt.pLogCtl->rotation_requested = false; // 初始化日志旋转请求标志为假
t_thrd.log_cxt.pLogCtl->flush_requested = false; // 初始化日志刷新请求标志为假
if (NULL == t_thrd.log_cxt.pLogCtl->log_dir) {
// 如果日志目录尚未设置,则获取日志目录
t_thrd.log_cxt.pLogCtl->log_dir = LogCtlGetLogDirectory(PROFILE_LOG_TAG, true);
// 创建日志目录,如果目录不存在
if (0 == mkdir(t_thrd.log_cxt.pLogCtl->log_dir, S_IRWXU) || (EEXIST == errno)) {
/* make sure dir permition is 700 */
(void)chmod(t_thrd.log_cxt.pLogCtl->log_dir, S_IRWXU);
} else {
/* this directory may be created already, don't care this case */
if (EEXIST != errno) {
ereport(FATAL,
(errmsg(
"ERROR: could not create directory \"%s\": %s\n", PROFILE_LOG_TAG, gs_strerror(errno))));
}
}
}
if (NULL == t_thrd.log_cxt.pLogCtl->filename_pattern) {
// 如果日志文件名模式尚未设置,则获取日志文件名模式
t_thrd.log_cxt.pLogCtl->file_suffix = PROFILE_LOG_SUFFIX;
/* plog file pattern should be the same with error log, but post suffix */
t_thrd.log_cxt.pLogCtl->filename_pattern = LogCtlGetFilenamePattern(PROFILE_LOG_SUFFIX);
t_thrd.log_cxt.pLogCtl->now_file_name = NULL;
t_thrd.log_cxt.pLogCtl->now_file_fd = NULL;
}
if (NULL == t_thrd.log_cxt.pLogCtl->log_buf) {
// 如果日志缓冲区尚未分配,则分配一个新的缓冲区
t_thrd.log_cxt.pLogCtl->log_buf = (char*)palloc(READ_BUF_SIZE);
t_thrd.log_cxt.pLogCtl->max_len = READ_BUF_SIZE;
t_thrd.log_cxt.pLogCtl->cur_len = 0;
}
/* do the last two steps */
t_thrd.log_cxt.pLogCtl->inited = true;
allLogCtl[LOG_TYPE_PLOG] = t_thrd.log_cxt.pLogCtl; // 将性能日志控制数据结构添加到全局数组中
}
/*
* @Description: create log directory for the other types if not present,
* ignore errors.
* @Return: void
* @See also:
*/
static void LogCtlCreateLogParentDirectory(void)
{
char* logdir = NULL;
/* create directory for profile log.
* if EEXIST == errno, this directory may be created already, don't care this case.
*/
logdir = LogCtlGetLogDirectory(PROFILE_LOG_TAG, false);
if (0 == mkdir(logdir, S_IRWXU) || (EEXIST == errno)) {
/*
* make sure dir permition is 700.
* parent directory may be created by OM tool. if not so
* chmod() may be called by many process, and it maybe failed.
* ignore its returned value of this case.
*/
(void)chmod(logdir, S_IRWXU);
} else if (EEXIST != errno) {
ereport(FATAL, (errmsg("could not create log directory \"%s\": %s\n", logdir, gs_strerror(errno))));
}
pfree(logdir);
/* for the other log types */
}
/*
* @Description: Postmaster must do the last flush for all the logs.
* Under thread mode, postmaster doesn't wait the syslogger exit
* before the whole process exit, and it finishes first. so syslogger
* thread may be died directly and log data maybe loss.
* error log doesn't have this problem, because it's written directly
* into disk and flushed by OS during process exits.
* @Return: void
* @See also:
*/
void LogCtlLastFlushBeforePMExit(void)
{
/* return in fast path if log is off */
if (!g_instance.attr.attr_common.Logging_collector) {
return;
}
LogControlData* logctl = NULL;
/*
* needn't lock, because no profile data is generated,
* and syslogger never call flushing buffer.
*/
foreach_logctl(logctl) {
LogCtlFlushBuf(logctl);
/*
* memory and fd will be released because
* the whole process is exiting.
*/
}
}
/*
* 功能用于打开ASAdaptive Server性能日志文件
*
* 参数:
* doOpen:指向布尔值的指针,用于指示是否需要打开新的日志文件
*
* 返回值返回指向打开的AS性能日志文件的指针
* 如果doOpen参数不为NULL且需要打开新文件则设置为true
* 否则设置为false
*/
void* ASPOpenLogFile(bool *doOpen)
{
if (doOpen != NULL) { // 检查是否传递了doOpen指针
*doOpen = false; // 如果有的话将其设置为false表示不需要打开新的日志文件
}
// 检查全局变量t_thrd.logger.asplogFile是否为NULL
if (t_thrd.logger.asplogFile == NULL) {
// 如果为NULL表示当前没有打开的AS性能日志文件
// 生成一个新的AS性能日志文件的文件名
char *filename = logfile_getname(time(NULL), ".log",
g_instance.attr.attr_common.asp_log_directory, u_sess->attr.attr_common.asp_log_filename);
// 使用生成的文件名和写入模式"a" 打开AS性能日志文件
t_thrd.logger.asplogFile = logfile_open(filename, "a", false);
// 检查全局变量t_thrd.logger.last_asp_file_name
if (t_thrd.logger.last_asp_file_name != NULL) /* probably shouldn't happen */
pfree(t_thrd.logger.last_asp_file_name); // 释放其内存
t_thrd.logger.last_asp_file_name = filename; // 设置新生成的文件名,以便在下次打开文件时记住上一个文件的名称
if (doOpen != NULL) { // 如果传递了doOpen指针
*doOpen = true; // 设置为true表示已经打开了新的AS性能日志文件
}
}
return (void *)t_thrd.logger.asplogFile; // 返回指向打开的AS性能日志文件的指针
}
/*
* 功能关闭ASAdaptive Server性能日志文件
*
* 参数:无
*
* 返回值:无
*/
void ASPCloseLogFile()
{
// 检查全局变量t_thrd.logger.asplogFile是否为NULL以确保已经打开了AS性能日志文件
if (t_thrd.logger.asplogFile != NULL) {
fclose(t_thrd.logger.asplogFile); // 如果AS性能日志文件已经打开关闭该文件
t_thrd.logger.asplogFile = NULL; // 表示AS性能日志文件已经关闭
}
}
/*
* * perform logfile rotation
* */
/*
* 功能AS性能日志文件轮换
*
* 参数:
* time_based_rotation:表示是否基于时间进行日志文件的轮换
* size_rotation_for表示用于决定是否进行日志文件旋转的条件
*
* 返回值:无
*/
static void asp_logfile_rotate(bool time_based_rotation, int size_rotation_for)
{
char* aspFilename = NULL;
pg_time_t fntime;
FILE* fh = NULL;
t_thrd.logger.rotation_requested = false;
/*
* When doing a time-based rotation, invent the new logfile name based on
* the planned rotation time, not current time, to avoid "slippage" in the
* file name when we don't do the rotation immediately.
*/
// 如果是基于时间的轮换
if (time_based_rotation)
fntime = t_thrd.logger.next_rotation_time; // 使用计划的轮换时间
else
fntime = time(NULL); // 否则使用当前时间
// 生成新的AS性能日志文件名
aspFilename =
logfile_getname(time(NULL), ".log",
g_instance.attr.attr_common.asp_log_directory,
u_sess->attr.attr_common.asp_log_filename);
/*
* Decide whether to overwrite or append. We can overwrite if (a)
* Log_truncate_on_rotation is set, (b) the rotation was triggered by
* elapsed time and not something else, and (c) the computed file name is
* different from what we were previously logging into.
*
* Note: t_thrd.logger.last_file_name should never be NULL here, but if it is, append.
*/
if ((time_based_rotation || (size_rotation_for & LOG_DESTINATION_ASPLOG)) && pmState == PM_RUN) {
if (u_sess->attr.attr_common.Log_truncate_on_rotation && time_based_rotation &&
t_thrd.logger.asplogFile != NULL && strcmp(aspFilename, t_thrd.logger.last_asp_file_name) != 0) {
fh = logfile_open(aspFilename, "w", true); // 覆盖
} else {
fh = logfile_open(aspFilename, "a", true); // 追加写入
}
if (fh == NULL) { // 如果无法打开新的AS性能日志文件
/*
* ENFILE/EMFILE are not too surprising on a busy system; just
* keep using the old file till we manage to get a new one.
* Otherwise, assume something's wrong with Log_directory and stop
* trying to create files.
*/
// 发出一些错误消息,禁用自动轮换
if (errno != ENFILE && errno != EMFILE) {
ereport(LOG, (errmsg("disabling automatic rotation (use SIGHUP to re-enable)")));
t_thrd.logger.rotation_disabled = true;
}
if (aspFilename != NULL)
pfree(aspFilename);
return;
}
// 如果新的文件打开成功关闭旧的AS性能日志文件并将文件句柄切换到新文件
if (t_thrd.logger.asplogFile != NULL) {
fclose(t_thrd.logger.asplogFile);
}
t_thrd.logger.asplogFile = fh;
/* instead of pfree'ing filename, remember it for next time */
if (t_thrd.logger.last_asp_file_name != NULL)
pfree(t_thrd.logger.last_asp_file_name); // 释放新文件名的内存
t_thrd.logger.last_asp_file_name = aspFilename;
aspFilename = NULL;
}
if (aspFilename != NULL) {
pfree(aspFilename);
}
}
/*
* 功能用于打开ASAdaptive Server性能日志文件
*
* 参数:
* doOpen:指向布尔值的指针,用于指示是否需要打开新的日志文件
*
* 返回值:返回查询日志文件的文件指针
*/
void* SQMOpenLogFile(bool *doOpen)
{
if (doOpen != NULL) // 检查传入的doOpen指针是否为NULL
*doOpen = false;
if (t_thrd.logger.querylogFile == NULL) { // 检查全局变量t_thrd.logger.querylogFile是否为空
// 如果为空,表示没有已经打开的查询日志文件,需要打开一个新的文件
// 构造查询日志文件的名称,包括时间戳、后缀等信息
char *filename = logfile_getname(time(NULL), ".log",
g_instance.attr.attr_common.query_log_directory,
u_sess->attr.attr_common.query_log_file);
// 打开查询日志文件,使用附加模式("a"),允许创建新文件
t_thrd.logger.querylogFile = logfile_open(filename, "a", false);
pfree(filename); // 释放构造文件名时分配的内存
if (doOpen != NULL) {
*doOpen = true; // 表示成功打开了查询日志文件
}
}
return (void *)t_thrd.logger.querylogFile; // 返回查询日志文件的文件指针
}
/*
* 功能关闭SQL MonitorSQM查询日志文件
*
* 参数:无
*
* 返回值:无
*/
void SQMCloseLogFile()
{
if (t_thrd.logger.querylogFile != NULL) {
// 如果不为空,表示有一个已经打开的查询日志文件需要关闭
// 关闭查询日志文件,确保任何未刷新的数据被刷新到磁盘
fclose(t_thrd.logger.querylogFile);
t_thrd.logger.querylogFile = NULL; // 表示查询日志文件已成功关闭
}
}
/*
* 功能:执行慢查询日志文件的轮换操作
*
* 参数:
* time_based_rotation:表示是否基于时间进行轮换
* size_rotation_for表示日志文件轮换的原因可以是以下几种之一
* LOG_DESTINATION_QUERYLOG表示基于慢查询日志大小进行轮换。
* LOG_DESTINATION_SYSLOG表示基于系统日志大小进行轮换。
* LOG_DESTINATION_CSVLOG表示基于CSV日志大小进行轮换
*
* 返回值:无
*/
static void slow_query_logfile_rotate(bool time_based_rotation, int size_rotation_for)
{
char* queryFilename = NULL; // 用于存储新日志文件的文件名
pg_time_t fntime; // 存储计划的轮换时间或当前时间
FILE* fh = NULL; // 文件句柄,用于打开新的日志文件
t_thrd.logger.rotation_requested = false;
/*
* When doing a time-based rotation, invent the new logfile name based on
* the planned rotation time, not current time, to avoid "slippage" in the
* file name when we don't do the rotation immediately.
*/
// 根据轮换类型选择计划的轮换时间或当前时间,以计算新日志文件名
if (time_based_rotation)
fntime = t_thrd.logger.next_rotation_time; // 基于时间的轮换,使用计划的轮换时间
else
fntime = time(NULL); // 非时间基础轮换,使用当前时间
queryFilename =
logfile_getname(time(NULL), ".log", g_instance.attr.attr_common.query_log_directory, u_sess->attr.attr_common.query_log_file);
/*
* Decide whether to overwrite or append. We can overwrite if (a)
* Log_truncate_on_rotation is set, (b) the rotation was triggered by
* elapsed time and not something else, and (c) the computed file name is
* different from what we were previously logging into.
*
* Note: t_thrd.logger.last_file_name should never be NULL here, but if it is, append.
*/
// 判断是否需要覆盖或追加到日志文件
if ((time_based_rotation || (size_rotation_for & LOG_DESTINATION_QUERYLOG)) && pmState == PM_RUN) {
if (u_sess->attr.attr_common.Log_truncate_on_rotation && time_based_rotation &&
t_thrd.logger.querylogFile != NULL && strcmp(queryFilename, t_thrd.logger.last_query_log_file_name) != 0) {
fh = logfile_open(queryFilename, "w", true); // 覆盖模式
} else {
fh = logfile_open(queryFilename, "a", true); // 追加模式
}
if (fh == NULL) {
/*
* ENFILE/EMFILE are not too surprising on a busy system; just
* keep using the old file till we manage to get a new one.
* Otherwise, assume something's wrong with Log_directory and stop
* trying to create files.
*/
// 处理文件打开失败的情况
if (errno != ENFILE && errno != EMFILE) {
ereport(LOG, (errmsg("disabling automatic rotation (use SIGHUP to re-enable)")));
t_thrd.logger.rotation_disabled = true;
}
if (queryFilename != NULL)
pfree(queryFilename); // 释放分配的文件名内存
return;
}
if (t_thrd.logger.querylogFile != NULL) {
fclose(t_thrd.logger.querylogFile); // 关闭当前日志文件
}
t_thrd.logger.querylogFile = fh; // 设置新的日志文件句柄
/* instead of pfree'ing filename, remember it for next time */
// 记录新的日志文件名
if (t_thrd.logger.last_query_log_file_name != NULL)
pfree(t_thrd.logger.last_query_log_file_name); // 释放上一个日志文件名的内存
t_thrd.logger.last_query_log_file_name = queryFilename; // 记录新的日志文件名
queryFilename = NULL;
}
if (queryFilename != NULL)
pfree(queryFilename); // 释放内存
}
/*
* 功能:初始化指定类型的日志目录,并根据需要设置全局变量以指定日志目录的路径
*
* 参数:
* include_nodename:指示是否应该在日志目录路径中包括节点名称
* logid表示要初始化的日志类型。
* 根据传入的值,可以是 "ASPY"、"SLOWQUERY" 或 "PERF_JOB"
*
* 返回值:无
*/
void init_instr_log_directory(bool include_nodename, const char* logid)
{
/* create directory for aspy & slow query log */
char* logdir = NULL; // 用于存储日志目录路径
logdir = LogCtlGetLogDirectory(logid, include_nodename); // 获取日志目录路径
// 检查是否成功创建日志目录或目录已存在
if (pg_mkdir_p(logdir, S_IRWXU) == 0 || (errno == EEXIST)) {
/*
* make sure dir permition is 700.
* parent directory may be created by OM tool. if not so
* chmod() may be called by many process, and it maybe failed.
* ignore its returned value of this case.
*/
(void)chmod(logdir, S_IRWXU); // 设置目录权限
} else {
/* this directory may be created already, don't care this case */
if (errno != EEXIST) {
pfree(logdir); // 释放分配的内存
ereport(FATAL,
(errmsg(
"ERROR: could not create instr log directory \"%s\": %s\n", logid, gs_strerror(errno))));
}
}
if (include_nodename) { // 如果包括节点名称
// 根据日志ID设置不同的全局目录路径
if (strcmp(logid, ASP_LOG_TAG) == 0) {
g_instance.attr.attr_common.asp_log_directory = logdir; // 设置aspy日志目录路径
} else if (strcmp(logid, SLOWQUERY_LOG_TAG) == 0) {
g_instance.attr.attr_common.query_log_directory = logdir; // 设置慢查询日志目录路径
} else if (strcmp(logid, PERF_JOB_TAG) == 0) {
g_instance.attr.attr_common.Perf_directory = logdir; // 设置性能日志目录路径
}
} else {
pfree(logdir); // 释放分配的内存
}
}