Skip to content

Commit d8c0054

Browse files
authored
Merge pull request github#13133 from github/redsun82/swift-diagnostics-locations
Swift: add location and visibility support to TSP diagnostics
2 parents 4be226f + dbff3e4 commit d8c0054

File tree

12 files changed

+196
-165
lines changed

12 files changed

+196
-165
lines changed

swift/integration-tests/diagnostics_test_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,5 @@ def check_diagnostics(test_dir=".", test_db="db"):
6363
actual_out.write(actual)
6464
actual = actual.splitlines(keepends=True)
6565
expected = expected.splitlines(keepends=True)
66-
print("".join(difflib.unified_diff(actual, expected, fromfile="diagnostics.actual", tofile="diagnostics.expected")), file=sys.stderr)
66+
print("".join(difflib.unified_diff(expected, actual, fromfile="diagnostics.expected", tofile="diagnostics.actual")), file=sys.stderr)
6767
sys.exit(1)

swift/logging/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ cc_library(
66
deps = [
77
"@absl//absl/strings",
88
"@binlog",
9+
"@fmt",
910
"@json",
1011
],
1112
)

swift/logging/SwiftDiagnostics.cpp

Lines changed: 31 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,60 @@
11
#include "swift/logging/SwiftDiagnostics.h"
22

33
#include <binlog/Entries.hpp>
4-
#include <nlohmann/json.hpp>
54
#include "absl/strings/str_join.h"
65
#include "absl/strings/str_cat.h"
76
#include "absl/strings/str_split.h"
87
#include "swift/logging/SwiftAssert.h"
98

