Skip to content

Commit 4b620b5

Browse files
committed
Options to roll empty logs and log trimming
Added 2 options: - proxy.config.log.rolling_allow_empty - ability to roll empty logs (i.e. rolling logs without traffic) - proxy.config.log.rolling_max_count - trimming logs to a certain number of rolled files on each rolling More info in records.config.en.rst and rotation.en.rst.
1 parent 206384e commit 4b620b5

File tree

11 files changed

+202
-22
lines changed

11 files changed

+202
-22
lines changed

doc/admin-guide/files/records.config.en.rst

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,31 @@ System Variables
273273
order of auto-deletion (if enabled). A default value of 0 means auto-deletion will try to keep
274274
output logs as much as possible. See :doc:`../logging/rotation.en` for guidance.
275275

276+
.. ts:cv:: CONFIG proxy.config.output.logfile.rolling_max_count INT 0
277+
:reloadable:
278+
279+
Specifies the maximum count of rolled output logs to keep. This value will be used by the
280+
auto-deletion (if enabled) to trim the number of rolled log files every time the log is rolled.
281+
A default value of 0 means auto-deletion will not try to limit the number of output logs.
282+
See :doc:`../logging/rotation.en` for an use-case for this option.
283+
284+
.. ts:cv:: CONFIG proxy.config.output.logfile.rolling_allow_empty INT 0
285+
:reloadable:
286+
287+
While rolling default behavior is to rename, close and re-open the log file *only* when/if there is
288+
something to log to the log file. This option opens a new log file right after rolling even if there
289+
is nothing to log (i.e. nothing to be logged due to lack of requests to the server)
290+
which may lead to 0-sized log files while rollong. See :doc:`../logging/rotation.en` for an use-case
291+
for this option.
292+
293+
===== ======================================================================
294+
Value Description
295+
===== ======================================================================
296+
``0`` No empty log files created and rolloed if there was nothing to log
297+
``1`` Allow empty log files to be created and rolled even if there was nothing to log
298+
===== ======================================================================
299+
300+
276301
Thread Variables
277302
----------------
278303

doc/admin-guide/logging/rotation.en.rst

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,3 +245,27 @@ To set log management options, follow the steps below:
245245
#. Run the command :option:`traffic_ctl config reload` to apply the configuration
246246
changes.
247247

248+
249+
Retaining Logs For No More Than a Specified Period
250+
--------------------------------------------------
251+
252+
If for security reasons logs need to be purged to make sure no log entry remains on the box
253+
for more then a specified period of time, we could achieve this by setting the rolling interval,
254+
the maximum number of rolled log files, and forcing |TS| to roll even when there is no traffic.
255+
256+
Let us say we wanted the oldest log entry to be kept on the box to be no older than 2-hour old.
257+
258+
Set :ts:cv:`proxy.config.output.logfile.rolling_interval_sec` (yaml: `rolling_interval_sec`) to 3600 (1h)
259+
which will lead to rolling every 1h.
260+
261+
Set :ts:cv:`proxy.config.output.logfile.rolling_max_count` (yaml: `rolling_max_count`) to 1
262+
which will lead to keeping only one rolled log file at any moment (rolled will be trimmed on every roll).
263+
264+
Set :ts:cv:`proxy.config.output.logfile.rolling_allow_empty` (yaml: `rolling_allow_empty`) to 1 (default: 0)
265+
which will allow logs to be open and rolled even if there was nothing to be logged during the previous period
266+
(i.e. no requests to |TS|).
267+
268+
The above will ensure logs are rolled every 1h hour, only 1 rolled log file to be kept
269+
(rest will be trimmed/removed) and logs will be rolling ("moving") even if nothing is logged
270+
(i.e. no traffic to |TS|).
271+

