1//===- AtomicLineLogger.cpp -----------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file defines the implementation of an AtomicLineLogger and the relevant
10// supporting classes.
11//
12//===----------------------------------------------------------------------===//
13
14#include "clang/Basic/AtomicLineLogger.h"
15#include "llvm/ADT/StringRef.h"
16#include "llvm/Support/Errno.h"
17#include "llvm/Support/ErrorHandling.h"
18#include "llvm/Support/FileSystem.h"
19#include "llvm/Support/Format.h"
20#include "llvm/Support/Process.h"
21#include "llvm/Support/Threading.h"
22#ifndef _WIN32
23#include <unistd.h>
24#endif
25#ifdef __APPLE__
26#include <sys/time.h>
27#endif
28
29using namespace clang;
30
31static uint64_t getTimestampMillis() {
32#ifdef __APPLE__
33 // Using chrono is roughly 50% slower.
34 struct timeval T;
35 gettimeofday(&T, 0);
36 return T.tv_sec * 1000 + T.tv_usec / 1000;
37#else
38 auto Time = std::chrono::system_clock::now();
39 auto Millis = std::chrono::duration_cast<std::chrono::milliseconds>(
40 d: Time.time_since_epoch());
41 return Millis.count();
42#endif
43}
44
45static int openLogFile(StringRef Path) {
46#ifdef _WIN32
47 // Logging is always disabled on Windows. openLogFile implements this policy
48 // by never returning a valid FD, so the logger and the LogLines it creates
49 // stay dormant (FD == -1). The reason is that writes to files opened with
50 // OF_Append are not guaranteed atomic on Windows. If a use case arises we'll
51 // need a different strategy to write LogLines atomically.
52 (void)Path;
53 return -1;
54#else
55 int FD = -1;
56 std::error_code EC = llvm::sys::fs::openFileForWrite(
57 Name: Path, ResultFD&: FD, Disp: llvm::sys::fs::CD_OpenAlways, Flags: llvm::sys::fs::OF_Append);
58 if (EC) {
59 llvm::errs() << "warning: unable to open log file '" << Path
60 << "': " << EC.message() << "\n";
61 return -1;
62 }
63 return FD;
64#endif
65}
66
67// Writes the whole line into an FD that is opened with OF_Append.
68// This function only does one write (up to retry due to interrupts), and the
69// single write is blocking and atomic on POSIX systems.
70static bool writeLineToFD(int FD, const char *Data, size_t Size) {
71#ifdef _WIN32
72 (void)FD, (void)Data, (void)Size;
73 llvm_unreachable("dependency scanning logging is unsupported on Windows");
74#else
75 ssize_t Written = llvm::sys::RetryAfterSignal(Fail: -1, F&: write, As: FD, As: Data, As: Size);
76 return Written >= 0 && (static_cast<size_t>(Written) == Size);
77#endif
78}
79
80LogLine::LogLine(int FD, std::atomic<uint64_t> *DroppedLines)
81 : FormattingOS(Buffer), FD(FD), DroppedLines(DroppedLines) {
82 auto Millis = getTimestampMillis();
83 *FormattingOS << llvm::format(Fmt: "[%lld.%0.3lld]", Vals: Millis / 1000, Vals: Millis % 1000);
84 *FormattingOS << ' ' << llvm::sys::Process::getProcessId() << ' '
85 << llvm::get_threadid() << ": ";
86}
87
88LogLine::LogLine(LogLine &&Other)
89 : Buffer(std::move(Other.Buffer)), FD(Other.FD),
90 DroppedLines(Other.DroppedLines) {
91 if (Other.FormattingOS)
92 FormattingOS.emplace(args&: Buffer);
93
94 // Destroy the info in Other so its destructor does not write out the line.
95 Other.FormattingOS.reset();
96 Other.FD = -1;
97 Other.DroppedLines = nullptr;
98}
99
100LogLine::~LogLine() {
101 if (!FormattingOS)
102 return;
103 *FormattingOS << "\n";
104 if (!writeLineToFD(FD, Data: Buffer.data(), Size: Buffer.size()))
105 DroppedLines->fetch_add(i: 1, m: std::memory_order_relaxed);
106}
107
108void AtomicLineLogger::initialize(StringRef LogFilePath) {
109 LogPath = LogFilePath.str();
110 int NewFD = openLogFile(Path: LogFilePath);
111 if (NewFD == -1)
112 return;
113 FD.store(i: NewFD, m: std::memory_order_relaxed);
114 log() << "logging_start";
115}
116
117AtomicLineLogger::AtomicLineLogger(StringRef LogFilePath) {
118 if (LogFilePath.empty())
119 return;
120 initialize(LogFilePath);
121 PathSource = LogPathSource::Constructor;
122}
123
124bool AtomicLineLogger::enable(StringRef RequestedLogPath) {
125 std::lock_guard<std::mutex> Lock(EnableMtx);
126 switch (PathSource) {
127 case LogPathSource::None:
128 PathSource = LogPathSource::EnableMethod;
129 if (!RequestedLogPath.empty())
130 initialize(LogFilePath: RequestedLogPath);
131 return true;
132 case LogPathSource::Constructor:
133 return RequestedLogPath.empty() || RequestedLogPath == LogPath;
134 case LogPathSource::EnableMethod:
135 return RequestedLogPath == LogPath;
136 }
137
138 llvm_unreachable("unhandled LogPathSource");
139}
140
141LogLine AtomicLineLogger::log() {
142 int CurFD = FD.load(m: std::memory_order_relaxed);
143 if (CurFD != -1)
144 return LogLine(CurFD, &DroppedLines);
145 return LogLine();
146}
147
148AtomicLineLogger::~AtomicLineLogger() {
149 int CurFD = FD.load(m: std::memory_order_relaxed);
150 if (CurFD == -1)
151 return;
152 log() << "logging_end";
153 if (uint64_t Dropped = DroppedLines.load(m: std::memory_order_relaxed))
154 llvm::errs() << "warning: log '" << LogPath
155 << "' is incomplete: " << Dropped
156 << " line(s) dropped due to write errors\n";
157 llvm::sys::Process::SafelyCloseFileDescriptor(FD);
158}
159