109
namespace codeql {
1110

12-
namespace {
13-
Logger& logger() {
14-
static Logger ret{"diagnostics"};
15-
return ret;
16-
}
17-
} // namespace
18-
19-
void SwiftDiagnosticsSource::emit(std::ostream& out,
20-
std::string_view timestamp,
21-
std::string_view message) const {
22-
nlohmann::json entry = {
11+
nlohmann::json SwiftDiagnostic::json(const std::chrono::system_clock::time_point& timestamp,
12+
std::string_view message) const {
13+
nlohmann::json ret{
2314
{"source",
2415
{
25-
{"id", sourceId()},
16+
{"id", absl::StrJoin({extractorName, programName, id}, "/")},
2617
{"name", name},
2718
{"extractorName", extractorName},
2819
}},
2920
{"visibility",
3021
{
31-
{"statusPage", true},
32-
{"cliSummaryTable", true},
33-
{"telemetry", true},
22+
{"statusPage", has(Visibility::statusPage)},
23+
{"cliSummaryTable", has(Visibility::cliSummaryTable)},
24+
{"telemetry", has(Visibility::telemetry)},
3425
}},
3526
{"severity", "error"},
3627
{"helpLinks", std::vector<std::string_view>(absl::StrSplit(helpLinks, ' '))},
3728
{"plaintextMessage", absl::StrCat(message, ".\n\n", action, ".")},
38-
{"timestamp", timestamp},
29+
{"timestamp", fmt::format("{:%FT%T%z}", timestamp)},
3930
};
40-
out << entry << '\n';
31+
if (location) {
32+
ret["location"] = location->json();
33+
}
34+
return ret;
4135
}
4236

43-
std::string SwiftDiagnosticsSource::sourceId() const {
44-
auto ret = absl::StrJoin({extractorName, programName, id}, "/");
45-
std::replace(ret.begin(), ret.end(), '_', '-');
46-
return ret;
37+
std::string SwiftDiagnostic::abbreviation() const {
38+
if (location) {
39+
return absl::StrCat(id, "@", location->str());
40+
}
41+
return std::string{id};
4742
}
48-
void SwiftDiagnosticsSource::registerImpl(const SwiftDiagnosticsSource* source) {
49-
auto [it, inserted] = map().emplace(source->id, source);
50-
CODEQL_ASSERT(inserted, "duplicate diagnostics source detected with id {}", source->id);
43+
44+
bool SwiftDiagnostic::has(SwiftDiagnostic::Visibility v) const {
45+
return (visibility & v) != Visibility::none;
5146
}
5247

53-
void SwiftDiagnosticsDumper::write(const char* buffer, std::size_t bufferSize) {
54-
binlog::Range range{buffer, bufferSize};
55-
binlog::RangeEntryStream input{range};
56-
while (auto event = events.nextEvent(input)) {
57-
const auto& source = SwiftDiagnosticsSource::get(event->source->category);
58-
std::ostringstream oss;
59-
timestampedMessagePrinter.printEvent(oss, *event, events.writerProp(), events.clockSync());
60-
// TODO(C++20) use oss.view() directly
61-
auto data = oss.str();
62-
std::string_view view = data;
63-
using ViewPair = std::pair<std::string_view, std::string_view>;
64-
auto [timestamp, message] = ViewPair(absl::StrSplit(view, absl::MaxSplits(' ', 1)));
65-
source.emit(output, timestamp, message);
66-
}
48+
nlohmann::json SwiftDiagnosticsLocation::json() const {
49+
nlohmann::json ret{{"file", file}};
50+
if (startLine) ret["startLine"] = startLine;
51+
if (startColumn) ret["startColumn"] = startColumn;
52+
if (endLine) ret["endLine"] = endLine;
53+
if (endColumn) ret["endColumn"] = endColumn;
54+
return ret;
55+
}
56+
57+
std::string SwiftDiagnosticsLocation::str() const {
58+
return absl::StrJoin(std::tuple{file, startLine, startColumn, endLine, endColumn}, ":");
6759
}
6860
} // namespace codeql

swift/logging/SwiftDiagnostics.h

Lines changed: 68 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,108 @@
11
#pragma once
22

3-
#include <binlog/EventStream.hpp>
4-
#include <binlog/PrettyPrinter.hpp>
3+
#include <binlog/binlog.hpp>
54
#include <string>
65
#include <vector>
76
#include <unordered_map>
7+
#include <optional>
88
#include <cassert>
99
#include <fstream>
1010
#include <filesystem>
1111
#include <sstream>
1212
#include <mutex>
13+
#include <fmt/format.h>
14+
#include <fmt/chrono.h>
15+
#include <nlohmann/json.hpp>
1316

1417
namespace codeql {
1518

1619
extern const std::string_view programName;
1720

21+
struct SwiftDiagnosticsLocation {
22+
std::string_view file;
23+
unsigned startLine;
24+
unsigned startColumn;
25+
unsigned endLine;
26+
unsigned endColumn;
27+
28+
nlohmann::json json() const;
29+
std::string str() const;
30+
};
31+
1832
// Models a diagnostic source for Swift, holding static information that goes out into a diagnostic
1933
// These are internally stored into a map on id's. A specific error log can use binlog's category
2034
// as id, which will then be used to recover the diagnostic source while dumping.
21-
struct SwiftDiagnosticsSource {
35+
struct SwiftDiagnostic {
36+
enum class Visibility : unsigned char {
37+
none = 0b000,
38+
statusPage = 0b001,
39+
cliSummaryTable = 0b010,
40+
telemetry = 0b100,
41+
all = 0b111,
42+
};
43+
2244
std::string_view id;
2345
std::string_view name;
2446
static constexpr std::string_view extractorName = "swift";
2547
std::string_view action;
2648
// space separated if more than 1. Not a vector to allow constexpr
2749
// TODO(C++20) with vector going constexpr this can be turned to `std::vector<std::string_view>`
2850
std::string_view helpLinks;
29-
3051
// for the moment, we only output errors, so no need to store the severity
3152

32-
// registers a diagnostics source for later retrieval with get, if not done yet
33-
template <const SwiftDiagnosticsSource* Source>
34-
static void ensureRegistered() {
35-
static std::once_flag once;
36-
std::call_once(once, registerImpl, Source);
37-
}
53+
Visibility visibility{Visibility::all};
3854

39-
// gets a previously inscribed SwiftDiagnosticsSource for the given id. Will abort if none exists
40-
static const SwiftDiagnosticsSource& get(const std::string& id) { return *map().at(id); }
55+
std::optional<SwiftDiagnosticsLocation> location{};
4156

42-
// emit a JSON diagnostics for this source with the given timestamp and message to out
43-
// A plaintextMessage is used that includes both the message and the action to take. Dots are
44-
// appended to both. The id is used to construct the source id in the form
45-
// `swift/<prog name>/<id with '-' replacing '_'>`
46-
void emit(std::ostream& out, std::string_view timestamp, std::string_view message) const;
47-
48-
private:
49-
static void registerImpl(const SwiftDiagnosticsSource* source);
57+
constexpr SwiftDiagnostic(std::string_view id,
58+
std::string_view name,
59+
std::string_view action = "",
60+
std::string_view helpLinks = "",
61+
Visibility visibility = Visibility::all)
62+
: id{id}, name{name}, action{action}, helpLinks{helpLinks}, visibility{visibility} {}
5063

51-
std::string sourceId() const;
52-
using Map = std::unordered_map<std::string, const SwiftDiagnosticsSource*>;
64+
constexpr SwiftDiagnostic(std::string_view id, std::string_view name, Visibility visibility)
65+
: SwiftDiagnostic(id, name, "", "", visibility) {}
5366

54-
static Map& map() {
55-
static Map ret;
67+
// create a JSON diagnostics for this source with the given timestamp and message to out
68+
// A plaintextMessage is used that includes both the message and the action to take. Dots are
69+
// appended to both. The id is used to construct the source id in the form
70+
// `swift/<prog name>/<id>`
71+
nlohmann::json json(const std::chrono::system_clock::time_point& timestamp,
72+
std::string_view message) const;
73+
74+
// returns <id> or <id>@<location> if a location is present
75+
std::string abbreviation() const;
76+
77+
SwiftDiagnostic withLocation(std::string_view file,
78+
unsigned startLine = 0,
79+
unsigned startColumn = 0,
80+
unsigned endLine = 0,
81+
unsigned endColumn = 0) const {
82+
auto ret = *this;
83+
ret.location = SwiftDiagnosticsLocation{file, startLine, startColumn, endLine, endColumn};
5684
return ret;
5785
}
58-
};
59-
60-
// An output modeling binlog's output stream concept that intercepts binlog entries and translates
61-
// them to appropriate diagnostics JSON entries
62-
class SwiftDiagnosticsDumper {
63-
public:
64-
// opens path for writing out JSON entries. Returns whether the operation was successful.
65-
bool open(const std::filesystem::path& path) {
66-
output.open(path);
67-
return output.good();
68-
}
69-
70-
void flush() { output.flush(); }
71-
72-
// write out binlog entries as corresponding JSON diagnostics entries. Expects all entries to have
73-
// a category equal to an id of a previously created SwiftDiagnosticSource.
74-
void write(const char* buffer, std::size_t bufferSize);
7586

7687
private:
77-
binlog::EventStream events;
78-
std::ofstream output;
79-
binlog::PrettyPrinter timestampedMessagePrinter{"%u %m", "%Y-%m-%dT%H:%M:%S.%NZ"};
88+
bool has(Visibility v) const;
8089
};
8190

82-
} // namespace codeql
91+
inline constexpr SwiftDiagnostic::Visibility operator|(SwiftDiagnostic::Visibility lhs,
92+
SwiftDiagnostic::Visibility rhs) {
93+
return static_cast<SwiftDiagnostic::Visibility>(static_cast<unsigned char>(lhs) |
94+
static_cast<unsigned char>(rhs));
95+
}
8396

84-
namespace codeql_diagnostics {
85-
constexpr codeql::SwiftDiagnosticsSource internal_error{
86-
"internal_error",
97+
inline constexpr SwiftDiagnostic::Visibility operator&(SwiftDiagnostic::Visibility lhs,
98+
SwiftDiagnostic::Visibility rhs) {
99+
return static_cast<SwiftDiagnostic::Visibility>(static_cast<unsigned char>(lhs) &
100+
static_cast<unsigned char>(rhs));
101+
}
102+
103+
constexpr SwiftDiagnostic internalError{
104+
"internal-error",
87105
"Internal error",
88-
"Contact us about this issue",
106+
SwiftDiagnostic::Visibility::telemetry,
89107
};
90-
} // namespace codeql_diagnostics
108+
} // namespace codeql

swift/logging/SwiftLogging.cpp

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ std::vector<std::string> Log::collectLevelRulesAndReturnProblems(const char* env
6060
// expect comma-separated <glob pattern>:<log severity>
6161
std::regex comma{","};
6262
std::regex levelAssignment{
63-
R"((?:([*./\w]+)|(?:out:(binary|text|console|diagnostics))):()" LEVEL_REGEX_PATTERN ")"};
63+
R"((?:([*./\w]+)|(?:out:(binary|text|console))):()" LEVEL_REGEX_PATTERN ")"};
6464
std::cregex_token_iterator begin{levels, levels + strlen(levels), comma, -1};
6565
std::cregex_token_iterator end{};
6666
for (auto it = begin; it != end; ++it) {
@@ -84,8 +84,6 @@ std::vector<std::string> Log::collectLevelRulesAndReturnProblems(const char* env
8484
text.level = level;
8585
} else if (out == "console") {
8686
console.level = level;
87-
} else if (out == "diagnostics") {
88-
diagnostics.level = level;
8987
}
9088
}
9189
} else {
@@ -132,30 +130,28 @@ void Log::configure() {
132130
text.level = Level::no_logs;
133131
}
134132
}
135-
if (diagnostics) {
136-
std::filesystem::path diagFile =
137-
getEnvOr("CODEQL_EXTRACTOR_SWIFT_DIAGNOSTIC_DIR", "extractor-out/diagnostics");
133+
if (auto diagDir = getEnvOr("CODEQL_EXTRACTOR_SWIFT_DIAGNOSTIC_DIR", nullptr)) {
134+
std::filesystem::path diagFile = diagDir;
138135
diagFile /= programName;
139136
diagFile /= logBaseName;
140137
diagFile.replace_extension(".jsonl");
141138
std::error_code ec;
142139
std::filesystem::create_directories(diagFile.parent_path(), ec);
143140
if (!ec) {
144-
if (!diagnostics.output.open(diagFile)) {
141+
diagnostics.open(diagFile);
142+
if (!diagnostics) {
145143
problems.emplace_back("Unable to open diagnostics json file " + diagFile.string());
146-
diagnostics.level = Level::no_logs;
147144
}
148145
} else {
149146
problems.emplace_back("Unable to create diagnostics directory " +
150147
diagFile.parent_path().string() + ": " + ec.message());
151-
diagnostics.level = Level::no_logs;
152148
}
153149
}
154150
for (const auto& problem : problems) {
155151
LOG_ERROR("{}", problem);
156152
}
157153
LOG_INFO("Logging configured (binary: {}, text: {}, console: {}, diagnostics: {})", binary.level,
158-
text.level, console.level, diagnostics.level);
154+
text.level, console.level, diagnostics.good());
159155
initialized = true;
160156
flushImpl();
161157
}
@@ -169,7 +165,17 @@ void Log::flushImpl() {
169165
binary.output.flush();
170166
}
171167
if (diagnostics) {
172-
diagnostics.output.flush();
168+
diagnostics.flush();
169+
}
170+
}
171+
172+
void Log::diagnoseImpl(const SwiftDiagnostic& source,
173+
const std::chrono::nanoseconds& elapsed,
174+
std::string_view message) {
175+
using Clock = std::chrono::system_clock;
176+
Clock::time_point time{std::chrono::duration_cast<Clock::duration>(elapsed)};
177+
if (diagnostics) {
178+
diagnostics << source.json(time, message) << '\n';
173179
}
174180
}
175181

@@ -190,13 +196,11 @@ Log& Log::write(const char* buffer, std::streamsize size) {
190196
if (text) text.write(buffer, size);
191197
if (binary) binary.write(buffer, size);
192198
if (console) console.write(buffer, size);
193-
if (diagnostics) diagnostics.write(buffer, size);
194199
return *this;
195200
}
196201

197202
Logger& Log::logger() {
198203
static Logger ret{getLoggerConfigurationImpl("logging")};
199204
return ret;
200205
}
201-
202206
} // namespace codeql

0 commit comments

Comments
 (0)