mgmt/RecordsConfig.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,6 +1008,10 @@ static const RecordElement RecordsConfig[] =
10081008
,
10091009
{RECT_CONFIG, "proxy.config.log.rolling_min_count", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_STR, "^0*[1-9][0-9]*$", RECA_NULL}
10101010
,
1011+
{RECT_CONFIG, "proxy.config.log.rolling_max_count", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
1012+
,
1013+
{RECT_CONFIG, "proxy.config.log.rolling_allow_empty", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-1]", RECA_NULL}
1014+
,
10111015
{RECT_CONFIG, "proxy.config.log.auto_delete_rolled_files", RECD_INT, "1", RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-1]", RECA_NULL}
10121016
,
10131017
{RECT_CONFIG, "proxy.config.log.sampling_frequency", RECD_INT, "1", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}

proxy/logging/LogConfig.cc

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ LogConfig::setup_default_values()
8585
rolling_interval_sec = 86400; // 24 hours
8686
rolling_offset_hr = 0;
8787
rolling_size_mb = 10;
88+
rolling_max_count = 0;
89+
rolling_allow_empty = false;
8890
auto_delete_rolled_files = true;
8991
roll_log_files_now = false;
9092

@@ -177,6 +179,9 @@ LogConfig::read_configuration_variables()
177179
val = (int)REC_ConfigReadInteger("proxy.config.log.auto_delete_rolled_files");
178180
auto_delete_rolled_files = (val > 0);
179181

182+
val = (int)REC_ConfigReadInteger("proxy.config.log.rolling_allow_empty");
183+
rolling_allow_empty = (val > 0);
184+
180185
// Read in min_count control values for auto deletion
181186
if (auto_delete_rolled_files) {
182187
// For diagnostic logs
@@ -193,6 +198,8 @@ LogConfig::read_configuration_variables()
193198
} else {
194199
deleting_info.insert(new LogDeletingInfo("traffic.out", val));
195200
}
201+
202+
rolling_max_count = (int)REC_ConfigReadInteger("proxy.config.log.rolling_max_count");
196203
}
197204
// PERFORMANCE
198205
val = (int)REC_ConfigReadInteger("proxy.config.log.sampling_frequency");
@@ -329,6 +336,9 @@ LogConfig::display(FILE *fd)
329336
fprintf(fd, " rolling_interval_sec = %d\n", rolling_interval_sec);
330337
fprintf(fd, " rolling_offset_hr = %d\n", rolling_offset_hr);
331338
fprintf(fd, " rolling_size_mb = %d\n", rolling_size_mb);
339+
fprintf(fd, " rolling_min_count = %d\n", rolling_min_count);
340+
fprintf(fd, " rolling_max_count = %d\n", rolling_max_count);
341+
fprintf(fd, " rolling_allow_empty = %d\n", rolling_allow_empty);
332342
fprintf(fd, " auto_delete_rolled_files = %d\n", auto_delete_rolled_files);
333343
fprintf(fd, " sampling_frequency = %d\n", sampling_frequency);
334344
fprintf(fd, " file_stat_frequency = %d\n", file_stat_frequency);
@@ -406,8 +416,8 @@ LogConfig::register_config_callbacks()
406416
"proxy.config.log.max_space_mb_headroom", "proxy.config.log.logfile_perm", "proxy.config.log.hostname",
407417
"proxy.config.log.logfile_dir", "proxy.config.log.rolling_enabled", "proxy.config.log.rolling_interval_sec",
408418
"proxy.config.log.rolling_offset_hr", "proxy.config.log.rolling_size_mb", "proxy.config.log.auto_delete_rolled_files",
409-
"proxy.config.log.config.filename", "proxy.config.log.sampling_frequency", "proxy.config.log.file_stat_frequency",
410-
"proxy.config.log.space_used_frequency",
419+
"proxy.config.log.rolling_max_count", "proxy.config.log.rolling_allow_empty", "proxy.config.log.config.filename",
420+
"proxy.config.log.sampling_frequency", "proxy.config.log.file_stat_frequency", "proxy.config.log.space_used_frequency",
411421
};
412422

