-
Notifications
You must be signed in to change notification settings - Fork 5.5k
access_log, router: add subsecond specifier for START_TIME #3269
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 42 commits
f85d68d
b0b741b
4c80a6d
3110ce0
42bd291
8fcc471
4e74570
79a3320
f94e020
07dc56f
91fb3da
aef2ed9
1970913
9004414
dd15262
ce1d3a0
e98f1a4
edbc437
8919c50
908fc61
0f417da
106c3e2
cc6c7c9
4dcfeb0
7d53b28
e6cb04e
96eb0c6
f475839
724a203
24518ef
0f0b1bf
f0fa285
f78e7ad
7aeb767
6310447
8a1571f
78e8430
325fdc5
4b9fa9e
9aba1e9
94e3add
612f829
96f915e
d5f082b
b85b275
513d4bf
9a16090
e5642ba
bda6762
ec35f6c
ea5e62f
22a94a4
3f01c64
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,7 @@ | |
| #include "common/common/assert.h" | ||
| #include "common/common/fmt.h" | ||
| #include "common/common/hash.h" | ||
| #include "common/singleton/const_singleton.h" | ||
|
|
||
| #include "absl/strings/ascii.h" | ||
| #include "absl/strings/match.h" | ||
|
|
@@ -20,8 +21,91 @@ | |
| #include "spdlog/spdlog.h" | ||
|
|
||
| namespace Envoy { | ||
|
|
||
| namespace { | ||
|
|
||
| class SubsecondConstantValues { | ||
| public: | ||
| const std::string PLACEHOLDER{"?????????"}; | ||
| const std::regex PATTERN{"%([1-9])?f", std::regex::optimize}; | ||
| }; | ||
|
|
||
| typedef ConstSingleton<SubsecondConstantValues> SubsecondConstants; | ||
|
|
||
| } // namespace | ||
|
|
||
| std::string DateFormatter::fromTime(const SystemTime& time) const { | ||
| return fromTime(std::chrono::system_clock::to_time_t(time)); | ||
| struct CachedTime { | ||
| size_t seconds_length; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comment would be useful here. |
||
| std::chrono::seconds epoch_time_seconds; | ||
|
|
||
| struct Formatted { | ||
| SubsecondOffsets subsecond_offsets; | ||
| std::string str; | ||
| }; | ||
| // A map is used to keep different formatted format strings at a given second. | ||
| std::unordered_map<std::string, const Formatted> formatted; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we slowly leak via this map? E.g. if I replace route configuration multiple times and change the format strings, do we accumulate and never clear entries?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems that is true. We need to find a way to detect when to clear this.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried to clear it at each second as exhibited in 513d4bf. |
||
| }; | ||
| static thread_local CachedTime cached_time; | ||
|
|
||
| const std::chrono::nanoseconds epoch_time_ns = | ||
| std::chrono::duration_cast<std::chrono::nanoseconds>(time.time_since_epoch()); | ||
|
|
||
| const std::chrono::seconds epoch_time_seconds = | ||
| std::chrono::duration_cast<std::chrono::seconds>(epoch_time_ns); | ||
|
|
||
| if (cached_time.formatted.find(format_string_) == cached_time.formatted.end() || | ||
| cached_time.epoch_time_seconds != epoch_time_seconds) { | ||
| time_t current_time = std::chrono::system_clock::to_time_t(time); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit:
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
|
|
||
| CachedTime::Formatted formatted; | ||
| if (subseconds_.empty()) { | ||
| formatted.str = fromTime(current_time); | ||
| } else { | ||
| formatted.str = fromTimeAndPrepareSubsecondOffsets(current_time, formatted.subsecond_offsets); | ||
| cached_time.seconds_length = fmt::FormatInt(epoch_time_seconds.count()).str().size(); | ||
| } | ||
|
|
||
| cached_time.formatted.emplace(std::make_pair(format_string_, formatted)); | ||
| cached_time.epoch_time_seconds = epoch_time_seconds; | ||
| } | ||
|
|
||
| const auto& formatted = cached_time.formatted.at(format_string_); | ||
| if (subseconds_.empty()) { | ||
| return formatted.str; | ||
| } | ||
|
|
||
| ASSERT(subseconds_.size() == formatted.subsecond_offsets.size()); | ||
|
|
||
| // Copy the current cached formatted format string, then replace its subseconds part using the | ||
| // prepared subseconds offsets. | ||
| std::string formatted_str = formatted.str; | ||
| const std::string nanoseconds = fmt::FormatInt(epoch_time_ns.count()).str(); | ||
| for (size_t i = 0; i < subseconds_.size(); ++i) { | ||
| const auto& subsecond = subseconds_.at(i); | ||
| const std::string digits = nanoseconds.substr(cached_time.seconds_length, subsecond.width_); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems I couldn't do this since it hits |
||
| formatted_str.replace(subsecond.position_ + formatted.subsecond_offsets.at(i), subsecond.width_, | ||
| digits); | ||
| } | ||
|
|
||
| ASSERT(formatted_str.size() == formatted.str.size()); | ||
| return formatted_str; | ||
| } | ||
|
|
||
| std::string DateFormatter::parse(const std::string& format_string) { | ||
| std::string new_format_string = format_string; | ||
| std::smatch matched; | ||
| while (regex_search(new_format_string, matched, SubsecondConstants::get().PATTERN)) { | ||
| const std::string& width_specifier = matched[1]; | ||
| const size_t width = width_specifier.empty() ? SubsecondConstants::get().PLACEHOLDER.size() | ||
| : width_specifier.at(0) - '0'; | ||
| new_format_string.replace(matched.position(), matched.length(), | ||
| SubsecondConstants::get().PLACEHOLDER.substr(0, width)); | ||
| SubsecondSpecifier subsecond(matched.position(), width); | ||
| subseconds_.emplace_back(subsecond); | ||
| } | ||
|
|
||
| return new_format_string; | ||
| } | ||
|
|
||
| std::string DateFormatter::fromTime(time_t time) const { | ||
|
|
@@ -33,6 +117,41 @@ std::string DateFormatter::fromTime(time_t time) const { | |
| return std::string(&buf[0]); | ||
| } | ||
|
|
||
| std::string | ||
| DateFormatter::fromTimeAndPrepareSubsecondOffsets(time_t time, | ||
| SubsecondOffsets& subsecond_offsets) const { | ||
| tm current_tm; | ||
| gmtime_r(&time, ¤t_tm); | ||
|
|
||
| std::array<char, 1024> buf; | ||
| std::string formatted; | ||
|
|
||
| size_t step = 0; | ||
| int32_t previous = 0; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: prefer using signed types where possible, since they describe the allowed range. |
||
| subsecond_offsets.reserve(subseconds_.size()); | ||
| for (const auto& subsecond : subseconds_) { | ||
| ASSERT(step < format_string_.size()); | ||
| const std::string segment = format_string_.substr(step, subsecond.position_ - step); | ||
| const size_t formatted_length = strftime(&buf[0], buf.size(), segment.c_str(), ¤t_tm); | ||
| absl::StrAppend(&formatted, &buf[0], | ||
| format_string_.substr(subsecond.position_, subsecond.width_)); | ||
|
|
||
| const int32_t offset = formatted_length - segment.size(); | ||
| subsecond_offsets.emplace_back(previous + offset); | ||
|
|
||
| step = subsecond.position_ + subsecond.width_; | ||
| previous += offset; | ||
| } | ||
|
|
||
| if (step < format_string_.size()) { | ||
| const std::string last_segment = format_string_.substr(step); | ||
| strftime(&buf[0], buf.size(), last_segment.c_str(), ¤t_tm); | ||
| absl::StrAppend(&formatted, &buf[0]); | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wow, yeah, this is complicated, but I'm not sure if there's a simpler way. I'm OK with the above if you clean it up and add comments. Alternatively, we could fragment into segments during the initial parse (which I think is what you may have had in an earlier iteration) and just do the append + substitute here.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do the pre-computation 96f915e. |
||
|
|
||
| return formatted; | ||
| } | ||
|
|
||
| std::string DateFormatter::now() { | ||
| time_t current_time_t; | ||
| time(¤t_time_t); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,7 +25,7 @@ namespace Envoy { | |
| */ | ||
| class DateFormatter { | ||
| public: | ||
| DateFormatter(const std::string& format_string) : format_string_(format_string) {} | ||
| DateFormatter(const std::string& format_string) : format_string_(parse(format_string)) {} | ||
|
|
||
| /** | ||
| * @return std::string representing the GMT/UTC time based on the input time. | ||
|
|
@@ -48,7 +48,22 @@ class DateFormatter { | |
| const std::string& formatString() const { return format_string_; } | ||
|
|
||
| private: | ||
| std::string format_string_; | ||
| std::string parse(const std::string& format_string); | ||
|
|
||
| typedef std::vector<int32_t> SubsecondOffsets; | ||
| std::string fromTimeAndPrepareSubsecondOffsets(time_t time, | ||
| SubsecondOffsets& subsecond_offsets) const; | ||
|
|
||
| struct SubsecondSpecifier { | ||
| SubsecondSpecifier(const size_t position, const size_t width) | ||
| : position_(position), width_(width) {} | ||
|
|
||
| const size_t position_; | ||
| const size_t width_; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comments on all these fields please. |
||
| }; | ||
| std::vector<SubsecondSpecifier> subseconds_; | ||
|
|
||
| const std::string format_string_; | ||
| }; | ||
|
|
||
| /** | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,6 +43,7 @@ class RequestInfoHeaderFormatter : public HeaderFormatter { | |
| private: | ||
| std::function<std::string(const Envoy::RequestInfo::RequestInfo&)> field_extractor_; | ||
| const bool append_; | ||
| std::map<std::string, std::vector<AccessLog::FormatterPtr>> start_time_formatters_; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, OK. Why did I put it as a map? 😅.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @htuch updated. |
||
| }; | ||
|
|
||
| /** | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You probably don't need this to be a constant. You can just do something like
std::string(N, '?')later on to generate a string of lengthN.