diff options
author | Billy Donahue <billy.donahue@mongodb.com> | 2020-04-02 13:49:54 -0400 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-04-21 07:40:44 +0000 |
commit | 10ee8c156f747c55895067cb418827c57c59557b (patch) | |
tree | 9d10144b571ff253895802656e9cde490470e84a /src/mongo | |
parent | 79cfaa04168ce3994b5499f45fd728ebccbef0bb (diff) | |
download | mongo-10ee8c156f747c55895067cb418827c57c59557b.tar.gz |
SERVER-47220 log json field padding
Diffstat (limited to 'src/mongo')
-rw-r--r-- | src/mongo/bson/generator_extended_relaxed_2_0_0.h | 5 | ||||
-rw-r--r-- | src/mongo/logv2/json_formatter.cpp | 199 | ||||
-rw-r--r-- | src/mongo/logv2/text_formatter.cpp | 16 | ||||
-rw-r--r-- | src/mongo/util/time_support.cpp | 178 | ||||
-rw-r--r-- | src/mongo/util/time_support.h | 80 |
5 files changed, 228 insertions, 250 deletions
diff --git a/src/mongo/bson/generator_extended_relaxed_2_0_0.h b/src/mongo/bson/generator_extended_relaxed_2_0_0.h index 4b18082d33e..c310e163090 100644 --- a/src/mongo/bson/generator_extended_relaxed_2_0_0.h +++ b/src/mongo/bson/generator_extended_relaxed_2_0_0.h @@ -82,10 +82,7 @@ public: // Date_t::millis is negative (before the epoch). if (val.isFormattable()) { appendTo(buffer, R"({"$date":")"_sd); - if (_localDate) - outputDateAsISOStringLocal(buffer, val); - else - outputDateAsISOStringUTC(buffer, val); + appendTo(buffer, StringData{DateStringBuffer{}.iso8601(val, _localDate)}); appendTo(buffer, R"("})"); } else { ExtendedCanonicalV200Generator::writeDate(buffer, val); diff --git a/src/mongo/logv2/json_formatter.cpp b/src/mongo/logv2/json_formatter.cpp index df87a5b40a8..98df9bb898c 100644 --- a/src/mongo/logv2/json_formatter.cpp +++ b/src/mongo/logv2/json_formatter.cpp @@ -230,109 +230,116 @@ void JSONFormatter::format(fmt::memory_buffer& buffer, const TypeErasedAttributeStorage& attrs, LogTag tags, LogTruncation truncation) const { + namespace c = constants; + static constexpr auto kFmt = JsonStringFormat::ExtendedRelaxedV2_0_0; StringData severityString = severity.toStringDataCompact(); StringData componentString = component.getNameForLog(); - - // Put all fields up until the message value - static const auto& fmtStrOpen = *new auto(fmt::compile<StringData>(R"({{)" - R"("{}":{{"$date":")")); - compiled_format_to(buffer, fmtStrOpen, constants::kTimestampFieldName); - switch (_timestampFormat) { - case LogTimestampFormat::kISO8601UTC: - outputDateAsISOStringUTC(buffer, date); - break; - case LogTimestampFormat::kISO8601Local: - outputDateAsISOStringLocal(buffer, date); - break; + bool local = _timestampFormat == LogTimestampFormat::kISO8601Local; + size_t attributeMaxSize = truncation != LogTruncation::Enabled + ? 0 + : (_maxAttributeSizeKB != 0 ? _maxAttributeSizeKB->loadRelaxed() * 1024 + : c::kDefaultMaxAttributeOutputSizeKB * 1024); + auto write = [&](StringData s) { buffer.append(s.rawData(), s.rawData() + s.size()); }; + + struct CommaTracker { + StringData comma; + size_t padDebt = 0; }; - static const auto& fmtStrBody = - *new auto(fmt::compile<StringData, // severity start - StringData, - StringData, - int, - StringData, // component start - StringData, - StringData, - int, - StringData, // id start - StringData, - StringData, - int, - StringData, // context start - StringData, - StringData> // message start - (R"("}},)" // close timestamp - R"("{}":"{}"{: <{}})" // severity with padding for the comma - R"("{}":"{}"{: <{}})" // component with padding for the comma - R"("{}":{}{: <{}})" // id with padding for the comma - R"("{}":"{}",)" // context - R"("{}":")" // message - )); - fmt::format_int idString(id); - compiled_format_to( - buffer, - fmtStrBody, - // severity, left align the comma and add padding to create fixed column width - constants::kSeverityFieldName, - severityString, - ","_sd, - 3 - severityString.size(), - // component, left align the comma and add padding to create fixed column width - constants::kComponentFieldName, - componentString, - ","_sd, - 9 - componentString.size(), - // id - constants::kIdFieldName, - StringData(idString.data(), idString.size()), - ","_sd, - 8 - idString.size(), - // context - constants::kContextFieldName, - context, - // message - constants::kMessageFieldName); - - str::escapeForJSON(buffer, message); - buffer.push_back('"'); - - static const auto& fmtStrAttr = *new auto(fmt::compile<StringData>(R"(,"{}":{{)")); - static const auto& fmtStrTruncated = *new auto(fmt::compile<StringData>(R"(,"{}":)")); - if (!attrs.empty()) { - compiled_format_to(buffer, fmtStrAttr, constants::kAttributesFieldName); - // comma separated list of attributes (no opening/closing brace are added here) - size_t attributeMaxSize = 0; - if (truncation == LogTruncation::Enabled) { - if (_maxAttributeSizeKB) - attributeMaxSize = _maxAttributeSizeKB->loadRelaxed() * 1024; - else - attributeMaxSize = constants::kDefaultMaxAttributeOutputSizeKB * 1024; - } - JSONValueExtractor extractor(buffer, attributeMaxSize); - attrs.apply(extractor); - buffer.push_back('}'); - - if (BSONObj truncated = extractor.truncated(); !truncated.isEmpty()) { - compiled_format_to(buffer, fmtStrTruncated, constants::kTruncatedFieldName); - truncated.jsonStringBuffer( - JsonStringFormat::ExtendedRelaxedV2_0_0, 0, false, buffer, 0); - } - if (BSONObj truncatedSizes = extractor.truncatedSizes(); !truncatedSizes.isEmpty()) { - compiled_format_to(buffer, fmtStrTruncated, constants::kTruncatedSizeFieldName); - truncatedSizes.jsonStringBuffer( - JsonStringFormat::ExtendedRelaxedV2_0_0, 0, false, buffer, 0); + // First call to `comma` emits nothing, subsequent calls emit ",", so this is a + // prefix used to join elements. If there is any padding debt in the + // tracker, then that amount of whitespace is emitted after the comma and the + // debt is cleared. + auto writeComma = [&](CommaTracker& tracker) { + write(tracker.comma); + tracker.comma = ","_sd; + while (tracker.padDebt) { + // Relies on substr's truncation. + StringData space = " "_sd.substr(0, tracker.padDebt); + write(space); + tracker.padDebt -= space.size(); } - } + }; - static const auto& fmtStrTags = *new auto(fmt::compile<StringData>(R"(,"{}":)")); - if (tags != LogTag::kNone) { - compiled_format_to(buffer, fmtStrTags, constants::kTagsFieldName); - tags.toBSONArray().jsonStringBuffer( - JsonStringFormat::ExtendedRelaxedV2_0_0, 0, true, buffer); - } + // Emit `f()` normally, noting its size. If it is narrower than `width`, we incur a + // padding debt which is stored in the `tracker`. The next `comma` emitted by the + // `tracker` will be followed by enough whitespace to pay off the debt. + auto padNextComma = [&](CommaTracker& tracker, size_t width, auto f) { + return [&, width, f] { + size_t pre = buffer.size(); + f(); + if (size_t wrote = buffer.size() - pre; wrote < width) + tracker.padDebt = width - wrote; + }; + }; + + auto strFn = [&](StringData s) { return [&, s] { write(s); }; }; + auto escFn = [&](StringData s) { return [&, s] { str::escapeForJSON(buffer, s); }; }; + auto intFn = [&](auto x) { + return [&, x] { + fmt::format_int s{x}; + write(StringData{s.data(), s.size()}); + }; + }; + + auto quote = [&](auto f) { + return [&, f] { + write("\""); + f(); + write("\""); + }; + }; + + auto field = [&](CommaTracker& tracker, StringData name, auto f) { + writeComma(tracker); + quote(strFn(name))(); + write(":"); + f(); + }; + + auto jsobj = [&](auto f) { + return [&, f] { + CommaTracker top; + write("{"); + f(top); + write("}"); + }; + }; - buffer.push_back('}'); + auto dateFn = [&](Date_t date) { + return jsobj([&, date](CommaTracker& tracker) { + field(tracker, "$date", quote([&, date] { + write(StringData{DateStringBuffer{}.iso8601(date, local)}); + })); + }); + }; + auto bsonObjFn = [&](BSONObj o) { + return [&, o] { o.jsonStringBuffer(kFmt, 0, false, buffer, 0); }; + }; + auto bsonArrFn = [&](BSONArray a) { + return [&, a] { a.jsonStringBuffer(kFmt, 0, true, buffer, 0); }; + }; + + jsobj([&](CommaTracker& top) { + field(top, c::kTimestampFieldName, dateFn(date)); + field(top, c::kSeverityFieldName, padNextComma(top, 5, quote(strFn(severityString)))); + field(top, c::kComponentFieldName, padNextComma(top, 11, quote(strFn(componentString)))); + field(top, c::kIdFieldName, padNextComma(top, 8, intFn(id))); + field(top, c::kContextFieldName, quote(strFn(context))); + field(top, c::kMessageFieldName, quote(escFn(message))); + if (!attrs.empty()) { + JSONValueExtractor extractor(buffer, attributeMaxSize); + field(top, c::kAttributesFieldName, jsobj([&](CommaTracker&) { + attrs.apply(extractor); + })); + if (BSONObj o = extractor.truncated(); !o.isEmpty()) + field(top, c::kTruncatedFieldName, bsonObjFn(o)); + if (BSONObj o = extractor.truncatedSizes(); !o.isEmpty()) + field(top, c::kTruncatedSizeFieldName, bsonObjFn(o)); + } + if (tags != LogTag::kNone) + field(top, c::kTagsFieldName, bsonArrFn(tags.toBSONArray())); + })(); } void JSONFormatter::operator()(boost::log::record_view const& rec, diff --git a/src/mongo/logv2/text_formatter.cpp b/src/mongo/logv2/text_formatter.cpp index 8314160d436..b0421abf8e9 100644 --- a/src/mongo/logv2/text_formatter.cpp +++ b/src/mongo/logv2/text_formatter.cpp @@ -44,20 +44,14 @@ namespace mongo::logv2 { void TextFormatter::operator()(boost::log::record_view const& rec, boost::log::formatting_ostream& strm) const { - using namespace boost::log; + using boost::log::extract; - Date_t timeStamp = extract<Date_t>(attributes::timeStamp(), rec).get(); fmt::memory_buffer buffer; - switch (_timestampFormat) { - case LogTimestampFormat::kISO8601UTC: - outputDateAsISOStringUTC(buffer, timeStamp); - break; - case LogTimestampFormat::kISO8601Local: - outputDateAsISOStringLocal(buffer, timeStamp); - break; - }; fmt::format_to(buffer, - " {:<2} {:<8} [{}] ", + "{} {:<2} {:<8} [{}] ", + StringData{DateStringBuffer{}.iso8601( + extract<Date_t>(attributes::timeStamp(), rec).get(), + _timestampFormat == LogTimestampFormat::kISO8601Local)}, extract<LogSeverity>(attributes::severity(), rec).get().toStringDataCompact(), extract<LogComponent>(attributes::component(), rec).get().getNameForLog(), extract<StringData>(attributes::threadName(), rec).get()); diff --git a/src/mongo/util/time_support.cpp b/src/mongo/util/time_support.cpp index 2d445650f20..4de71bcc251 100644 --- a/src/mongo/util/time_support.cpp +++ b/src/mongo/util/time_support.cpp @@ -115,8 +115,6 @@ bool Date_t::isFormattable() const { long long jsTime_virtual_skew = 0; thread_local long long jsTime_virtual_thread_skew = 0; -using std::string; - void time_t_to_Struct(time_t t, struct tm* buf, bool local) { #if defined(_WIN32) if (local) @@ -147,7 +145,7 @@ std::string time_t_to_String_short(time_t t) { // uses ISO 8601 dates without trailing Z // colonsOk should be false when creating filenames -string terseCurrentTime(bool colonsOk) { +std::string terseCurrentTime(bool colonsOk) { struct tm t; time_t_to_Struct(time(nullptr), &t); @@ -157,39 +155,37 @@ string terseCurrentTime(bool colonsOk) { return buf; } -string terseUTCCurrentTime() { +std::string terseUTCCurrentTime() { return terseCurrentTime(false) + "Z"; } -#define MONGO_ISO_DATE_FMT_NO_TZ "%Y-%m-%dT%H:%M:%S" - -namespace { -struct DateStringBuffer { - static const int dataCapacity = 64; - char data[dataCapacity]; - int size; -}; - -void _dateToISOString(Date_t date, bool local, DateStringBuffer* result) { +DateStringBuffer& DateStringBuffer::iso8601(Date_t date, bool local) { invariant(date.isFormattable()); - static const int bufSize = DateStringBuffer::dataCapacity; - char* const buf = result->data; + struct tm t; time_t_to_Struct(date.toTimeT(), &t, local); - int pos = strftime(buf, bufSize, MONGO_ISO_DATE_FMT_NO_TZ, &t); - dassert(0 < pos); - char* cur = buf + pos; - int bufRemaining = bufSize - pos; - static const auto fmt_str_millis = fmt::compile<int32_t>(".{:03}"); - pos = fmt::format_to_n( - cur, bufRemaining, fmt_str_millis, static_cast<int32_t>(date.asInt64() % 1000)) - .size; - dassert(bufRemaining > pos && pos > 0); - cur += pos; - bufRemaining -= pos; + + char* cur = _data.data(); + char* end = _data.data() + _data.size(); + + { + static constexpr char kIsoDateFmtNoTz[] = "%Y-%m-%dT%H:%M:%S"; + size_t n = strftime(cur, end - cur, kIsoDateFmtNoTz, &t); + dassert(n > 0); + cur += n; + } + + { + static const auto& fmt_str_millis = *new auto(fmt::compile<int32_t>(".{:03}")); + auto res = fmt::format_to_n( + cur, end - cur, fmt_str_millis, static_cast<int32_t>(date.asInt64() % 1000)); + cur = res.out; + dassert(cur < end && res.size > 0); + } + if (local) { - static const int localTzSubstrLen = 6; - dassert(bufRemaining >= localTzSubstrLen + 1); + static const size_t localTzSubstrLen = 6; + dassert(static_cast<size_t>(end - cur) >= localTzSubstrLen + 1); #ifdef _WIN32 // NOTE(schwerin): The value stored by _get_timezone is the value one adds to local time // to get UTC. This is opposite of the ISO-8601 meaning of the timezone offset. @@ -204,47 +200,59 @@ void _dateToISOString(Date_t date, bool local, DateStringBuffer* result) { const long tzOffsetSeconds = msTimeZone * (tzIsWestOfUTC ? 1 : -1); const long tzOffsetHoursPart = tzOffsetSeconds / 3600; const long tzOffsetMinutesPart = (tzOffsetSeconds / 60) % 60; + + // "+hh:mm" static const auto& fmtStrTime = *new auto(fmt::compile<char, long, long>("{}{:02}:{:02}")); - fmt::format_to_n(cur, - localTzSubstrLen + 1, - fmtStrTime, - tzIsWestOfUTC ? '-' : '+', - tzOffsetHoursPart, - tzOffsetMinutesPart); + cur = fmt::format_to_n(cur, + localTzSubstrLen + 1, + fmtStrTime, + tzIsWestOfUTC ? '-' : '+', + tzOffsetHoursPart, + tzOffsetMinutesPart) + .out; #else // ISO 8601 requires the timezone to be in hh:mm format which strftime can't produce // See https://tools.ietf.org/html/rfc3339#section-5.6 - strftime(cur, bufRemaining, "%z:", &t); + strftime(cur, end - cur, "%z:", &t); // cur will be written as +hhmm:, transform to +hh:mm std::rotate(cur + 3, cur + 5, cur + 6); + cur += 6; #endif - cur += localTzSubstrLen; } else { - dassert(bufRemaining >= 2); - *cur = 'Z'; - ++cur; + dassert(cur + 2 <= end); + *cur++ = 'Z'; } - result->size = cur - buf; - dassert(result->size < DateStringBuffer::dataCapacity); + + dassert(cur <= end); + _size = cur - _data.data(); + return *this; } -void _dateToCtimeString(Date_t date, DateStringBuffer* result) { - static const size_t ctimeSubstrLen = 19; - static const size_t millisSubstrLen = 4; +DateStringBuffer& DateStringBuffer::ctime(Date_t date) { + // "Wed Jun 30 21:49:08 1993\n" // full asctime/ctime format + // "Wed Jun 30 21:49:08" // clip after position 19. + // "Wed Jun 30 21:49:08.996" // append millis + // 12345678901234567890123456 time_t t = date.toTimeT(); #if defined(_WIN32) - ctime_s(result->data, sizeof(result->data), &t); + ctime_s(_data.data(), _data.size(), &t); #else - ctime_r(&t, result->data); + ctime_r(&t, _data.data()); #endif - char* milliSecStr = result->data + ctimeSubstrLen; + + static constexpr size_t ctimeSubstrLen = 19; + static constexpr size_t millisSubstrLen = 4; + char* milliSecStr = _data.data() + ctimeSubstrLen; snprintf(milliSecStr, millisSubstrLen + 1, ".%03u", static_cast<unsigned>(date.toMillisSinceEpoch() % 1000)); - result->size = ctimeSubstrLen + millisSubstrLen; + _size = ctimeSubstrLen + millisSubstrLen; + return *this; } +namespace { + #if defined(_WIN32) uint64_t fileTimeToMicroseconds(FILETIME const ft) { @@ -267,57 +275,6 @@ uint64_t fileTimeToMicroseconds(FILETIME const ft) { #endif -} // namespace - -std::string dateToISOStringUTC(Date_t date) { - DateStringBuffer buf; - _dateToISOString(date, false, &buf); - return std::string(buf.data, buf.size); -} - -std::string dateToISOStringLocal(Date_t date) { - DateStringBuffer buf; - _dateToISOString(date, true, &buf); - return std::string(buf.data, buf.size); -} - -std::string dateToCtimeString(Date_t date) { - DateStringBuffer buf; - _dateToCtimeString(date, &buf); - return std::string(buf.data, buf.size); -} - -void outputDateAsISOStringUTC(std::ostream& os, Date_t date) { - DateStringBuffer buf; - _dateToISOString(date, false, &buf); - os << StringData(buf.data, buf.size); -} - -void outputDateAsISOStringUTC(fmt::memory_buffer& buffer, Date_t date) { - DateStringBuffer buf; - _dateToISOString(date, false, &buf); - buffer.append(buf.data, buf.data + buf.size); -} - -void outputDateAsISOStringLocal(std::ostream& os, Date_t date) { - DateStringBuffer buf; - _dateToISOString(date, true, &buf); - os << StringData(buf.data, buf.size); -} - -void outputDateAsISOStringLocal(fmt::memory_buffer& buffer, Date_t date) { - DateStringBuffer buf; - _dateToISOString(date, true, &buf); - buffer.append(buf.data, buf.data + buf.size); -} - -void outputDateAsCtime(std::ostream& os, Date_t date) { - DateStringBuffer buf; - _dateToCtimeString(date, &buf); - os << StringData(buf.data, buf.size); -} - -namespace { StringData getNextToken(StringData currentString, StringData terminalChars, size_t startIndex, @@ -753,8 +710,6 @@ StatusWith<Date_t> dateFromISOString(StringData dateString) { return Date_t::fromMillisSinceEpoch(static_cast<long long>(resultMillis)); } -#undef MONGO_ISO_DATE_FMT_NO_TZ - std::string Date_t::toString() const { if (isFormattable()) { return dateToISOStringLocal(*this); @@ -925,5 +880,24 @@ Nanoseconds getMinimumTimerResolution() { return minTimerResolution; } +std::string dateToISOStringUTC(Date_t date) { + return std::string{DateStringBuffer{}.iso8601(date, false)}; +} +std::string dateToISOStringLocal(Date_t date) { + return std::string{DateStringBuffer{}.iso8601(date, true)}; +} +std::string dateToCtimeString(Date_t date) { + return std::string{DateStringBuffer{}.ctime(date)}; +} + +void outputDateAsISOStringUTC(std::ostream& os, Date_t date) { + os << StringData{DateStringBuffer{}.iso8601(date, false)}; +} +void outputDateAsISOStringLocal(std::ostream& os, Date_t date) { + os << StringData{DateStringBuffer{}.iso8601(date, true)}; +} +void outputDateAsCtime(std::ostream& os, Date_t date) { + os << StringData{DateStringBuffer{}.ctime(date)}; +} } // namespace mongo diff --git a/src/mongo/util/time_support.h b/src/mongo/util/time_support.h index 3072e62302a..f7ba73ca26d 100644 --- a/src/mongo/util/time_support.h +++ b/src/mongo/util/time_support.h @@ -29,8 +29,8 @@ #pragma once +#include <array> #include <ctime> -#include <fmt/format.h> #include <iosfwd> #include <limits> #include <string> @@ -263,8 +263,34 @@ private: static AtomicWord<long long> lastNowVal; }; -// uses ISO 8601 dates without trailing Z -// colonsOk should be false when creating filenames +class DateStringBuffer { +public: + /** Fill with formatted `date`, either in `local` or UTC. */ + DateStringBuffer& iso8601(Date_t date, bool local); + + /** + * Fill with formatted `date`, in modified ctime format. + * Like ctime, but newline and year removed, and milliseconds added. + */ + DateStringBuffer& ctime(Date_t date); + + explicit operator StringData() const { + return StringData{_data.data(), _size}; + } + + explicit operator std::string() const { + return std::string{StringData{*this}}; + } + +private: + std::array<char, 64> _data; + size_t _size = 0; +}; + +/** + * uses ISO 8601 dates without trailing "Z". + * `colonsOk` should be false when creating filenames. + */ std::string terseCurrentTime(bool colonsOk = true); /** @@ -272,28 +298,25 @@ std::string terseCurrentTime(bool colonsOk = true); */ std::string terseUTCCurrentTime(); +/** @{ */ /** - * Formats "date" according to the ISO 8601 extended form standard, including date, - * and time with milliseconds decimal component, in the UTC timezone. - * - * Sample format: "2013-07-23T18:42:14.072Z" + * Formats "date" in 3 formats to 3 kinds of output. + * Function variants are provided to produce ISO local, ISO UTC, or modified ctime formats. + * The ISO formats are according to the ISO 8601 extended form standard, including date and + * time with a milliseconds decimal component. + * Modified ctime format is like `ctime`, but with milliseconds and no year. + * "2013-07-23T18:42:14.072Z" // *ToISOStringUTC + * "2013-07-23T18:42:14.072-05:00" // *ToISOStringLocal + * "Wed Oct 31 13:34:47.996" // *ToCtimeString (modified ctime) + * Output can be a std::string, or put to a std::ostream. */ std::string dateToISOStringUTC(Date_t date); - -/** - * Formats "date" according to the ISO 8601 extended form standard, including date, - * and time with milliseconds decimal component, in the local timezone. - * - * Sample format: "2013-07-23T18:42:14.072-05:00" - */ std::string dateToISOStringLocal(Date_t date); - -/** - * Formats "date" in fixed width in the local time zone. - * - * Sample format: "Wed Oct 31 13:34:47.996" - */ std::string dateToCtimeString(Date_t date); +void outputDateAsISOStringUTC(std::ostream& os, Date_t date); +void outputDateAsISOStringLocal(std::ostream& os, Date_t date); +void outputDateAsCtime(std::ostream& os, Date_t date); +/** @} */ /** * Parses a Date_t from an ISO 8601 std::string representation. @@ -305,23 +328,6 @@ std::string dateToCtimeString(Date_t date); */ StatusWith<Date_t> dateFromISOString(StringData dateString); -/** - * Like dateToISOStringUTC, except outputs to a std::ostream or fmt::memory_buffer. - */ -void outputDateAsISOStringUTC(std::ostream& os, Date_t date); -void outputDateAsISOStringUTC(fmt::memory_buffer& buffer, Date_t date); - -/** - * Like dateToISOStringLocal, except outputs to a std::ostream or fmt::memory_buffer. - */ -void outputDateAsISOStringLocal(std::ostream& os, Date_t date); -void outputDateAsISOStringLocal(fmt::memory_buffer& buffer, Date_t date); - -/** - * Like dateToCtimeString, except outputs to a std::ostream. - */ -void outputDateAsCtime(std::ostream& os, Date_t date); - void sleepsecs(int s); void sleepmillis(long long ms); void sleepmicros(long long micros); |