413423
for (unsigned i = 0; i < countof(names); ++i) {

proxy/logging/LogConfig.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,8 @@ class LogConfig : public ConfigInfo
250250
int rolling_offset_hr;
251251
int rolling_size_mb;
252252
int rolling_min_count;
253+
int rolling_max_count;
254+
bool rolling_allow_empty;
253255
bool auto_delete_rolled_files;
254256

255257
IntrusiveHashMap<LogDeletingInfoDescriptor> deleting_info;

proxy/logging/LogFile.cc

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@
2727
2828
***************************************************************************/
2929

30+
#include <vector>
31+
#include <string>
32+
#include <algorithm>
33+
3034
#include "tscore/ink_platform.h"
3135
#include "tscore/SimpleTokenizer.h"
3236
#include "tscore/ink_file.h"
@@ -35,6 +39,7 @@
3539
#include <sys/types.h>
3640
#include <sys/stat.h>
3741
#include <fcntl.h>
42+
#include <libgen.h>
3843

3944
#include "P_EventSystem.h"
4045
#include "I_Machine.h"
@@ -235,14 +240,90 @@ LogFile::close_file()
235240
RecIncrRawStat(log_rsb, this_thread()->mutex->thread_holding, log_stat_log_files_open_stat, -1);
236241
}
237242

