quill
StreamSink.h
1 
7 #pragma once
8 
9 #include "quill/core/Attributes.h"
10 #include "quill/core/Filesystem.h"
11 #include "quill/core/LogLevel.h"
12 #include "quill/core/QuillError.h"
13 #include "quill/sinks/Sink.h"
14 
15 #include <cerrno>
16 #include <cstdint>
17 #include <cstdio>
18 #include <cstring>
19 #include <functional>
20 #include <optional>
21 #include <string>
22 #include <string_view>
23 #include <system_error>
24 #include <utility>
25 #include <vector>
26 
27 QUILL_BEGIN_NAMESPACE
28 
30 class MacroMetadata;
31 
37 {
38  std::function<void(fs::path const& file_path)> before_open;
39  std::function<void(fs::path const& file_path, FILE* f)> after_open;
40  std::function<void(fs::path const& file_path, FILE* f)> before_close;
41  std::function<void(fs::path const& file_path)> after_close;
42  std::function<std::string(std::string_view message)> before_write;
43 };
44 
48 class StreamSink : public Sink
49 {
50 public:
59  explicit StreamSink(fs::path stream, FILE* file = nullptr,
60  std::optional<PatternFormatterOptions> const& override_pattern_formatter_options = std::nullopt,
61  FileEventNotifier file_event_notifier = FileEventNotifier{})
62  : Sink(override_pattern_formatter_options),
63  _filename(std::move(stream)),
64  _file(file),
65  _file_event_notifier(std::move(file_event_notifier))
66  {
67  // reserve stdout and stderr as filenames
68  if (_filename == std::string{"stdout"})
69  {
70  _file = stdout;
71  }
72  else if (_filename == std::string{"stderr"})
73  {
74  _file = stderr;
75  }
76  else if (_filename == std::string{"/dev/null"})
77  {
78  _is_null = true;
79  }
80  else
81  {
82  // first attempt to create any non-existing directories
83  std::error_code ec;
84  fs::path parent_path;
85 
86  if (!_filename.parent_path().empty())
87  {
88  parent_path = _filename.parent_path();
89 
90  // The call to fs::status is necessary due to a known issue in GCC versions 8.3.0 to 9.4.0.
91  // In these versions, fs::create_directories(path, ec) internally uses
92  // fs::symlink_status(path, ec) instead of fs::status(path, ec) for checking the path.
93  // This causes a problem because fs::symlink_status does not follow the symlink to the
94  // target directory. As a result, it fails the is_directory() check but still indicates
95  // that the path exists, leading to a not_a_directory exception being set in the error code
96  auto const st = fs::status(parent_path, ec);
97  if (!is_directory(st))
98  {
99  fs::create_directories(parent_path, ec);
100  if (ec)
101  {
102  // use .string() to also support experimental fs
103  QUILL_THROW(QuillError{std::string{"cannot create directories for path "} +
104  parent_path.string() + std::string{" - error: "} + ec.message()});
105  }
106  }
107  }
108  else
109  {
110  parent_path = fs::current_path();
111  }
112 
113  // convert the parent path to an absolute path
114  fs::path const canonical_path = fs::canonical(parent_path, ec);
115 
116  if (ec)
117  {
118  // use .string() to also support experimental fs
119  QUILL_THROW(QuillError{std::string{"cannot create canonical path for path "} +
120  parent_path.string() + std::string{" - error: "} + ec.message()});
121  }
122 
123  // finally replace the given filename's parent_path with the equivalent canonical path
124  _filename = canonical_path / _filename.filename();
125  }
126  }
127 
128  ~StreamSink() override = default;
129 
133  QUILL_ATTRIBUTE_HOT void write_log(MacroMetadata const* /* log_metadata */,
134  uint64_t /* log_timestamp */, std::string_view /* thread_id */,
135  std::string_view /* thread_name */, std::string const& /* process_id */,
136  std::string_view /* logger_name */, LogLevel /* log_level */,
137  std::string_view /* log_level_description */,
138  std::string_view /* log_level_short_code */,
139  std::vector<std::pair<std::string, std::string>> const* /* named_args */,
140  std::string_view /* log_message */, std::string_view log_statement) override
141  {
142  if (QUILL_UNLIKELY(!_file))
143  {
144  // FileSink::flush() tries to re-open a deleted file and if it fails _file can be null
145  return;
146  }
147 
148  if (_file_event_notifier.before_write)
149  {
150  std::string const user_log_statement = _file_event_notifier.before_write(log_statement);
151  safe_fwrite(user_log_statement.data(), sizeof(char), user_log_statement.size(), _file);
152  _file_size += user_log_statement.size();
153  }
154  else
155  {
156  safe_fwrite(log_statement.data(), sizeof(char), log_statement.size(), _file);
157  _file_size += log_statement.size();
158  }
159 
160  _write_occurred = true;
161  }
162 
166  QUILL_ATTRIBUTE_HOT void flush_sink() override
167  {
168  if (!_write_occurred || !_file)
169  {
170  return;
171  }
172 
173  flush();
174  }
175 
180  QUILL_NODISCARD virtual fs::path const& get_filename() const noexcept { return _filename; }
181 
186  QUILL_NODISCARD bool is_null() const noexcept { return _is_null; }
187 
195  QUILL_ATTRIBUTE_HOT static void safe_fwrite(void const* ptr, size_t size, size_t count, FILE* stream)
196  {
197  size_t const written = std::fwrite(ptr, size, count, stream);
198 
199  if (QUILL_UNLIKELY(written < count))
200  {
201  QUILL_THROW(QuillError{std::string{"fwrite failed errno: "} + std::to_string(errno) +
202  " error: " + strerror(errno)});
203  }
204  }
205 
206 protected:
210  QUILL_ATTRIBUTE_HOT void flush()
211  {
212  _write_occurred = false;
213  fflush(_file);
214  }
215 
216 protected:
217  fs::path _filename;
218  FILE* _file{nullptr};
219  size_t _file_size{0};
220  FileEventNotifier _file_event_notifier;
221  bool _is_null{false};
222  bool _write_occurred{false};
223 };
224 
225 QUILL_END_NAMESPACE
QUILL_ATTRIBUTE_HOT void write_log(MacroMetadata const *, uint64_t, std::string_view, std::string_view, std::string const &, std::string_view, LogLevel, std::string_view, std::string_view, std::vector< std::pair< std::string, std::string >> const *, std::string_view, std::string_view log_statement) override
Writes a formatted log message to the stream.
Definition: StreamSink.h:133
Base class for sinks.
Definition: Sink.h:40
QUILL_NODISCARD bool is_null() const noexcept
Checks if the stream is null.
Definition: StreamSink.h:186
StreamSink(fs::path stream, FILE *file=nullptr, std::optional< PatternFormatterOptions > const &override_pattern_formatter_options=std::nullopt, FileEventNotifier file_event_notifier=FileEventNotifier{})
Constructor for StreamSink.
Definition: StreamSink.h:59
Captures and stores information about a logging event in compile time.
Definition: MacroMetadata.h:22
static QUILL_ATTRIBUTE_HOT void safe_fwrite(void const *ptr, size_t size, size_t count, FILE *stream)
Writes data safely to the stream.
Definition: StreamSink.h:195
custom exception
Definition: QuillError.h:45
QUILL_ATTRIBUTE_HOT void flush_sink() override
Flushes the stream.
Definition: StreamSink.h:166
Notifies on file events by calling the appropriate callback, the callback is executed on the backend ...
Definition: StreamSink.h:36
StreamSink class for handling log messages.
Definition: StreamSink.h:48
QUILL_ATTRIBUTE_HOT void flush()
Flushes the stream.
Definition: StreamSink.h:210
virtual QUILL_NODISCARD fs::path const & get_filename() const noexcept
Returns the name of the file.
Definition: StreamSink.h:180