2605 lines
110 KiB
C++
2605 lines
110 KiB
C++
/* -------------------------------------------------------------------------
|
||
*
|
||
* 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 是否符合一定的特征
|
||
*
|
||
* 参数:
|
||
* P:LogPipeProtoHeader 结构体
|
||
*
|
||
* 返回值:bool类型,表示是否符合特定特征
|
||
* 如果符合特征,返回 true,表示通过检查;
|
||
* 否则返回 false,表示未通过检查
|
||
*/
|
||
static bool CheckPipeProtoHeader(const LogPipeProtoHeader p)
|
||
{
|
||
/* 检查结构体 p 是否符合:
|
||
* 结构体的前两个字节是否都为 NULL 字符('\0'),表示字符串结束符
|
||
* 结构体的 len 字段是否大于 0 且不超过 LOGPIPE_MAX_PAYLOAD,len 表示数据负载的大小
|
||
* 结构体的 pid 字段是否不等于 0,pid 表示进程的 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_CSVLOG:CSV 日志文件。
|
||
* 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(×tamp, 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.
|
||
*/
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 功能:用于打开AS(Adaptive 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性能日志文件的指针
|
||
}
|
||
|
||
/*
|
||
* 功能:关闭AS(Adaptive 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);
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 功能:用于打开AS(Adaptive 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 Monitor(SQM)查询日志文件
|
||
*
|
||
* 参数:无
|
||
*
|
||
* 返回值:无
|
||
*/
|
||
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); // 释放分配的内存
|
||
|
||
}
|
||
}
|