| /** @file | |
| This file implements the LogConfig object. | |
| @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. | |
| */ | |
| #include "tscore/ink_platform.h" | |
| #include "tscore/Layout.h" | |
| #include "iocore/utils/Machine.h" | |
| #include <memory> | |
| using namespace std::literals; | |
| #include "tscore/ink_platform.h" | |
| #include "tscore/ink_file.h" | |
| #include "tscore/Filenames.h" | |
| #include "tscore/List.h" | |
| #include "tscore/LogMessage.h" | |
| #include "proxy/logging/Log.h" | |
| #include "proxy/logging/LogField.h" | |
| #include "proxy/logging/LogFilter.h" | |
| #include "proxy/logging/LogFormat.h" | |
| #include "proxy/logging/LogFile.h" | |
| #include "proxy/logging/LogBuffer.h" | |
| #include "proxy/logging/LogObject.h" | |
| #include "proxy/logging/LogConfig.h" | |
| #include "proxy/logging/LogUtils.h" | |
| #include "tscore/SimpleTokenizer.h" | |
| #include "proxy/logging/YamlLogConfig.h" | |
| #include "mgmt/config/ConfigContextDiags.h" | |
| #include "mgmt/config/ConfigRegistry.h" | |
| #define DISK_IS_CONFIG_FULL_MESSAGE \ | |
| "Access logging to local log directory suspended - " \ | |
| "configured space allocation exhausted." | |
| #define DISK_IS_ACTUAL_FULL_MESSAGE \ | |
| "Access logging to local log directory suspended - " \ | |
| "no more space on the logging partition." | |
| #define DISK_IS_CONFIG_LOW_MESSAGE \ | |
| "Access logging to local log directory suspended - " \ | |
| "configured space allocation almost exhausted." | |
| #define DISK_IS_ACTUAL_LOW_MESSAGE "Access logging to local log directory suspended - partition space is low." | |
| #define PARTITION_HEADROOM_MB 10 | |
| #define DIAGS_LOG_FILENAME "diags.log" | |
| #define MANAGER_LOG_FILENAME "manager.log" | |
| namespace | |
| { | |
| DbgCtl dbg_ctl_logspace{"logspace"}; | |
| DbgCtl dbg_ctl_log{"log"}; | |
| DbgCtl dbg_ctl_log_config{"log-config"}; | |
| } // end anonymous namespace | |
| void | |
| LogConfig::reconfigure_mgmt_variables(swoc::MemSpan<void>) | |
| { | |
| Note("received log reconfiguration event, rolling now"); | |
| Log::config->roll_log_files_now = true; | |
| } | |
| void | |
| LogConfig::register_rolled_log_auto_delete(std::string_view logname, int rolling_min_count) | |
| { | |
| if (!auto_delete_rolled_files) { | |
| // Nothing to do if auto-deletion is not configured. | |
| return; | |
| } | |
| Dbg(dbg_ctl_logspace, "Registering rotated log deletion for %s with min roll count %d", std::string(logname).c_str(), | |
| rolling_min_count); | |
| rolledLogDeleter.register_log_type_for_deletion(logname, rolling_min_count); | |
| } | |
| void | |
| LogConfig::read_configuration_variables() | |
| { | |
| int val; | |
| val = RecGetRecordInt("proxy.config.log.log_buffer_size").value_or(0); | |
| if (val > 0) { | |
| log_buffer_size = val; | |
| } | |
| val = RecGetRecordInt("proxy.config.log.log_fast_buffer").value_or(0); | |
| if (val > 0) { | |
| log_fast_buffer = true; | |
| } | |
| val = RecGetRecordInt("proxy.config.log.max_secs_per_buffer").value_or(0); | |
| if (val > 0) { | |
| max_secs_per_buffer = val; | |
| } | |
| val = RecGetRecordInt("proxy.config.log.max_space_mb_for_logs").value_or(0); | |
| if (val > 0) { | |
| max_space_mb_for_logs = val; | |
| } | |
| val = RecGetRecordInt("proxy.config.log.max_space_mb_headroom").value_or(0); | |
| if (val > 0) { | |
| max_space_mb_headroom = val; | |
| } | |
| val = RecGetRecordInt("proxy.config.log.io.max_buffer_index").value_or(0); | |
| if (val > 0) { | |
| logbuffer_max_iobuf_index = val; | |
| } | |
| { | |
| auto str{RecGetRecordStringAlloc("proxy.config.log.logfile_perm")}; | |
| if (auto logfile_perm_parsed{ink_fileperm_parse(ats_as_c_str(str))}; logfile_perm_parsed != -1) { | |
| logfile_perm = logfile_perm_parsed; | |
| } | |
| } | |
| if (auto str{RecGetRecordStringAlloc("proxy.config.log.hostname")}; str && !str.value().empty() && str.value() != "localhost"sv) { | |
| ats_free(hostname); | |
| hostname = ats_stringdup(str); | |
| } | |
| if (auto str{RecGetRecordStringAlloc("proxy.config.error.logfile.filename")}; str && !str.value().empty()) { | |
| ats_free(error_log_filename); | |
| error_log_filename = ats_stringdup(str); | |
| } | |
| ats_free(logfile_dir); | |
| logfile_dir = ats_stringdup(RecConfigReadLogDir()); | |
| if (access(logfile_dir, R_OK | W_OK | X_OK) == -1) { | |
| // Try 'system_root_dir/var/log/trafficserver' directory | |
| fprintf(stderr, "unable to access log directory '%s': %d, %s\n", logfile_dir, errno, strerror(errno)); | |
| fprintf(stderr, "please set 'proxy.config.log.logfile_dir'\n"); | |
| ::exit(1); | |
| } | |
| val = RecGetRecordInt("proxy.config.log.preproc_threads").value_or(0); | |
| if (val > 0 && val <= 128) { | |
| preproc_threads = val; | |
| } | |
| // ROLLING | |
| // we don't check for valid values of rolling_enabled, rolling_interval_sec, | |
| // rolling_offset_hr, or rolling_size_mb because the LogObject takes care of this | |
| // | |
| rolling_interval_sec = RecGetRecordInt("proxy.config.log.rolling_interval_sec").value_or(0); | |
| rolling_offset_hr = RecGetRecordInt("proxy.config.log.rolling_offset_hr").value_or(0); | |
| rolling_size_mb = RecGetRecordInt("proxy.config.log.rolling_size_mb").value_or(0); | |
| rolling_min_count = RecGetRecordInt("proxy.config.log.rolling_min_count").value_or(0); | |
| val = RecGetRecordInt("proxy.config.log.rolling_enabled").value_or(0); | |
| if (LogRollingEnabledIsValid(val)) { | |
| rolling_enabled = static_cast<Log::RollingEnabledValues>(val); | |
| } else { | |
| Warning("invalid value '%d' for '%s', disabling log rolling", val, "proxy.config.log.rolling_enabled"); | |
| rolling_enabled = Log::NO_ROLLING; | |
| } | |
| val = RecGetRecordInt("proxy.config.log.auto_delete_rolled_files").value_or(0); | |
| auto_delete_rolled_files = (val > 0); | |
| val = RecGetRecordInt("proxy.config.log.rolling_allow_empty").value_or(0); | |
| rolling_allow_empty = (val > 0); | |
| // THROTTLING | |
| val = RecGetRecordInt("proxy.config.log.throttling_interval_msec").value_or(0); | |
| if (LogThrottlingIsValid(val)) { | |
| LogMessage::set_default_log_throttling_interval(std::chrono::milliseconds{val}); | |
| } else { | |
| Warning("invalid value '%d' for '%s', disabling log rolling", val, "proxy.config.log.throttling_interval_msec"); | |
| } | |
| val = RecGetRecordInt("proxy.config.diags.debug.throttling_interval_msec").value_or(0); | |
| if (LogThrottlingIsValid(val)) { | |
| LogMessage::set_default_debug_throttling_interval(std::chrono::milliseconds{val}); | |
| } else { | |
| Warning("invalid value '%d' for '%s', disabling log rolling", val, "proxy.config.diags.debug.throttling_interval_msec"); | |
| } | |
| // Read in min_count control values for auto deletion | |
| if (auto_delete_rolled_files) { | |
| // The majority of register_rolled_log_auto_delete() updates come in | |
| // through LogObject. However, not all ATS logs are managed by LogObject. | |
| // The following register these other core logs for log rotation deletion. | |
| // For diagnostic logs | |
| val = RecGetRecordInt("proxy.config.diags.logfile.rolling_min_count").value_or(0); | |
| register_rolled_log_auto_delete(DIAGS_LOG_FILENAME, val); | |
| register_rolled_log_auto_delete(MANAGER_LOG_FILENAME, val); | |
| // For traffic.out | |
| auto configured_name{RecGetRecordStringAlloc("proxy.config.output.logfile.name")}; | |
| auto traffic_logname{configured_name && !configured_name.value().empty() ? configured_name.value() : "traffic.out"sv}; | |
| val = static_cast<int>(RecGetRecordInt("proxy.config.output.logfile.rolling_min_count").value_or(0)); | |
| register_rolled_log_auto_delete(traffic_logname, val); | |
| rolling_max_count = RecGetRecordInt("proxy.config.log.rolling_max_count").value_or(0); | |
| } | |
| // PERFORMANCE | |
| val = RecGetRecordInt("proxy.config.log.sampling_frequency").value_or(0); | |
| if (val > 0) { | |
| sampling_frequency = val; | |
| } | |
| val = RecGetRecordInt("proxy.config.log.file_stat_frequency").value_or(0); | |
| if (val > 0) { | |
| file_stat_frequency = val; | |
| } | |
| val = RecGetRecordInt("proxy.config.log.space_used_frequency").value_or(0); | |
| if (val > 0) { | |
| space_used_frequency = val; | |
| } | |
| // ASCII BUFFER | |
| val = RecGetRecordInt("proxy.config.log.ascii_buffer_size").value_or(0); | |
| if (val > 0) { | |
| ascii_buffer_size = val; | |
| } | |
| val = RecGetRecordInt("proxy.config.log.max_line_size").value_or(0); | |
| if (val > 0) { | |
| max_line_size = val; | |
| } | |
| } | |
| /*------------------------------------------------------------------------- | |
| LogConfig::LogConfig | |
| Read the logging configuration variables from the config file and | |
| initialize the LogConfig member variables. Assign some meaningful | |
| default value if we get garbage back from the config file. | |
| -------------------------------------------------------------------------*/ | |
| // TODO: Is UINT_MAX here really correct? | |
| LogConfig::LogConfig() | |
| { | |
| // Setup the default values for all LogConfig public variables so that | |
| // a LogConfig object is valid upon return from the constructor even | |
| // if no configuration file is read | |
| hostname = ats_strdup(Machine::instance()->host_name.c_str()); | |
| error_log_filename = ats_strdup("error.log"); | |
| logfile_dir = ats_strdup("."); | |
| } | |
| /*------------------------------------------------------------------------- | |
| LogConfig::~LogConfig | |
| Delete all config variable strings. | |
| -------------------------------------------------------------------------*/ | |
| LogConfig::~LogConfig() | |
| { | |
| ats_free(hostname); | |
| ats_free(error_log_filename); | |
| ats_free(logfile_dir); | |
| } | |
| /*------------------------------------------------------------------------- | |
| LogConfig::init | |
| -------------------------------------------------------------------------*/ | |
| void | |
| LogConfig::init(LogConfig *prev_config) | |
| { | |
| LogObject *errlog = nullptr; | |
| ink_assert(!initialized); | |
| // Inherit the reload context so evaluate_config() can log parse details | |
| // and access RPC-supplied YAML content. At startup prev_config is nullptr, | |
| // so reload_ctx stays default-constructed (all calls are safe no-ops). | |
| if (prev_config) { | |
| reload_ctx = prev_config->reload_ctx; | |
| } | |
| update_space_used(); | |
| // create log objects | |
| // | |
| if (Log::transaction_logging_enabled()) { | |
| setup_log_objects(); | |
| } | |
| // ---------------------------------------------------------------------- | |
| // Construct a new error log object candidate. | |
| if (Log::error_logging_enabled()) { | |
| std::unique_ptr<LogFormat> fmt(MakeTextLogFormat("error")); | |
| Dbg(dbg_ctl_log, "creating predefined error log object"); | |
| errlog = new LogObject(this, fmt.get(), logfile_dir, error_log_filename, LOG_FILE_ASCII, nullptr, rolling_enabled, | |
| preproc_threads, rolling_interval_sec, rolling_offset_hr, rolling_size_mb, /* auto_created */ false, | |
| rolling_max_count, rolling_min_count); | |
| log_object_manager.manage_object(errlog); | |
| errlog->set_fmt_timestamps(); | |
| } else { | |
| Log::error_log = nullptr; | |
| } | |
| if (prev_config) { | |
| // Transfer objects from previous configuration. | |
| transfer_objects(prev_config); | |
| // After transferring objects, we are going to keep either the new error log or the old one. Figure out | |
| // which one we are keeping and make that the global ... | |
| if (Log::error_log) { | |
| errlog = this->log_object_manager.find_by_format_name(Log::error_log->m_format->name()); | |
| } | |
| } | |
| ink_atomic_swap(&Log::error_log, errlog); | |
| initialized = true; | |
| } | |
| /*------------------------------------------------------------------------- | |
| LogConfig::display | |
| Dump the values for the current LogConfig object. | |
| -------------------------------------------------------------------------*/ | |
| void | |
| LogConfig::display(FILE *fd) | |
| { | |
| fprintf(fd, "-----------------------------\n"); | |
| fprintf(fd, "--- Logging Configuration ---\n"); | |
| fprintf(fd, "-----------------------------\n"); | |
| fprintf(fd, "Config variables:\n"); | |
| fprintf(fd, " log_buffer_size = %d\n", log_buffer_size); | |
| fprintf(fd, " max_secs_per_buffer = %d\n", max_secs_per_buffer); | |
| fprintf(fd, " max_space_mb_for_logs = %d\n", max_space_mb_for_logs); | |
| fprintf(fd, " max_space_mb_headroom = %d\n", max_space_mb_headroom); | |
| fprintf(fd, " hostname = %s\n", hostname); | |
| fprintf(fd, " logfile_dir = %s\n", logfile_dir); | |
| fprintf(fd, " logfile_perm = 0%o\n", logfile_perm); | |
| fprintf(fd, " error_log_filename = %s\n", error_log_filename); | |
| fprintf(fd, " preproc_threads = %d\n", preproc_threads); | |
| fprintf(fd, " rolling_enabled = %d\n", rolling_enabled); | |
| fprintf(fd, " rolling_interval_sec = %d\n", rolling_interval_sec); | |
| fprintf(fd, " rolling_offset_hr = %d\n", rolling_offset_hr); | |
| fprintf(fd, " rolling_size_mb = %d\n", rolling_size_mb); | |
| fprintf(fd, " rolling_min_count = %d\n", rolling_min_count); | |
| fprintf(fd, " rolling_max_count = %d\n", rolling_max_count); | |
| fprintf(fd, " rolling_allow_empty = %d\n", rolling_allow_empty); | |
| fprintf(fd, " auto_delete_rolled_files = %d\n", auto_delete_rolled_files); | |
| fprintf(fd, " sampling_frequency = %d\n", sampling_frequency); | |
| fprintf(fd, " file_stat_frequency = %d\n", file_stat_frequency); | |
| fprintf(fd, " space_used_frequency = %d\n", space_used_frequency); | |
| fprintf(fd, " logbuffer_max_iobuf_index = %d\n", logbuffer_max_iobuf_index); | |
| fprintf(fd, "\n"); | |
| fprintf(fd, "************ Log Objects (%u objects) ************\n", log_object_manager.get_num_objects()); | |
| log_object_manager.display(fd); | |
| fprintf(fd, "************ Filter List (%u filters) ************\n", filter_list.count()); | |
| filter_list.display(fd); | |
| fprintf(fd, "************ Format List (%u formats) ************\n", format_list.count()); | |
| format_list.display(fd); | |
| } | |
| //----------------------------------------------------------------------------- | |
| // setup_log_objects | |
| // | |
| // Construct: All custom objects. | |
| // | |
| // Upon return from this function: | |
| // - global_object_list has the aforementioned objects | |
| // - global_filter_list has all custom filters | |
| // | |
| void | |
| LogConfig::setup_log_objects() | |
| { | |
| Dbg(dbg_ctl_log, "creating objects..."); | |
| filter_list.clear(); | |
| // Evaluate logging.yaml to construct the custom log objects. | |
| evaluate_config(); | |
| // Open local pipes so readers can see them. | |
| log_object_manager.open_local_pipes(); | |
| if (dbg_ctl_log.on()) { | |
| log_object_manager.display(); | |
| } | |
| } | |
| /*------------------------------------------------------------------------- | |
| LogConfig::reconfigure | |
| This is the manager callback for any logging config variable change. | |
| Since we want to access the config variables to build a new config | |
| object, but can't from this function (big lock technology in the | |
| manager), we'll just set a flag and call the real reconfiguration | |
| function from the logging thread. | |
| -------------------------------------------------------------------------*/ | |
| void | |
| LogConfig::reconfigure(ConfigContext ctx) | |
| { | |
| CfgLoadDbg(ctx, dbg_ctl_log_config, "Reconfiguration request accepted"); | |
| Log::config->reconfiguration_needed = true; | |
| Log::config->reload_ctx = ctx; | |
| } | |
| /*------------------------------------------------------------------------- | |
| LogConfig::register_config_callbacks | |
| This static function is called by Log::init to register the config update | |
| function for each of the logging configuration variables. | |
| -------------------------------------------------------------------------*/ | |
| void | |
| LogConfig::register_config_callbacks() | |
| { | |
| static const char *names[] = { | |
| "proxy.config.log.log_buffer_size", | |
| "proxy.config.log.max_secs_per_buffer", | |
| "proxy.config.log.max_space_mb_for_logs", | |
| "proxy.config.log.max_space_mb_headroom", | |
| "proxy.config.log.error_log_filename", | |
| "proxy.config.log.logfile_perm", | |
| "proxy.config.log.hostname", | |
| "proxy.config.log.logfile_dir", | |
| "proxy.config.log.rolling_enabled", | |
| "proxy.config.log.rolling_interval_sec", | |
| "proxy.config.log.rolling_offset_hr", | |
| "proxy.config.log.rolling_size_mb", | |
| "proxy.config.log.auto_delete_rolled_files", | |
| "proxy.config.log.rolling_max_count", | |
| "proxy.config.log.rolling_allow_empty", | |
| "proxy.config.log.config.filename", | |
| "proxy.config.log.sampling_frequency", | |
| "proxy.config.log.file_stat_frequency", | |
| "proxy.config.log.space_used_frequency", | |
| "proxy.config.log.io.max_buffer_index", | |
| "proxy.config.log.throttling_interval_msec", | |
| "proxy.config.diags.debug.throttling_interval_msec", | |
| }; | |
| auto ®istry = config::ConfigRegistry::Get_Instance(); | |
| registry.register_config( | |
| "logging", ts::filename::LOGGING, "proxy.config.log.config.filename", [](ConfigContext ctx) { LogConfig::reconfigure(ctx); }, | |
| config::ConfigSource::FileOnly); | |
| for (unsigned i = 0; i < countof(names); ++i) { | |
| registry.attach("logging", names[i]); | |
| } | |
| } | |
| /*------------------------------------------------------------------------- | |
| LogConfig::register_stat_callbacks | |
| This static function is called by Log::init to register the stat update | |
| function for each of the logging stats variables. | |
| -------------------------------------------------------------------------*/ | |
| void | |
| LogConfig::register_stat_callbacks() | |
| { | |
| // | |
| // events | |
| // | |
| log_rsb.event_log_error_skip = Metrics::Counter::createPtr("proxy.process.log.event_log_error_skip"); | |
| log_rsb.event_log_error_ok = Metrics::Counter::createPtr("proxy.process.log.event_log_error_ok"); | |
| log_rsb.event_log_error_aggr = Metrics::Counter::createPtr("proxy.process.log.event_log_error_aggr"); | |
| log_rsb.event_log_error_full = Metrics::Counter::createPtr("proxy.process.log.event_log_error_full"); | |
| log_rsb.event_log_error_fail = Metrics::Counter::createPtr("proxy.process.log.event_log_error_fail"); | |
| log_rsb.event_log_access_ok = Metrics::Counter::createPtr("proxy.process.log.event_log_access_ok"); | |
| log_rsb.event_log_access_skip = Metrics::Counter::createPtr("proxy.process.log.event_log_access_skip"); | |
| log_rsb.event_log_access_aggr = Metrics::Counter::createPtr("proxy.process.log.event_log_access_aggr"); | |
| log_rsb.event_log_access_full = Metrics::Counter::createPtr("proxy.process.log.event_log_access_full"); | |
| log_rsb.event_log_access_fail = Metrics::Counter::createPtr("proxy.process.log.event_log_access_fail"); | |
| log_rsb.marshalled_bytes = Metrics::Counter::createPtr("proxy.process.log.marshalled_bytes"); | |
| log_rsb.num_sent_to_network = Metrics::Counter::createPtr("proxy.process.log.num_sent_to_network"); | |
| log_rsb.num_lost_before_sent_to_network = Metrics::Counter::createPtr("proxy.process.log.num_lost_before_sent_to_network"); | |
| log_rsb.num_received_from_network = Metrics::Counter::createPtr("proxy.process.log.num_received_from_network"); | |
| log_rsb.num_flush_to_disk = Metrics::Counter::createPtr("proxy.process.log.num_flush_to_disk"); | |
| log_rsb.num_lost_before_flush_to_disk = Metrics::Counter::createPtr("proxy.process.log.num_lost_before_flush_to_disk"); | |
| log_rsb.bytes_lost_before_preproc = Metrics::Counter::createPtr("proxy.process.log.bytes_lost_before_preproc"); | |
| log_rsb.bytes_sent_to_network = Metrics::Counter::createPtr("proxy.process.log.bytes_sent_to_network"); | |
| log_rsb.bytes_lost_before_sent_to_network = Metrics::Counter::createPtr("proxy.process.log.bytes_lost_before_sent_to_network"); | |
| log_rsb.bytes_received_from_network = Metrics::Counter::createPtr("proxy.process.log.bytes_received_from_network"); | |
| log_rsb.bytes_flush_to_disk = Metrics::Counter::createPtr("proxy.process.log.bytes_flush_to_disk"); | |
| log_rsb.bytes_lost_before_flush_to_disk = Metrics::Counter::createPtr("proxy.process.log.bytes_lost_before_flush_to_disk"); | |
| log_rsb.bytes_written_to_disk = Metrics::Counter::createPtr("proxy.process.log.bytes_written_to_disk"); | |
| log_rsb.bytes_lost_before_written_to_disk = Metrics::Counter::createPtr("proxy.process.log.bytes_lost_before_written_to_disk"); | |
| log_rsb.log_files_open = Metrics::Gauge::createPtr("proxy.process.log.log_files_open"); | |
| log_rsb.log_files_space_used = Metrics::Gauge::createPtr("proxy.process.log.log_files_space_used"); | |
| } | |
| /*------------------------------------------------------------------------- | |
| LogConfig::space_to_write | |
| This function returns true if there is enough disk space to write the | |
| given number of bytes, false otherwise. | |
| -------------------------------------------------------------------------*/ | |
| bool | |
| LogConfig::space_to_write(int64_t bytes_to_write) const | |
| { | |
| int64_t config_space, partition_headroom; | |
| int64_t logical_space_used, physical_space_left; | |
| bool space; | |
| config_space = static_cast<int64_t>(get_max_space_mb()) * LOG_MEGABYTE; | |
| partition_headroom = static_cast<int64_t>(PARTITION_HEADROOM_MB) * LOG_MEGABYTE; | |
| logical_space_used = m_space_used + bytes_to_write; | |
| physical_space_left = m_partition_space_left - bytes_to_write; | |
| space = ((logical_space_used < config_space) && (physical_space_left > partition_headroom)); | |
| Dbg(dbg_ctl_logspace, | |
| "logical space used %" PRId64 ", configured space %" PRId64 ", physical space left %" PRId64 ", partition headroom %" PRId64 | |
| ", space %s available", | |
| logical_space_used, config_space, physical_space_left, partition_headroom, space ? "is" : "is not"); | |
| return space; | |
| } | |
| /*------------------------------------------------------------------------- | |
| LogConfig::update_space_used | |
| Update the m_space_used variable by reading the logging dir and counting | |
| the total bytes being occupied by files. If we've used too much space | |
| (space_used > max_space - headroom) then start deleting some files (if | |
| auto_delete_rolled_files is set) to make room. Finally, update the | |
| space_used stat. | |
| This routine will only be executed SINGLE-THREADED, either by the main | |
| thread when a LogConfig is initialized, or by the event thread during the | |
| periodic space check. | |
| -------------------------------------------------------------------------*/ | |
| void | |
| LogConfig::update_space_used() | |
| { | |
| // no need to update space used if log directory is inaccessible | |
| // | |
| if (m_log_directory_inaccessible) { | |
| return; | |
| } | |
| int64_t total_space_used, partition_space_left; | |
| char path[MAXPATHLEN]; | |
| int sret; | |
| struct dirent *entry; | |
| struct stat sbuf; | |
| DIR *ld; | |
| // check if logging directory has been specified | |
| // | |
| if (!logfile_dir) { | |
| const char *msg = "Logging directory not specified"; | |
| Error("%s", msg); | |
| m_log_directory_inaccessible = true; | |
| return; | |
| } | |
| // check if logging directory exists and is searchable readable & writable | |
| int err; | |
| do { | |
| err = access(logfile_dir, R_OK | W_OK | X_OK); | |
| } while ((err < 0) && (errno == EINTR)); | |
| if (err < 0) { | |
| const char *msg = "Error accessing logging directory %s: %s."; | |
| Error(msg, logfile_dir, strerror(errno)); | |
| m_log_directory_inaccessible = true; | |
| return; | |
| } | |
| ld = ::opendir(logfile_dir); | |
| if (ld == nullptr) { | |
| const char *msg = "Error opening logging directory %s to perform a space check: %s."; | |
| Error(msg, logfile_dir, strerror(errno)); | |
| m_log_directory_inaccessible = true; | |
| return; | |
| } | |
| total_space_used = 0LL; | |
| while ((entry = readdir(ld))) { | |
| snprintf(path, MAXPATHLEN, "%s/%s", logfile_dir, entry->d_name); | |
| sret = ::stat(path, &sbuf); | |
| if (sret != -1 && S_ISREG(sbuf.st_mode)) { | |
| total_space_used += static_cast<int64_t>(sbuf.st_size); | |
| if (auto_delete_rolled_files && LogFile::rolled_logfile(entry->d_name)) { | |
| rolledLogDeleter.consider_for_candidacy(path, sbuf.st_size, sbuf.st_mtime); | |
| } | |
| } | |
| } | |
| ::closedir(ld); | |
| // | |
| // Now check the partition to see if there is enough *actual* space. | |
| // | |
| partition_space_left = m_partition_space_left; | |
| struct statvfs fs; | |
| if (::statvfs(logfile_dir, &fs) >= 0) { | |
| partition_space_left = static_cast<int64_t>(fs.f_bavail) * static_cast<int64_t>(fs.f_bsize); | |
| } | |
| // | |
| // Update the config variables for space used/left | |
| // | |
| m_space_used = total_space_used; | |
| m_partition_space_left = partition_space_left; | |
| Metrics::Gauge::store(log_rsb.log_files_space_used, m_space_used); | |
| Dbg(dbg_ctl_logspace, "%" PRId64 " bytes being used for logs", m_space_used); | |
| Dbg(dbg_ctl_logspace, "%" PRId64 " bytes left on partition", m_partition_space_left); | |
| // | |
| // Now that we have an accurate picture of the amount of space being | |
| // used by logging, we can see if we're running low on space. If so, | |
| // we might consider deleting some files that are stored in the | |
| // candidate array. | |
| // | |
| // To delete oldest files first, we'll sort our candidate array by | |
| // timestamps, making the oldest files first in the array (thus first | |
| // selected). | |
| // | |
| int64_t max_space = static_cast<int64_t>(get_max_space_mb()) * LOG_MEGABYTE; | |
| int64_t headroom = static_cast<int64_t>(max_space_mb_headroom) * LOG_MEGABYTE; | |
| if (!space_to_write(headroom)) { | |
| Dbg(dbg_ctl_logspace, "headroom reached, trying to clear space ..."); | |
| if (!rolledLogDeleter.has_candidates()) { | |
| Note("Cannot clear space because there are no recognized Traffic Server rolled logs for auto deletion."); | |
| } else { | |
| Dbg(dbg_ctl_logspace, "Considering %zu delete candidates ...", rolledLogDeleter.get_candidate_count()); | |
| } | |
| while (rolledLogDeleter.has_candidates()) { | |
| if (space_to_write(headroom + log_buffer_size)) { | |
| Dbg(dbg_ctl_logspace, "low water mark reached; stop deleting"); | |
| break; | |
| } | |
| auto victim = rolledLogDeleter.take_next_candidate_to_delete(); | |
| // Check if any candidate exists | |
| if (!victim) { | |
| // This shouldn't be triggered unless min_count are configured wrong or extra non-log files occupy the directory | |
| Dbg(dbg_ctl_logspace, "No more victims. Check your rolling_min_count settings and logging directory."); | |
| } else { | |
| Dbg(dbg_ctl_logspace, "auto-deleting %s", victim->rolled_log_path.c_str()); | |
| if (unlink(victim->rolled_log_path.c_str()) < 0) { | |
| Note("Traffic Server was unable to auto-delete rolled " | |
| "logfile %s: %s.", | |
| victim->rolled_log_path.c_str(), strerror(errno)); | |
| } else { | |
| Dbg(dbg_ctl_logspace, | |
| "The rolled logfile, %s, was auto-deleted; " | |
| "%" PRId64 " bytes were reclaimed.", | |
| victim->rolled_log_path.c_str(), victim->size); | |
| // Update after successful unlink; | |
| m_space_used -= victim->size; | |
| m_partition_space_left += victim->size; | |
| } | |
| } | |
| } | |
| } | |
| // The set of files in the logs dir may change between iterations to check | |
| // for logs to delete. To deal with this, we simply clear our internal | |
| // candidates metadata and regenerate it on each iteration. | |
| rolledLogDeleter.clear_candidates(); | |
| // | |
| // Now that we've updated the m_space_used value, see if we need to | |
| // issue any alarms or warnings about space | |
| // | |
| if (!space_to_write(headroom)) { | |
| if (!logging_space_exhausted) { | |
| Note("Logging space exhausted, any logs writing to local disk will be dropped!"); | |
| } | |
| logging_space_exhausted = true; | |
| // | |
| // Despite our best efforts, we still can't write to the disk. | |
| // Find out why and set/clear warnings. | |
| // | |
| // First, are we out of space based on configuration? | |
| // | |
| if (m_space_used >= max_space) { | |
| if (!m_disk_full) { | |
| m_disk_full = true; | |
| Warning(DISK_IS_CONFIG_FULL_MESSAGE); | |
| } | |
| } | |
| // | |
| // How about out of actual space on the partition? | |
| // | |
| else if (m_partition_space_left <= 0) { | |
| if (!m_partition_full) { | |
| m_partition_full = true; | |
| Warning(DISK_IS_ACTUAL_FULL_MESSAGE); | |
| } | |
| } | |
| // | |
| // How about being within the headroom limit? | |
| // | |
| else if (m_space_used + headroom >= max_space) { | |
| if (!m_disk_low) { | |
| m_disk_low = true; | |
| Warning(DISK_IS_CONFIG_LOW_MESSAGE); | |
| } | |
| } else { | |
| if (!m_partition_low) { | |
| m_partition_low = true; | |
| Warning(DISK_IS_ACTUAL_LOW_MESSAGE); | |
| } | |
| } | |
| } else { | |
| // | |
| // We have enough space to log again; clear any previous messages | |
| // | |
| if (logging_space_exhausted) { | |
| Note("Logging space is no longer exhausted."); | |
| } | |
| logging_space_exhausted = false; | |
| if (m_disk_full || m_partition_full) { | |
| Note("Logging disk is no longer full; access logging to local log directory resumed."); | |
| m_disk_full = false; | |
| m_partition_full = false; | |
| } | |
| if (m_disk_low || m_partition_low) { | |
| Note("Logging disk is no longer low; access logging to local log directory resumed."); | |
| m_disk_low = false; | |
| m_partition_low = false; | |
| } | |
| } | |
| } | |
| bool | |
| LogConfig::evaluate_config() | |
| { | |
| ats_scoped_str path(RecConfigReadConfigPath("proxy.config.log.config.filename", ts::filename::LOGGING)); | |
| struct stat sbuf; | |
| if (stat(path.get(), &sbuf) == -1 && errno == ENOENT) { | |
| // File doesn't exist — not a failure; ATS uses default logging. | |
| CfgLoadComplete(reload_ctx, "logging configuration '%s' doesn't exist, using defaults", path.get()); | |
| return false; | |
| } | |
| Note("%s loading ...", path.get()); | |
| YamlLogConfig y(this); | |
| bool zret = y.parse(path.get()); | |
| if (zret) { | |
| CfgLoadComplete(reload_ctx, "%s finished loading", path.get()); | |
| } else { | |
| CfgLoadFail(reload_ctx, "%s failed to load", path.get()); | |
| } | |
| return zret; | |
| } |