Line data Source code
1 : #pragma once 2 : 3 : #include <string> 4 : 5 : #include "envoy/access_log/access_log.h" 6 : #include "envoy/api/api.h" 7 : #include "envoy/event/dispatcher.h" 8 : #include "envoy/filesystem/filesystem.h" 9 : #include "envoy/stats/stats_macros.h" 10 : #include "envoy/stats/store.h" 11 : 12 : #include "source/common/buffer/buffer_impl.h" 13 : #include "source/common/common/logger.h" 14 : #include "source/common/common/thread.h" 15 : 16 : #include "absl/container/node_hash_map.h" 17 : 18 : namespace Envoy { 19 : 20 : #define ACCESS_LOG_FILE_STATS(COUNTER, GAUGE) \ 21 : COUNTER(flushed_by_timer) \ 22 : COUNTER(reopen_failed) \ 23 : COUNTER(write_buffered) \ 24 : COUNTER(write_completed) \ 25 : COUNTER(write_failed) \ 26 : GAUGE(write_total_buffered, Accumulate) 27 : 28 : struct AccessLogFileStats { 29 : ACCESS_LOG_FILE_STATS(GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT) 30 : }; 31 : 32 : namespace AccessLog { 33 : 34 : class AccessLogManagerImpl : public AccessLogManager, Logger::Loggable<Logger::Id::main> { 35 : public: 36 : AccessLogManagerImpl(std::chrono::milliseconds file_flush_interval_msec, Api::Api& api, 37 : Event::Dispatcher& dispatcher, Thread::BasicLockable& lock, 38 : Stats::Store& stats_store) 39 : : file_flush_interval_msec_(file_flush_interval_msec), api_(api), dispatcher_(dispatcher), 40 : lock_(lock), file_stats_{ 41 : ACCESS_LOG_FILE_STATS(POOL_COUNTER_PREFIX(stats_store, "filesystem."), 42 455 : POOL_GAUGE_PREFIX(stats_store, "filesystem."))} {} 43 : ~AccessLogManagerImpl() override; 44 : 45 : // AccessLog::AccessLogManager 46 : void reopen() override; 47 : AccessLogFileSharedPtr createAccessLog(const Filesystem::FilePathAndType& file_info) override; 48 : 49 : private: 50 : const std::chrono::milliseconds file_flush_interval_msec_; 51 : Api::Api& api_; 52 : Event::Dispatcher& dispatcher_; 53 : Thread::BasicLockable& lock_; 54 : AccessLogFileStats file_stats_; 55 : absl::node_hash_map<std::string, AccessLogFileSharedPtr> access_logs_; 56 : }; 57 : 58 : /** 59 : * This is a file implementation geared for writing out access logs. It turn out that in certain 60 : * cases even if a standard file is opened with O_NONBLOCK, the kernel can still block when writing. 61 : * This implementation uses a flush thread per file, with the idea there aren't that many 62 : * files. If this turns out to be a good implementation we can potentially have a single flush 63 : * thread that flushes all files, but we will start with this. 64 : */ 65 : class AccessLogFileImpl : public AccessLogFile { 66 : public: 67 : AccessLogFileImpl(Filesystem::FilePtr&& file, Event::Dispatcher& dispatcher, 68 : Thread::BasicLockable& lock, AccessLogFileStats& stats, 69 : std::chrono::milliseconds flush_interval_msec, 70 : Thread::ThreadFactory& thread_factory); 71 : ~AccessLogFileImpl() override; 72 : 73 : // AccessLog::AccessLogFile 74 : void write(absl::string_view data) override; 75 : 76 : /** 77 : * Reopen file asynchronously. 78 : * This only sets reopen flag, actual reopen operation is delayed. 79 : * Reopen happens before the next write operation. 80 : */ 81 : void reopen() override; 82 : void flush() override; 83 : 84 : private: 85 : void doWrite(Buffer::Instance& buffer); 86 : void flushThreadFunc(); 87 : Api::IoCallBoolResult open(); 88 : void createFlushStructures(); 89 : 90 : // return default flags set which used by open 91 : static Filesystem::FlagSet defaultFlags(); 92 : 93 : // Minimum size before the flush thread will be told to flush. 94 : static const uint64_t MIN_FLUSH_SIZE = 1024 * 64; 95 : 96 : Filesystem::FilePtr file_; 97 : 98 : // These locks are always acquired in the following order if multiple locks are held: 99 : // 1) write_lock_ 100 : // 2) flush_lock_ 101 : // 3) file_lock_ 102 : Thread::BasicLockable& file_lock_; // This lock is used only by the flush thread when writing 103 : // to disk. This is used to make sure that file blocks do 104 : // not get interleaved by multiple processes writing to 105 : // the same file during hot-restart. 106 : Thread::MutexBasicLockable flush_lock_; // This lock is used to prevent simultaneous flushes from 107 : // the flush thread and a synchronous flush. This protects 108 : // concurrent access to the about_to_write_buffer_, fd_, 109 : // and all other data used during flushing and file 110 : // re-opening. 111 : Thread::MutexBasicLockable 112 : write_lock_; // The lock is used when filling the flush buffer. It allows 113 : // multiple threads to write to the same file at relatively 114 : // high performance. It is always local to the process. 115 : Thread::ThreadPtr flush_thread_; 116 : Thread::CondVar flush_event_; 117 : bool flush_thread_exit_ ABSL_GUARDED_BY(write_lock_){false}; 118 : bool reopen_file_ ABSL_GUARDED_BY(write_lock_){false}; 119 : Buffer::OwnedImpl 120 : flush_buffer_ ABSL_GUARDED_BY(write_lock_); // This buffer is used by multiple threads. It 121 : // gets filled and then flushed either when max 122 : // size is reached or when a timer fires. 123 : // TODO(jmarantz): this should be ABSL_GUARDED_BY(flush_lock_) but the analysis cannot poke 124 : // through the std::make_unique assignment. I do not believe it's possible to annotate this 125 : // properly now due to limitations in the clang thread annotation analysis. 126 : Buffer::OwnedImpl about_to_write_buffer_; // This buffer is used only by the flush thread. Data 127 : // is moved from flush_buffer_ under lock, and then 128 : // the lock is released so that flush_buffer_ can 129 : // continue to fill. This buffer is then used for the 130 : // final write to disk. 131 : Event::TimerPtr flush_timer_; 132 : Thread::ThreadFactory& thread_factory_; 133 : const std::chrono::milliseconds flush_interval_msec_; // Time interval buffer gets flushed no 134 : // matter if it reached the MIN_FLUSH_SIZE 135 : // or not. 136 : AccessLogFileStats& stats_; 137 : }; 138 : 139 : } // namespace AccessLog 140 : } // namespace Envoy