| /** @file |
| |
| A brief file description |
| |
| @section license License |
| |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| /**************************************************************************** |
| |
| Diags.cc |
| |
| This file contains code to manipulate run-time diagnostics, and print |
| warnings and errors at runtime. Action tags and debugging tags are |
| supported, allowing run-time conditionals affecting diagnostics. |
| |
| Joe User should only need to use the macros at the bottom of Diags.h |
| |
| |
| ****************************************************************************/ |
| |
| #include "tscore/BufferWriter.h" |
| #include "tscore/bwf_std_format.h" |
| #include "tscore/ink_platform.h" |
| #include "tscore/ink_memory.h" |
| #include "tscore/ink_defs.h" |
| #include "tscore/ink_error.h" |
| #include "tscore/ink_assert.h" |
| #include "tscore/ink_time.h" |
| #include "tscore/ink_hrtime.h" |
| #include "tscore/ink_thread.h" |
| #include "tscore/BufferWriter.h" |
| #include "tscore/Diags.h" |
| |
| int diags_on_for_plugins = 0; |
| int DiagsConfigState::enabled[2] = {0, 0}; |
| |
| // Global, used for all diagnostics |
| inkcoreapi Diags *diags = nullptr; |
| |
| static bool |
| location(const SourceLocation *loc, DiagsShowLocation show, DiagsLevel level) |
| { |
| if (loc && loc->valid()) { |
| switch (show) { |
| case SHOW_LOCATION_ALL: |
| return true; |
| case SHOW_LOCATION_DEBUG: |
| return level <= DL_Debug; |
| default: |
| return false; |
| } |
| } |
| |
| return false; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // |
| // Diags::Diags(char *bdt, char *bat) |
| // |
| // This is the constructor for the Diags class. The constructor takes |
| // two strings called the "base debug tags" (bdt) and the |
| // "base action tags" (bat). These represent debug/action overrides, |
| // to override the records.config values. They current come from |
| // command-line options. |
| // |
| // If bdt is not nullptr, and not "", it overrides records.config settings. |
| // If bat is not nullptr, and not "", it overrides records.config settings. |
| // |
| // When the constructor is done, records.config callbacks will be set, |
| // the initial values read, and the Diags instance will be ready to use. |
| // |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| Diags::Diags(std::string_view prefix_string, const char *bdt, const char *bat, BaseLogFile *_diags_log, int dl_perm, int ol_perm) |
| : diags_log(nullptr), |
| stdout_log(nullptr), |
| stderr_log(nullptr), |
| magic(DIAGS_MAGIC), |
| show_location(SHOW_LOCATION_NONE), |
| base_debug_tags(nullptr), |
| base_action_tags(nullptr), |
| prefix_str(prefix_string) |
| { |
| ink_release_assert(!prefix_str.empty()); |
| int i; |
| |
| cleanup_func = nullptr; |
| ink_mutex_init(&tag_table_lock); |
| |
| //////////////////////////////////////////////////////// |
| // initialize the default, base debugging/action tags // |
| //////////////////////////////////////////////////////// |
| |
| if (bdt && *bdt) { |
| base_debug_tags = ats_strdup(bdt); |
| } |
| if (bat && *bat) { |
| base_action_tags = ats_strdup(bat); |
| } |
| |
| config.enabled[DiagsTagType_Debug] = (base_debug_tags != nullptr); |
| config.enabled[DiagsTagType_Action] = (base_action_tags != nullptr); |
| diags_on_for_plugins = config.enabled[DiagsTagType_Debug]; |
| |
| // The caller must always provide a non-empty prefix. |
| |
| for (i = 0; i < DiagsLevel_Count; i++) { |
| config.outputs[i].to_stdout = false; |
| config.outputs[i].to_stderr = false; |
| config.outputs[i].to_syslog = false; |
| config.outputs[i].to_diagslog = true; |
| } |
| |
| // create default stdout and stderr BaseLogFile objects |
| // (in case the user of this class doesn't specify in the future) |
| stdout_log = new BaseLogFile("stdout"); |
| stderr_log = new BaseLogFile("stderr"); |
| stdout_log->open_file(); // should never fail |
| stderr_log->open_file(); // should never fail |
| |
| ////////////////////////////////////////////////////////////////// |
| // start off with empty tag tables, will build in reconfigure() // |
| ////////////////////////////////////////////////////////////////// |
| |
| activated_tags[DiagsTagType_Debug] = nullptr; |
| activated_tags[DiagsTagType_Action] = nullptr; |
| |
| outputlog_rolling_enabled = RollingEnabledValues::NO_ROLLING; |
| outputlog_rolling_interval = -1; |
| outputlog_rolling_size = -1; |
| diagslog_rolling_enabled = RollingEnabledValues::NO_ROLLING; |
| diagslog_rolling_interval = -1; |
| diagslog_rolling_size = -1; |
| |
| outputlog_time_last_roll = time(nullptr); |
| diagslog_time_last_roll = time(nullptr); |
| |
| diags_logfile_perm = dl_perm; |
| output_logfile_perm = ol_perm; |
| |
| if (setup_diagslog(_diags_log)) { |
| diags_log = _diags_log; |
| } |
| } |
| |
| Diags::~Diags() |
| { |
| if (diags_log) { |
| delete diags_log; |
| diags_log = nullptr; |
| } |
| |
| if (stdout_log) { |
| delete stdout_log; |
| stdout_log = nullptr; |
| } |
| |
| if (stderr_log) { |
| delete stderr_log; |
| stderr_log = nullptr; |
| } |
| |
| ats_free((void *)base_debug_tags); |
| ats_free((void *)base_action_tags); |
| |
| deactivate_all(DiagsTagType_Debug); |
| deactivate_all(DiagsTagType_Action); |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // |
| // void Diags::print_va(...) |
| // |
| // This is the lowest-level diagnostic printing routine, that does the |
| // work of formatting and outputting diagnostic and error messages, |
| // in the standard format. |
| // |
| // This routine takes an optional <debug_tag>, which is printed in |
| // parentheses if its value is not nullptr. It takes a <diags_level>, |
| // which is converted to a prefix string. |
| // print_va takes an optional source location structure pointer <loc>, |
| // which can be nullptr. If <loc> is not NULL, the source code location |
| // is converted to a string, and printed between angle brackets. |
| // Finally, it takes a printf format string <format_string>, and a |
| // va_list list of varargs. |
| // |
| // This routine outputs to all of the output targets enabled for this |
| // debugging level in config.outputs[diags_level]. Many higher level |
| // diagnostics printing routines are built upon print_va, including: |
| // |
| // void print(...) |
| // void log_va(...) |
| // void log(...) |
| // |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| void |
| Diags::print_va(const char *debug_tag, DiagsLevel diags_level, const SourceLocation *loc, const char *format_string, |
| va_list ap) const |
| { |
| ink_release_assert(diags_level < DiagsLevel_Count); |
| ts::LocalBufferWriter<1024> format_writer; |
| |
| // Save room for optional newline and terminating NUL bytes. |
| format_writer.clip(2); |
| |
| format_writer.print("[{timestamp}] "); |
| auto timestamp_offset = format_writer.size(); |
| |
| format_writer.print("{thread-name}"); |
| format_writer.print(" {}: ", level_name(diags_level)); |
| |
| if (location(loc, show_location, diags_level)) { |
| format_writer.print("<{}> ", *loc); |
| } |
| |
| if (debug_tag) { |
| format_writer.print("({}) ", debug_tag); |
| } |
| |
| format_writer.print("{}", format_string); |
| |
| format_writer.extend(2); // restore the space for required termination. |
| if (format_writer.view().back() != '\n') { // safe because always some chars in the buffer. |
| format_writer.write('\n'); |
| } |
| format_writer.write('\0'); |
| |
| ////////////////////////////////////// |
| // now, finally, output the message // |
| ////////////////////////////////////// |
| |
| lock(); |
| if (config.outputs[diags_level].to_diagslog) { |
| if (diags_log && diags_log->m_fp) { |
| va_list tmp; |
| va_copy(tmp, ap); |
| vfprintf(diags_log->m_fp, format_writer.data(), tmp); |
| va_end(tmp); |
| } |
| } |
| |
| if (config.outputs[diags_level].to_stdout) { |
| if (stdout_log && stdout_log->m_fp) { |
| va_list tmp; |
| va_copy(tmp, ap); |
| vfprintf(stdout_log->m_fp, format_writer.data(), tmp); |
| va_end(tmp); |
| } |
| } |
| |
| if (config.outputs[diags_level].to_stderr) { |
| if (stderr_log && stderr_log->m_fp) { |
| va_list tmp; |
| va_copy(tmp, ap); |
| vfprintf(stderr_log->m_fp, format_writer.data(), tmp); |
| va_end(tmp); |
| } |
| } |
| |
| #if !defined(freebsd) |
| unlock(); |
| #endif |
| |
| if (config.outputs[diags_level].to_syslog) { |
| int priority; |
| char syslog_buffer[2048]; |
| |
| switch (diags_level) { |
| case DL_Diag: |
| case DL_Debug: |
| priority = LOG_DEBUG; |
| |
| break; |
| case DL_Status: |
| priority = LOG_INFO; |
| break; |
| case DL_Note: |
| priority = LOG_NOTICE; |
| break; |
| case DL_Warning: |
| priority = LOG_WARNING; |
| break; |
| case DL_Error: |
| priority = LOG_ERR; |
| break; |
| case DL_Fatal: |
| priority = LOG_CRIT; |
| break; |
| case DL_Alert: |
| priority = LOG_ALERT; |
| break; |
| case DL_Emergency: |
| priority = LOG_EMERG; |
| break; |
| default: |
| priority = LOG_NOTICE; |
| break; |
| } |
| vsnprintf(syslog_buffer, sizeof(syslog_buffer), format_writer.data() + timestamp_offset, ap); |
| syslog(priority, "%s", syslog_buffer); |
| } |
| |
| #if defined(freebsd) |
| unlock(); |
| #endif |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // |
| // bool Diags::tag_activated(char * tag, DiagsTagType mode) |
| // |
| // This routine inquires if a particular <tag> in the tag table of |
| // type <mode> is activated, returning true if it is, false if it |
| // isn't. If <tag> is nullptr, true is returned. The call uses a lock |
| // to get atomic access to the tag tables. |
| // |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| bool |
| Diags::tag_activated(const char *tag, DiagsTagType mode) const |
| { |
| bool activated = false; |
| |
| if (tag == nullptr) { |
| return (true); |
| } |
| |
| lock(); |
| if (activated_tags[mode]) { |
| activated = (activated_tags[mode]->match(tag) != -1); |
| } |
| unlock(); |
| |
| return (activated); |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // |
| // void Diags::activate_taglist(char * taglist, DiagsTagType mode) |
| // |
| // This routine adds all tags in the vertical-bar-separated taglist |
| // to the tag table of type <mode>. Each addition is done under a lock. |
| // If an individual tag is already set, that tag is ignored. If |
| // <taglist> is nullptr, this routine exits immediately. |
| // |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| void |
| Diags::activate_taglist(const char *taglist, DiagsTagType mode) |
| { |
| if (taglist) { |
| lock(); |
| if (activated_tags[mode]) { |
| delete activated_tags[mode]; |
| } |
| activated_tags[mode] = new DFA; |
| activated_tags[mode]->compile(taglist); |
| unlock(); |
| } |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // |
| // void Diags::deactivate_all(DiagsTagType mode) |
| // |
| // This routine deactivates all tags in the tag table of type <mode>. |
| // The deactivation is done under a lock. When done, the taglist will |
| // be empty. |
| // |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| void |
| Diags::deactivate_all(DiagsTagType mode) |
| { |
| lock(); |
| if (activated_tags[mode]) { |
| delete activated_tags[mode]; |
| activated_tags[mode] = nullptr; |
| } |
| unlock(); |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // |
| // const char *Diags::level_name(DiagsLevel dl) |
| // |
| // This routine returns a string name corresponding to the error |
| // level <dl>, suitable for us as an output log entry prefix. |
| // |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| const char * |
| Diags::level_name(DiagsLevel dl) const |
| { |
| switch (dl) { |
| case DL_Diag: |
| return ("DIAG"); |
| case DL_Debug: |
| return ("DEBUG"); |
| case DL_Status: |
| return ("STATUS"); |
| case DL_Note: |
| return ("NOTE"); |
| case DL_Warning: |
| return ("WARNING"); |
| case DL_Error: |
| return ("ERROR"); |
| case DL_Fatal: |
| return ("FATAL"); |
| case DL_Alert: |
| return ("ALERT"); |
| case DL_Emergency: |
| return ("EMERGENCY"); |
| default: |
| return ("DIAG"); |
| } |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // |
| // void Diags::dump(FILE *fp) |
| // |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| void |
| Diags::dump(FILE *fp) const |
| { |
| int i; |
| |
| fprintf(fp, "Diags:\n"); |
| fprintf(fp, " debug.enabled: %d\n", config.enabled[DiagsTagType_Debug]); |
| fprintf(fp, " debug default tags: '%s'\n", (base_debug_tags ? base_debug_tags : "NULL")); |
| fprintf(fp, " action.enabled: %d\n", config.enabled[DiagsTagType_Action]); |
| fprintf(fp, " action default tags: '%s'\n", (base_action_tags ? base_action_tags : "NULL")); |
| fprintf(fp, " outputs:\n"); |
| for (i = 0; i < DiagsLevel_Count; i++) { |
| fprintf(fp, " %10s [stdout=%d, stderr=%d, syslog=%d, diagslog=%d]\n", level_name(static_cast<DiagsLevel>(i)), |
| config.outputs[i].to_stdout, config.outputs[i].to_stderr, config.outputs[i].to_syslog, config.outputs[i].to_diagslog); |
| } |
| } |
| |
| void |
| Diags::error_va(DiagsLevel level, const SourceLocation *loc, const char *format_string, va_list ap) const |
| { |
| print_va(nullptr, level, loc, format_string, ap); |
| |
| if (DiagsLevel_IsTerminal(level)) { |
| va_list ap2; |
| |
| va_copy(ap2, ap); |
| if (cleanup_func) { |
| cleanup_func(); |
| } |
| |
| // DL_Emergency means the process cannot recover from a reboot |
| if (level == DL_Emergency) { |
| ink_emergency_va(format_string, ap2); |
| } else { |
| ink_fatal_va(format_string, ap2); |
| } |
| va_end(ap2); |
| } |
| } |
| |
| /* |
| * Sets up and error handles the given BaseLogFile object to work |
| * with this instance of Diags. |
| * |
| * Returns true on success, false otherwise |
| */ |
| bool |
| Diags::setup_diagslog(BaseLogFile *blf) |
| { |
| if (blf != nullptr) { |
| if (blf->open_file(diags_logfile_perm) != BaseLogFile::LOG_FILE_NO_ERROR) { |
| log_log_error("Could not open diags log file: %s\n", strerror(errno)); |
| delete blf; |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| void |
| Diags::config_roll_diagslog(RollingEnabledValues re, int ri, int rs) |
| { |
| diagslog_rolling_enabled = re; |
| diagslog_rolling_interval = ri; |
| diagslog_rolling_size = rs; |
| } |
| |
| void |
| Diags::config_roll_outputlog(RollingEnabledValues re, int ri, int rs) |
| { |
| outputlog_rolling_enabled = re; |
| outputlog_rolling_interval = ri; |
| outputlog_rolling_size = rs; |
| } |
| |
| /* |
| * Update diags_log to use the underlying file on disk. |
| * |
| * This function will replace the current BaseLogFile object with a new one, as |
| * each BaseLogFile object logically represents one file on disk. It can be |
| * used when we want to re-open the log file if the initial one was moved. |
| * |
| * Note that, however, cross process race conditions may still exist, |
| * especially with the metafile, and further work with flock() for fcntl() may |
| * still need to be done. |
| * |
| * Returns true if the log was reseated, false otherwise. |
| */ |
| bool |
| Diags::reseat_diagslog() |
| { |
| if (diags_log == nullptr || !diags_log->is_init()) { |
| return false; |
| } |
| fflush(diags_log->m_fp); |
| char *oldname = ats_strdup(diags_log->get_name()); |
| log_log_trace("in %s for diags.log, oldname=%s\n", __func__, oldname); |
| BaseLogFile *n = new BaseLogFile(oldname); |
| if (setup_diagslog(n)) { |
| BaseLogFile *old_diags = diags_log; |
| lock(); |
| diags_log = n; |
| unlock(); |
| delete old_diags; |
| } |
| ats_free(oldname); |
| return true; |
| } |
| |
| /* |
| * Checks diags_log 's underlying file on disk and see if it needs to be rolled, |
| * and does so if necessary. |
| * |
| * This function will replace the current BaseLogFile object with a new one |
| * (if we choose to roll), as each BaseLogFile object logically represents one |
| * file on disk. |
| * |
| * Note that, however, cross process race conditions may still exist, especially with |
| * the metafile, and further work with flock() for fcntl() may still need to be done. |
| * |
| * Returns true if any logs rolled, false otherwise |
| */ |
| bool |
| Diags::should_roll_diagslog() |
| { |
| bool ret_val = false; |
| |
| log_log_trace("%s was called\n", __func__); |
| log_log_trace("%s: rolling_enabled = %d, output_rolling_size = %d, output_rolling_interval = %d\n", __func__, |
| diagslog_rolling_enabled, diagslog_rolling_size, diagslog_rolling_interval); |
| log_log_trace("%s: RollingEnabledValues::ROLL_ON_TIME = %d\n", __func__, RollingEnabledValues::ROLL_ON_TIME); |
| log_log_trace("%s: time(0) - last_roll_time = %d\n", __func__, time(nullptr) - diagslog_time_last_roll); |
| |
| // Roll diags_log if necessary |
| if (diags_log && diags_log->is_init()) { |
| if (diagslog_rolling_enabled == RollingEnabledValues::ROLL_ON_SIZE || |
| diagslog_rolling_enabled == RollingEnabledValues::ROLL_ON_TIME_OR_SIZE) { |
| // if we can't even check the file, we can forget about rotating |
| struct stat buf; |
| if (fstat(fileno(diags_log->m_fp), &buf) != 0) { |
| return false; |
| } |
| |
| off_t size = buf.st_size; |
| if (diagslog_rolling_size != -1 && size >= (static_cast<off_t>(diagslog_rolling_size) * BYTES_IN_MB)) { |
| fflush(diags_log->m_fp); |
| if (diags_log->roll()) { |
| char *oldname = ats_strdup(diags_log->get_name()); |
| log_log_trace("in %s for diags.log, oldname=%s\n", __func__, oldname); |
| BaseLogFile *n = new BaseLogFile(oldname); |
| if (setup_diagslog(n)) { |
| BaseLogFile *old_diags = diags_log; |
| lock(); |
| diags_log = n; |
| unlock(); |
| delete old_diags; |
| } |
| ats_free(oldname); |
| ret_val = true; |
| } |
| } |
| } |
| |
| if (diagslog_rolling_enabled == RollingEnabledValues::ROLL_ON_TIME || |
| diagslog_rolling_enabled == RollingEnabledValues::ROLL_ON_TIME_OR_SIZE) { |
| time_t now = time(nullptr); |
| if (diagslog_rolling_interval != -1 && (now - diagslog_time_last_roll) >= diagslog_rolling_interval) { |
| fflush(diags_log->m_fp); |
| if (diags_log->roll()) { |
| diagslog_time_last_roll = now; |
| char *oldname = ats_strdup(diags_log->get_name()); |
| log_log_trace("in %s for diags.log, oldname=%s\n", __func__, oldname); |
| BaseLogFile *n = new BaseLogFile(oldname); |
| if (setup_diagslog(n)) { |
| BaseLogFile *old_diags = diags_log; |
| lock(); |
| diags_log = n; |
| unlock(); |
| delete old_diags; |
| } |
| ats_free(oldname); |
| ret_val = true; |
| } |
| } |
| } |
| } |
| |
| return ret_val; |
| } |
| |
| /* |
| * Checks stdout_log and stderr_log if their underlying files on disk need to be |
| * rolled, and does so if necessary. |
| * |
| * This function will replace the current BaseLogFile objects with a |
| * new one (if we choose to roll), as each BaseLogFile object logically |
| * represents one file on disk |
| * |
| * Note that, however, cross process race conditions may still exist, especially with |
| * the metafile, and further work with flock() for fcntl() may still need to be done. |
| * |
| * Returns true if any logs rolled, false otherwise |
| */ |
| bool |
| Diags::should_roll_outputlog() |
| { |
| // stdout_log and stderr_log should never be nullptr as this point in time |
| ink_assert(stdout_log != nullptr); |
| ink_assert(stderr_log != nullptr); |
| |
| bool ret_val = false; |
| bool need_consider_stderr = true; |
| |
| log_log_trace("%s was called\n", __func__); |
| log_log_trace("%s: rolling_enabled = %d, output_rolling_size = %d, output_rolling_interval = %d\n", __func__, |
| outputlog_rolling_enabled, outputlog_rolling_size, outputlog_rolling_interval); |
| log_log_trace("%s: RollingEnabledValues::ROLL_ON_TIME = %d\n", __func__, RollingEnabledValues::ROLL_ON_TIME); |
| log_log_trace("%s: time(0) - last_roll_time = %d\n", __func__, time(nullptr) - outputlog_time_last_roll); |
| log_log_trace("%s: stdout_log = %p\n", __func__, stdout_log); |
| |
| // Roll stdout_log if necessary |
| if (stdout_log->is_init()) { |
| if (outputlog_rolling_enabled == RollingEnabledValues::ROLL_ON_SIZE || |
| outputlog_rolling_enabled == RollingEnabledValues::ROLL_ON_TIME_OR_SIZE) { |
| // if we can't even check the file, we can forget about rotating |
| struct stat buf; |
| if (fstat(fileno(stdout_log->m_fp), &buf) != 0) { |
| return false; |
| } |
| |
| off_t size = buf.st_size; |
| if (outputlog_rolling_size != -1 && size >= static_cast<off_t>(outputlog_rolling_size) * BYTES_IN_MB) { |
| // since usually stdout and stderr are the same file on disk, we should just |
| // play it safe and just flush both BaseLogFiles |
| if (stderr_log->is_init()) { |
| fflush(stderr_log->m_fp); |
| } |
| fflush(stdout_log->m_fp); |
| |
| if (stdout_log->roll()) { |
| char *oldname = ats_strdup(stdout_log->get_name()); |
| log_log_trace("in %s(), oldname=%s\n", __func__, oldname); |
| set_std_output(StdStream::STDOUT, oldname); |
| |
| // if stderr and stdout are redirected to the same place, we should |
| // update the stderr_log object as well |
| if (!strcmp(oldname, stderr_log->get_name())) { |
| log_log_trace("oldname == stderr_log->get_name()\n"); |
| set_std_output(StdStream::STDERR, oldname); |
| need_consider_stderr = false; |
| } |
| ats_free(oldname); |
| ret_val = true; |
| } |
| } |
| } |
| |
| if (outputlog_rolling_enabled == RollingEnabledValues::ROLL_ON_TIME || |
| outputlog_rolling_enabled == RollingEnabledValues::ROLL_ON_TIME_OR_SIZE) { |
| time_t now = time(nullptr); |
| if (outputlog_rolling_interval != -1 && (now - outputlog_time_last_roll) >= outputlog_rolling_interval) { |
| // since usually stdout and stderr are the same file on disk, we should just |
| // play it safe and just flush both BaseLogFiles |
| if (stderr_log->is_init()) { |
| fflush(stderr_log->m_fp); |
| } |
| fflush(stdout_log->m_fp); |
| |
| if (stdout_log->roll()) { |
| outputlog_time_last_roll = now; |
| char *oldname = ats_strdup(stdout_log->get_name()); |
| log_log_trace("in %s, oldname=%s\n", __func__, oldname); |
| set_std_output(StdStream::STDOUT, oldname); |
| |
| // if stderr and stdout are redirected to the same place, we should |
| // update the stderr_log object as well |
| if (!strcmp(oldname, stderr_log->get_name())) { |
| log_log_trace("oldname == stderr_log->get_name()\n"); |
| set_std_output(StdStream::STDERR, oldname); |
| need_consider_stderr = false; |
| } |
| ats_free(oldname); |
| ret_val = true; |
| } |
| } |
| } |
| } |
| |
| // This assertion has to be true since log rolling for traffic.out is only ever enabled |
| // (and useful) when traffic_server is NOT running in stand alone mode. If traffic_server |
| // is NOT running in stand alone mode, then stderr and stdout SHOULD ALWAYS be pointing |
| // to the same file (traffic.out). |
| // |
| // If for some reason, someone wants the feature to have stdout pointing to some file on |
| // disk, and stderr pointing to a different file on disk, and then also wants both files to |
| // rotate according to the (same || different) scheme, it would not be difficult to add |
| // some more config options in records.config and said feature into this function. |
| if (ret_val) { |
| ink_assert(!need_consider_stderr); |
| } |
| |
| return ret_val; |
| } |
| |
| /* |
| * Sets up a BaseLogFile for the specified file. Then it binds the specified standard steam |
| * to the aforementioned BaseLogFile. |
| * |
| * Returns true on successful binding and setup, false otherwise |
| */ |
| bool |
| Diags::set_std_output(StdStream stream, const char *file) |
| { |
| const char *target_stream; |
| BaseLogFile **current; |
| BaseLogFile *old_log, *new_log; |
| |
| // If the caller is stupid, we give up |
| if (strcmp(file, "") == 0) { |
| return false; |
| } |
| |
| // Figure out which standard stream we want to redirect |
| if (stream == StdStream::STDOUT) { |
| target_stream = "stdout"; |
| current = &stdout_log; |
| } else { |
| target_stream = "stderr"; |
| current = &stderr_log; |
| } |
| (void)target_stream; // silence clang-analyzer for now |
| old_log = *current; |
| new_log = new BaseLogFile(file); |
| |
| // On any errors we quit |
| if (!new_log || new_log->open_file(output_logfile_perm) != BaseLogFile::LOG_FILE_NO_ERROR) { |
| log_log_error("[Warning]: unable to open file=%s to bind %s to\n", file, target_stream); |
| log_log_error("[Warning]: %s is currently not bound to anything\n", target_stream); |
| delete new_log; |
| lock(); |
| *current = nullptr; |
| unlock(); |
| return false; |
| } |
| if (!new_log->is_open()) { |
| log_log_error("[Warning]: file pointer for %s %s = nullptr\n", target_stream, file); |
| log_log_error("[Warning]: %s is currently not bound to anything\n", target_stream); |
| delete new_log; |
| lock(); |
| *current = nullptr; |
| unlock(); |
| return false; |
| } |
| |
| // Now exchange the pointer to the standard stream in question |
| lock(); |
| *current = new_log; |
| bool ret = rebind_std_stream(stream, fileno(new_log->m_fp)); |
| unlock(); |
| |
| // Free the BaseLogFile we rotated out |
| if (old_log) { |
| delete old_log; |
| } |
| |
| // "this should never happen"^{TM} |
| ink_release_assert(ret); |
| |
| return ret; |
| } |
| |
| /* |
| * Helper function that rebinds a specified stream to specified file descriptor |
| * |
| * Returns true on success, false otherwise |
| */ |
| bool |
| Diags::rebind_std_stream(StdStream stream, int new_fd) |
| { |
| const char *target_stream; |
| int stream_fd; |
| |
| // Figure out which stream to dup2 |
| if (stream == StdStream::STDOUT) { |
| target_stream = "stdout"; |
| stream_fd = STDOUT_FILENO; |
| } else { |
| target_stream = "stderr"; |
| stream_fd = STDERR_FILENO; |
| } |
| (void)target_stream; // silence clang-analyzer for now |
| |
| if (new_fd < 0) { |
| log_log_error("[Warning]: TS unable to bind %s to new file descriptor=%d", target_stream, new_fd); |
| } else { |
| dup2(new_fd, stream_fd); |
| return true; |
| } |
| return false; |
| } |