1 /** @file
2 
3   A brief file description
4 
5   @section license License
6 
7   Licensed to the Apache Software Foundation (ASF) under one
8   or more contributor license agreements.  See the NOTICE file
9   distributed with this work for additional information
10   regarding copyright ownership.  The ASF licenses this file
11   to you under the Apache License, Version 2.0 (the
12   "License"); you may not use this file except in compliance
13   with the License.  You may obtain a copy of the License at
14 
15       http://www.apache.org/licenses/LICENSE-2.0
16 
17   Unless required by applicable law or agreed to in writing, software
18   distributed under the License is distributed on an "AS IS" BASIS,
19   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20   See the License for the specific language governing permissions and
21   limitations under the License.
22  */
23 
24 /***************************************************************************
25  LogObject.cc
26 
27 
28  ***************************************************************************/
29 #include "tscore/ink_platform.h"
30 #include "tscore/CryptoHash.h"
31 #include "P_EventSystem.h"
32 #include "LogUtils.h"
33 #include "LogField.h"
34 #include "LogObject.h"
35 #include "LogConfig.h"
36 #include "LogAccess.h"
37 #include "Log.h"
38 #include "tscore/TestBox.h"
39 
40 #include <algorithm>
41 #include <vector>
42 #include <thread>
43 
44 static bool
should_roll_on_time(Log::RollingEnabledValues roll)45 should_roll_on_time(Log::RollingEnabledValues roll)
46 {
47   return roll == Log::ROLL_ON_TIME_ONLY || roll == Log::ROLL_ON_TIME_OR_SIZE;
48 }
49 
50 static bool
should_roll_on_size(Log::RollingEnabledValues roll)51 should_roll_on_size(Log::RollingEnabledValues roll)
52 {
53   return roll == Log::ROLL_ON_SIZE_ONLY || roll == Log::ROLL_ON_TIME_OR_SIZE;
54 }
55 
56 size_t
preproc_buffers(LogBufferSink * sink)57 LogBufferManager::preproc_buffers(LogBufferSink *sink)
58 {
59   SList(LogBuffer, write_link) q(write_list.popall()), new_q;
60   LogBuffer *b = nullptr;
61   while ((b = q.pop())) {
62     if (b->m_references || b->m_state.s.num_writers) {
63       // Still has outstanding references.
64       write_list.push(b);
65     } else if (_num_flush_buffers > FLUSH_ARRAY_SIZE) {
66       ink_atomic_increment(&_num_flush_buffers, -1);
67       Warning("Dropping log buffer, can't keep up.");
68       RecIncrRawStat(log_rsb, this_thread()->mutex->thread_holding, log_stat_bytes_lost_before_preproc_stat,
69                      b->header()->byte_count);
70       delete b;
71     } else {
72       new_q.push(b);
73     }
74   }
75 
76   int prepared = 0;
77   while ((b = new_q.pop())) {
78     b->update_header_data();
79     sink->preproc_and_try_delete(b);
80     ink_atomic_increment(&_num_flush_buffers, -1);
81     prepared++;
82   }
83 
84   Debug("log-logbuffer", "prepared %d buffers", prepared);
85   return prepared;
86 }
87 
88 /*-------------------------------------------------------------------------
89   LogObject
90   -------------------------------------------------------------------------*/
91 
LogObject(LogConfig * cfg,const LogFormat * format,const char * log_dir,const char * basename,LogFileFormat file_format,const char * header,Log::RollingEnabledValues rolling_enabled,int flush_threads,int rolling_interval_sec,int rolling_offset_hr,int rolling_size_mb,bool auto_created,int rolling_max_count,int rolling_min_count,bool reopen_after_rolling,int pipe_buffer_size)92 LogObject::LogObject(LogConfig *cfg, const LogFormat *format, const char *log_dir, const char *basename, LogFileFormat file_format,
93                      const char *header, Log::RollingEnabledValues rolling_enabled, int flush_threads, int rolling_interval_sec,
94                      int rolling_offset_hr, int rolling_size_mb, bool auto_created, int rolling_max_count, int rolling_min_count,
95                      bool reopen_after_rolling, int pipe_buffer_size)
96   : m_alt_filename(nullptr),
97     m_flags(0),
98     m_signature(0),
99     m_flush_threads(flush_threads),
100     m_rolling_interval_sec(rolling_interval_sec),
101     m_rolling_offset_hr(rolling_offset_hr),
102     m_rolling_size_mb(rolling_size_mb),
103     m_last_roll_time(0),
104     m_max_rolled(rolling_max_count),
105     m_min_rolled(rolling_min_count),
106     m_reopen_after_rolling(reopen_after_rolling),
107     m_buffer_manager_idx(0),
108     m_pipe_buffer_size(pipe_buffer_size)
109 {
110   ink_release_assert(format);
111   m_format         = new LogFormat(*format);
112   m_buffer_manager = new LogBufferManager[m_flush_threads];
113 
114   if (file_format == LOG_FILE_BINARY) {
115     m_flags |= BINARY;
116   } else if (file_format == LOG_FILE_PIPE) {
117     m_flags |= WRITES_TO_PIPE;
118   }
119 
120   generate_filenames(log_dir, basename, file_format);
121 
122   // compute_signature is a static function
123   m_signature = compute_signature(m_format, m_basename, m_flags);
124 
125   m_logFile =
126     new LogFile(m_filename, header, file_format, m_signature, cfg->ascii_buffer_size, cfg->max_line_size, m_pipe_buffer_size);
127 
128   if (m_reopen_after_rolling) {
129     m_logFile->open_file();
130   }
131 
132   LogBuffer *b = new LogBuffer(cfg, this, cfg->log_buffer_size);
133   ink_assert(b);
134   SET_FREELIST_POINTER_VERSION(m_log_buffer, b, 0);
135 
136   _setup_rolling(cfg, rolling_enabled, rolling_interval_sec, rolling_offset_hr, rolling_size_mb);
137 
138   Debug("log-config", "exiting LogObject constructor, filename=%s this=%p", m_filename, this);
139 }
140 
~LogObject()141 LogObject::~LogObject()
142 {
143   Debug("log-config", "entering LogObject destructor, this=%p", this);
144 
145   preproc_buffers();
146   ats_free(m_basename);
147   ats_free(m_filename);
148   ats_free(m_alt_filename);
149   delete m_format;
150   delete[] m_buffer_manager;
151   delete static_cast<LogBuffer *>(FREELIST_POINTER(m_log_buffer));
152 }
153 
154 //-----------------------------------------------------------------------------
155 //
156 // This function generates an object filename according to the following rules:
157 //
158 // 1.- if no extension is given, add .log for ascii logs, and .blog for
159 //     binary logs
160 // 2.- if an extension is given, then do not modify filename and use that
161 //     extension regardless of type of log
162 // 3.- if there is a '.' at the end of the name, then do not add an extension
163 //     and remove the '.'. To have a dot at the end of the filename, specify
164 //     two ('..').
165 //
166 void
generate_filenames(const char * log_dir,const char * basename,LogFileFormat file_format)167 LogObject::generate_filenames(const char *log_dir, const char *basename, LogFileFormat file_format)
168 {
169   ink_assert(log_dir && basename);
170 
171   int i = -1, len = 0;
172   char c;
173   while (c = basename[len], c != 0) {
174     if (c == '.') {
175       i = len;
176     }
177     ++len;
178   }
179   if (i == len - 1) {
180     --len;
181   }; // remove dot at end of name
182 
183   const char *ext = nullptr;
184   int ext_len     = 0;
185   if (i < 0) { // no extension, add one
186     switch (file_format) {
187     case LOG_FILE_ASCII:
188       ext     = LOG_FILE_ASCII_OBJECT_FILENAME_EXTENSION;
189       ext_len = 4;
190       break;
191     case LOG_FILE_BINARY:
192       ext     = LOG_FILE_BINARY_OBJECT_FILENAME_EXTENSION;
193       ext_len = 5;
194       break;
195     case LOG_FILE_PIPE:
196       ext     = LOG_FILE_PIPE_OBJECT_FILENAME_EXTENSION;
197       ext_len = 5;
198       break;
199     default:
200       ink_assert(!"unknown file format");
201     }
202   }
203 
204   int dir_len      = static_cast<int>(strlen(log_dir));
205   int basename_len = len + ext_len + 1;          // include null terminator
206   int total_len    = dir_len + 1 + basename_len; // include '/'
207 
208   m_filename = static_cast<char *>(ats_malloc(total_len));
209   m_basename = static_cast<char *>(ats_malloc(basename_len));
210 
211   memcpy(m_filename, log_dir, dir_len);
212   m_filename[dir_len++] = '/';
213   memcpy(&m_filename[dir_len], basename, len);
214   memcpy(m_basename, basename, len);
215 
216   if (ext_len) {
217     memcpy(&m_filename[dir_len + len], ext, ext_len);
218     memcpy(&m_basename[len], ext, ext_len);
219   }
220   m_filename[total_len - 1]    = 0;
221   m_basename[basename_len - 1] = 0;
222 }
223 
224 void
rename(char * new_name)225 LogObject::rename(char *new_name)
226 {
227   // NOTE: this function is intended to be called by the LogObjectManager
228   // while solving filename conflicts. It DOES NOT modify the signature of
229   // the LogObject to match the new filename.
230   //
231   ats_free(m_alt_filename);
232   m_alt_filename = ats_strdup(new_name);
233   m_logFile->change_name(new_name);
234 }
235 
236 void
add_filter(LogFilter * filter,bool copy)237 LogObject::add_filter(LogFilter *filter, bool copy)
238 {
239   if (!filter) {
240     return;
241   }
242   m_filter_list.add(filter, copy);
243 }
244 
245 void
set_filter_list(const LogFilterList & list,bool copy)246 LogObject::set_filter_list(const LogFilterList &list, bool copy)
247 {
248   LogFilter *f;
249 
250   m_filter_list.clear();
251   for (f = list.first(); f != nullptr; f = list.next(f)) {
252     m_filter_list.add(f, copy);
253   }
254   m_filter_list.set_conjunction(list.does_conjunction());
255 }
256 
257 // we compute the object signature from the fieldlist_str and the printf_str
258 // of the LogFormat rather than from the format_str because the format_str
259 // is not part of a LogBuffer header
260 //
261 uint64_t
compute_signature(LogFormat * format,char * filename,unsigned int flags)262 LogObject::compute_signature(LogFormat *format, char *filename, unsigned int flags)
263 {
264   char *fl           = format->fieldlist();
265   char *ps           = format->printf_str();
266   uint64_t signature = 0;
267 
268   if (fl && ps && filename) {
269     int buf_size = strlen(fl) + strlen(ps) + strlen(filename) + 2;
270     char *buffer = static_cast<char *>(ats_malloc(buf_size));
271 
272     ink_string_concatenate_strings(buffer, fl, ps, filename,
273                                    flags & LogObject::BINARY ? "B" : (flags & LogObject::WRITES_TO_PIPE ? "P" : "A"), NULL);
274 
275     CryptoHash hash;
276     CryptoContext().hash_immediate(hash, buffer, buf_size - 1);
277     signature = hash.fold();
278 
279     ats_free(buffer);
280   }
281   return signature;
282 }
283 
284 void
display(FILE * fd)285 LogObject::display(FILE *fd)
286 {
287   fprintf(fd, "++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n");
288   fprintf(fd,
289           "LogObject [%p]: format = %s (%p)\nbasename = %s\n"
290           "flags = %u\n"
291           "signature = %" PRIu64 "\n",
292           this, m_format->name(), m_format, m_basename, m_flags, m_signature);
293 
294   fprintf(fd, "full path = %s\n", get_full_filename());
295   m_filter_list.display(fd);
296   fprintf(fd, "++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n");
297 }
298 
299 static head_p
increment_pointer_version(head_p * dst)300 increment_pointer_version(head_p *dst)
301 {
302   head_p h;
303   head_p new_h;
304 
305   do {
306     INK_QUEUE_LD(h, *dst);
307     SET_FREELIST_POINTER_VERSION(new_h, FREELIST_POINTER(h), FREELIST_VERSION(h) + 1);
308   } while (ink_atomic_cas(&dst->data, h.data, new_h.data) == false);
309 
310   return h;
311 }
312 
313 static bool
write_pointer_version(head_p * dst,head_p old_h,void * ptr,head_p::version_type vers)314 write_pointer_version(head_p *dst, head_p old_h, void *ptr, head_p::version_type vers)
315 {
316   head_p tmp_h;
317 
318   SET_FREELIST_POINTER_VERSION(tmp_h, ptr, vers);
319   return ink_atomic_cas(&dst->data, old_h.data, tmp_h.data);
320 }
321 
322 LogBuffer *
_checkout_write(size_t * write_offset,size_t bytes_needed)323 LogObject::_checkout_write(size_t *write_offset, size_t bytes_needed)
324 {
325   LogBuffer::LB_ResultCode result_code;
326   LogBuffer *buffer;
327   LogBuffer *new_buffer = nullptr;
328   bool retry            = true;
329   head_p old_h;
330 
331   do {
332     // To avoid a race condition, we keep a count of held references in
333     // the pointer itself and add this to m_outstanding_references.
334 
335     // Increment the version of m_log_buffer, returning the previous version.
336     head_p h = increment_pointer_version(&m_log_buffer);
337 
338     buffer           = static_cast<LogBuffer *>(FREELIST_POINTER(h));
339     result_code      = buffer->checkout_write(write_offset, bytes_needed);
340     bool decremented = false;
341 
342     switch (result_code) {
343     case LogBuffer::LB_OK:
344       // checkout succeeded
345       retry = false;
346       break;
347 
348     case LogBuffer::LB_FULL_ACTIVE_WRITERS:
349     case LogBuffer::LB_FULL_NO_WRITERS:
350       // no more room in current buffer, create a new one
351       new_buffer = new LogBuffer(Log::config, this, Log::config->log_buffer_size);
352 
353       // swap the new buffer for the old one
354       INK_WRITE_MEMORY_BARRIER;
355 
356       do {
357         INK_QUEUE_LD(old_h, m_log_buffer);
358         // we may depend on comparing the old pointer to the new pointer to detect buffer swaps
359         // without worrying about pointer collisions because we always allocate a new LogBuffer
360         // before freeing the old one
361         if (FREELIST_POINTER(old_h) != FREELIST_POINTER(h)) {
362           ink_atomic_increment(&buffer->m_references, -1);
363 
364           // another thread is already creating a new buffer,
365           // so delete new_buffer and try again next loop iteration
366           delete new_buffer;
367           new_buffer = nullptr;
368           break;
369         }
370       } while (write_pointer_version(&m_log_buffer, old_h, new_buffer, 0) == false);
371 
372       if (FREELIST_POINTER(old_h) == FREELIST_POINTER(h)) {
373         ink_atomic_increment(&buffer->m_references, FREELIST_VERSION(old_h) - 1);
374 
375         int idx = m_buffer_manager_idx++ % m_flush_threads;
376         Debug("log-logbuffer", "adding buffer %d to flush list after checkout", buffer->get_id());
377         m_buffer_manager[idx].add_to_flush_queue(buffer);
378         Log::preproc_notify[idx].signal();
379         buffer = nullptr;
380       }
381 
382       decremented = true;
383       break;
384 
385     case LogBuffer::LB_RETRY:
386       // no more room, but another thread should be taking care of creating a new buffer, so yield to let
387       // the other thread finish, then try again
388       std::this_thread::yield();
389       break;
390 
391     case LogBuffer::LB_BUFFER_TOO_SMALL:
392       // return a null buffer to signal the caller that this transaction cannot be logged
393       retry = false;
394       break;
395 
396     default:
397       ink_assert(false);
398     }
399 
400     if (!decremented) {
401       head_p old_h;
402 
403       // The do-while loop protects us from races while we're examining ptr(old_h) and ptr(h)
404       // (essentially an optimistic lock)
405       do {
406         INK_QUEUE_LD(old_h, m_log_buffer);
407         if (FREELIST_POINTER(old_h) != FREELIST_POINTER(h)) {
408           // Another thread's allocated a new LogBuffer, we don't need to do anything more
409           break;
410         }
411 
412       } while (!write_pointer_version(&m_log_buffer, old_h, FREELIST_POINTER(h), FREELIST_VERSION(old_h) - 1));
413 
414       if (FREELIST_POINTER(old_h) != FREELIST_POINTER(h)) {
415         // Another thread's allocated a new LogBuffer, meaning this LogObject is no longer referencing the old LogBuffer
416         ink_atomic_increment(&buffer->m_references, -1);
417       }
418     } else {
419 #ifdef __clang_analyzer__
420       if (new_buffer != nullptr) {
421         delete new_buffer;
422       }
423 #endif
424     }
425 
426   } while (retry && write_offset); // if write_offset is null, we do
427   // not retry because we really do not want to write to the buffer,
428   // only to mark the buffer as full
429   if (result_code == LogBuffer::LB_BUFFER_TOO_SMALL) {
430     buffer = nullptr;
431   }
432 
433   return buffer;
434 }
435 
436 int
va_log(LogAccess * lad,const char * fmt,va_list ap)437 LogObject::va_log(LogAccess *lad, const char *fmt, va_list ap)
438 {
439   static const unsigned MAX_ENTRY = 16 * LOG_KILOBYTE; // 16K? Really?
440   char entry[MAX_ENTRY];
441   unsigned len = 0;
442 
443   ink_assert(fmt != nullptr);
444   len = 0;
445 
446   if (this->m_flags & LOG_OBJECT_FMT_TIMESTAMP) {
447     len = LogUtils::timestamp_to_str(LogUtils::timestamp(), entry, MAX_ENTRY);
448     if (unlikely(len <= 0 || len >= MAX_ENTRY)) {
449       return Log::FAIL;
450     }
451 
452     // Add a space after the timestamp
453     entry[len++] = ' ';
454 
455     if (unlikely(len >= MAX_ENTRY)) {
456       return Log::FAIL;
457     }
458   }
459 
460   vsnprintf(&entry[len], MAX_ENTRY - len, fmt, ap);
461 
462   // Now that we have an entry and it's length (len), we can place it
463   // into the associated logbuffer.
464   return this->log(lad, entry);
465 }
466 
467 int
log(LogAccess * lad,const char * text_entry)468 LogObject::log(LogAccess *lad, const char *text_entry)
469 {
470   // Clang doesn't like initializing a view with nullptr, have to check.
471   return this->log(lad, std::string_view{text_entry ? text_entry : ""});
472 }
473 
474 int
log(LogAccess * lad,std::string_view text_entry)475 LogObject::log(LogAccess *lad, std::string_view text_entry)
476 {
477   LogBuffer *buffer;
478   size_t offset       = 0; // prevent warning
479   size_t bytes_needed = 0, bytes_used = 0;
480 
481   // log to a pipe even if space is exhausted since pipe uses no space
482   // likewise, send data to a remote client even if local space is exhausted
483   // (if there is a remote client, m_logFile will be NULL
484   if (Log::config->logging_space_exhausted && !writes_to_pipe() && m_logFile) {
485     Debug("log", "logging space exhausted, can't write to:%s, drop this entry", m_logFile->get_name());
486     return Log::FULL;
487   }
488   // this verification must be done here in order to avoid 'dead' LogBuffers
489   // with none zero 'in usage' counters (see _checkout_write for more details)
490   if (!lad && text_entry.empty()) {
491     Note("Call to LogAccess without LAD or text entry; skipping");
492     return Log::FAIL;
493   }
494 
495   if (lad && m_filter_list.toss_this_entry(lad)) {
496     Debug("log", "entry filtered, skipping ...");
497     return Log::SKIP;
498   }
499 
500   if (lad && m_filter_list.wipe_this_entry(lad)) {
501     Debug("log", "entry wiped, ...");
502   }
503 
504   if (lad && m_format->is_aggregate()) {
505     // marshal the field data into the temp space provided by the
506     // LogFormat object for aggregate formats
507     if (m_format->m_agg_marshal_space == nullptr) {
508       Note("No temp space to marshal aggregate fields into");
509       return Log::FAIL;
510     }
511 
512     long time_now = LogUtils::timestamp();
513     m_format->m_field_list.marshal(lad, m_format->m_agg_marshal_space);
514 
515     // step through each of the fields and update the LogField object
516     // with the newly-marshalled data
517     LogFieldList *fl = &m_format->m_field_list;
518     char *data_ptr   = m_format->m_agg_marshal_space;
519     LogField *f;
520     int64_t val;
521     for (f = fl->first(); f; f = fl->next(f)) {
522       // convert to host order to do computations
523       val = (f->is_time_field()) ? time_now : *(reinterpret_cast<int64_t *>(data_ptr));
524       f->update_aggregate(val);
525       data_ptr += INK_MIN_ALIGN;
526     }
527 
528     if (time_now < m_format->m_interval_next) {
529       Debug("log-agg",
530             "Time now = %ld, next agg = %ld; not time "
531             "for aggregate entry",
532             time_now, m_format->m_interval_next);
533       return Log::AGGR;
534     }
535     // can easily compute bytes_needed because all fields are INTs
536     // and will use INK_MIN_ALIGN each
537     bytes_needed = m_format->field_count() * INK_MIN_ALIGN;
538   } else if (lad) {
539     bytes_needed = m_format->m_field_list.marshal_len(lad);
540   } else if (!text_entry.empty()) {
541     bytes_needed = INK_ALIGN_DEFAULT(text_entry.size() + 1); // must include null terminator.
542   }
543 
544   if (bytes_needed == 0) {
545     Debug("log-buffer", "Nothing to log, bytes_needed = 0");
546     return Log::SKIP;
547   }
548 
549   // Now try to place this entry in the current LogBuffer.
550   buffer = _checkout_write(&offset, bytes_needed);
551 
552   if (!buffer) {
553     Note("Skipping the current log entry for %s because its size (%zu) exceeds "
554          "the maximum payload space in a log buffer",
555          m_basename, bytes_needed);
556     return Log::FAIL;
557   }
558   //
559   // Ok, the checkout_write was successful, which means we have a valid
560   // offset into the current buffer.  Marshal the entry into the buffer,
561   // and the commit (checkin) the changes.
562   //
563 
564   if (lad && m_format->is_aggregate()) {
565     // the "real" entry data is contained in the LogField objects
566     // themselves, not in this lad.
567     bytes_used = m_format->m_field_list.marshal_agg(&(*buffer)[offset]);
568     ink_assert(bytes_needed >= bytes_used);
569     m_format->m_interval_next += m_format->m_interval_sec;
570     Debug("log-agg", "Aggregate entry created; next time is %ld", m_format->m_interval_next);
571   } else if (lad) {
572     bytes_used = m_format->m_field_list.marshal(lad, &(*buffer)[offset]);
573     ink_assert(bytes_needed >= bytes_used);
574   } else if (!text_entry.empty()) {
575     char *dst = &(*buffer)[offset];
576     memcpy(dst, text_entry.data(), text_entry.size());
577     memset(dst + text_entry.size(), 0, bytes_needed - text_entry.size());
578   }
579 
580   buffer->checkin_write(offset);
581 
582   return Log::LOG_OK;
583 }
584 
585 void
_setup_rolling(LogConfig * cfg,Log::RollingEnabledValues rolling_enabled,int rolling_interval_sec,int rolling_offset_hr,int rolling_size_mb)586 LogObject::_setup_rolling(LogConfig *cfg, Log::RollingEnabledValues rolling_enabled, int rolling_interval_sec,
587                           int rolling_offset_hr, int rolling_size_mb)
588 {
589   if (!LogRollingEnabledIsValid(static_cast<int>(rolling_enabled))) {
590     m_rolling_enabled      = Log::NO_ROLLING;
591     m_rolling_interval_sec = 0;
592     m_rolling_offset_hr    = 0;
593     m_rolling_size_mb      = 0;
594     if (rolling_enabled != Log::NO_ROLLING) {
595       Warning("Valid rolling_enabled values are %d to %d, invalid value "
596               "(%d) specified for %s, rolling will be disabled for this file.",
597               Log::NO_ROLLING, Log::INVALID_ROLLING_VALUE - 1, rolling_enabled, m_filename);
598     } else {
599       Status("Rolling disabled for %s", m_filename);
600     }
601   } else {
602     // do checks for rolling based on time
603     //
604     if (rolling_enabled == Log::ROLL_ON_TIME_ONLY || rolling_enabled == Log::ROLL_ON_TIME_OR_SIZE ||
605         rolling_enabled == Log::ROLL_ON_TIME_AND_SIZE) {
606       if (rolling_interval_sec < Log::MIN_ROLLING_INTERVAL_SEC) {
607         // check minimum
608         m_rolling_interval_sec = Log::MIN_ROLLING_INTERVAL_SEC;
609       } else if (rolling_interval_sec > Log::MAX_ROLLING_INTERVAL_SEC) {
610         // 1 day maximum
611         m_rolling_interval_sec = Log::MAX_ROLLING_INTERVAL_SEC;
612       } else if (Log::MAX_ROLLING_INTERVAL_SEC % rolling_interval_sec == 0) {
613         // OK, divides day evenly
614         m_rolling_interval_sec = rolling_interval_sec;
615       } else {
616         m_rolling_interval_sec = rolling_interval_sec;
617         // increase so it divides day evenly
618         while (Log::MAX_ROLLING_INTERVAL_SEC % ++m_rolling_interval_sec) {
619           ;
620         }
621       }
622 
623       if (m_rolling_interval_sec != rolling_interval_sec) {
624         Note("Rolling interval adjusted from %d sec to %d sec for %s", rolling_interval_sec, m_rolling_interval_sec, m_filename);
625       }
626 
627       if (rolling_offset_hr < 0 || rolling_offset_hr > 23) {
628         rolling_offset_hr = 0;
629         Note("Rolling offset out of bounds for %s, setting it to %d", m_filename, rolling_offset_hr);
630       }
631 
632       m_rolling_offset_hr = rolling_offset_hr;
633       m_rolling_size_mb   = 0; // it is safe to set it as 0, if we set SIZE rolling,
634                                // it will be updated later
635     }
636 
637     if (rolling_enabled == Log::ROLL_ON_SIZE_ONLY || rolling_enabled == Log::ROLL_ON_TIME_OR_SIZE ||
638         rolling_enabled == Log::ROLL_ON_TIME_AND_SIZE) {
639       if (rolling_size_mb < 10) {
640         m_rolling_size_mb = 10;
641         Note("Rolling size invalid(%d) for %s, setting it to 10 MB", rolling_size_mb, m_filename);
642       } else {
643         m_rolling_size_mb = rolling_size_mb;
644       }
645     }
646     cfg->register_rolled_log_auto_delete(m_basename, m_min_rolled);
647     m_rolling_enabled = rolling_enabled;
648   }
649 }
650 
651 unsigned
roll_files(long time_now)652 LogObject::roll_files(long time_now)
653 {
654   if (!m_rolling_enabled) {
655     return 0;
656   }
657 
658   unsigned num_rolled = 0;
659   bool roll_on_time   = false;
660   bool roll_on_size   = false;
661 
662   if (!time_now) {
663     time_now = LogUtils::timestamp();
664   }
665 
666   if (m_rolling_enabled != Log::ROLL_ON_SIZE_ONLY) {
667     if (m_rolling_interval_sec > 0) {
668       // We make no assumptions about the current time not having
669       // changed underneath us. This could happen during daylight
670       // savings time adjustments, or if time is adjusted via NTP.
671       //
672       // For this reason we don't cache the number of seconds
673       // remaining until the next roll, but we calculate this figure
674       // every time ...
675       //
676       int secs_to_next = LogUtils::seconds_to_next_roll(time_now, m_rolling_offset_hr, m_rolling_interval_sec);
677 
678       // ... likewise, we make sure we compute the absolute value
679       // of the seconds since the last roll (which would otherwise
680       // be negative if time "went back"). We will use this value
681       // to make sure we don't roll twice if time goes back shortly
682       // after rolling.
683       //
684       int secs_since_last = (m_last_roll_time < time_now ? time_now - m_last_roll_time : m_last_roll_time - time_now);
685 
686       // number of seconds we allow for periodic_tasks() not to be
687       // called and still be able to roll
688       //
689       const int missed_window = 10;
690 
691       roll_on_time =
692         ((secs_to_next == 0 || secs_to_next >= m_rolling_interval_sec - missed_window) && secs_since_last > missed_window);
693     }
694   }
695 
696   if (m_rolling_enabled != Log::ROLL_ON_TIME_ONLY) {
697     if (m_rolling_size_mb) {
698       // Get file size and check if the file size if greater than the
699       // configured file size for rolling
700       roll_on_size = (get_file_size_bytes() > m_rolling_size_mb * LOG_MEGABYTE);
701     }
702   }
703 
704   if ((roll_on_time && should_roll_on_time(m_rolling_enabled)) || (roll_on_size && should_roll_on_size(m_rolling_enabled)) ||
705       (roll_on_time && roll_on_size && m_rolling_enabled == Log::ROLL_ON_TIME_AND_SIZE)) {
706     num_rolled = _roll_files(m_last_roll_time, time_now ? time_now : LogUtils::timestamp());
707   }
708 
709   return num_rolled;
710 }
711 
712 unsigned
_roll_files(long last_roll_time,long time_now)713 LogObject::_roll_files(long last_roll_time, long time_now)
714 {
715   unsigned num_rolled = 0;
716 
717   if (m_logFile) {
718     // no need to roll if object writes to a pipe
719     if (!writes_to_pipe()) {
720       num_rolled += m_logFile->roll(last_roll_time, time_now, m_reopen_after_rolling);
721 
722       if (Log::config->auto_delete_rolled_files && m_max_rolled > 0) {
723         m_logFile->trim_rolled(m_max_rolled);
724       }
725     }
726   }
727 
728   m_last_roll_time = time_now;
729   return num_rolled;
730 }
731 
732 void
check_buffer_expiration(long time_now)733 LogObject::check_buffer_expiration(long time_now)
734 {
735   LogBuffer *b = static_cast<LogBuffer *>(FREELIST_POINTER(m_log_buffer));
736   if (b && time_now > b->expiration_time()) {
737     force_new_buffer();
738   }
739 }
740 
741 /*-------------------------------------------------------------------------
742   TextLogObject::TextLogObject
743   -------------------------------------------------------------------------*/
744 const LogFormat *TextLogObject::textfmt = MakeTextLogFormat();
745 
TextLogObject(const char * name,const char * log_dir,bool timestamps,const char * header,Log::RollingEnabledValues rolling_enabled,int flush_threads,int rolling_interval_sec,int rolling_offset_hr,int rolling_size_mb,int rolling_max_count,int rolling_min_count,bool reopen_after_rolling)746 TextLogObject::TextLogObject(const char *name, const char *log_dir, bool timestamps, const char *header,
747                              Log::RollingEnabledValues rolling_enabled, int flush_threads, int rolling_interval_sec,
748                              int rolling_offset_hr, int rolling_size_mb, int rolling_max_count, int rolling_min_count,
749                              bool reopen_after_rolling)
750   : LogObject(Log::config, TextLogObject::textfmt, log_dir, name, LOG_FILE_ASCII, header, rolling_enabled, flush_threads,
751               rolling_interval_sec, rolling_offset_hr, rolling_size_mb, /* auto_created */ false, rolling_max_count,
752               rolling_min_count, reopen_after_rolling)
753 {
754   if (timestamps) {
755     this->set_fmt_timestamps();
756   }
757 }
758 
759 /*-------------------------------------------------------------------------
760   TextLogObject::write
761 
762   This routine will take a printf-style format string and variable number
763   of arguments, and write them to the text file.
764 
765   It really just creates a va_list and calls va_write to do the work.
766   Returns the number of bytes written to the file.
767   -------------------------------------------------------------------------*/
768 int
write(const char * format,...)769 TextLogObject::write(const char *format, ...)
770 {
771   int ret_val;
772 
773   ink_assert(format != nullptr);
774   va_list ap;
775   va_start(ap, format);
776   ret_val = va_write(format, ap);
777   va_end(ap);
778 
779   return ret_val;
780 }
781 
782 /*-------------------------------------------------------------------------
783   TextLogObject::va_write
784 
785   This routine will take a format string and va_list and write it as a
786   single entry (line) in the text file.  If timestamps are on, then the
787   entry will be preceeded by a timestamp.
788 
789   Returns ReturnCodeFlags.
790   -------------------------------------------------------------------------*/
791 int
va_write(const char * format,va_list ap)792 TextLogObject::va_write(const char *format, va_list ap)
793 {
794   return this->va_log(nullptr, format, ap);
795 }
796 
797 /*-------------------------------------------------------------------------
798   LogObjectManager
799   -------------------------------------------------------------------------*/
800 
LogObjectManager()801 LogObjectManager::LogObjectManager()
802 {
803   _APImutex = new ink_mutex;
804   ink_mutex_init(_APImutex);
805 }
806 
~LogObjectManager()807 LogObjectManager::~LogObjectManager()
808 {
809   for (auto &_object : _objects) {
810     if (_object->refcount_dec() == 0) {
811       delete _object;
812     }
813   }
814 
815   for (auto &_APIobject : _APIobjects) {
816     if (_APIobject->refcount_dec() == 0) {
817       delete _APIobject;
818     }
819   }
820 
821   delete _APImutex;
822 }
823 
824 int
_manage_object(LogObject * log_object,bool is_api_object,int maxConflicts)825 LogObjectManager::_manage_object(LogObject *log_object, bool is_api_object, int maxConflicts)
826 {
827   if (is_api_object) {
828     ACQUIRE_API_MUTEX("A LogObjectManager::_manage_object");
829   }
830 
831   int retVal = _solve_internal_filename_conflicts(log_object, maxConflicts);
832 
833   if (retVal == NO_FILENAME_CONFLICTS) {
834     if (retVal = _solve_filename_conflicts(log_object, maxConflicts), retVal == NO_FILENAME_CONFLICTS) {
835       // do filesystem checks
836       //
837       {
838         // no conflicts, add object to the list of managed objects
839         //
840         log_object->refcount_inc();
841         if (is_api_object) {
842           _APIobjects.push_back(log_object);
843         } else {
844           _objects.push_back(log_object);
845         }
846 
847         ink_release_assert(retVal == NO_FILENAME_CONFLICTS);
848 
849         Debug("log",
850               "LogObjectManager managing object %s (%s) "
851               "[signature = %" PRIu64 ", address = %p]",
852               log_object->get_base_filename(), log_object->get_full_filename(), log_object->get_signature(), log_object);
853 
854         if (log_object->has_alternate_name()) {
855           Warning("The full path for the (%s) LogObject "
856                   "with signature %" PRIu64 " "
857                   "has been set to %s rather than %s because the latter "
858                   "is being used by another LogObject",
859                   log_object->get_base_filename(), log_object->get_signature(), log_object->get_full_filename(),
860                   log_object->get_original_filename());
861         }
862       }
863     }
864   }
865 
866   if (is_api_object) {
867     RELEASE_API_MUTEX("R LogObjectManager::_manage_object");
868   }
869 
870   return retVal;
871 }
872 
873 int
_solve_filename_conflicts(LogObject * log_object,int maxConflicts)874 LogObjectManager::_solve_filename_conflicts(LogObject *log_object, int maxConflicts)
875 {
876   int retVal = NO_FILENAME_CONFLICTS;
877 
878   const char *filename = log_object->get_full_filename();
879 
880   if (access(filename, F_OK)) {
881     if (errno != ENOENT) {
882       const char *msg = "Cannot access log file %s: %s";
883       const char *se  = strerror(errno);
884 
885       Error(msg, filename, se);
886       LogUtils::manager_alarm(LogUtils::LOG_ALARM_ERROR, msg, filename, se);
887       retVal = ERROR_ACCESSING_LOG_FILE;
888     }
889   } else {
890     // file exists, try to read metafile to get object signature
891     //
892     uint64_t signature = 0;
893     BaseMetaInfo meta_info(filename);
894     bool conflicts = true;
895 
896     if (meta_info.file_open_successful()) {
897       bool got_sig     = meta_info.get_log_object_signature(&signature);
898       uint64_t obj_sig = log_object->get_signature();
899 
900       if (got_sig && signature == obj_sig) {
901         conflicts = false;
902       }
903       Debug("log",
904             "LogObjectManager::_solve_filename_conflicts\n"
905             "\tfilename = %s\n"
906             "\tmeta file signature = %" PRIu64 "\n"
907             "\tlog object signature = %" PRIu64 "\n"
908             "\tconflicts = %d",
909             filename, signature, obj_sig, conflicts);
910     }
911 
912     if (conflicts) {
913       if (maxConflicts == 0) {
914         // do not take any action, and return an error status
915         //
916         const char *msg = "Cannot solve filename conflicts for log file %s";
917 
918         Error(msg, filename);
919         LogUtils::manager_alarm(LogUtils::LOG_ALARM_ERROR, msg, filename);
920         retVal = CANNOT_SOLVE_FILENAME_CONFLICTS;
921       } else {
922         // Either the meta file could not be read, or the new object's
923         // signature and the metafile signature do not match.
924         // Roll the old filename so the new object can use the filename
925         // it requested (previously we used to rename the NEW file
926         // but now we roll the OLD file). However, if the log object writes to
927         // a pipe don't roll because rolling is not applicable to pipes.
928 
929         bool roll_file = true;
930 
931         if (log_object->writes_to_pipe()) {
932           // Verify whether the existing file is a pipe. If it is,
933           // disable the roll_file flag so we don't attempt rolling.
934           struct stat s;
935           if (stat(filename, &s) < 0) {
936             const char *msg = "Cannot stat log file %s: %s";
937             char *se        = strerror(errno);
938 
939             Error(msg, filename, se);
940             LogUtils::manager_alarm(LogUtils::LOG_ALARM_ERROR, msg, filename, se);
941             retVal    = ERROR_DETERMINING_FILE_INFO;
942             roll_file = false;
943           } else {
944             if (S_ISFIFO(s.st_mode)) {
945               roll_file = false;
946             }
947           }
948         }
949         if (roll_file) {
950           Warning("File %s will be rolled because a LogObject with "
951                   "different format is requesting the same "
952                   "filename",
953                   filename);
954           LogFile logfile(filename, nullptr, LOG_FILE_ASCII, 0);
955           if (logfile.open_file() == LogFile::LOG_FILE_NO_ERROR) {
956             long time_now = LogUtils::timestamp();
957 
958             if (logfile.roll(time_now - log_object->get_rolling_interval(), time_now) == 0) {
959               // an error happened while trying to roll the file
960               //
961               _filename_resolution_abort(filename);
962               retVal = CANNOT_SOLVE_FILENAME_CONFLICTS;
963             }
964           } else {
965             _filename_resolution_abort(filename);
966             retVal = CANNOT_SOLVE_FILENAME_CONFLICTS;
967           }
968         }
969       }
970     }
971   }
972   return retVal;
973 }
974 
975 void
_filename_resolution_abort(const char * filename)976 LogObjectManager::_filename_resolution_abort(const char *filename)
977 {
978   const char *msg = "Cannot roll log file %s to fix log "
979                     "conflicts (filename or log format): %s";
980   const char *err = strerror(errno);
981   Error(msg, filename, err);
982   LogUtils::manager_alarm(LogUtils::LOG_ALARM_ERROR, msg, filename, err);
983 }
984 
985 bool
_has_internal_filename_conflict(const char * filename,LogObjectList & objects)986 LogObjectManager::_has_internal_filename_conflict(const char *filename, LogObjectList &objects)
987 {
988   for (auto &object : objects) {
989     // an internal conflict exists if two objects request the
990     // same filename, regardless of the object signatures, since
991     // two objects writing to the same file would produce a
992     // log with duplicate entries and non monotonic timestamps
993     if (strcmp(object->get_full_filename(), filename) == 0) {
994       return true;
995     }
996   }
997   return false;
998 }
999 
1000 int
_solve_internal_filename_conflicts(LogObject * log_object,int maxConflicts,int fileNum)1001 LogObjectManager::_solve_internal_filename_conflicts(LogObject *log_object, int maxConflicts, int fileNum)
1002 {
1003   int retVal           = NO_FILENAME_CONFLICTS;
1004   const char *filename = log_object->get_full_filename();
1005 
1006   if (_has_internal_filename_conflict(filename, _objects) || _has_internal_filename_conflict(filename, _APIobjects)) {
1007     if (fileNum < maxConflicts) {
1008       char new_name[MAXPATHLEN];
1009 
1010       snprintf(new_name, sizeof(new_name), "%s%s%d", log_object->get_original_filename(), LOGFILE_SEPARATOR_STRING, ++fileNum);
1011       log_object->rename(new_name);
1012       retVal = _solve_internal_filename_conflicts(log_object, maxConflicts, fileNum);
1013     } else {
1014       const char *msg = "Cannot solve filename conflicts for log file %s";
1015 
1016       Error(msg, filename);
1017       LogUtils::manager_alarm(LogUtils::LOG_ALARM_ERROR, msg, filename);
1018       retVal = CANNOT_SOLVE_FILENAME_CONFLICTS;
1019     }
1020   }
1021   return retVal;
1022 }
1023 
1024 LogObject *
get_object_with_signature(uint64_t signature)1025 LogObjectManager::get_object_with_signature(uint64_t signature)
1026 {
1027   for (auto obj : this->_objects) {
1028     if (obj->get_signature() == signature) {
1029       return obj;
1030     }
1031   }
1032   return nullptr;
1033 }
1034 
1035 void
check_buffer_expiration(long time_now)1036 LogObjectManager::check_buffer_expiration(long time_now)
1037 {
1038   for (auto &_object : this->_objects) {
1039     _object->check_buffer_expiration(time_now);
1040   }
1041 
1042   ACQUIRE_API_MUTEX("A LogObjectManager::check_buffer_expiration");
1043 
1044   for (auto &_APIobject : this->_APIobjects) {
1045     _APIobject->check_buffer_expiration(time_now);
1046   }
1047 
1048   RELEASE_API_MUTEX("R LogObjectManager::check_buffer_expiration");
1049 }
1050 
1051 size_t
preproc_buffers(int idx)1052 LogObjectManager::preproc_buffers(int idx)
1053 {
1054   size_t buffers_preproced = 0;
1055 
1056   for (auto &_object : this->_objects) {
1057     buffers_preproced += _object->preproc_buffers(idx);
1058   }
1059 
1060   ACQUIRE_API_MUTEX("A LogObjectManager::preproc_buffers");
1061 
1062   for (auto &_APIobject : this->_APIobjects) {
1063     buffers_preproced += _APIobject->preproc_buffers(idx);
1064   }
1065 
1066   RELEASE_API_MUTEX("R LogObjectManager::preproc_buffers");
1067 
1068   return buffers_preproced;
1069 }
1070 
1071 bool
unmanage_api_object(LogObject * logObject)1072 LogObjectManager::unmanage_api_object(LogObject *logObject)
1073 {
1074   if (!logObject) {
1075     return false;
1076   }
1077 
1078   ACQUIRE_API_MUTEX("A LogObjectManager::unmanage_api_object");
1079 
1080   auto index = std::find(this->_APIobjects.begin(), this->_APIobjects.end(), logObject);
1081 
1082   if (index != this->_APIobjects.end()) {
1083     this->_APIobjects.erase(index);
1084 
1085     // Force a buffer flush, then schedule this LogObject to be deleted on the eventProcessor.
1086     logObject->force_new_buffer();
1087     new_Derefer(logObject, HRTIME_SECONDS(60));
1088 
1089     RELEASE_API_MUTEX("R LogObjectManager::unmanage_api_object");
1090     return true;
1091   }
1092 
1093   RELEASE_API_MUTEX("R LogObjectManager::unmanage_api_object");
1094   return false;
1095 }
1096 
1097 void
add_filter_to_all(LogFilter * filter)1098 LogObjectManager::add_filter_to_all(LogFilter *filter)
1099 {
1100   for (unsigned i = 0; i < this->_objects.size(); i++) {
1101     _objects[i]->add_filter(filter);
1102   }
1103 }
1104 
1105 void
open_local_pipes()1106 LogObjectManager::open_local_pipes()
1107 {
1108   // for all local objects that write to a pipe, call open_file to force
1109   // the creation of the pipe so that any potential reader can see it
1110   //
1111   for (unsigned i = 0; i < this->_objects.size(); i++) {
1112     LogObject *obj = _objects[i];
1113     if (obj->writes_to_pipe()) {
1114       obj->m_logFile->open_file();
1115     }
1116   }
1117 }
1118 
1119 void
transfer_objects(LogObjectManager & old_mgr)1120 LogObjectManager::transfer_objects(LogObjectManager &old_mgr)
1121 {
1122   unsigned num_kept_objects = 0;
1123 
1124   Debug("log-config-transfer", "transferring objects from LogObjectManager %p, to %p", &old_mgr, this);
1125 
1126   if (is_debug_tag_set("log-config-transfer")) {
1127     Debug("log-config-transfer", "TRANSFER OBJECTS: list of old objects");
1128     for (auto &_object : old_mgr._objects) {
1129       Debug("log-config-transfer", "%s", _object->get_original_filename());
1130     }
1131 
1132     Debug("log-config-transfer", "TRANSFER OBJECTS : list of new objects");
1133     for (unsigned i = 0; i < this->_objects.size(); i++) {
1134       Debug("log-config-transfer", "%s", _objects[i]->get_original_filename());
1135     }
1136   }
1137 
1138   // Transfer the API objects from the old manager. The old manager will retain its refcount.
1139   for (auto &_APIobject : old_mgr._APIobjects) {
1140     manage_api_object(_APIobject);
1141   }
1142 
1143   for (auto old_obj : old_mgr._objects) {
1144     LogObject *new_obj;
1145 
1146     Debug("log-config-transfer", "examining existing object %s", old_obj->get_base_filename());
1147 
1148     // See if any of the new objects is just a copy of an old one. If so, transfer the
1149     // old one to the new manager and delete the new one. We don't use Vec::in here because
1150     // we need to compare the object hash, not the pointers.
1151     for (unsigned j = 0; j < _objects.size(); j++) {
1152       new_obj = _objects[j];
1153 
1154       Debug("log-config-transfer", "comparing existing object %s to new object %s", old_obj->get_base_filename(),
1155             new_obj->get_base_filename());
1156 
1157       if (*new_obj == *old_obj) {
1158         Debug("log-config-transfer", "keeping existing object %s", old_obj->get_base_filename());
1159 
1160         old_obj->refcount_inc();
1161         this->_objects[j] = old_obj;
1162 
1163         if (new_obj->refcount_dec() == 0) {
1164           delete new_obj;
1165         }
1166         ++num_kept_objects;
1167         break;
1168       }
1169     }
1170   }
1171 
1172   if (is_debug_tag_set("log-config-transfer")) {
1173     Debug("log-config-transfer", "Log Object List after transfer:");
1174     display();
1175   }
1176 }
1177 
1178 unsigned
roll_files(long time_now)1179 LogObjectManager::roll_files(long time_now)
1180 {
1181   int num_rolled = 0;
1182 
1183   for (auto &_object : this->_objects) {
1184     num_rolled += _object->roll_files(time_now);
1185   }
1186 
1187   ACQUIRE_API_MUTEX("A LogObjectManager::roll_files");
1188 
1189   for (auto &_APIobject : this->_APIobjects) {
1190     num_rolled += _APIobject->roll_files(time_now);
1191   }
1192 
1193   RELEASE_API_MUTEX("R LogObjectManager::roll_files");
1194 
1195   return num_rolled;
1196 }
1197 
1198 void
reopen_moved_log_files()1199 LogObjectManager::reopen_moved_log_files()
1200 {
1201   for (auto &_object : this->_objects) {
1202     _object->m_logFile->reopen_if_moved();
1203   }
1204 
1205   ACQUIRE_API_MUTEX("A LogObjectManager::reopen_moved_log_files");
1206 
1207   for (auto &_APIobject : this->_APIobjects) {
1208     _APIobject->m_logFile->reopen_if_moved();
1209   }
1210 
1211   RELEASE_API_MUTEX("R LogObjectManager::reopen_moved_log_files");
1212 }
1213 
1214 void
display(FILE * str)1215 LogObjectManager::display(FILE *str)
1216 {
1217   for (unsigned i = 0; i < this->_objects.size(); i++) {
1218     _objects[i]->display(str);
1219   }
1220 
1221   ACQUIRE_API_MUTEX("A LogObjectManager::display");
1222   for (unsigned i = 0; i < this->_APIobjects.size(); i++) {
1223     _APIobjects[i]->display(str);
1224   }
1225   RELEASE_API_MUTEX("R LogObjectManager::display");
1226 }
1227 
1228 LogObject *
find_by_format_name(const char * name) const1229 LogObjectManager::find_by_format_name(const char *name) const
1230 {
1231   for (auto _object : this->_objects) {
1232     if (_object && _object->m_format->name_id() == LogFormat::id_from_name(name)) {
1233       return _object;
1234     }
1235   }
1236   return nullptr;
1237 }
1238 
1239 int
log(LogAccess * lad)1240 LogObjectManager::log(LogAccess *lad)
1241 {
1242   int ret           = Log::SKIP;
1243   ProxyMutex *mutex = this_thread()->mutex.get();
1244 
1245   for (unsigned i = 0; i < this->_objects.size(); i++) {
1246     ret |= _objects[i]->log(lad);
1247   }
1248 
1249   //
1250   // The bit-field code in *ret* are priority chain:
1251   // FAIL > FULL > LOG_OK > AGGR > SKIP
1252   // The if-statement should keep step with the priority order.
1253   //
1254   if (unlikely(ret & Log::FAIL)) {
1255     RecIncrRawStat(log_rsb, mutex->thread_holding, log_stat_event_log_access_fail_stat, 1);
1256   } else if (unlikely(ret & Log::FULL)) {
1257     RecIncrRawStat(log_rsb, mutex->thread_holding, log_stat_event_log_access_full_stat, 1);
1258   } else if (likely(ret & Log::LOG_OK)) {
1259     RecIncrRawStat(log_rsb, mutex->thread_holding, log_stat_event_log_access_ok_stat, 1);
1260   } else if (unlikely(ret & Log::AGGR)) {
1261     RecIncrRawStat(log_rsb, mutex->thread_holding, log_stat_event_log_access_aggr_stat, 1);
1262   } else if (likely(ret & Log::SKIP)) {
1263     RecIncrRawStat(log_rsb, mutex->thread_holding, log_stat_event_log_access_skip_stat, 1);
1264   } else {
1265     ink_release_assert(!"Unexpected result");
1266   }
1267 
1268   return ret;
1269 }
1270 
1271 void
flush_all_objects()1272 LogObjectManager::flush_all_objects()
1273 {
1274   for (auto &_object : this->_objects) {
1275     _object->force_new_buffer();
1276   }
1277 
1278   ACQUIRE_API_MUTEX("A LogObjectManager::flush_all_objects");
1279 
1280   for (auto &_APIobject : this->_APIobjects) {
1281     _APIobject->force_new_buffer();
1282   }
1283 
1284   RELEASE_API_MUTEX("R LogObjectManager::flush_all_objects");
1285 }
1286 
1287 #if TS_HAS_TESTS
1288 
1289 static LogObject *
MakeTestLogObject(const char * name)1290 MakeTestLogObject(const char *name)
1291 {
1292   const char *tmpdir = getenv("TMPDIR");
1293   LogFormat format("testfmt", nullptr);
1294 
1295   if (!tmpdir) {
1296     tmpdir = "/tmp";
1297   }
1298 
1299   return new LogObject(Log::config, &format, tmpdir, name, LOG_FILE_ASCII /* file_format */, name /* header */,
1300                        Log::ROLL_ON_TIME_ONLY /* rolling_enabled */, 1 /* flush_threads */);
1301 }
1302 
REGRESSION_TEST(LogObjectManager_Transfer)1303 REGRESSION_TEST(LogObjectManager_Transfer)(RegressionTest *t, int /* atype ATS_UNUSED */, int *pstatus)
1304 {
1305   TestBox box(t, pstatus);
1306 
1307   // There used to be a lot of confusion around whether LogObjects were owned by ome or more LogObjectManager
1308   // objects, or handed off to static storage in the Log class. This test just verifies that this is no longer
1309   // the case.
1310   {
1311     LogObjectManager mgr1;
1312     LogObjectManager mgr2;
1313 
1314     mgr1.manage_object(MakeTestLogObject("object1"));
1315     mgr1.manage_object(MakeTestLogObject("object2"));
1316     mgr1.manage_object(MakeTestLogObject("object3"));
1317     mgr1.manage_object(MakeTestLogObject("object4"));
1318 
1319     mgr2.transfer_objects(mgr1);
1320 
1321     rprintf(t, "mgr1 has %d objects, mgr2 has %d objects\n", static_cast<int>(mgr1.get_num_objects()),
1322             static_cast<int>(mgr2.get_num_objects()));
1323     box.check(mgr1.get_num_objects() == 0, "Testing that manager 1 has 0 objects");
1324     box.check(mgr2.get_num_objects() == 4, "Testing that manager 2 has 4 objects");
1325 
1326     rprintf(t, "running Log::periodoc_tasks()\n");
1327     Log::periodic_tasks(Thread::get_hrtime() / HRTIME_SECOND);
1328     rprintf(t, "Log::periodoc_tasks() done\n");
1329   }
1330 
1331   box = REGRESSION_TEST_PASSED;
1332 }
1333 
1334 #endif
1335