243+
struct RolledFile {
244+
RolledFile(const std::string &name, time_t mtime) : _name(name), _mtime(mtime) {}
245+
std::string _name;
246+
time_t _mtime;
247+
};
248+
249+
/**
250+
* @brief trim rolled files to max number of rolled files, older first
251+
*
252+
* @param rolling_max_count - limit to which rolled files will be trimmed
253+
* @return true if success, false if failure
254+
*/
255+
bool
256+
LogFile::trim_rolled(size_t rolling_max_count)
257+
{
258+
/* man: "dirname() may modify the contents of path, so it may be
259+
* desirable to pass a copy when calling one of these functions." */
260+
char *name = ats_strdup(m_name);
261+
std::string logfile_dir(::dirname((name)));
262+
ats_free(name);
263+
264+
/* Check logging directory access */
265+
int err;
266+
do {
267+
err = access(logfile_dir.c_str(), R_OK | W_OK | X_OK);
268+
} while ((err < 0) && (errno == EINTR));
269+
270+
if (err < 0) {
271+
Error("Error accessing logging directory %s: %s.", logfile_dir.c_str(), strerror(errno));
272+
return false;
273+
}
274+
275+
/* Open logging directory */
276+
DIR *ld = ::opendir(logfile_dir.c_str());
277+
if (ld == nullptr) {
278+
Error("Error opening logging directory %s to collect trim candidates: %s.", logfile_dir.c_str(), strerror(errno));
279+
return false;
280+
}
281+
282+
/* Collect the rolled file names from the logging directory that match the specified log file name */
283+
std::vector<RolledFile> rolled;
284+
char path[MAXPATHLEN];
285+
struct dirent *entry;
286+
while ((entry = readdir(ld))) {
287+
struct stat sbuf;
288+
snprintf(path, MAXPATHLEN, "%s/%s", logfile_dir.c_str(), entry->d_name);
289+
int sret = ::stat(path, &sbuf);
290+
if (sret != -1 && S_ISREG(sbuf.st_mode)) {
291+
int name_len = strlen(m_name);
292+
int path_len = strlen(path);
293+
if (path_len > name_len && 0 == ::strncmp(m_name, path, name_len) && LogFile::rolled_logfile(entry->d_name)) {
294+
rolled.push_back(RolledFile(path, sbuf.st_mtime));
295+
}
296+
}
297+
}
298+
299+
::closedir(ld);
300+
301+
bool result = true;
302+
std::sort(rolled.begin(), rolled.end(), [](const RolledFile a, const RolledFile b) { return a._mtime > b._mtime; });
303+
if (rolling_max_count < rolled.size()) {
304+
for (auto it = rolled.begin() + rolling_max_count; it != rolled.end(); it++) {
305+
const RolledFile &file = *it;
306+
if (unlink(file._name.c_str()) < 0) {
307+
Error("unable to auto-delete rolled logfile %s: %s.", file._name.c_str(), strerror(errno));
308+
result = false;
309+
} else {
310+
Debug("log-file", "rolled logfile, %s, was auto-deleted", file._name.c_str());
311+
}
312+
}
313+
}
314+
315+
rolled.clear();
316+
return result;
317+
}
318+
238319
/*-------------------------------------------------------------------------
239320
* LogFile::roll
240321
* This function is called by a LogObject to roll its files.
241322
*
242323
* Return 1 if file rolled, 0 otherwise
243324
-------------------------------------------------------------------------*/
244325
int
245-
LogFile::roll(long interval_start, long interval_end)
326+
LogFile::roll(long interval_start, long interval_end, bool reopen_after_rolling)
246327
{
247328
if (m_log) {
248329
// Due to commit 346b419 the BaseLogFile::close_file() is no longer called within BaseLogFile::roll().
@@ -257,6 +338,12 @@ LogFile::roll(long interval_start, long interval_end)
257338
// close file operation here within the containing LogFile object.
258339
if (m_log->roll(interval_start, interval_end)) {
259340
m_log->close_file();
341+
342+
if (reopen_after_rolling) {
343+
/* If we re-open now log file will be created even if there is nothing being logged */
344+
m_log->open_file();
345+
}
346+
260347
return 1;
261348
}
262349
}

proxy/logging/LogFile.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ class LogFile : public LogBufferSink, public RefCountObj
5858

5959
int preproc_and_try_delete(LogBuffer *lb) override;
6060

61-
int roll(long interval_start, long interval_end);
61+
bool trim_rolled(size_t rolling_max_count);
62+
int roll(long interval_start, long interval_end, bool reopen_after_rolling = false);
6263

6364
const char *
6465
get_name() const

proxy/logging/LogObject.cc

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ LogBufferManager::preproc_buffers(LogBufferSink *sink)
9090

9191
LogObject::LogObject(const LogFormat *format, const char *log_dir, const char *basename, LogFileFormat file_format,
9292
const char *header, Log::RollingEnabledValues rolling_enabled, int flush_threads, int rolling_interval_sec,
93-
int rolling_offset_hr, int rolling_size_mb, bool auto_created)
93+
int rolling_offset_hr, int rolling_size_mb, bool auto_created, int max_rolled, bool reopen_after_rolling)
9494
: m_alt_filename(nullptr),
9595
m_flags(0),
9696
m_signature(0),
@@ -99,6 +99,8 @@ LogObject::LogObject(const LogFormat *format, const char *log_dir, const char *b
9999
m_rolling_offset_hr(rolling_offset_hr),
100100
m_rolling_size_mb(rolling_size_mb),
101101
m_last_roll_time(0),
102+
m_max_rolled(max_rolled),
103+
m_reopen_after_rolling(reopen_after_rolling),
102104
m_buffer_manager_idx(0)
103105
{
104106
ink_release_assert(format);
@@ -118,6 +120,10 @@ LogObject::LogObject(const LogFormat *format, const char *log_dir, const char *b
118120

119121
m_logFile = new LogFile(m_filename, header, file_format, m_signature, Log::config->ascii_buffer_size, Log::config->max_line_size);
120122

123+
if (m_reopen_after_rolling) {
124+
m_logFile->open_file();
125+
}
126+
121127
LogBuffer *b = new LogBuffer(this, Log::config->log_buffer_size);
122128
ink_assert(b);
123129
SET_FREELIST_POINTER_VERSION(m_log_buffer, b, 0);
@@ -140,14 +146,19 @@ LogObject::LogObject(LogObject &rhs)
140146
m_rolling_offset_hr(rhs.m_rolling_offset_hr),
141147
m_rolling_size_mb(rhs.m_rolling_size_mb),
142148
m_last_roll_time(rhs.m_last_roll_time),
149+
m_max_rolled(rhs.m_max_rolled),
150+
m_reopen_after_rolling(rhs.m_reopen_after_rolling),
143151
m_buffer_manager_idx(rhs.m_buffer_manager_idx)
144-
145152
{
146153
m_format = new LogFormat(*(rhs.m_format));
147154
m_buffer_manager = new LogBufferManager[m_flush_threads];
148155

149156
if (rhs.m_logFile) {
150157
m_logFile = new LogFile(*(rhs.m_logFile));
158+
159+
if (m_reopen_after_rolling) {
160+
m_logFile->open_file();
161+
}
151162
} else {
152163
m_logFile = nullptr;
153164
}
@@ -746,7 +757,11 @@ LogObject::_roll_files(long last_roll_time, long time_now)
746757
if (m_logFile) {
747758
// no need to roll if object writes to a pipe
748759
if (!writes_to_pipe()) {
749-
num_rolled += m_logFile->roll(last_roll_time, time_now);
760+
num_rolled += m_logFile->roll(last_roll_time, time_now, m_reopen_after_rolling);
761+
762+
if (Log::config->auto_delete_rolled_files && m_max_rolled > 0) {
763+
m_logFile->trim_rolled(m_max_rolled);
764+
}
750765
}
751766
}
752767

@@ -770,9 +785,9 @@ const LogFormat *TextLogObject::textfmt = MakeTextLogFormat();
770785

771786
TextLogObject::TextLogObject(const char *name, const char *log_dir, bool timestamps, const char *header,
772787
Log::RollingEnabledValues rolling_enabled, int flush_threads, int rolling_interval_sec,
773-
int rolling_offset_hr, int rolling_size_mb)
788+
int rolling_offset_hr, int rolling_size_mb, int max_rolled, bool reopen_after_rolling)
774789
: LogObject(TextLogObject::textfmt, log_dir, name, LOG_FILE_ASCII, header, rolling_enabled, flush_threads, rolling_interval_sec,
775-
rolling_offset_hr, rolling_size_mb)
790+
rolling_offset_hr, rolling_size_mb, max_rolled, reopen_after_rolling)
776791
{
777792
if (timestamps) {
778793
this->set_fmt_timestamps();

proxy/logging/LogObject.h

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ class LogObject : public RefCountObj
9595

9696
LogObject(const LogFormat *format, const char *log_dir, const char *basename, LogFileFormat file_format, const char *header,
9797
Log::RollingEnabledValues rolling_enabled, int flush_threads, int rolling_interval_sec = 0, int rolling_offset_hr = 0,
98-
int rolling_size_mb = 0, bool auto_created = false);
98+
int rolling_size_mb = 0, bool auto_created = false, int rolling_max_count = 0, bool reopen_after_rolling = false);
9999
LogObject(LogObject &);
100100
~LogObject() override;
101101

@@ -275,10 +275,11 @@ class LogObject : public RefCountObj
275275
int m_flush_threads; // number of flush threads
276276
int m_rolling_interval_sec; // time interval between rolls
277277
// 0 means no rolling
278-
int m_rolling_offset_hr; //
279-
int m_rolling_size_mb; // size at which the log file rolls
280-
long m_last_roll_time; // the last time this object rolled
281-
// its files
278+
int m_rolling_offset_hr; //
279+
int m_rolling_size_mb; // size at which the log file rolls
280+
long m_last_roll_time; // the last time this object rolled its files
281+
int m_max_rolled; // maximum number of rolled logs to be kept, 0 no limit
282+
bool m_reopen_after_rolling; // reopen log file after rolling (normally it is just renamed and closed)
282283

283284
head_p m_log_buffer; // current work buffer
284285
unsigned m_buffer_manager_idx;
@@ -309,7 +310,7 @@ class TextLogObject : public LogObject
309310
public:
310311
inkcoreapi TextLogObject(const char *name, const char *log_dir, bool timestamps, const char *header,
311312
Log::RollingEnabledValues rolling_enabled, int flush_threads, int rolling_interval_sec,
312-
int rolling_offset_hr, int rolling_size_mb);
313+
int rolling_offset_hr, int rolling_size_mb, int max_rolled, bool reopen_after_rolling);
313314

314315
inkcoreapi int write(const char *format, ...) TS_PRINTFLIKE(2, 3);
315316
inkcoreapi int va_write(const char *format, va_list ap);
@@ -409,7 +410,8 @@ LogObject::operator==(LogObject &old)
409410
return (get_signature() == old.get_signature() && m_logFile && old.m_logFile &&
410411
strcmp(m_logFile->get_name(), old.m_logFile->get_name()) == 0 && (m_filter_list == old.m_filter_list) &&
411412
(m_rolling_interval_sec == old.m_rolling_interval_sec && m_rolling_offset_hr == old.m_rolling_offset_hr &&
412-
m_rolling_size_mb == old.m_rolling_size_mb));
413+
m_rolling_size_mb == old.m_rolling_size_mb && m_reopen_after_rolling == old.m_reopen_after_rolling &&
414+
m_max_rolled == old.m_max_rolled));
413415
}
414416

415417
inline off_t

0 commit comments

Comments
 (0)