xref: /aosp_15_r20/external/cronet/net/log/file_net_log_observer.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2016 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "net/log/file_net_log_observer.h"
6 
7 #include <algorithm>
8 #include <memory>
9 #include <string>
10 #include <string_view>
11 #include <utility>
12 
13 #include "base/containers/queue.h"
14 #include "base/files/file.h"
15 #include "base/files/file_util.h"
16 #include "base/functional/bind.h"
17 #include "base/json/json_writer.h"
18 #include "base/logging.h"
19 #include "base/memory/ptr_util.h"
20 #include "base/numerics/clamped_math.h"
21 #include "base/strings/string_number_conversions.h"
22 #include "base/synchronization/lock.h"
23 #include "base/task/sequenced_task_runner.h"
24 #include "base/task/thread_pool.h"
25 #include "base/values.h"
26 #include "net/log/net_log_capture_mode.h"
27 #include "net/log/net_log_entry.h"
28 #include "net/log/net_log_util.h"
29 #include "net/url_request/url_request_context.h"
30 
31 namespace {
32 
33 // Number of events that can build up in |write_queue_| before a task is posted
34 // to the file task runner to flush them to disk.
35 const int kNumWriteQueueEvents = 15;
36 
37 // TODO(eroman): Should use something other than 10 for number of files?
38 const int kDefaultNumFiles = 10;
39 
CreateFileTaskRunner()40 scoped_refptr<base::SequencedTaskRunner> CreateFileTaskRunner() {
41   // The tasks posted to this sequenced task runner do synchronous File I/O for
42   // the purposes of writing NetLog files.
43   //
44   // These intentionally block shutdown to ensure the log file has finished
45   // being written.
46   return base::ThreadPool::CreateSequencedTaskRunner(
47       {base::MayBlock(), base::TaskPriority::USER_VISIBLE,
48        base::TaskShutdownBehavior::BLOCK_SHUTDOWN});
49 }
50 
51 // Truncates a file, also reseting the seek position.
TruncateFile(base::File * file)52 void TruncateFile(base::File* file) {
53   if (!file->IsValid())
54     return;
55   file->Seek(base::File::FROM_BEGIN, 0);
56   file->SetLength(0);
57 }
58 
59 // Opens |path| in write mode.
OpenFileForWrite(const base::FilePath & path)60 base::File OpenFileForWrite(const base::FilePath& path) {
61   base::File result(path,
62                     base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
63   LOG_IF(ERROR, !result.IsValid()) << "Failed opening: " << path.value();
64   return result;
65 }
66 
67 // Helper that writes data to a file. |file->IsValid()| may be false,
68 // in which case nothing will be written. Returns the number of bytes
69 // successfully written (may be less than input data in case of errors).
WriteToFile(base::File * file,std::string_view data1,std::string_view data2=std::string_view (),std::string_view data3=std::string_view ())70 size_t WriteToFile(base::File* file,
71                    std::string_view data1,
72                    std::string_view data2 = std::string_view(),
73                    std::string_view data3 = std::string_view()) {
74   size_t bytes_written = 0;
75 
76   if (file->IsValid()) {
77     // Append each of data1, data2 and data3.
78     if (!data1.empty())
79       bytes_written +=
80           std::max(0, file->WriteAtCurrentPos(data1.data(), data1.size()));
81     if (!data2.empty())
82       bytes_written +=
83           std::max(0, file->WriteAtCurrentPos(data2.data(), data2.size()));
84     if (!data3.empty())
85       bytes_written +=
86           std::max(0, file->WriteAtCurrentPos(data3.data(), data3.size()));
87   }
88 
89   return bytes_written;
90 }
91 
92 // Copies all of the data at |source_path| and appends it to |destination_file|,
93 // then deletes |source_path|.
AppendToFileThenDelete(const base::FilePath & source_path,base::File * destination_file,char * read_buffer,size_t read_buffer_size)94 void AppendToFileThenDelete(const base::FilePath& source_path,
95                             base::File* destination_file,
96                             char* read_buffer,
97                             size_t read_buffer_size) {
98   base::ScopedFILE source_file(base::OpenFile(source_path, "rb"));
99   if (!source_file)
100     return;
101 
102   // Read |source_path|'s contents in chunks of read_buffer_size and append
103   // to |destination_file|.
104   size_t num_bytes_read;
105   while ((num_bytes_read =
106               fread(read_buffer, 1, read_buffer_size, source_file.get())) > 0) {
107     WriteToFile(destination_file,
108                 std::string_view(read_buffer, num_bytes_read));
109   }
110 
111   // Now that it has been copied, delete the source file.
112   source_file.reset();
113   base::DeleteFile(source_path);
114 }
115 
SiblingInprogressDirectory(const base::FilePath & log_path)116 base::FilePath SiblingInprogressDirectory(const base::FilePath& log_path) {
117   return log_path.AddExtension(FILE_PATH_LITERAL(".inprogress"));
118 }
119 
120 }  // namespace
121 
122 namespace net {
123 
124 // Used to store events to be written to file.
125 using EventQueue = base::queue<std::unique_ptr<std::string>>;
126 
127 // WriteQueue receives events from FileNetLogObserver on the main thread and
128 // holds them in a queue until they are drained from the queue and written to
129 // file on the file task runner.
130 //
131 // WriteQueue contains the resources shared between the main thread and the
132 // file task runner. |lock_| must be acquired to read or write to |queue_| and
133 // |memory_|.
134 //
135 // WriteQueue is refcounted and should be destroyed once all events on the
136 // file task runner have finished executing.
137 class FileNetLogObserver::WriteQueue
138     : public base::RefCountedThreadSafe<WriteQueue> {
139  public:
140   // |memory_max| indicates the maximum amount of memory that the virtual write
141   // queue can use. If |memory_| exceeds |memory_max_|, the |queue_| of events
142   // is overwritten.
143   explicit WriteQueue(uint64_t memory_max);
144 
145   WriteQueue(const WriteQueue&) = delete;
146   WriteQueue& operator=(const WriteQueue&) = delete;
147 
148   // Adds |event| to |queue_|. Also manages the size of |memory_|; if it
149   // exceeds |memory_max_|, then old events are dropped from |queue_| without
150   // being written to file.
151   //
152   // Returns the number of events in the |queue_|.
153   size_t AddEntryToQueue(std::unique_ptr<std::string> event);
154 
155   // Swaps |queue_| with |local_queue|. |local_queue| should be empty, so that
156   // |queue_| is emptied. Resets |memory_| to 0.
157   void SwapQueue(EventQueue* local_queue);
158 
159  private:
160   friend class base::RefCountedThreadSafe<WriteQueue>;
161 
162   ~WriteQueue();
163 
164   // Queue of events to be written, shared between main thread and file task
165   // runner. Main thread adds events to the queue and the file task runner
166   // drains them and writes the events to file.
167   //
168   // |lock_| must be acquired to read or write to this.
169   EventQueue queue_;
170 
171   // Tracks how much memory is being used by the virtual write queue.
172   // Incremented in AddEntryToQueue() when events are added to the
173   // buffer, and decremented when SwapQueue() is called and the file task
174   // runner's local queue is swapped with the shared write queue.
175   //
176   // |lock_| must be acquired to read or write to this.
177   uint64_t memory_ = 0;
178 
179   // Indicates the maximum amount of memory that the |queue_| is allowed to
180   // use.
181   const uint64_t memory_max_;
182 
183   // Protects access to |queue_| and |memory_|.
184   //
185   // A lock is necessary because |queue_| and |memory_| are shared between the
186   // file task runner and the main thread. NetLog's lock protects OnAddEntry(),
187   // which calls AddEntryToQueue(), but it does not protect access to the
188   // observer's member variables. Thus, a race condition exists if a thread is
189   // calling OnAddEntry() at the same time that the file task runner is
190   // accessing |memory_| and |queue_| to write events to file. The |queue_| and
191   // |memory_| counter are necessary to bound the amount of memory that is used
192   // for the queue in the event that the file task runner lags significantly
193   // behind the main thread in writing events to file.
194   base::Lock lock_;
195 };
196 
197 // FileWriter is responsible for draining events from a WriteQueue and writing
198 // them to disk. FileWriter can be constructed on any thread, and
199 // afterwards is only accessed on the file task runner.
200 class FileNetLogObserver::FileWriter {
201  public:
202   // If max_event_file_size == kNoLimit, then no limit is enforced.
203   FileWriter(const base::FilePath& log_path,
204              const base::FilePath& inprogress_dir_path,
205              std::optional<base::File> pre_existing_log_file,
206              uint64_t max_event_file_size,
207              size_t total_num_event_files,
208              scoped_refptr<base::SequencedTaskRunner> task_runner);
209 
210   FileWriter(const FileWriter&) = delete;
211   FileWriter& operator=(const FileWriter&) = delete;
212 
213   ~FileWriter();
214 
215   // Writes |constants_value| to disk and opens the events array (closed in
216   // Stop()).
217   void Initialize(std::unique_ptr<base::Value::Dict> constants_value);
218 
219   // Closes the events array opened in Initialize() and writes |polled_data| to
220   // disk. If |polled_data| cannot be converted to proper JSON, then it
221   // is ignored.
222   void Stop(std::unique_ptr<base::Value> polled_data);
223 
224   // Drains |queue_| from WriteQueue into a local file queue and writes the
225   // events in the queue to disk.
226   void Flush(scoped_refptr<WriteQueue> write_queue);
227 
228   // Deletes all netlog files. It is not valid to call any method of
229   // FileNetLogObserver after DeleteAllFiles().
230   void DeleteAllFiles();
231 
232   void FlushThenStop(scoped_refptr<WriteQueue> write_queue,
233                      std::unique_ptr<base::Value> polled_data);
234 
235  private:
236   // Returns true if there is no file size bound to enforce.
237   //
238   // When operating in unbounded mode, the implementation is optimized to stream
239   // writes to a single file, rather than chunking them across temporary event
240   // files.
241   bool IsUnbounded() const;
242   bool IsBounded() const;
243 
244   // Increments |current_event_file_number_|, and updates all state relating to
245   // the current event file (open file handle, num bytes written, current file
246   // number).
247   void IncrementCurrentEventFile();
248 
249   // Returns the path to the event file having |index|. This looks like
250   // "LOGDIR/event_file_<index>.json".
251   base::FilePath GetEventFilePath(size_t index) const;
252 
253   // Gets the file path where constants are saved at the start of
254   // logging. This looks like "LOGDIR/constants.json".
255   base::FilePath GetConstantsFilePath() const;
256 
257   // Gets the file path where the final data is written at the end of logging.
258   // This looks like "LOGDIR/end_netlog.json".
259   base::FilePath GetClosingFilePath() const;
260 
261   // Returns the corresponding index for |file_number|. File "numbers" are a
262   // monotonically increasing identifier that start at 1 (a value of zero means
263   // it is uninitialized), whereas the file "index" is a bounded value that
264   // wraps and identifies the file path to use.
265   //
266   // Keeping track of the current number rather than index makes it a bit easier
267   // to assemble a file at the end, since it is unambiguous which paths have
268   // been used/re-used.
269   size_t FileNumberToIndex(size_t file_number) const;
270 
271   // Writes |constants_value| to a file.
272   static void WriteConstantsToFile(
273       std::unique_ptr<base::Value::Dict> constants_value,
274       base::File* file);
275 
276   // Writes |polled_data| to a file.
277   static void WritePolledDataToFile(std::unique_ptr<base::Value> polled_data,
278                                     base::File* file);
279 
280   // If any events were written (wrote_event_bytes_), rewinds |file| by 2 bytes
281   // in order to overwrite the trailing ",\n" that was written by the last event
282   // line.
283   void RewindIfWroteEventBytes(base::File* file) const;
284 
285   // Concatenates all the log files to assemble the final
286   // |final_log_file_|. This single "stitched" file is what other
287   // log ingesting tools expect.
288   void StitchFinalLogFile();
289 
290   // Creates the .inprogress directory used by bounded mode.
291   void CreateInprogressDirectory();
292 
293   // The file the final netlog is written to. In bounded mode this is mostly
294   // written to once logging is stopped, whereas in unbounded mode events will
295   // be directly written to it.
296   base::File final_log_file_;
297 
298   // If non-empty, this is the path to |final_log_file_| created and owned
299   // by FileWriter itself (rather than passed in to Create*PreExisting
300   // methods of FileNetLogObserver).
301   const base::FilePath final_log_path_;
302 
303   // Path to a (temporary) directory where files are written in bounded mode.
304   // When logging is stopped these files are stitched together and written
305   // to the final log path.
306   const base::FilePath inprogress_dir_path_;
307 
308   // Holds the numbered events file where data is currently being written to.
309   // The file path of this file is GetEventFilePath(current_event_file_number_).
310   // The file may be !IsValid() if an error previously occurred opening the
311   // file, or logging has been stopped.
312   base::File current_event_file_;
313   uint64_t current_event_file_size_;
314 
315   // Indicates the total number of netlog event files allowed.
316   // (The files GetConstantsFilePath() and GetClosingFilePath() do
317   // not count against the total.)
318   const size_t total_num_event_files_;
319 
320   // Counter for the events file currently being written into. See
321   // FileNumberToIndex() for an explanation of what "number" vs "index" mean.
322   size_t current_event_file_number_ = 0;
323 
324   // Indicates the maximum size of each individual events file. May be kNoLimit
325   // to indicate that it can grow arbitrarily large.
326   const uint64_t max_event_file_size_;
327 
328   // Whether any bytes were written for events. This is used to properly format
329   // JSON (events list shouldn't end with a comma).
330   bool wrote_event_bytes_ = false;
331 
332   // Task runner for doing file operations.
333   const scoped_refptr<base::SequencedTaskRunner> task_runner_;
334 };
335 
CreateBounded(const base::FilePath & log_path,uint64_t max_total_size,NetLogCaptureMode capture_mode,std::unique_ptr<base::Value::Dict> constants)336 std::unique_ptr<FileNetLogObserver> FileNetLogObserver::CreateBounded(
337     const base::FilePath& log_path,
338     uint64_t max_total_size,
339     NetLogCaptureMode capture_mode,
340     std::unique_ptr<base::Value::Dict> constants) {
341   return CreateInternal(log_path, SiblingInprogressDirectory(log_path),
342                         std::nullopt, max_total_size, kDefaultNumFiles,
343                         capture_mode, std::move(constants));
344 }
345 
CreateUnbounded(const base::FilePath & log_path,NetLogCaptureMode capture_mode,std::unique_ptr<base::Value::Dict> constants)346 std::unique_ptr<FileNetLogObserver> FileNetLogObserver::CreateUnbounded(
347     const base::FilePath& log_path,
348     NetLogCaptureMode capture_mode,
349     std::unique_ptr<base::Value::Dict> constants) {
350   return CreateInternal(log_path, base::FilePath(), std::nullopt, kNoLimit,
351                         kDefaultNumFiles, capture_mode, std::move(constants));
352 }
353 
354 std::unique_ptr<FileNetLogObserver>
CreateBoundedPreExisting(const base::FilePath & inprogress_dir_path,base::File output_file,uint64_t max_total_size,NetLogCaptureMode capture_mode,std::unique_ptr<base::Value::Dict> constants)355 FileNetLogObserver::CreateBoundedPreExisting(
356     const base::FilePath& inprogress_dir_path,
357     base::File output_file,
358     uint64_t max_total_size,
359     NetLogCaptureMode capture_mode,
360     std::unique_ptr<base::Value::Dict> constants) {
361   return CreateInternal(base::FilePath(), inprogress_dir_path,
362                         std::make_optional<base::File>(std::move(output_file)),
363                         max_total_size, kDefaultNumFiles, capture_mode,
364                         std::move(constants));
365 }
366 
367 std::unique_ptr<FileNetLogObserver>
CreateUnboundedPreExisting(base::File output_file,NetLogCaptureMode capture_mode,std::unique_ptr<base::Value::Dict> constants)368 FileNetLogObserver::CreateUnboundedPreExisting(
369     base::File output_file,
370     NetLogCaptureMode capture_mode,
371     std::unique_ptr<base::Value::Dict> constants) {
372   return CreateInternal(base::FilePath(), base::FilePath(),
373                         std::make_optional<base::File>(std::move(output_file)),
374                         kNoLimit, kDefaultNumFiles, capture_mode,
375                         std::move(constants));
376 }
377 
~FileNetLogObserver()378 FileNetLogObserver::~FileNetLogObserver() {
379   if (net_log()) {
380     // StopObserving was not called.
381     net_log()->RemoveObserver(this);
382     file_task_runner_->PostTask(
383         FROM_HERE,
384         base::BindOnce(&FileNetLogObserver::FileWriter::DeleteAllFiles,
385                        base::Unretained(file_writer_.get())));
386   }
387   file_task_runner_->DeleteSoon(FROM_HERE, file_writer_.release());
388 }
389 
StartObserving(NetLog * net_log)390 void FileNetLogObserver::StartObserving(NetLog* net_log) {
391   net_log->AddObserver(this, capture_mode_);
392 }
393 
StopObserving(std::unique_ptr<base::Value> polled_data,base::OnceClosure optional_callback)394 void FileNetLogObserver::StopObserving(std::unique_ptr<base::Value> polled_data,
395                                        base::OnceClosure optional_callback) {
396   net_log()->RemoveObserver(this);
397 
398   base::OnceClosure bound_flush_then_stop =
399       base::BindOnce(&FileNetLogObserver::FileWriter::FlushThenStop,
400                      base::Unretained(file_writer_.get()), write_queue_,
401                      std::move(polled_data));
402 
403   // Note that PostTaskAndReply() requires a non-null closure.
404   if (!optional_callback.is_null()) {
405     file_task_runner_->PostTaskAndReply(FROM_HERE,
406                                         std::move(bound_flush_then_stop),
407                                         std::move(optional_callback));
408   } else {
409     file_task_runner_->PostTask(FROM_HERE, std::move(bound_flush_then_stop));
410   }
411 }
412 
OnAddEntry(const NetLogEntry & entry)413 void FileNetLogObserver::OnAddEntry(const NetLogEntry& entry) {
414   auto json = std::make_unique<std::string>();
415 
416   *json = SerializeNetLogValueToJson(entry.ToDict());
417 
418   size_t queue_size = write_queue_->AddEntryToQueue(std::move(json));
419 
420   // If events build up in |write_queue_|, trigger the file task runner to drain
421   // the queue. Because only 1 item is added to the queue at a time, if
422   // queue_size > kNumWriteQueueEvents a task has already been posted, or will
423   // be posted.
424   if (queue_size == kNumWriteQueueEvents) {
425     file_task_runner_->PostTask(
426         FROM_HERE,
427         base::BindOnce(&FileNetLogObserver::FileWriter::Flush,
428                        base::Unretained(file_writer_.get()), write_queue_));
429   }
430 }
431 
CreateBoundedForTests(const base::FilePath & log_path,uint64_t max_total_size,size_t total_num_event_files,NetLogCaptureMode capture_mode,std::unique_ptr<base::Value::Dict> constants)432 std::unique_ptr<FileNetLogObserver> FileNetLogObserver::CreateBoundedForTests(
433     const base::FilePath& log_path,
434     uint64_t max_total_size,
435     size_t total_num_event_files,
436     NetLogCaptureMode capture_mode,
437     std::unique_ptr<base::Value::Dict> constants) {
438   return CreateInternal(log_path, SiblingInprogressDirectory(log_path),
439                         std::nullopt, max_total_size, total_num_event_files,
440                         capture_mode, std::move(constants));
441 }
442 
CreateInternal(const base::FilePath & log_path,const base::FilePath & inprogress_dir_path,std::optional<base::File> pre_existing_log_file,uint64_t max_total_size,size_t total_num_event_files,NetLogCaptureMode capture_mode,std::unique_ptr<base::Value::Dict> constants)443 std::unique_ptr<FileNetLogObserver> FileNetLogObserver::CreateInternal(
444     const base::FilePath& log_path,
445     const base::FilePath& inprogress_dir_path,
446     std::optional<base::File> pre_existing_log_file,
447     uint64_t max_total_size,
448     size_t total_num_event_files,
449     NetLogCaptureMode capture_mode,
450     std::unique_ptr<base::Value::Dict> constants) {
451   DCHECK_GT(total_num_event_files, 0u);
452 
453   scoped_refptr<base::SequencedTaskRunner> file_task_runner =
454       CreateFileTaskRunner();
455 
456   const uint64_t max_event_file_size =
457       max_total_size == kNoLimit ? kNoLimit
458                                  : max_total_size / total_num_event_files;
459 
460   // The FileWriter uses a soft limit to write events to file that allows
461   // the size of the file to exceed the limit, but the WriteQueue uses a hard
462   // limit which the size of |WriteQueue::queue_| cannot exceed. Thus, the
463   // FileWriter may write more events to file than can be contained by
464   // the WriteQueue if they have the same size limit. The maximum size of the
465   // WriteQueue is doubled to allow |WriteQueue::queue_| to hold enough events
466   // for the FileWriter to fill all files. As long as all events have
467   // sizes <= the size of an individual event file, the discrepancy between the
468   // hard limit and the soft limit will not cause an issue.
469   // TODO(dconnol): Handle the case when the WriteQueue  still doesn't
470   // contain enough events to fill all files, because of very large events
471   // relative to file size.
472   auto file_writer = std::make_unique<FileWriter>(
473       log_path, inprogress_dir_path, std::move(pre_existing_log_file),
474       max_event_file_size, total_num_event_files, file_task_runner);
475 
476   uint64_t write_queue_memory_max =
477       base::MakeClampedNum<uint64_t>(max_total_size) * 2;
478 
479   return base::WrapUnique(new FileNetLogObserver(
480       file_task_runner, std::move(file_writer),
481       base::MakeRefCounted<WriteQueue>(write_queue_memory_max), capture_mode,
482       std::move(constants)));
483 }
484 
FileNetLogObserver(scoped_refptr<base::SequencedTaskRunner> file_task_runner,std::unique_ptr<FileWriter> file_writer,scoped_refptr<WriteQueue> write_queue,NetLogCaptureMode capture_mode,std::unique_ptr<base::Value::Dict> constants)485 FileNetLogObserver::FileNetLogObserver(
486     scoped_refptr<base::SequencedTaskRunner> file_task_runner,
487     std::unique_ptr<FileWriter> file_writer,
488     scoped_refptr<WriteQueue> write_queue,
489     NetLogCaptureMode capture_mode,
490     std::unique_ptr<base::Value::Dict> constants)
491     : file_task_runner_(std::move(file_task_runner)),
492       write_queue_(std::move(write_queue)),
493       file_writer_(std::move(file_writer)),
494       capture_mode_(capture_mode) {
495   if (!constants)
496     constants = std::make_unique<base::Value::Dict>(GetNetConstants());
497 
498   DCHECK(!constants->Find("logCaptureMode"));
499   constants->Set("logCaptureMode", CaptureModeToString(capture_mode));
500   file_task_runner_->PostTask(
501       FROM_HERE, base::BindOnce(&FileNetLogObserver::FileWriter::Initialize,
502                                 base::Unretained(file_writer_.get()),
503                                 std::move(constants)));
504 }
505 
CaptureModeToString(NetLogCaptureMode mode)506 std::string FileNetLogObserver::CaptureModeToString(NetLogCaptureMode mode) {
507   switch (mode) {
508     case NetLogCaptureMode::kDefault:
509       return "Default";
510     case NetLogCaptureMode::kIncludeSensitive:
511       return "IncludeSensitive";
512     case NetLogCaptureMode::kEverything:
513       return "Everything";
514   }
515   NOTREACHED();
516   return "UNKNOWN";
517 }
518 
WriteQueue(uint64_t memory_max)519 FileNetLogObserver::WriteQueue::WriteQueue(uint64_t memory_max)
520     : memory_max_(memory_max) {}
521 
AddEntryToQueue(std::unique_ptr<std::string> event)522 size_t FileNetLogObserver::WriteQueue::AddEntryToQueue(
523     std::unique_ptr<std::string> event) {
524   base::AutoLock lock(lock_);
525 
526   memory_ += event->size();
527   queue_.push(std::move(event));
528 
529   while (memory_ > memory_max_ && !queue_.empty()) {
530     // Delete oldest events in the queue.
531     DCHECK(queue_.front());
532     memory_ -= queue_.front()->size();
533     queue_.pop();
534   }
535 
536   return queue_.size();
537 }
538 
SwapQueue(EventQueue * local_queue)539 void FileNetLogObserver::WriteQueue::SwapQueue(EventQueue* local_queue) {
540   DCHECK(local_queue->empty());
541   base::AutoLock lock(lock_);
542   queue_.swap(*local_queue);
543   memory_ = 0;
544 }
545 
546 FileNetLogObserver::WriteQueue::~WriteQueue() = default;
547 
FileWriter(const base::FilePath & log_path,const base::FilePath & inprogress_dir_path,std::optional<base::File> pre_existing_log_file,uint64_t max_event_file_size,size_t total_num_event_files,scoped_refptr<base::SequencedTaskRunner> task_runner)548 FileNetLogObserver::FileWriter::FileWriter(
549     const base::FilePath& log_path,
550     const base::FilePath& inprogress_dir_path,
551     std::optional<base::File> pre_existing_log_file,
552     uint64_t max_event_file_size,
553     size_t total_num_event_files,
554     scoped_refptr<base::SequencedTaskRunner> task_runner)
555     : final_log_path_(log_path),
556       inprogress_dir_path_(inprogress_dir_path),
557       total_num_event_files_(total_num_event_files),
558       max_event_file_size_(max_event_file_size),
559       task_runner_(std::move(task_runner)) {
560   DCHECK_EQ(pre_existing_log_file.has_value(), log_path.empty());
561   DCHECK_EQ(IsBounded(), !inprogress_dir_path.empty());
562 
563   if (pre_existing_log_file.has_value()) {
564     // pre_existing_log_file.IsValid() being false is fine.
565     final_log_file_ = std::move(pre_existing_log_file.value());
566   }
567 }
568 
569 FileNetLogObserver::FileWriter::~FileWriter() = default;
570 
Initialize(std::unique_ptr<base::Value::Dict> constants_value)571 void FileNetLogObserver::FileWriter::Initialize(
572     std::unique_ptr<base::Value::Dict> constants_value) {
573   DCHECK(task_runner_->RunsTasksInCurrentSequence());
574 
575   // Open the final log file, and keep it open for the duration of logging (even
576   // in bounded mode).
577   if (!final_log_path_.empty())
578     final_log_file_ = OpenFileForWrite(final_log_path_);
579   else
580     TruncateFile(&final_log_file_);
581 
582   if (IsBounded()) {
583     CreateInprogressDirectory();
584     base::File constants_file = OpenFileForWrite(GetConstantsFilePath());
585     WriteConstantsToFile(std::move(constants_value), &constants_file);
586   } else {
587     WriteConstantsToFile(std::move(constants_value), &final_log_file_);
588   }
589 }
590 
Stop(std::unique_ptr<base::Value> polled_data)591 void FileNetLogObserver::FileWriter::Stop(
592     std::unique_ptr<base::Value> polled_data) {
593   DCHECK(task_runner_->RunsTasksInCurrentSequence());
594 
595   // Write out the polled data.
596   if (IsBounded()) {
597     base::File closing_file = OpenFileForWrite(GetClosingFilePath());
598     WritePolledDataToFile(std::move(polled_data), &closing_file);
599   } else {
600     RewindIfWroteEventBytes(&final_log_file_);
601     WritePolledDataToFile(std::move(polled_data), &final_log_file_);
602   }
603 
604   // If operating in bounded mode, the events were written to separate files
605   // within |inprogress_dir_path_|. Assemble them into the final destination
606   // file.
607   if (IsBounded())
608     StitchFinalLogFile();
609 
610   // Ensure the final log file has been flushed.
611   final_log_file_.Close();
612 }
613 
Flush(scoped_refptr<FileNetLogObserver::WriteQueue> write_queue)614 void FileNetLogObserver::FileWriter::Flush(
615     scoped_refptr<FileNetLogObserver::WriteQueue> write_queue) {
616   DCHECK(task_runner_->RunsTasksInCurrentSequence());
617 
618   EventQueue local_file_queue;
619   write_queue->SwapQueue(&local_file_queue);
620 
621   while (!local_file_queue.empty()) {
622     base::File* output_file;
623 
624     // If in bounded mode, output events to the current event file. Otherwise
625     // output events to the final log path.
626     if (IsBounded()) {
627       if (current_event_file_number_ == 0 ||
628           current_event_file_size_ >= max_event_file_size_) {
629         IncrementCurrentEventFile();
630       }
631       output_file = &current_event_file_;
632     } else {
633       output_file = &final_log_file_;
634     }
635 
636     size_t bytes_written =
637         WriteToFile(output_file, *local_file_queue.front(), ",\n");
638 
639     wrote_event_bytes_ |= bytes_written > 0;
640 
641     // Keep track of the filesize for current event file when in bounded mode.
642     if (IsBounded())
643       current_event_file_size_ += bytes_written;
644 
645     local_file_queue.pop();
646   }
647 }
648 
DeleteAllFiles()649 void FileNetLogObserver::FileWriter::DeleteAllFiles() {
650   DCHECK(task_runner_->RunsTasksInCurrentSequence());
651 
652   final_log_file_.Close();
653 
654   if (IsBounded()) {
655     current_event_file_.Close();
656     base::DeletePathRecursively(inprogress_dir_path_);
657   }
658 
659   // Only delete |final_log_file_| if it was created internally.
660   // (If it was provided as a base::File by the caller, don't try to delete it).
661   if (!final_log_path_.empty())
662     base::DeleteFile(final_log_path_);
663 }
664 
FlushThenStop(scoped_refptr<FileNetLogObserver::WriteQueue> write_queue,std::unique_ptr<base::Value> polled_data)665 void FileNetLogObserver::FileWriter::FlushThenStop(
666     scoped_refptr<FileNetLogObserver::WriteQueue> write_queue,
667     std::unique_ptr<base::Value> polled_data) {
668   Flush(write_queue);
669   Stop(std::move(polled_data));
670 }
671 
IsUnbounded() const672 bool FileNetLogObserver::FileWriter::IsUnbounded() const {
673   return max_event_file_size_ == kNoLimit;
674 }
675 
IsBounded() const676 bool FileNetLogObserver::FileWriter::IsBounded() const {
677   return !IsUnbounded();
678 }
679 
IncrementCurrentEventFile()680 void FileNetLogObserver::FileWriter::IncrementCurrentEventFile() {
681   DCHECK(task_runner_->RunsTasksInCurrentSequence());
682   DCHECK(IsBounded());
683 
684   current_event_file_number_++;
685   current_event_file_ = OpenFileForWrite(
686       GetEventFilePath(FileNumberToIndex(current_event_file_number_)));
687   current_event_file_size_ = 0;
688 }
689 
GetEventFilePath(size_t index) const690 base::FilePath FileNetLogObserver::FileWriter::GetEventFilePath(
691     size_t index) const {
692   DCHECK_LT(index, total_num_event_files_);
693   DCHECK(IsBounded());
694   return inprogress_dir_path_.AppendASCII(
695       "event_file_" + base::NumberToString(index) + ".json");
696 }
697 
GetConstantsFilePath() const698 base::FilePath FileNetLogObserver::FileWriter::GetConstantsFilePath() const {
699   return inprogress_dir_path_.AppendASCII("constants.json");
700 }
701 
GetClosingFilePath() const702 base::FilePath FileNetLogObserver::FileWriter::GetClosingFilePath() const {
703   return inprogress_dir_path_.AppendASCII("end_netlog.json");
704 }
705 
FileNumberToIndex(size_t file_number) const706 size_t FileNetLogObserver::FileWriter::FileNumberToIndex(
707     size_t file_number) const {
708   DCHECK_GT(file_number, 0u);
709   // Note that "file numbers" start at 1 not 0.
710   return (file_number - 1) % total_num_event_files_;
711 }
712 
WriteConstantsToFile(std::unique_ptr<base::Value::Dict> constants_value,base::File * file)713 void FileNetLogObserver::FileWriter::WriteConstantsToFile(
714     std::unique_ptr<base::Value::Dict> constants_value,
715     base::File* file) {
716   // Print constants to file and open events array.
717   std::string json = SerializeNetLogValueToJson(*constants_value);
718   WriteToFile(file, "{\"constants\":", json, ",\n\"events\": [\n");
719 }
720 
WritePolledDataToFile(std::unique_ptr<base::Value> polled_data,base::File * file)721 void FileNetLogObserver::FileWriter::WritePolledDataToFile(
722     std::unique_ptr<base::Value> polled_data,
723     base::File* file) {
724   // Close the events array.
725   WriteToFile(file, "]");
726 
727   // Write the polled data (if any).
728   if (polled_data) {
729     std::string polled_data_json;
730     base::JSONWriter::Write(*polled_data, &polled_data_json);
731     if (!polled_data_json.empty())
732       WriteToFile(file, ",\n\"polledData\": ", polled_data_json, "\n");
733   }
734 
735   // Close the log.
736   WriteToFile(file, "}\n");
737 }
738 
RewindIfWroteEventBytes(base::File * file) const739 void FileNetLogObserver::FileWriter::RewindIfWroteEventBytes(
740     base::File* file) const {
741   if (file->IsValid() && wrote_event_bytes_) {
742     // To be valid JSON the events array should not end with a comma. If events
743     // were written though, they will have been terminated with "\n," so strip
744     // it before closing the events array.
745     file->Seek(base::File::FROM_END, -2);
746   }
747 }
748 
StitchFinalLogFile()749 void FileNetLogObserver::FileWriter::StitchFinalLogFile() {
750   // Make sure all the events files are flushed (as will read them next).
751   current_event_file_.Close();
752 
753   // Allocate a 64K buffer used for reading the files. At most kReadBufferSize
754   // bytes will be in memory at a time.
755   const size_t kReadBufferSize = 1 << 16;  // 64KiB
756   auto read_buffer = std::make_unique<char[]>(kReadBufferSize);
757 
758   if (final_log_file_.IsValid()) {
759     // Truncate the final log file.
760     TruncateFile(&final_log_file_);
761 
762     // Append the constants file.
763     AppendToFileThenDelete(GetConstantsFilePath(), &final_log_file_,
764                            read_buffer.get(), kReadBufferSize);
765 
766     // Iterate over the events files, from oldest to most recent, and append
767     // them to the final destination. Note that "file numbers" start at 1 not 0.
768     size_t end_filenumber = current_event_file_number_ + 1;
769     size_t begin_filenumber =
770         current_event_file_number_ <= total_num_event_files_
771             ? 1
772             : end_filenumber - total_num_event_files_;
773     for (size_t filenumber = begin_filenumber; filenumber < end_filenumber;
774          ++filenumber) {
775       AppendToFileThenDelete(GetEventFilePath(FileNumberToIndex(filenumber)),
776                              &final_log_file_, read_buffer.get(),
777                              kReadBufferSize);
778     }
779 
780     // Account for the final event line ending in a ",\n". Strip it to form
781     // valid JSON.
782     RewindIfWroteEventBytes(&final_log_file_);
783 
784     // Append the polled data.
785     AppendToFileThenDelete(GetClosingFilePath(), &final_log_file_,
786                            read_buffer.get(), kReadBufferSize);
787   }
788 
789   // Delete the inprogress directory (and anything that may still be left inside
790   // it).
791   base::DeletePathRecursively(inprogress_dir_path_);
792 }
793 
CreateInprogressDirectory()794 void FileNetLogObserver::FileWriter::CreateInprogressDirectory() {
795   DCHECK(IsBounded());
796 
797   // If an output file couldn't be created, either creation of intermediate
798   // files will also fail (if they're in a sibling directory), or are they are
799   // hidden somewhere the user would be unlikely to find them, so there is
800   // little reason to progress.
801   if (!final_log_file_.IsValid())
802     return;
803 
804   if (!base::CreateDirectory(inprogress_dir_path_)) {
805     LOG(WARNING) << "Failed creating directory: "
806                  << inprogress_dir_path_.value();
807     return;
808   }
809 
810   // It is OK if the path is wrong due to encoding - this is really just a
811   // convenience display for the user in understanding what the file means.
812   std::string in_progress_path = inprogress_dir_path_.AsUTF8Unsafe();
813 
814   // Since |final_log_file_| will not be written to until the very end, leave
815   // some data in it explaining that the real data is currently in the
816   // .inprogress directory. This ordinarily won't be visible (overwritten when
817   // stopping) however if logging does not end gracefully the comments are
818   // useful for recovery.
819   WriteToFile(
820       &final_log_file_, "Logging is in progress writing data to:\n    ",
821       in_progress_path,
822       "\n\n"
823       "That data will be stitched into a single file (this one) once logging\n"
824       "has stopped.\n"
825       "\n"
826       "If logging was interrupted, you can stitch a NetLog file out of the\n"
827       ".inprogress directory manually using:\n"
828       "\n"
829       "https://chromium.googlesource.com/chromium/src/+/main/net/tools/"
830       "stitch_net_log_files.py\n");
831 }
832 
SerializeNetLogValueToJson(const base::ValueView & value)833 std::string SerializeNetLogValueToJson(const base::ValueView& value) {
834   // Omit trailing ".0" when printing a DOUBLE that is representable as a 64-bit
835   // integer. This makes the values returned by NetLogNumberValue() look more
836   // pleasant (for representing integers between 32 and 53 bits large).
837   int options = base::JSONWriter::OPTIONS_OMIT_DOUBLE_TYPE_PRESERVATION;
838 
839   std::string json;
840   bool ok = base::JSONWriter::WriteWithOptions(value, options, &json);
841 
842   // Serialization shouldn't fail. However it can if a consumer has passed a
843   // parameter of type BINARY, since JSON serialization can't handle that.
844   DCHECK(ok);
845 
846   return json;
847 }
848 
849 }  // namespace net
850