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