xref: /trafficserver/src/tscore/Diags.cc (revision 4cfd5a73)
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 
26   Diags.cc
27 
28   This file contains code to manipulate run-time diagnostics, and print
29   warnings and errors at runtime.  Action tags and debugging tags are
30   supported, allowing run-time conditionals affecting diagnostics.
31 
32   Joe User should only need to use the macros at the bottom of Diags.h
33 
34 
35  ****************************************************************************/
36 
37 #include "tscore/BufferWriter.h"
38 #include "tscore/bwf_std_format.h"
39 #include "tscore/ink_platform.h"
40 #include "tscore/ink_memory.h"
41 #include "tscore/ink_defs.h"
42 #include "tscore/ink_error.h"
43 #include "tscore/ink_assert.h"
44 #include "tscore/ink_time.h"
45 #include "tscore/ink_hrtime.h"
46 #include "tscore/ink_thread.h"
47 #include "tscore/BufferWriter.h"
48 #include "tscore/Diags.h"
49 
50 int diags_on_for_plugins         = 0;
51 int DiagsConfigState::enabled[2] = {0, 0};
52 
53 // Global, used for all diagnostics
54 inkcoreapi Diags *diags = nullptr;
55 
56 static bool
location(const SourceLocation * loc,DiagsShowLocation show,DiagsLevel level)57 location(const SourceLocation *loc, DiagsShowLocation show, DiagsLevel level)
58 {
59   if (loc && loc->valid()) {
60     switch (show) {
61     case SHOW_LOCATION_ALL:
62       return true;
63     case SHOW_LOCATION_DEBUG:
64       return level <= DL_Debug;
65     default:
66       return false;
67     }
68   }
69 
70   return false;
71 }
72 
73 //////////////////////////////////////////////////////////////////////////////
74 //
75 //      Diags::Diags(char *bdt, char *bat)
76 //
77 //      This is the constructor for the Diags class.  The constructor takes
78 //      two strings called the "base debug tags" (bdt) and the
79 //      "base action tags" (bat).  These represent debug/action overrides,
80 //      to override the records.config values.  They current come from
81 //      command-line options.
82 //
83 //      If bdt is not nullptr, and not "", it overrides records.config settings.
84 //      If bat is not nullptr, and not "", it overrides records.config settings.
85 //
86 //      When the constructor is done, records.config callbacks will be set,
87 //      the initial values read, and the Diags instance will be ready to use.
88 //
89 //////////////////////////////////////////////////////////////////////////////
90 
Diags(const char * prefix_string,const char * bdt,const char * bat,BaseLogFile * _diags_log,int dl_perm,int ol_perm)91 Diags::Diags(const char *prefix_string, const char *bdt, const char *bat, BaseLogFile *_diags_log, int dl_perm, int ol_perm)
92   : diags_log(nullptr),
93     stdout_log(nullptr),
94     stderr_log(nullptr),
95     magic(DIAGS_MAGIC),
96     show_location(SHOW_LOCATION_NONE),
97     base_debug_tags(nullptr),
98     base_action_tags(nullptr)
99 {
100   int i;
101 
102   cleanup_func = nullptr;
103   ink_mutex_init(&tag_table_lock);
104 
105   ////////////////////////////////////////////////////////
106   // initialize the default, base debugging/action tags //
107   ////////////////////////////////////////////////////////
108 
109   if (bdt && *bdt) {
110     base_debug_tags = ats_strdup(bdt);
111   }
112   if (bat && *bat) {
113     base_action_tags = ats_strdup(bat);
114   }
115 
116   config.enabled[DiagsTagType_Debug]  = (base_debug_tags != nullptr);
117   config.enabled[DiagsTagType_Action] = (base_action_tags != nullptr);
118   diags_on_for_plugins                = config.enabled[DiagsTagType_Debug];
119   prefix_str                          = prefix_string;
120 
121   // The caller must always provide a non-empty prefix.
122   ink_release_assert(prefix_str);
123   ink_release_assert(*prefix_str);
124 
125   for (i = 0; i < DiagsLevel_Count; i++) {
126     config.outputs[i].to_stdout   = false;
127     config.outputs[i].to_stderr   = false;
128     config.outputs[i].to_syslog   = false;
129     config.outputs[i].to_diagslog = true;
130   }
131 
132   // create default stdout and stderr BaseLogFile objects
133   // (in case the user of this class doesn't specify in the future)
134   stdout_log = new BaseLogFile("stdout");
135   stderr_log = new BaseLogFile("stderr");
136   stdout_log->open_file(); // should never fail
137   stderr_log->open_file(); // should never fail
138 
139   //////////////////////////////////////////////////////////////////
140   // start off with empty tag tables, will build in reconfigure() //
141   //////////////////////////////////////////////////////////////////
142 
143   activated_tags[DiagsTagType_Debug]  = nullptr;
144   activated_tags[DiagsTagType_Action] = nullptr;
145 
146   outputlog_rolling_enabled  = RollingEnabledValues::NO_ROLLING;
147   outputlog_rolling_interval = -1;
148   outputlog_rolling_size     = -1;
149   diagslog_rolling_enabled   = RollingEnabledValues::NO_ROLLING;
150   diagslog_rolling_interval  = -1;
151   diagslog_rolling_size      = -1;
152 
153   outputlog_time_last_roll = time(nullptr);
154   diagslog_time_last_roll  = time(nullptr);
155 
156   diags_logfile_perm  = dl_perm;
157   output_logfile_perm = ol_perm;
158 
159   if (setup_diagslog(_diags_log)) {
160     diags_log = _diags_log;
161   }
162 }
163 
~Diags()164 Diags::~Diags()
165 {
166   if (diags_log) {
167     delete diags_log;
168     diags_log = nullptr;
169   }
170 
171   if (stdout_log) {
172     delete stdout_log;
173     stdout_log = nullptr;
174   }
175 
176   if (stderr_log) {
177     delete stderr_log;
178     stderr_log = nullptr;
179   }
180 
181   ats_free((void *)base_debug_tags);
182   ats_free((void *)base_action_tags);
183 
184   deactivate_all(DiagsTagType_Debug);
185   deactivate_all(DiagsTagType_Action);
186 }
187 
188 //////////////////////////////////////////////////////////////////////////////
189 //
190 //      void Diags::print_va(...)
191 //
192 //      This is the lowest-level diagnostic printing routine, that does the
193 //      work of formatting and outputting diagnostic and error messages,
194 //      in the standard format.
195 //
196 //      This routine takes an optional <debug_tag>, which is printed in
197 //      parentheses if its value is not nullptr.  It takes a <diags_level>,
198 //      which is converted to a prefix string.
199 //      print_va takes an optional source location structure pointer <loc>,
200 //      which can be nullptr.  If <loc> is not NULL, the source code location
201 //      is converted to a string, and printed between angle brackets.
202 //      Finally, it takes a printf format string <format_string>, and a
203 //      va_list list of varargs.
204 //
205 //      This routine outputs to all of the output targets enabled for this
206 //      debugging level in config.outputs[diags_level].  Many higher level
207 //      diagnostics printing routines are built upon print_va, including:
208 //
209 //              void print(...)
210 //              void log_va(...)
211 //              void log(...)
212 //
213 //////////////////////////////////////////////////////////////////////////////
214 
215 void
print_va(const char * debug_tag,DiagsLevel diags_level,const SourceLocation * loc,const char * format_string,va_list ap) const216 Diags::print_va(const char *debug_tag, DiagsLevel diags_level, const SourceLocation *loc, const char *format_string,
217                 va_list ap) const
218 {
219   ink_release_assert(diags_level < DiagsLevel_Count);
220   ts::LocalBufferWriter<1024> format_writer;
221 
222   // Save room for optional newline and terminating NUL bytes.
223   format_writer.clip(2);
224 
225   format_writer.print("[{timestamp}] ");
226   auto timestamp_offset = format_writer.size();
227 
228   format_writer.print("{thread-name}");
229   format_writer.print(" {}: ", level_name(diags_level));
230 
231   if (location(loc, show_location, diags_level)) {
232     format_writer.print("<{}> ", *loc);
233   }
234 
235   if (debug_tag) {
236     format_writer.print("({}) ", debug_tag);
237   }
238 
239   format_writer.print("{}", format_string);
240 
241   format_writer.extend(2);                   // restore the space for required termination.
242   if (format_writer.view().back() != '\n') { // safe because always some chars in the buffer.
243     format_writer.write('\n');
244   }
245   format_writer.write('\0');
246 
247   //////////////////////////////////////
248   // now, finally, output the message //
249   //////////////////////////////////////
250 
251   lock();
252   if (config.outputs[diags_level].to_diagslog) {
253     if (diags_log && diags_log->m_fp) {
254       va_list tmp;
255       va_copy(tmp, ap);
256       vfprintf(diags_log->m_fp, format_writer.data(), tmp);
257       va_end(tmp);
258     }
259   }
260 
261   if (config.outputs[diags_level].to_stdout) {
262     if (stdout_log && stdout_log->m_fp) {
263       va_list tmp;
264       va_copy(tmp, ap);
265       vfprintf(stdout_log->m_fp, format_writer.data(), tmp);
266       va_end(tmp);
267     }
268   }
269 
270   if (config.outputs[diags_level].to_stderr) {
271     if (stderr_log && stderr_log->m_fp) {
272       va_list tmp;
273       va_copy(tmp, ap);
274       vfprintf(stderr_log->m_fp, format_writer.data(), tmp);
275       va_end(tmp);
276     }
277   }
278 
279 #if !defined(freebsd)
280   unlock();
281 #endif
282 
283   if (config.outputs[diags_level].to_syslog) {
284     int priority;
285     char syslog_buffer[2048];
286 
287     switch (diags_level) {
288     case DL_Diag:
289     case DL_Debug:
290       priority = LOG_DEBUG;
291 
292       break;
293     case DL_Status:
294       priority = LOG_INFO;
295       break;
296     case DL_Note:
297       priority = LOG_NOTICE;
298       break;
299     case DL_Warning:
300       priority = LOG_WARNING;
301       break;
302     case DL_Error:
303       priority = LOG_ERR;
304       break;
305     case DL_Fatal:
306       priority = LOG_CRIT;
307       break;
308     case DL_Alert:
309       priority = LOG_ALERT;
310       break;
311     case DL_Emergency:
312       priority = LOG_EMERG;
313       break;
314     default:
315       priority = LOG_NOTICE;
316       break;
317     }
318     vsnprintf(syslog_buffer, sizeof(syslog_buffer), format_writer.data() + timestamp_offset, ap);
319     syslog(priority, "%s", syslog_buffer);
320   }
321 
322 #if defined(freebsd)
323   unlock();
324 #endif
325 }
326 
327 //////////////////////////////////////////////////////////////////////////////
328 //
329 //      bool Diags::tag_activated(char * tag, DiagsTagType mode)
330 //
331 //      This routine inquires if a particular <tag> in the tag table of
332 //      type <mode> is activated, returning true if it is, false if it
333 //      isn't.  If <tag> is nullptr, true is returned.  The call uses a lock
334 //      to get atomic access to the tag tables.
335 //
336 //////////////////////////////////////////////////////////////////////////////
337 
338 bool
tag_activated(const char * tag,DiagsTagType mode) const339 Diags::tag_activated(const char *tag, DiagsTagType mode) const
340 {
341   bool activated = false;
342 
343   if (tag == nullptr) {
344     return (true);
345   }
346 
347   lock();
348   if (activated_tags[mode]) {
349     activated = (activated_tags[mode]->match(tag) != -1);
350   }
351   unlock();
352 
353   return (activated);
354 }
355 
356 //////////////////////////////////////////////////////////////////////////////
357 //
358 //      void Diags::activate_taglist(char * taglist, DiagsTagType mode)
359 //
360 //      This routine adds all tags in the vertical-bar-separated taglist
361 //      to the tag table of type <mode>.  Each addition is done under a lock.
362 //      If an individual tag is already set, that tag is ignored.  If
363 //      <taglist> is nullptr, this routine exits immediately.
364 //
365 //////////////////////////////////////////////////////////////////////////////
366 
367 void
activate_taglist(const char * taglist,DiagsTagType mode)368 Diags::activate_taglist(const char *taglist, DiagsTagType mode)
369 {
370   if (taglist) {
371     lock();
372     if (activated_tags[mode]) {
373       delete activated_tags[mode];
374     }
375     activated_tags[mode] = new DFA;
376     activated_tags[mode]->compile(taglist);
377     unlock();
378   }
379 }
380 
381 //////////////////////////////////////////////////////////////////////////////
382 //
383 //      void Diags::deactivate_all(DiagsTagType mode)
384 //
385 //      This routine deactivates all tags in the tag table of type <mode>.
386 //      The deactivation is done under a lock.  When done, the taglist will
387 //      be empty.
388 //
389 //////////////////////////////////////////////////////////////////////////////
390 
391 void
deactivate_all(DiagsTagType mode)392 Diags::deactivate_all(DiagsTagType mode)
393 {
394   lock();
395   if (activated_tags[mode]) {
396     delete activated_tags[mode];
397     activated_tags[mode] = nullptr;
398   }
399   unlock();
400 }
401 
402 //////////////////////////////////////////////////////////////////////////////
403 //
404 //      const char *Diags::level_name(DiagsLevel dl)
405 //
406 //      This routine returns a string name corresponding to the error
407 //      level <dl>, suitable for us as an output log entry prefix.
408 //
409 //////////////////////////////////////////////////////////////////////////////
410 
411 const char *
level_name(DiagsLevel dl) const412 Diags::level_name(DiagsLevel dl) const
413 {
414   switch (dl) {
415   case DL_Diag:
416     return ("DIAG");
417   case DL_Debug:
418     return ("DEBUG");
419   case DL_Status:
420     return ("STATUS");
421   case DL_Note:
422     return ("NOTE");
423   case DL_Warning:
424     return ("WARNING");
425   case DL_Error:
426     return ("ERROR");
427   case DL_Fatal:
428     return ("FATAL");
429   case DL_Alert:
430     return ("ALERT");
431   case DL_Emergency:
432     return ("EMERGENCY");
433   default:
434     return ("DIAG");
435   }
436 }
437 
438 //////////////////////////////////////////////////////////////////////////////
439 //
440 //      void Diags::dump(FILE *fp)
441 //
442 //////////////////////////////////////////////////////////////////////////////
443 
444 void
dump(FILE * fp) const445 Diags::dump(FILE *fp) const
446 {
447   int i;
448 
449   fprintf(fp, "Diags:\n");
450   fprintf(fp, "  debug.enabled: %d\n", config.enabled[DiagsTagType_Debug]);
451   fprintf(fp, "  debug default tags: '%s'\n", (base_debug_tags ? base_debug_tags : "NULL"));
452   fprintf(fp, "  action.enabled: %d\n", config.enabled[DiagsTagType_Action]);
453   fprintf(fp, "  action default tags: '%s'\n", (base_action_tags ? base_action_tags : "NULL"));
454   fprintf(fp, "  outputs:\n");
455   for (i = 0; i < DiagsLevel_Count; i++) {
456     fprintf(fp, "    %10s [stdout=%d, stderr=%d, syslog=%d, diagslog=%d]\n", level_name(static_cast<DiagsLevel>(i)),
457             config.outputs[i].to_stdout, config.outputs[i].to_stderr, config.outputs[i].to_syslog, config.outputs[i].to_diagslog);
458   }
459 }
460 
461 void
error_va(DiagsLevel level,const SourceLocation * loc,const char * format_string,va_list ap) const462 Diags::error_va(DiagsLevel level, const SourceLocation *loc, const char *format_string, va_list ap) const
463 {
464   va_list ap2;
465 
466   if (DiagsLevel_IsTerminal(level)) {
467     va_copy(ap2, ap);
468   }
469 
470   print_va(nullptr, level, loc, format_string, ap);
471 
472   if (DiagsLevel_IsTerminal(level)) {
473     if (cleanup_func) {
474       cleanup_func();
475     }
476 
477     // DL_Emergency means the process cannot recover from a reboot
478     if (level == DL_Emergency) {
479       ink_emergency_va(format_string, ap2);
480     } else {
481       ink_fatal_va(format_string, ap2);
482     }
483   }
484 
485   va_end(ap2);
486 }
487 
488 /*
489  * Sets up and error handles the given BaseLogFile object to work
490  * with this instance of Diags.
491  *
492  * Returns true on success, false otherwise
493  */
494 bool
setup_diagslog(BaseLogFile * blf)495 Diags::setup_diagslog(BaseLogFile *blf)
496 {
497   if (blf != nullptr) {
498     if (blf->open_file(diags_logfile_perm) != BaseLogFile::LOG_FILE_NO_ERROR) {
499       log_log_error("Could not open diags log file: %s\n", strerror(errno));
500       delete blf;
501       return false;
502     }
503   }
504 
505   return true;
506 }
507 
508 void
config_roll_diagslog(RollingEnabledValues re,int ri,int rs)509 Diags::config_roll_diagslog(RollingEnabledValues re, int ri, int rs)
510 {
511   diagslog_rolling_enabled  = re;
512   diagslog_rolling_interval = ri;
513   diagslog_rolling_size     = rs;
514 }
515 
516 void
config_roll_outputlog(RollingEnabledValues re,int ri,int rs)517 Diags::config_roll_outputlog(RollingEnabledValues re, int ri, int rs)
518 {
519   outputlog_rolling_enabled  = re;
520   outputlog_rolling_interval = ri;
521   outputlog_rolling_size     = rs;
522 }
523 
524 /*
525  * Checks diags_log 's underlying file on disk and see if it needs to be rolled,
526  * and does so if necessary.
527  *
528  * This function will replace the current BaseLogFile object with a new one
529  * (if we choose to roll), as each BaseLogFile object logically represents one
530  * file on disk.
531  *
532  * Note that, however, cross process race conditions may still exist, especially with
533  * the metafile, and further work with flock() for fcntl() may still need to be done.
534  *
535  * Returns true if any logs rolled, false otherwise
536  */
537 bool
should_roll_diagslog()538 Diags::should_roll_diagslog()
539 {
540   bool ret_val = false;
541 
542   log_log_trace("%s was called\n", __func__);
543   log_log_trace("%s: rolling_enabled = %d, output_rolling_size = %d, output_rolling_interval = %d\n", __func__,
544                 diagslog_rolling_enabled, diagslog_rolling_size, diagslog_rolling_interval);
545   log_log_trace("%s: RollingEnabledValues::ROLL_ON_TIME = %d\n", __func__, RollingEnabledValues::ROLL_ON_TIME);
546   log_log_trace("%s: time(0) - last_roll_time = %d\n", __func__, time(nullptr) - diagslog_time_last_roll);
547 
548   // Roll diags_log if necessary
549   if (diags_log && diags_log->is_init()) {
550     if (diagslog_rolling_enabled == RollingEnabledValues::ROLL_ON_SIZE ||
551         diagslog_rolling_enabled == RollingEnabledValues::ROLL_ON_TIME_OR_SIZE) {
552       // if we can't even check the file, we can forget about rotating
553       struct stat buf;
554       if (fstat(fileno(diags_log->m_fp), &buf) != 0) {
555         return false;
556       }
557 
558       off_t size = buf.st_size;
559       if (diagslog_rolling_size != -1 && size >= (static_cast<off_t>(diagslog_rolling_size) * BYTES_IN_MB)) {
560         fflush(diags_log->m_fp);
561         if (diags_log->roll()) {
562           char *oldname = ats_strdup(diags_log->get_name());
563           log_log_trace("in %s for diags.log, oldname=%s\n", __func__, oldname);
564           BaseLogFile *n = new BaseLogFile(oldname);
565           if (setup_diagslog(n)) {
566             BaseLogFile *old_diags = diags_log;
567             lock();
568             diags_log = n;
569             unlock();
570             delete old_diags;
571           }
572           ats_free(oldname);
573           ret_val = true;
574         }
575       }
576     }
577 
578     if (diagslog_rolling_enabled == RollingEnabledValues::ROLL_ON_TIME ||
579         diagslog_rolling_enabled == RollingEnabledValues::ROLL_ON_TIME_OR_SIZE) {
580       time_t now = time(nullptr);
581       if (diagslog_rolling_interval != -1 && (now - diagslog_time_last_roll) >= diagslog_rolling_interval) {
582         fflush(diags_log->m_fp);
583         if (diags_log->roll()) {
584           diagslog_time_last_roll = now;
585           char *oldname           = ats_strdup(diags_log->get_name());
586           log_log_trace("in %s for diags.log, oldname=%s\n", __func__, oldname);
587           BaseLogFile *n = new BaseLogFile(oldname);
588           if (setup_diagslog(n)) {
589             BaseLogFile *old_diags = diags_log;
590             lock();
591             diags_log = n;
592             unlock();
593             delete old_diags;
594           }
595           ats_free(oldname);
596           ret_val = true;
597         }
598       }
599     }
600   }
601 
602   return ret_val;
603 }
604 
605 /*
606  * Checks stdout_log and stderr_log if their underlying files on disk need to be
607  * rolled, and does so if necessary.
608  *
609  * This function will replace the current BaseLogFile objects with a
610  * new one (if we choose to roll), as each BaseLogFile object logically
611  * represents one file on disk
612  *
613  * Note that, however, cross process race conditions may still exist, especially with
614  * the metafile, and further work with flock() for fcntl() may still need to be done.
615  *
616  * Returns true if any logs rolled, false otherwise
617  */
618 bool
should_roll_outputlog()619 Diags::should_roll_outputlog()
620 {
621   // stdout_log and stderr_log should never be nullptr as this point in time
622   ink_assert(stdout_log != nullptr);
623   ink_assert(stderr_log != nullptr);
624 
625   bool ret_val              = false;
626   bool need_consider_stderr = true;
627 
628   log_log_trace("%s was called\n", __func__);
629   log_log_trace("%s: rolling_enabled = %d, output_rolling_size = %d, output_rolling_interval = %d\n", __func__,
630                 outputlog_rolling_enabled, outputlog_rolling_size, outputlog_rolling_interval);
631   log_log_trace("%s: RollingEnabledValues::ROLL_ON_TIME = %d\n", __func__, RollingEnabledValues::ROLL_ON_TIME);
632   log_log_trace("%s: time(0) - last_roll_time = %d\n", __func__, time(nullptr) - outputlog_time_last_roll);
633   log_log_trace("%s: stdout_log = %p\n", __func__, stdout_log);
634 
635   // Roll stdout_log if necessary
636   if (stdout_log->is_init()) {
637     if (outputlog_rolling_enabled == RollingEnabledValues::ROLL_ON_SIZE ||
638         outputlog_rolling_enabled == RollingEnabledValues::ROLL_ON_TIME_OR_SIZE) {
639       // if we can't even check the file, we can forget about rotating
640       struct stat buf;
641       if (fstat(fileno(stdout_log->m_fp), &buf) != 0) {
642         return false;
643       }
644 
645       off_t size = buf.st_size;
646       if (outputlog_rolling_size != -1 && size >= static_cast<off_t>(outputlog_rolling_size) * BYTES_IN_MB) {
647         // since usually stdout and stderr are the same file on disk, we should just
648         // play it safe and just flush both BaseLogFiles
649         if (stderr_log->is_init()) {
650           fflush(stderr_log->m_fp);
651         }
652         fflush(stdout_log->m_fp);
653 
654         if (stdout_log->roll()) {
655           char *oldname = ats_strdup(stdout_log->get_name());
656           log_log_trace("in %s(), oldname=%s\n", __func__, oldname);
657           set_std_output(StdStream::STDOUT, oldname);
658 
659           // if stderr and stdout are redirected to the same place, we should
660           // update the stderr_log object as well
661           if (!strcmp(oldname, stderr_log->get_name())) {
662             log_log_trace("oldname == stderr_log->get_name()\n");
663             set_std_output(StdStream::STDERR, oldname);
664             need_consider_stderr = false;
665           }
666           ats_free(oldname);
667           ret_val = true;
668         }
669       }
670     }
671 
672     if (outputlog_rolling_enabled == RollingEnabledValues::ROLL_ON_TIME ||
673         outputlog_rolling_enabled == RollingEnabledValues::ROLL_ON_TIME_OR_SIZE) {
674       time_t now = time(nullptr);
675       if (outputlog_rolling_interval != -1 && (now - outputlog_time_last_roll) >= outputlog_rolling_interval) {
676         // since usually stdout and stderr are the same file on disk, we should just
677         // play it safe and just flush both BaseLogFiles
678         if (stderr_log->is_init()) {
679           fflush(stderr_log->m_fp);
680         }
681         fflush(stdout_log->m_fp);
682 
683         if (stdout_log->roll()) {
684           outputlog_time_last_roll = now;
685           char *oldname            = ats_strdup(stdout_log->get_name());
686           log_log_trace("in %s, oldname=%s\n", __func__, oldname);
687           set_std_output(StdStream::STDOUT, oldname);
688 
689           // if stderr and stdout are redirected to the same place, we should
690           // update the stderr_log object as well
691           if (!strcmp(oldname, stderr_log->get_name())) {
692             log_log_trace("oldname == stderr_log->get_name()\n");
693             set_std_output(StdStream::STDERR, oldname);
694             need_consider_stderr = false;
695           }
696           ats_free(oldname);
697           ret_val = true;
698         }
699       }
700     }
701   }
702 
703   // This assertion has to be true since log rolling for traffic.out is only ever enabled
704   // (and useful) when traffic_server is NOT running in stand alone mode. If traffic_server
705   // is NOT running in stand alone mode, then stderr and stdout SHOULD ALWAYS be pointing
706   // to the same file (traffic.out).
707   //
708   // If for some reason, someone wants the feature to have stdout pointing to some file on
709   // disk, and stderr pointing to a different file on disk, and then also wants both files to
710   // rotate according to the (same || different) scheme, it would not be difficult to add
711   // some more config options in records.config and said feature into this function.
712   if (ret_val) {
713     ink_assert(!need_consider_stderr);
714   }
715 
716   return ret_val;
717 }
718 
719 /*
720  * Sets up a BaseLogFile for the specified file. Then it binds the specified standard steam
721  * to the aforementioned BaseLogFile.
722  *
723  * Returns true on successful binding and setup, false otherwise
724  */
725 bool
set_std_output(StdStream stream,const char * file)726 Diags::set_std_output(StdStream stream, const char *file)
727 {
728   const char *target_stream;
729   BaseLogFile **current;
730   BaseLogFile *old_log, *new_log;
731 
732   // If the caller is stupid, we give up
733   if (strcmp(file, "") == 0) {
734     return false;
735   }
736 
737   // Figure out which standard stream we want to redirect
738   if (stream == StdStream::STDOUT) {
739     target_stream = "stdout";
740     current       = &stdout_log;
741   } else {
742     target_stream = "stderr";
743     current       = &stderr_log;
744   }
745   (void)target_stream; // silence clang-analyzer for now
746   old_log = *current;
747   new_log = new BaseLogFile(file);
748 
749   // On any errors we quit
750   if (!new_log || new_log->open_file(output_logfile_perm) != BaseLogFile::LOG_FILE_NO_ERROR) {
751     log_log_error("[Warning]: unable to open file=%s to bind %s to\n", file, target_stream);
752     log_log_error("[Warning]: %s is currently not bound to anything\n", target_stream);
753     delete new_log;
754     lock();
755     *current = nullptr;
756     unlock();
757     return false;
758   }
759   if (!new_log->is_open()) {
760     log_log_error("[Warning]: file pointer for %s %s = nullptr\n", target_stream, file);
761     log_log_error("[Warning]: %s is currently not bound to anything\n", target_stream);
762     delete new_log;
763     lock();
764     *current = nullptr;
765     unlock();
766     return false;
767   }
768 
769   // Now exchange the pointer to the standard stream in question
770   lock();
771   *current = new_log;
772   bool ret = rebind_std_stream(stream, fileno(new_log->m_fp));
773   unlock();
774 
775   // Free the BaseLogFile we rotated out
776   if (old_log) {
777     delete old_log;
778   }
779 
780   // "this should never happen"^{TM}
781   ink_release_assert(ret);
782 
783   return ret;
784 }
785 
786 /*
787  * Helper function that rebinds a specified stream to specified file descriptor
788  *
789  * Returns true on success, false otherwise
790  */
791 bool
rebind_std_stream(StdStream stream,int new_fd)792 Diags::rebind_std_stream(StdStream stream, int new_fd)
793 {
794   const char *target_stream;
795   int stream_fd;
796 
797   // Figure out which stream to dup2
798   if (stream == StdStream::STDOUT) {
799     target_stream = "stdout";
800     stream_fd     = STDOUT_FILENO;
801   } else {
802     target_stream = "stderr";
803     stream_fd     = STDERR_FILENO;
804   }
805   (void)target_stream; // silence clang-analyzer for now
806 
807   if (new_fd < 0) {
808     log_log_error("[Warning]: TS unable to bind %s to new file descriptor=%d", target_stream, new_fd);
809   } else {
810     dup2(new_fd, stream_fd);
811     return true;
812   }
813   return false;
814 }
815