| /** @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. | |
| */ | |
| /*************************************************************************** | |
| LogFile.cc | |
| ***************************************************************************/ | |
| #include <vector> | |
| #include <string> | |
| #include <algorithm> | |
| #include "tscore/ink_platform.h" | |
| #include "tscore/SimpleTokenizer.h" | |
| #include "tscore/ink_file.h" | |
| #include <cerrno> | |
| #include <sys/types.h> | |
| #include <sys/stat.h> | |
| #include <fcntl.h> | |
| #include <libgen.h> | |
| #include "iocore/utils/Machine.h" | |
| #include "tscore/BaseLogFile.h" | |
| #include "proxy/logging/LogField.h" | |
| #include "proxy/logging/LogFilter.h" | |
| #include "proxy/logging/LogFormat.h" | |
| #include "proxy/logging/LogBuffer.h" | |
| #include "proxy/logging/LogFile.h" | |
| #include "proxy/logging/LogObject.h" | |
| #include "proxy/logging/LogUtils.h" | |
| #include "proxy/logging/LogConfig.h" | |
| #include "proxy/logging/Log.h" | |
| namespace | |
| { | |
| DbgCtl dbg_ctl_log_file{"log-file"}; | |
| DbgCtl dbg_ctl_log{"log"}; | |
| } // end anonymous namespace | |
| /*------------------------------------------------------------------------- | |
| LogFile::LogFile | |
| This constructor builds a LogFile object given the path, filename, header, | |
| and logfile format type. This is the common way to create a new logfile. | |
| -------------------------------------------------------------------------*/ | |
| LogFile::LogFile(const char *name, const char *header, LogFileFormat format, uint64_t signature, size_t ascii_buffer_size, | |
| size_t max_line_size, int pipe_buffer_size, LogEscapeType escape_type) | |
| : m_file_format(format), | |
| m_name(ats_strdup(name)), | |
| m_escape_type(escape_type), | |
| m_header(ats_strdup(header)), | |
| m_signature(signature), | |
| m_max_line_size(max_line_size), | |
| m_pipe_buffer_size(pipe_buffer_size) | |
| { | |
| if (m_file_format != LOG_FILE_PIPE) { | |
| m_log = std::make_unique<BaseLogFile>(name, m_signature); | |
| // Use Log::config->hostname rather than Machine::instance()->hostname | |
| // because the former is reloadable. | |
| m_log->set_hostname(Log::config->hostname); | |
| } else { | |
| m_log = nullptr; | |
| } | |
| m_fd = -1; | |
| m_ascii_buffer_size = (ascii_buffer_size < max_line_size ? max_line_size : ascii_buffer_size); | |
| Dbg(dbg_ctl_log_file, "exiting LogFile constructor, m_name=%s, this=%p, escape_type=%d", m_name, this, escape_type); | |
| } | |
| /*------------------------------------------------------------------------- | |
| LogFile::LogFile | |
| This (copy) constructor builds a LogFile object from another LogFile object. | |
| -------------------------------------------------------------------------*/ | |
| /*------------------------------------------------------------------------- | |
| LogFile::~LogFile | |
| -------------------------------------------------------------------------*/ | |
| LogFile::~LogFile() | |
| { | |
| Dbg(dbg_ctl_log_file, "entering LogFile destructor, this=%p", this); | |
| // close_file() checks whether a file is open before attempting to close, so | |
| // this is safe to call even if a file had not been opened. Further, calling | |
| // close_file() here ensures that we do not leak file descriptors. | |
| close_file(); | |
| ats_free(m_header); | |
| ats_free(m_name); | |
| Dbg(dbg_ctl_log_file, "exiting LogFile destructor, this=%p", this); | |
| } | |
| /*------------------------------------------------------------------------- | |
| LogFile::change_name | |
| -------------------------------------------------------------------------*/ | |
| void | |
| LogFile::change_name(const char *new_name) | |
| { | |
| ats_free(m_name); | |
| if (m_log) { | |
| m_log->change_name(new_name); | |
| } | |
| m_name = ats_strdup(new_name); | |
| } | |
| /*------------------------------------------------------------------------- | |
| LogFile::change_header | |
| -------------------------------------------------------------------------*/ | |
| void | |
| LogFile::change_header(const char *header) | |
| { | |
| ats_free(m_header); | |
| m_header = ats_strdup(header); | |
| } | |
| /*------------------------------------------------------------------------- | |
| LogFile::open | |
| Open the logfile for append access. This will create a logfile if the | |
| file does not already exist. | |
| -------------------------------------------------------------------------*/ | |
| int | |
| LogFile::open_file() | |
| { | |
| // whatever we want to open should have a name | |
| ink_assert(m_name != nullptr); | |
| // is_open() takes into account if we're using BaseLogFile or a naked fd | |
| if (is_open()) { | |
| return LOG_FILE_NO_ERROR; | |
| } | |
| bool file_exists = LogFile::exists(m_name); | |
| if (m_file_format == LOG_FILE_PIPE) { | |
| // setup pipe | |
| if (mkfifo(m_name, S_IRUSR | S_IWUSR | S_IRGRP) < 0) { | |
| if (errno != EEXIST) { | |
| Error("Could not create named pipe %s for logging: %s", m_name, strerror(errno)); | |
| return LOG_FILE_COULD_NOT_CREATE_PIPE; | |
| } | |
| } else { | |
| Dbg(dbg_ctl_log_file, "Created named pipe %s for logging", m_name); | |
| } | |
| // now open the pipe | |
| Dbg(dbg_ctl_log_file, "attempting to open pipe %s", m_name); | |
| m_fd = ::open(m_name, O_WRONLY | O_NDELAY, 0); | |
| if (m_fd < 0) { | |
| Dbg(dbg_ctl_log_file, "no readers for pipe %s", m_name); | |
| return LOG_FILE_NO_PIPE_READERS; | |
| } | |
| #ifdef F_GETPIPE_SZ | |
| // adjust pipe size if necessary | |
| if (m_pipe_buffer_size) { | |
| long pipe_size = static_cast<long>(fcntl(m_fd, F_GETPIPE_SZ)); | |
| if (pipe_size == -1) { | |
| Error("Get pipe size failed for pipe %s: %s", m_name, strerror(errno)); | |
| } else { | |
| Dbg(dbg_ctl_log_file, "Previous buffer size for pipe %s: %ld", m_name, pipe_size); | |
| } | |
| int ret = fcntl(m_fd, F_SETPIPE_SZ, m_pipe_buffer_size); | |
| if (ret == -1) { | |
| Error("Set pipe size failed for pipe %s to size %d: %s", m_name, m_pipe_buffer_size, strerror(errno)); | |
| } | |
| pipe_size = static_cast<long>(fcntl(m_fd, F_GETPIPE_SZ)); | |
| if (pipe_size == -1) { | |
| Error("Get pipe size after setting it failed for pipe %s: %s", m_name, strerror(errno)); | |
| } else { | |
| Dbg(dbg_ctl_log_file, "New buffer size for pipe %s: %ld", m_name, pipe_size); | |
| } | |
| } | |
| #endif // F_GETPIPE_SZ | |
| } else { | |
| if (m_log) { | |
| int status = m_log->open_file(Log::config->logfile_perm); | |
| if (status == BaseLogFile::LOG_FILE_COULD_NOT_OPEN_FILE) { | |
| return LOG_FILE_COULD_NOT_OPEN_FILE; | |
| } | |
| } else { | |
| return LOG_FILE_COULD_NOT_OPEN_FILE; | |
| } | |
| } | |
| // | |
| // If we've opened the file and it didn't already exist, then this is a | |
| // "new" file and we need to make some initializations. This is the | |
| // time to write any headers and do any one-time initialization of the | |
| // file. | |
| // | |
| if (!file_exists) { | |
| if (m_file_format != LOG_FILE_BINARY && m_header && m_log) { | |
| Dbg(dbg_ctl_log_file, "writing header to LogFile %s", m_name); | |
| writeln(m_header, strlen(m_header), fileno(m_log->m_fp), m_name); | |
| } | |
| } | |
| Metrics::Gauge::increment(log_rsb.log_files_open); | |
| Dbg(dbg_ctl_log, "exiting LogFile::open_file(), file=%s presumably open", m_name); | |
| return LOG_FILE_NO_ERROR; | |
| } | |
| /*------------------------------------------------------------------------- | |
| LogFile::close | |
| Close the current logfile. | |
| -------------------------------------------------------------------------*/ | |
| void | |
| LogFile::close_file() | |
| { | |
| if (is_open()) { | |
| if (m_file_format == LOG_FILE_PIPE) { | |
| if (::close(m_fd)) { | |
| Error("Error closing LogFile %s: %s.", m_name, strerror(errno)); | |
| } else { | |
| Dbg(dbg_ctl_log_file, "LogFile %s (fd=%d) is closed", m_name, m_fd); | |
| Metrics::Gauge::decrement(log_rsb.log_files_open); | |
| } | |
| m_fd = -1; | |
| } else if (m_log) { | |
| if (m_log->close_file()) { | |
| Error("Error closing LogFile %s: %s.", m_log->get_name(), strerror(errno)); | |
| } else { | |
| Dbg(dbg_ctl_log_file, "LogFile %s is closed", m_log->get_name()); | |
| Metrics::Gauge::decrement(log_rsb.log_files_open); | |
| } | |
| } else { | |
| Warning("LogFile %s is open but was not closed", m_name); | |
| } | |
| } | |
| } | |
| struct RolledFile { | |
| RolledFile(const std::string &name, time_t mtime) : _name(name), _mtime(mtime) {} | |
| std::string _name; | |
| time_t _mtime; | |
| }; | |
| /** | |
| * @brief trim rolled files to max number of rolled files, older first | |
| * | |
| * @param rolling_max_count - limit to which rolled files will be trimmed | |
| * @return true if success, false if failure | |
| */ | |
| bool | |
| LogFile::trim_rolled(size_t rolling_max_count) | |
| { | |
| // man: "dirname() may modify the contents of path, so it may be desirable to pass a copy when calling one of these functions." | |
| char *name = ats_strdup(m_name); | |
| std::string logfile_dir(::dirname((name))); | |
| ats_free(name); | |
| // Open the directory | |
| int dirfd = open(logfile_dir.c_str(), O_RDONLY); | |
| if (dirfd < 0) { | |
| Error("Error opening logging directory %s to collect trim candidates: %s", logfile_dir.c_str(), strerror(errno)); | |
| return false; | |
| } | |
| // Check logging directory access | |
| int err; | |
| do { | |
| err = faccessat(dirfd, logfile_dir.c_str(), R_OK | W_OK | X_OK, 0); | |
| } while ((err < 0) && (errno == EINTR)); | |
| if (err < 0) { | |
| close(dirfd); | |
| Error("Error accessing logging directory %s: %s", logfile_dir.c_str(), strerror(errno)); | |
| return false; | |
| } | |
| // Open the logging directory | |
| DIR *ld = fdopendir(dirfd); | |
| if (ld == nullptr) { | |
| close(dirfd); | |
| Error("Error opening logging directory %s to collect trim candidates: %s", logfile_dir.c_str(), strerror(errno)); | |
| return false; | |
| } | |
| // Collect the rolled file names from the logging directory that match the specified log file name | |
| std::vector<RolledFile> rolled; | |
| char path[MAXPATHLEN]; | |
| struct dirent *entry; | |
| while ((entry = readdir(ld))) { | |
| struct stat sbuf; | |
| snprintf(path, MAXPATHLEN, "%s/%s", logfile_dir.c_str(), entry->d_name); | |
| int sret = ::stat(path, &sbuf); | |
| if (sret != -1 && S_ISREG(sbuf.st_mode)) { | |
| int name_len = strlen(m_name); | |
| int path_len = strlen(path); | |
| if (path_len > name_len && 0 == ::strncmp(m_name, path, name_len) && LogFile::rolled_logfile(entry->d_name)) { | |
| rolled.push_back(RolledFile(path, sbuf.st_mtime)); | |
| } | |
| } | |
| } | |
| closedir(ld); | |
| bool result = true; | |
| std::sort(rolled.begin(), rolled.end(), [](const RolledFile &a, const RolledFile &b) { return a._mtime > b._mtime; }); | |
| if (rolling_max_count < rolled.size()) { | |
| for (auto it = rolled.begin() + rolling_max_count; it != rolled.end(); it++) { | |
| const RolledFile &file = *it; | |
| if (unlink(file._name.c_str()) < 0) { | |
| Error("unable to auto-delete rolled logfile %s: %s", file._name.c_str(), strerror(errno)); | |
| result = false; | |
| } else { | |
| Dbg(dbg_ctl_log_file, "rolled logfile, %s, was auto-deleted", file._name.c_str()); | |
| } | |
| } | |
| } | |
| rolled.clear(); | |
| return result; | |
| } | |
| /*------------------------------------------------------------------------- | |
| * LogFile::roll | |
| * This function is called by a LogObject to roll its files. | |
| * | |
| * Return 1 if file rolled, 0 otherwise | |
| -------------------------------------------------------------------------*/ | |
| int | |
| LogFile::roll(long interval_start, long interval_end, bool reopen_after_rolling) | |
| { | |
| if (m_log) { | |
| // Due to commit 346b419 the BaseLogFile::close_file() is no longer called within BaseLogFile::roll(). | |
| // For diagnostic log files, the rolling is implemented by renaming and destroying the BaseLogFile object | |
| // and then creating a new BaseLogFile object with the original filename. Due to possible race conditions | |
| // the old/new object swap happens within lock/unlock calls within Diags.cc. | |
| // For logging log files, the rolling is implemented by renaming the original file and closing it. | |
| // Afterwards, the LogFile object will re-open a new file with the original file name using the original object. | |
| // There is no need to protect against contention since the open/close/writes are all executed under a | |
| // single log flush thread. | |
| // Since these two methods of using BaseLogFile are not compatible, we perform the logging log file specific | |
| // close file operation here within the containing LogFile object. | |
| if (m_log->roll(interval_start, interval_end)) { | |
| if (m_log->close_file()) { | |
| Error("Error closing LogFile %s: %s.", m_log->get_name(), strerror(errno)); | |
| } | |
| if (reopen_after_rolling) { | |
| /* If we re-open now log file will be created even if there is nothing being logged */ | |
| m_log->open_file(); | |
| } | |
| return 1; | |
| } | |
| } | |
| return 0; | |
| } | |
| /*------------------------------------------------------------------------- | |
| LogFile::reopen_if_moved | |
| Check whether the file at the log's filename exists and, if not, close | |
| the current file descriptor and reopen it. This function can be used to | |
| facilitate external log rotation mechanisms which will move the original | |
| file to a rolled filename. Logging will happen to that same file | |
| descriptor until this function is called, at which point the non-existent | |
| original file will be detected, the file descriptor will be closed, and | |
| the log file will be re-opened. | |
| Returns True if the file was re-opened, false otherwise. | |
| -------------------------------------------------------------------------*/ | |
| bool | |
| LogFile::reopen_if_moved() | |
| { | |
| if (!m_name) { | |
| return false; | |
| } | |
| if (LogFile::exists(m_name)) { | |
| return false; | |
| } | |
| // Both of the following log if there are problems. | |
| close_file(); | |
| open_file(); | |
| return true; | |
| } | |
| /*------------------------------------------------------------------------- | |
| LogFile::preproc_and_try_delete | |
| preprocess the given buffer data before write to target file | |
| and try to delete it when its reference become zero. | |
| -------------------------------------------------------------------------*/ | |
| int | |
| LogFile::preproc_and_try_delete(LogBuffer *lb) | |
| { | |
| int ret = -1; | |
| LogBufferHeader *buffer_header; | |
| if (lb == nullptr) { | |
| Note("Cannot write LogBuffer to LogFile %s; LogBuffer is NULL", m_name); | |
| return -1; | |
| } | |
| ink_atomic_increment(&lb->m_references, 1); | |
| if ((buffer_header = lb->header()) == nullptr) { | |
| Note("Cannot write LogBuffer to LogFile %s; LogBufferHeader is NULL", m_name); | |
| goto done; | |
| } | |
| if (buffer_header->entry_count == 0) { | |
| // no bytes to write | |
| Note("LogBuffer with 0 entries for LogFile %s, nothing to write", m_name); | |
| goto done; | |
| } | |
| // | |
| // If the start time for this file has yet to be established, then grab | |
| // the low_timestamp from the given LogBuffer. Then, we always set the | |
| // end time to the high_timestamp, so it's always up to date. | |
| // | |
| if (m_log) { | |
| if (!m_log->m_start_time) { | |
| m_log->m_start_time = buffer_header->low_timestamp; | |
| } | |
| m_log->m_end_time = buffer_header->high_timestamp; | |
| } | |
| if (m_file_format == LOG_FILE_BINARY) { | |
| // | |
| // Ok, now we need to write the binary buffer to the file, and we | |
| // can do so in one swift write. The question is, do we write the | |
| // LogBufferHeader with each buffer or not? The answer is yes. | |
| // Even though we'll be putting down redundant data (things that | |
| // don't change between buffers), it's not worth trying to separate | |
| // out the buffer-dependent data from the buffer-independent data. | |
| // | |
| LogFlushData *flush_data = new LogFlushData(this, lb); | |
| Metrics::Counter::increment(log_rsb.num_flush_to_disk, lb->header()->entry_count); | |
| Metrics::Counter::increment(log_rsb.bytes_flush_to_disk, lb->header()->byte_count); | |
| ink_atomiclist_push(Log::flush_data_list, flush_data); | |
| Log::flush_notify->signal(); | |
| // | |
| // LogBuffer will be deleted in flush thread | |
| // | |
| return 0; | |
| } else if (m_file_format == LOG_FILE_ASCII || m_file_format == LOG_FILE_PIPE) { | |
| write_ascii_logbuffer3(buffer_header); | |
| ret = 0; | |
| } else { | |
| Note("Cannot write LogBuffer to LogFile %s; invalid file format: %d", m_name, m_file_format); | |
| } | |
| done: | |
| LogBuffer::destroy(lb); | |
| return ret; | |
| } | |
| /*------------------------------------------------------------------------- | |
| LogFile::write_ascii_logbuffer | |
| This routine takes the given LogBuffer and writes it (in ASCII) to the | |
| given file descriptor. Written as a stand-alone function, it can be | |
| called from either the local LogBuffer::write routine from inside of the | |
| proxy, or from an external program (since it is a static function). The | |
| return value is the number of bytes written. | |
| -------------------------------------------------------------------------*/ | |
| int | |
| LogFile::write_ascii_logbuffer(LogBufferHeader *buffer_header, int fd, const char *path, const char *alt_format) | |
| { | |
| ink_assert(buffer_header != nullptr); | |
| ink_assert(fd >= 0); | |
| char fmt_buf[LOG_MAX_FORMATTED_BUFFER]; | |
| char fmt_line[LOG_MAX_FORMATTED_LINE]; | |
| LogBufferIterator iter(buffer_header); | |
| LogEntryHeader *entry_header; | |
| int fmt_buf_bytes = 0; | |
| int fmt_line_bytes = 0; | |
| int bytes = 0; | |
| LogFormatType format_type; | |
| char *fieldlist_str; | |
| char *printf_str; | |
| // v3 is identical to v2 for ASCII output: it still uses fmt_fieldlist and | |
| // fmt_printf; the extra field-type schema is additive and ignored here. | |
| if (log_segment_version_supported(buffer_header->version)) { | |
| format_type = static_cast<LogFormatType>(buffer_header->format_type); | |
| fieldlist_str = buffer_header->fmt_fieldlist(); | |
| printf_str = buffer_header->fmt_printf(); | |
| } else { | |
| Note("Invalid LogBuffer version %d in write_ascii_logbuffer; " | |
| "supported versions are %d-%d", | |
| buffer_header->version, LOG_SEGMENT_VERSION_MIN_SUPPORTED, LOG_SEGMENT_VERSION); | |
| return 0; | |
| } | |
| while ((entry_header = iter.next())) { | |
| fmt_line_bytes = LogBuffer::to_ascii(entry_header, format_type, &fmt_line[0], LOG_MAX_FORMATTED_LINE, fieldlist_str, printf_str, | |
| buffer_header->version, alt_format); | |
| ink_assert(fmt_line_bytes > 0); | |
| if (fmt_line_bytes > 0) { | |
| if ((fmt_line_bytes + fmt_buf_bytes) >= LOG_MAX_FORMATTED_BUFFER) { | |
| if (!Log::config->logging_space_exhausted) { | |
| bytes += writeln(fmt_buf, fmt_buf_bytes, fd, path); | |
| } | |
| fmt_buf_bytes = 0; | |
| } | |
| ink_assert(fmt_buf_bytes < LOG_MAX_FORMATTED_BUFFER); | |
| ink_assert(fmt_line_bytes < LOG_MAX_FORMATTED_BUFFER - fmt_buf_bytes); | |
| memcpy(&fmt_buf[fmt_buf_bytes], fmt_line, fmt_line_bytes); | |
| fmt_buf_bytes += fmt_line_bytes; | |
| ink_assert(fmt_buf_bytes < LOG_MAX_FORMATTED_BUFFER); | |
| fmt_buf[fmt_buf_bytes] = '\n'; // keep entries separate | |
| fmt_buf_bytes += 1; | |
| } | |
| } | |
| if (fmt_buf_bytes > 0) { | |
| if (!Log::config->logging_space_exhausted) { | |
| ink_assert(fmt_buf_bytes < LOG_MAX_FORMATTED_BUFFER); | |
| bytes += writeln(fmt_buf, fmt_buf_bytes, fd, path); | |
| } | |
| } | |
| return bytes; | |
| } | |
| int | |
| LogFile::write_ascii_logbuffer3(LogBufferHeader *buffer_header, const char *alt_format) | |
| { | |
| Dbg(dbg_ctl_log_file, | |
| "entering LogFile::write_ascii_logbuffer3 for %s " | |
| "(this=%p)", | |
| m_name, this); | |
| ink_assert(buffer_header != nullptr); | |
| LogBufferIterator iter(buffer_header); | |
| LogEntryHeader *entry_header; | |
| int fmt_entry_count = 0; | |
| int fmt_buf_bytes = 0; | |
| int total_bytes = 0; | |
| LogFormatType format_type; | |
| char *fieldlist_str; | |
| char *printf_str; | |
| char *ascii_buffer; | |
| // Same v2/v3 handling as write_ascii_logbuffer: the v3 schema is additive | |
| // and not needed for ASCII output. | |
| if (log_segment_version_supported(buffer_header->version)) { | |
| format_type = static_cast<LogFormatType>(buffer_header->format_type); | |
| fieldlist_str = buffer_header->fmt_fieldlist(); | |
| printf_str = buffer_header->fmt_printf(); | |
| } else { | |
| Note("Invalid LogBuffer version %d in write_ascii_logbuffer; " | |
| "supported versions are %d-%d", | |
| buffer_header->version, LOG_SEGMENT_VERSION_MIN_SUPPORTED, LOG_SEGMENT_VERSION); | |
| return 0; | |
| } | |
| while ((entry_header = iter.next())) { | |
| fmt_entry_count = 0; | |
| fmt_buf_bytes = 0; | |
| if (m_file_format == LOG_FILE_PIPE) { | |
| ascii_buffer = static_cast<char *>(ats_malloc(m_max_line_size)); | |
| } else { | |
| ascii_buffer = static_cast<char *>(ats_malloc(m_ascii_buffer_size)); | |
| } | |
| // fill the buffer with as many records as possible | |
| // | |
| do { | |
| if (entry_header->entry_len >= m_max_line_size) { | |
| Warning("Log is too long(%" PRIu32 "), it would be truncated. max_len:%zu", entry_header->entry_len, m_max_line_size); | |
| } | |
| int bytes = LogBuffer::to_ascii(entry_header, format_type, &ascii_buffer[fmt_buf_bytes], m_max_line_size - 1, fieldlist_str, | |
| printf_str, buffer_header->version, alt_format, get_escape_type()); | |
| if (bytes > 0) { | |
| fmt_buf_bytes += bytes; | |
| ascii_buffer[fmt_buf_bytes] = '\n'; | |
| ++fmt_buf_bytes; | |
| ++fmt_entry_count; | |
| } else { | |
| Note("Failed to convert LogBuffer to ascii, have dropped (%" PRIu32 ") bytes.", entry_header->entry_len); | |
| Metrics::Counter::increment(log_rsb.num_lost_before_flush_to_disk, fmt_entry_count); | |
| Metrics::Counter::increment(log_rsb.bytes_lost_before_flush_to_disk, fmt_buf_bytes); | |
| } | |
| // if writing to a pipe, fill the buffer with a single | |
| // record to avoid as much as possible overflowing the | |
| // pipe buffer | |
| // | |
| if (m_file_format == LOG_FILE_PIPE) { | |
| break; | |
| } | |
| if (m_ascii_buffer_size - fmt_buf_bytes < m_max_line_size) { | |
| break; | |
| } | |
| } while ((entry_header = iter.next())); | |
| // send the buffer to flush thread | |
| // | |
| LogFlushData *flush_data = new LogFlushData(this, ascii_buffer, fmt_buf_bytes); | |
| Metrics::Counter::increment(log_rsb.num_flush_to_disk, fmt_entry_count); | |
| Metrics::Counter::increment(log_rsb.bytes_flush_to_disk, fmt_buf_bytes); | |
| ink_atomiclist_push(Log::flush_data_list, flush_data); | |
| Log::flush_notify->signal(); | |
| total_bytes += fmt_buf_bytes; | |
| } | |
| return total_bytes; | |
| } | |
| bool | |
| LogFile::rolled_logfile(char *file) | |
| { | |
| return BaseLogFile::rolled_logfile(file); | |
| } | |
| bool | |
| LogFile::exists(const char *pathname) | |
| { | |
| return BaseLogFile::exists(pathname); | |
| } | |
| /*------------------------------------------------------------------------- | |
| LogFile::writeln | |
| This function will make sure the following data is written to the | |
| output file (m_fd) with a trailing newline. | |
| -------------------------------------------------------------------------*/ | |
| int | |
| LogFile::writeln(char *data, int len, int fd, const char *path) | |
| { | |
| int total_bytes = 0; | |
| if (len > 0 && data && fd >= 0) { | |
| struct iovec wvec[2]; | |
| memset(&wvec, 0, sizeof(iovec)); | |
| memset(&wvec[1], 0, sizeof(iovec)); | |
| int bytes_this_write, vcnt = 1; | |
| wvec[0].iov_base = (void *)data; | |
| wvec[0].iov_len = static_cast<size_t>(len); | |
| if (data[len - 1] != '\n') { | |
| wvec[1].iov_base = (void *)"\n"; | |
| wvec[1].iov_len = static_cast<size_t>(1); | |
| vcnt++; | |
| } | |
| if ((bytes_this_write = static_cast<int>(::writev(fd, (const struct iovec *)wvec, vcnt))) < 0) { | |
| SiteThrottledWarning("An error was encountered in writing to %s: %s.", ((path) ? path : "logfile"), strerror(errno)); | |
| } else { | |
| total_bytes = bytes_this_write; | |
| } | |
| } | |
| return total_bytes; | |
| } | |
| /*------------------------------------------------------------------------- | |
| LogFile::check_fd | |
| This routine will occasionally stat the current logfile to make sure that | |
| it really does exist. We use fstat() on the open file descriptor to check | |
| if the file has been unlinked. | |
| Failure to open the logfile will generate a manager alarm and a Warning. | |
| -------------------------------------------------------------------------*/ | |
| void | |
| LogFile::check_fd() | |
| { | |
| static bool failure_last_call = false; | |
| static unsigned stat_check_count = 1; | |
| if ((stat_check_count % Log::config->file_stat_frequency) == 0) { | |
| // | |
| // Check if the file still exists using fstat() on the open file descriptor. | |
| // For regular files that have been unlinked (e.g., by log rotation tools), | |
| // st_nlink will be 0. Non-regular files like stderr/stdout will never have | |
| // st_nlink == 0, so this approach works safely for all file types. | |
| // | |
| if (m_name && is_open()) { | |
| int fd = get_fd(); | |
| if (fd >= 0) { | |
| struct stat st; | |
| if (fstat(fd, &st) == 0) { | |
| // If the file has been unlinked, st_nlink will be 0 | |
| // This only happens for regular files, not special files like stderr/stdout | |
| if (st.st_nlink == 0) { | |
| close_file(); | |
| } | |
| } else { | |
| // fstat failed, the file descriptor may be invalid | |
| // Fall back to path-based existence check | |
| if (!LogFile::exists(m_name)) { | |
| close_file(); | |
| } | |
| } | |
| } else { | |
| // No valid fd, fall back to path-based check | |
| if (!LogFile::exists(m_name)) { | |
| close_file(); | |
| } | |
| } | |
| } | |
| stat_check_count = 0; | |
| } | |
| stat_check_count++; | |
| int err = open_file(); | |
| // XXX if open_file() returns, LOG_FILE_FILESYSTEM_CHECKS_FAILED, raise a more informative alarm ... | |
| if (err != LOG_FILE_NO_ERROR && err != LOG_FILE_NO_PIPE_READERS) { | |
| if (!failure_last_call) { | |
| Warning("Traffic Server could not open logfile %s: %s.", m_name, strerror(errno)); | |
| } | |
| failure_last_call = true; | |
| return; | |
| } | |
| failure_last_call = false; | |
| } | |
| void | |
| LogFile::display(FILE *fd) | |
| { | |
| fprintf(fd, "Logfile: %s, %s\n", get_name(), (is_open()) ? "file is open" : "file is not open"); | |
| } | |
| bool | |
| LogFile::is_open() | |
| { | |
| if (m_file_format == LOG_FILE_PIPE) { | |
| return m_fd >= 0; | |
| } else { | |
| return m_log && m_log->is_open(); | |
| } | |
| } | |
| /* | |
| * Returns the fd of the entity (pipe or regular file ) that this object is | |
| * representing | |
| * | |
| * Returns -1 on error, the correct fd otherwise | |
| */ | |
| int | |
| LogFile::get_fd() | |
| { | |
| if (m_file_format == LOG_FILE_PIPE) { | |
| return m_fd; | |
| } else if (m_log && m_log->m_fp) { | |
| return fileno(m_log->m_fp); | |
| } else { | |
| return -1; | |
| } | |
| } |