1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
|
#include "format.h"
#include "context.h"
#include "fwd_backend.h"
#include <yql/essentials/utils/yql_panic.h>
#include <library/cpp/logger/record.h>
#include <library/cpp/json/json_writer.h>
#include <util/string/builder.h>
#include <util/generic/algorithm.h>
#include <ranges>
namespace NYql::NLog {
namespace {
constexpr size_t MaxRequiredContextKey = static_cast<size_t>(EContextKey::Line);
auto RequiredContextAccessor(const TLogRecord& rec) {
return [&](EContextKey key) -> TStringBuf {
return rec.MetaFlags.at(static_cast<size_t>(key)).second;
};
}
auto OptionalContextAccessor(const TLogRecord& rec) {
return [&](TStringBuf key) -> TMaybe<TStringBuf> {
const auto isContextKeyPath = [&](const auto& pair) {
return pair.first == key;
};
const auto* path = FindIfPtr(
rec.MetaFlags.begin() + MaxRequiredContextKey + 1,
rec.MetaFlags.end(),
isContextKeyPath);
if (!path) {
return Nothing();
}
return path->second;
};
}
void PrintBody(TStringBuilder& out, const TLogRecord& rec, size_t flagBegin) {
out << TStringBuf(rec.Data, rec.Len);
if (flagBegin < rec.MetaFlags.size()) {
out << ". Extra context: ";
}
for (size_t i = flagBegin; i < rec.MetaFlags.size(); ++i) {
const auto& [key, value] = rec.MetaFlags[i];
out << key << " = " << value;
if (i + 1 != rec.MetaFlags.size()) {
out << ", ";
}
}
}
TString FallbackFormat(const TLogRecord& rec) {
TStringBuilder out;
PrintBody(out, rec, /*flagBegin=*/0);
return out;
}
class TFormattingLogBackend final: public TForwardingLogBackend {
public:
explicit TFormattingLogBackend(TFormatter formatter, bool isStrict, TAutoPtr<TLogBackend> child)
: TForwardingLogBackend(std::move(child))
, Formatter_(std::move(formatter))
, IsStrict_(isStrict)
{
}
void WriteData(const TLogRecord& rec) final {
if (rec.MetaFlags.empty()) {
// NB. For signal handler.
return TForwardingLogBackend::WriteData(rec);
}
TString message;
if (IsSupported(rec.MetaFlags)) {
message = Formatter_(rec);
} else if (IsStrict_) {
TStringBuilder message;
message << "LogRecord is not supported: ";
PrintBody(message, rec, /* flagBegin = */ 0);
ythrow yexception() << std::move(message);
} else {
message = FallbackFormat(rec);
}
message.append('\n');
const TLogRecord formatted(rec.Priority, message.data(), message.size());
return TForwardingLogBackend::WriteData(formatted);
}
protected:
static bool IsSupported(const TLogRecord::TMetaFlags& flags) {
const auto isSupported = [&](size_t i) -> bool {
const EContextKey key = static_cast<EContextKey>(i);
const TStringBuf expected = ToStringBuf(key);
if (flags.size() <= i) {
return false;
}
const TStringBuf actual = flags[i].first;
if (actual != expected) {
return false;
}
return true;
};
return AllOf(std::views::iota(Min<size_t>(), MaxRequiredContextKey), isSupported);
}
private:
TFormatter Formatter_;
bool IsStrict_;
};
} // namespace
TString LegacyFormat(const TLogRecord& rec) {
const auto get = RequiredContextAccessor(rec);
const auto opt = OptionalContextAccessor(rec);
TStringBuilder out;
out << get(EContextKey::DateTime) << ' '
<< get(EContextKey::Level) << ' '
<< get(EContextKey::ProcessName)
<< "(pid=" << get(EContextKey::ProcessID)
<< ", tid=" << get(EContextKey::ThreadID)
<< ") [" << get(EContextKey::Component) << "] "
<< get(EContextKey::FileName)
<< ':' << get(EContextKey::Line) << ": ";
size_t unknownContextBegin = MaxRequiredContextKey + 1;
if (auto path = opt(ToStringBuf(EContextKey::Path))) {
out << "{" << *path << "} ";
unknownContextBegin += 1;
}
PrintBody(out, rec, unknownContextBegin);
return out;
}
TString JsonFormat(const TLogRecord& rec) {
TStringStream out;
NJsonWriter::TBuf buf(NJsonWriter::HEM_DONT_ESCAPE_HTML, &out);
buf.BeginObject();
buf.WriteKey("message");
buf.WriteString(TStringBuf(rec.Data, rec.Len));
buf.WriteKey("@fields");
buf.BeginObject();
for (const auto& [key, value] : rec.MetaFlags) {
buf.WriteKey(key);
buf.WriteString(value);
}
buf.EndObject();
buf.EndObject();
return std::move(out.Str());
}
TAutoPtr<TLogBackend> MakeFormattingLogBackend(TFormatter formatter, bool isStrict, TAutoPtr<TLogBackend> child) {
return new TFormattingLogBackend(std::move(formatter), isStrict, std::move(child));
}
} // namespace NYql::NLog
|