1 /** @file
2
3 This file contains a set of utility routines that are used throughout the
4 logging implementation.
5
6 @section license License
7
8 Licensed to the Apache Software Foundation (ASF) under one
9 or more contributor license agreements. See the NOTICE file
10 distributed with this work for additional information
11 regarding copyright ownership. The ASF licenses this file
12 to you under the Apache License, Version 2.0 (the
13 "License"); you may not use this file except in compliance
14 with the License. You may obtain a copy of the License at
15
16 http://www.apache.org/licenses/LICENSE-2.0
17
18 Unless required by applicable law or agreed to in writing, software
19 distributed under the License is distributed on an "AS IS" BASIS,
20 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21 See the License for the specific language governing permissions and
22 limitations under the License.
23 */
24
25 #include <tscore/ink_align.h>
26 #include "tscore/ink_config.h"
27 #include "tscore/ink_string.h"
28 #include <tscore/ink_assert.h>
29 #include <tscore/BufferWriter.h>
30
31 #ifdef TEST_LOG_UTILS
32
33 #include "unit-tests/test_LogUtils.h"
34
35 #else
36
37 #include <MIME.h>
38
39 #endif
40
41 #include <cassert>
42 #include <cstdio>
43 #include <cstdlib>
44 #include <cstdarg>
45 #include <cstring>
46 #include <ctime>
47 #include <string_view>
48 #include <cstdint>
49
50 #include <sys/time.h>
51 #include <sys/types.h>
52 #include <sys/stat.h>
53 #include <unistd.h>
54 #include <sys/socket.h>
55 #include <netinet/in.h>
56 #include <arpa/inet.h>
57 #include <netdb.h>
58
59 #include "records/P_RecProcess.h"
60 // REC_SIGNAL_LOGGING_ERROR is defined in I_RecSignals.h
61 // REC_SIGNAL_LOGGING_WARNING is defined in I_RecSignals.h
62
63 #include "LogUtils.h"
64 #include "LogLimits.h"
65
66 /*-------------------------------------------------------------------------
67 LogUtils::timestamp_to_str
68
69 This routine will convert a timestamp (seconds) into a short string,
70 of the format "%Y%m%d.%Hh%Mm%Ss".
71
72 Since the resulting buffer is passed in, this routine is thread-safe.
73 Return value is the number of characters placed into the array, not
74 including the NULL.
75 -------------------------------------------------------------------------*/
76
77 int
timestamp_to_str(long timestamp,char * buf,int size)78 LogUtils::timestamp_to_str(long timestamp, char *buf, int size)
79 {
80 static const char *format_str = "%Y%m%d.%Hh%Mm%Ss";
81 struct tm res;
82 struct tm *tms;
83 tms = ink_localtime_r((const time_t *)×tamp, &res);
84 return strftime(buf, size, format_str, tms);
85 }
86
87 /*-------------------------------------------------------------------------
88 LogUtils::timestamp_to_netscape_str
89
90 This routine will convert a timestamp (seconds) into a string compatible
91 with the Netscape logging formats.
92
93 This routine is intended to be called from the (single) logging thread,
94 and is therefore NOT MULTITHREADED SAFE. There is a single, static,
95 string buffer that the time string is constructed into and returned.
96 -------------------------------------------------------------------------*/
97
98 char *
timestamp_to_netscape_str(long timestamp)99 LogUtils::timestamp_to_netscape_str(long timestamp)
100 {
101 static char timebuf[64]; // NOTE: not MT safe
102 static long last_timestamp = 0;
103
104 // safety check
105 if (timestamp < 0) {
106 static char bad_time[] = "Bad timestamp";
107 return bad_time;
108 }
109 //
110 // since we may have many entries per second, lets only do the
111 // formatting if we actually have a new timestamp.
112 //
113
114 if (timestamp != last_timestamp) {
115 //
116 // most of this garbage is simply to find out the offset from GMT,
117 // taking daylight savings into account.
118 //
119 struct tm res;
120 struct tm *tms = ink_localtime_r((const time_t *)×tamp, &res);
121 #if defined(solaris)
122 long zone = (tms->tm_isdst > 0) ? altzone : timezone;
123 #else
124 long zone = -tms->tm_gmtoff; // double negative!
125 #endif
126 int offset;
127 char sign;
128
129 if (zone >= 0) {
130 offset = zone / 60;
131 sign = '-';
132 } else {
133 offset = zone / -60;
134 sign = '+';
135 }
136
137 static char gmtstr[16];
138 int glen = snprintf(gmtstr, 16, "%c%.2d%.2d", sign, offset / 60, offset % 60);
139
140 strftime(timebuf, 64 - glen, "%d/%b/%Y:%H:%M:%S ", tms);
141 ink_strlcat(timebuf, gmtstr, sizeof(timebuf));
142 last_timestamp = timestamp;
143 }
144 return timebuf;
145 }
146
147 /*-------------------------------------------------------------------------
148 LogUtils::timestamp_to_date_str
149
150 This routine will convert a timestamp (seconds) into a W3C compatible
151 date string.
152 -------------------------------------------------------------------------*/
153
154 char *
timestamp_to_date_str(long timestamp)155 LogUtils::timestamp_to_date_str(long timestamp)
156 {
157 static char timebuf[64]; // NOTE: not MT safe
158 static long last_timestamp = 0;
159
160 // safety check
161 if (timestamp < 0) {
162 static char bad_time[] = "Bad timestamp";
163 return bad_time;
164 }
165 //
166 // since we may have many entries per second, lets only do the
167 // formatting if we actually have a new timestamp.
168 //
169
170 if (timestamp != last_timestamp) {
171 struct tm res;
172 struct tm *tms = ink_localtime_r((const time_t *)×tamp, &res);
173 strftime(timebuf, 64, "%Y-%m-%d", tms);
174 last_timestamp = timestamp;
175 }
176 return timebuf;
177 }
178
179 /*-------------------------------------------------------------------------
180 LogUtils::timestamp_to_time_str
181
182 This routine will convert a timestamp (seconds) into a W3C compatible
183 time string.
184 -------------------------------------------------------------------------*/
185
186 char *
timestamp_to_time_str(long timestamp)187 LogUtils::timestamp_to_time_str(long timestamp)
188 {
189 static char timebuf[64]; // NOTE: not MT safe
190 static long last_timestamp = 0;
191
192 // safety check
193 if (timestamp < 0) {
194 static char bad_time[] = "Bad timestamp";
195 return bad_time;
196 }
197 //
198 // since we may have many entries per second, lets only do the
199 // formatting if we actually have a new timestamp.
200 //
201
202 if (timestamp != last_timestamp) {
203 struct tm res;
204 struct tm *tms = ink_localtime_r((const time_t *)×tamp, &res);
205 strftime(timebuf, 64, "%H:%M:%S", tms);
206 last_timestamp = timestamp;
207 }
208 return timebuf;
209 }
210
211 /*-------------------------------------------------------------------------
212 LogUtils::manager_alarm
213
214 This routine provides a convenient abstraction for sending the traffic
215 server manager process an alarm. The logging system can send
216 LOG_ALARM_N_TYPES different types of alarms, as defined in LogUtils.h.
217 Subsequent alarms of the same type will override the previous alarm
218 entry.
219 -------------------------------------------------------------------------*/
220
221 void
manager_alarm(LogUtils::AlarmType alarm_type,const char * msg,...)222 LogUtils::manager_alarm(LogUtils::AlarmType alarm_type, const char *msg, ...)
223 {
224 char msg_buf[LOG_MAX_FORMATTED_LINE];
225
226 ink_assert(alarm_type >= 0 && alarm_type < LogUtils::LOG_ALARM_N_TYPES);
227
228 if (msg == nullptr) {
229 snprintf(msg_buf, sizeof(msg_buf), "No Message");
230 } else {
231 va_list ap;
232 va_start(ap, msg);
233 vsnprintf(msg_buf, LOG_MAX_FORMATTED_LINE, msg, ap);
234 va_end(ap);
235 }
236
237 switch (alarm_type) {
238 case LogUtils::LOG_ALARM_ERROR:
239 RecSignalManager(REC_SIGNAL_LOGGING_ERROR, msg_buf);
240 break;
241
242 case LogUtils::LOG_ALARM_WARNING:
243 RecSignalManager(REC_SIGNAL_LOGGING_WARNING, msg_buf);
244 break;
245
246 default:
247 ink_assert(false);
248 }
249 }
250
251 /*-------------------------------------------------------------------------
252 LogUtils::strip_trailing_newline
253
254 This routine examines the given string buffer to see if the last
255 character before the trailing NULL is a newline ('\n'). If so, it will
256 be replaced with a NULL, thus stripping it and reducing the length of
257 the string by one.
258 -------------------------------------------------------------------------*/
259
260 void
strip_trailing_newline(char * buf)261 LogUtils::strip_trailing_newline(char *buf)
262 {
263 if (buf != nullptr) {
264 int len = ::strlen(buf);
265 if (len > 0) {
266 if (buf[len - 1] == '\n') {
267 buf[len - 1] = '\0';
268 }
269 }
270 }
271 }
272
273 /*-------------------------------------------------------------------------
274 LogUtils::escapify_url_common
275
276 This routine will escapify a URL to remove spaces (and perhaps other ugly
277 characters) from a URL and replace them with a hex escape sequence.
278 Since the escapes are larger (multi-byte) than the characters being
279 replaced, the string returned will be longer than the string passed.
280
281 This is a worker function called by escapify_url and pure_escapify_url. These
282 functions differ on whether the function tries to detect and avoid
283 double URL encoding (escapify_url) or not (pure_escapify_url)
284 -------------------------------------------------------------------------*/
285
286 namespace
287 {
288 char *
escapify_url_common(Arena * arena,char * url,size_t len_in,int * len_out,char * dst,size_t dst_size,const unsigned char * map,bool pure_escape)289 escapify_url_common(Arena *arena, char *url, size_t len_in, int *len_out, char *dst, size_t dst_size, const unsigned char *map,
290 bool pure_escape)
291 {
292 // codes_to_escape is a bitmap encoding the codes that should be escaped.
293 // These are all the codes defined in section 2.4.3 of RFC 2396
294 // (control, space, delims, and unwise) plus the tilde. In RFC 2396
295 // the tilde is an "unreserved" character, but we escape it because
296 // historically this is what the traffic_server has done.
297 // Note that we leave codes beyond 127 unmodified.
298 //
299 static const unsigned char codes_to_escape[32] = {
300 0xFF, 0xFF, 0xFF,
301 0xFF, // control
302 0xB4, // space " # %
303 0x00, 0x00, //
304 0x0A, // < >
305 0x00, 0x00, 0x00, //
306 0x1E, 0x80, // [ \ ] ^ `
307 0x00, 0x00, //
308 0x1F, // { | } ~ DEL
309 0x00, 0x00, 0x00,
310 0x00, // all non-ascii characters unmodified
311 0x00, 0x00, 0x00,
312 0x00, // .
313 0x00, 0x00, 0x00,
314 0x00, // .
315 0x00, 0x00, 0x00,
316 0x00 // .
317 };
318
319 static char hex_digit[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
320
321 if (!url || (dst && dst_size < len_in)) {
322 *len_out = 0;
323 return nullptr;
324 }
325
326 if (!map) {
327 map = codes_to_escape;
328 }
329
330 // Count specials in the url, assuming that there won't be any.
331 //
332 int count = 0;
333 char *p = url;
334 char *in_url_end = url + len_in;
335
336 while (p < in_url_end) {
337 unsigned char c = *p;
338 if (map[c / 8] & (1 << (7 - c % 8))) {
339 ++count;
340 }
341 ++p;
342 }
343
344 if (!count) {
345 // The common case, no escapes, so just return the source string.
346 //
347 *len_out = len_in;
348 if (dst) {
349 ink_strlcpy(dst, url, dst_size);
350 }
351 return url;
352 }
353
354 // For each special char found, we'll need an escape string, which is
355 // three characters long. Count this and allocate the string required.
356 //
357 // make sure we take into account the characters we are substituting
358 // for when we calculate out_len !!! in other words,
359 // out_len = len_in + 3*count - count
360 //
361 size_t out_len = len_in + 2 * count;
362
363 if (dst && (out_len + 1) > dst_size) {
364 *len_out = 0;
365 return nullptr;
366 }
367
368 // To play it safe, we null terminate the string we return in case
369 // a module that expects null-terminated strings calls escapify_url,
370 // so we allocate an extra byte for the EOS
371 //
372 char *new_url;
373
374 if (dst) {
375 new_url = dst;
376 } else {
377 new_url = arena->str_alloc(out_len + 1);
378 }
379
380 char *from = url;
381 char *to = new_url;
382
383 while (from < in_url_end) {
384 unsigned char c = *from;
385 if (map[c / 8] & (1 << (7 - c % 8))) {
386 /*
387 * If two characters following a '%' don't need to be encoded, then it must
388 * mean that the three character sequence is already encoded. Just copy it over.
389 */
390 if (!pure_escape && (*from == '%') && ((from + 2) < in_url_end)) {
391 unsigned char c1 = *(from + 1);
392 unsigned char c2 = *(from + 2);
393 bool needsEncoding = ((map[c1 / 8] & (1 << (7 - c1 % 8))) || (map[c2 / 8] & (1 << (7 - c2 % 8))));
394 if (!needsEncoding) {
395 out_len -= 2;
396 Debug("log-utils", "character already encoded..skipping %c, %c, %c", *from, *(from + 1), *(from + 2));
397 *to++ = *from++;
398 continue;
399 }
400 }
401
402 *to++ = '%';
403 *to++ = hex_digit[c / 16];
404 *to++ = hex_digit[c % 16];
405 } else {
406 *to++ = *from;
407 }
408 from++;
409 }
410 *to = '\0'; // null terminate string
411
412 *len_out = out_len;
413 return new_url;
414 }
415 } // namespace
416
417 char *
escapify_url(Arena * arena,char * url,size_t len_in,int * len_out,char * dst,size_t dst_size,const unsigned char * map)418 LogUtils::escapify_url(Arena *arena, char *url, size_t len_in, int *len_out, char *dst, size_t dst_size, const unsigned char *map)
419 {
420 return escapify_url_common(arena, url, len_in, len_out, dst, dst_size, map, false);
421 }
422
423 char *
pure_escapify_url(Arena * arena,char * url,size_t len_in,int * len_out,char * dst,size_t dst_size,const unsigned char * map)424 LogUtils::pure_escapify_url(Arena *arena, char *url, size_t len_in, int *len_out, char *dst, size_t dst_size,
425 const unsigned char *map)
426 {
427 return escapify_url_common(arena, url, len_in, len_out, dst, dst_size, map, true);
428 }
429
430 /*-------------------------------------------------------------------------
431 LogUtils::remove_content_type_attributes
432
433 HTTP allows content types to have attributes following the main type and
434 subtype. For example, attributes of text/html might be charset=iso-8859.
435 The content type attributes are not logged, so this function strips them
436 from the given buffer, if present.
437 -------------------------------------------------------------------------*/
438
439 void
remove_content_type_attributes(char * type_str,int * type_len)440 LogUtils::remove_content_type_attributes(char *type_str, int *type_len)
441 {
442 if (!type_str) {
443 *type_len = 0;
444 return;
445 }
446 // Look for a semicolon and cut out everything after that
447 //
448 char *p = static_cast<char *>(memchr(type_str, ';', *type_len));
449 if (p) {
450 *type_len = p - type_str;
451 }
452 }
453
454 /*
455 int
456 LogUtils::ip_to_str (unsigned ip, char *str, unsigned len)
457 {
458 int ret = snprintf (str, len, "%u.%u.%u.%u",
459 (ip >> 24) & 0xff,
460 (ip >> 16) & 0xff,
461 (ip >> 8) & 0xff,
462 ip & 0xff);
463
464 return ((ret <= (int)len)? ret : (int)len);
465 }
466 */
467
468 // return the seconds remaining until the time of the next roll given
469 // the current time, the rolling offset, and the rolling interval
470 //
471 int
seconds_to_next_roll(time_t time_now,int rolling_offset,int rolling_interval)472 LogUtils::seconds_to_next_roll(time_t time_now, int rolling_offset, int rolling_interval)
473 {
474 struct tm lt;
475 ink_localtime_r((const time_t *)&time_now, <);
476 int sidl = lt.tm_sec + lt.tm_min * 60 + lt.tm_hour * 3600;
477 int tr = rolling_offset * 3600;
478 return ((tr >= sidl ? (tr - sidl) % rolling_interval : (86400 - (sidl - tr)) % rolling_interval));
479 }
480
481 ts::TextView
get_unrolled_filename(ts::TextView rolled_filename)482 LogUtils::get_unrolled_filename(ts::TextView rolled_filename)
483 {
484 auto unrolled_name = rolled_filename;
485
486 // A rolled log will look something like:
487 // squid.log_some.hostname.com.20191029.18h15m02s-20191029.18h30m02s.old
488 auto suffix = rolled_filename;
489
490 suffix.remove_prefix_at('.');
491 // Using the above squid.log example, suffix now looks like:
492 // log_some.hostname.com.20191029.18h15m02s-20191029.18h30m02s.old
493
494 // Some suffixes do not have the hostname. Rolled diags.log files will look
495 // something like this, for example:
496 // diags.log.20191114.21h43m16s-20191114.21h43m17s.old
497 //
498 // For these, the second delimiter will be a period. For this reason, we also
499 // split_prefix_at with a period as well.
500 if (suffix.split_prefix_at('_') || suffix.split_prefix_at('.')) {
501 // ' + 1' to remove the '_' or second '.':
502 return unrolled_name.remove_suffix(suffix.size() + 1);
503 }
504 // If there isn't a '.' or an '_' after the first '.', then this
505 // doesn't look like a rolled file.
506 return rolled_filename;
507 }
508
509 // Checks if the file pointed to by full_filename either is a regular
510 // file or a pipe and has write permission, or, if the file does not
511 // exist, if the path prefix of full_filename names a directory that
512 // has both execute and write permissions, so there will be no problem
513 // creating the file. If the size_bytes pointer is not NULL, it returns
514 // the size of the file through it.
515 // Also checks the current size limit for the file. If there is a
516 // limit and has_size_limit is not null, *has_size_limit is set to
517 // true. If there is no limit and has_size_limit is not null,
518 // *has_size_limit is set to false. If there is a limit and if the
519 // current_size_limit_bytes pointer is not null, it returns the limit
520 // through it.
521 //
522 // returns:
523 // 0 on success
524 // -1 on system error (no permission, etc.)
525 // 1 if the file full_filename points to is neither a regular file
526 // nor a pipe
527 //
528 int
file_is_writeable(const char * full_filename,off_t * size_bytes,bool * has_size_limit,uint64_t * current_size_limit_bytes)529 LogUtils::file_is_writeable(const char *full_filename, off_t *size_bytes, bool *has_size_limit, uint64_t *current_size_limit_bytes)
530 {
531 int ret_val = 0;
532 int e;
533 struct stat stat_data;
534
535 e = stat(full_filename, &stat_data);
536 if (e == 0) {
537 // stat succeeded, check if full_filename points to a regular
538 // file/fifo and if so, check if file has write permission
539 //
540 if (!(S_ISREG(stat_data.st_mode) || S_ISFIFO(stat_data.st_mode))) {
541 ret_val = 1;
542 } else if (!(stat_data.st_mode & S_IWUSR)) {
543 errno = EACCES;
544 ret_val = -1;
545 }
546 if (size_bytes) {
547 *size_bytes = stat_data.st_size;
548 }
549 } else {
550 // stat failed
551 //
552 if (errno != ENOENT) {
553 // can't stat file
554 //
555 ret_val = -1;
556 } else {
557 // file does not exist, check that the prefix is a directory with
558 // write and execute permissions
559
560 char *dir;
561 char *prefix = nullptr;
562
563 // search for forward or reverse slash in full_filename
564 // starting from the end
565 //
566 const char *slash = strrchr(full_filename, '/');
567 if (slash) {
568 size_t prefix_len = slash - full_filename + 1;
569 prefix = new char[prefix_len + 1];
570 memcpy(prefix, full_filename, prefix_len);
571 prefix[prefix_len] = 0;
572 dir = prefix;
573 } else {
574 dir = (char *)"."; // full_filename has no prefix, use .
575 }
576
577 // check if directory is executable and writeable
578 //
579 e = access(dir, X_OK | W_OK);
580 if (e < 0) {
581 ret_val = -1;
582 } else {
583 if (size_bytes) {
584 *size_bytes = 0;
585 }
586 }
587
588 if (prefix) {
589 delete[] prefix;
590 }
591 }
592 }
593
594 // check for the current filesize limit
595 //
596 if (ret_val == 0) {
597 struct rlimit limit_data;
598 e = getrlimit(RLIMIT_FSIZE, &limit_data);
599 if (e < 0) {
600 ret_val = -1;
601 } else {
602 if (limit_data.rlim_cur != static_cast<rlim_t> RLIM_INFINITY) {
603 if (has_size_limit) {
604 *has_size_limit = true;
605 }
606 if (current_size_limit_bytes) {
607 *current_size_limit_bytes = limit_data.rlim_cur;
608 }
609 } else {
610 if (has_size_limit) {
611 *has_size_limit = false;
612 }
613 }
614 }
615 }
616
617 return ret_val;
618 }
619
620 namespace
621 {
622 // Get a string out of a MIMEField using one of its member functions, and put it into a buffer writer, terminated with a nul.
623 //
624 void
marshalStr(ts::FixedBufferWriter & bw,const MIMEField & mf,const char * (MIMEField::* get_func)(int * length)const)625 marshalStr(ts::FixedBufferWriter &bw, const MIMEField &mf, const char *(MIMEField::*get_func)(int *length) const)
626 {
627 int length;
628 const char *data = (mf.*get_func)(&length);
629
630 if (!data or (*data == '\0')) {
631 // Empty string. This is a problem, since it would result in two successive nul characters, which indicates the end of the
632 // marshaled hearer. Change the string to a single blank character.
633
634 static const char Blank[] = " ";
635 data = Blank;
636 length = 1;
637 }
638
639 bw << std::string_view(data, length) << '\0';
640 }
641
642 void
unmarshalStr(ts::FixedBufferWriter & bw,const char * & data)643 unmarshalStr(ts::FixedBufferWriter &bw, const char *&data)
644 {
645 bw << '{';
646
647 while (*data) {
648 bw << *(data++);
649 }
650
651 // Skip over terminal nul.
652 ++data;
653
654 bw << '}';
655 }
656
657 } // end anonymous namespace
658
659 namespace LogUtils
660 {
661 // Marshals header tags and values together, with a single terminating nul character. Returns buffer space required. 'buf' points
662 // to where to put the marshaled data. If 'buf' is null, no data is marshaled, but the function returns the amount of space that
663 // would have been used.
664 //
665 int
marshalMimeHdr(MIMEHdr * hdr,char * buf)666 marshalMimeHdr(MIMEHdr *hdr, char *buf)
667 {
668 std::size_t bwSize = buf ? SIZE_MAX : 0;
669
670 ts::FixedBufferWriter bw(buf, bwSize);
671
672 if (hdr) {
673 MIMEFieldIter mfIter;
674 const MIMEField *mfp = hdr->iter_get_first(&mfIter);
675
676 while (mfp) {
677 marshalStr(bw, *mfp, &MIMEField::name_get);
678 marshalStr(bw, *mfp, &MIMEField::value_get);
679
680 mfp = hdr->iter_get_next(&mfIter);
681 }
682 }
683
684 bw << '\0';
685
686 return int(INK_ALIGN_DEFAULT(bw.extent()));
687 }
688
689 // Unmarshaled/printable format is {{{tag1}:{value1}}{{tag2}:{value2}} ... }
690 //
691 int
unmarshalMimeHdr(char ** buf,char * dest,int destLength)692 unmarshalMimeHdr(char **buf, char *dest, int destLength)
693 {
694 ink_assert(*buf != nullptr);
695
696 const char *data = *buf;
697
698 ink_assert(data != nullptr);
699
700 ts::FixedBufferWriter bw(dest, destLength);
701
702 bw << '{';
703
704 int pairEndFallback{0}, pairEndFallback2{0}, pairSeparatorFallback{0};
705
706 while (*data) {
707 if (!bw.error()) {
708 pairEndFallback2 = pairEndFallback;
709 pairEndFallback = bw.size();
710 }
711
712 // Add open bracket of pair.
713 //
714 bw << '{';
715
716 // Unmarshal field name.
717 unmarshalStr(bw, data);
718
719 bw << ':';
720
721 if (!bw.error()) {
722 pairSeparatorFallback = bw.size();
723 }
724
725 // Unmarshal field value.
726 unmarshalStr(bw, data);
727
728 // Add close bracket of pair.
729 bw << '}';
730
731 } // end for loop
732
733 bw << '}';
734
735 if (bw.error()) {
736 // The output buffer wasn't big enough.
737
738 static std::string_view FULL_ELLIPSES("...}}}");
739
740 if ((pairSeparatorFallback > pairEndFallback) and ((pairSeparatorFallback + 7) <= destLength)) {
741 // In the report, we can show the existence of the last partial tag/value pair, and maybe part of the value. If we only
742 // show part of the value, we want to end it with an elipsis, to make it clear it's not complete.
743
744 bw.reduce(destLength - FULL_ELLIPSES.size());
745 bw << FULL_ELLIPSES;
746
747 } else if (pairEndFallback and (pairEndFallback < destLength)) {
748 bw.reduce(pairEndFallback);
749 bw << '}';
750
751 } else if ((pairSeparatorFallback > pairEndFallback2) and ((pairSeparatorFallback + 7) <= destLength)) {
752 bw.reduce(destLength - FULL_ELLIPSES.size());
753 bw << FULL_ELLIPSES;
754
755 } else if (pairEndFallback2 and (pairEndFallback2 < destLength)) {
756 bw.reduce(pairEndFallback2);
757 bw << '}';
758
759 } else if (destLength > 1) {
760 bw.reduce(1);
761 bw << '}';
762
763 } else {
764 bw.reduce(0);
765 }
766 }
767
768 *buf += INK_ALIGN_DEFAULT(data - *buf + 1);
769
770 return bw.size();
771 }
772
773 } // end namespace LogUtils
774