1 /** @file
2 
3   A brief file description
4 
5   @section license License
6 
7   Licensed to the Apache Software Foundation (ASF) under one
8   or more contributor license agreements.  See the NOTICE file
9   distributed with this work for additional information
10   regarding copyright ownership.  The ASF licenses this file
11   to you under the Apache License, Version 2.0 (the
12   "License"); you may not use this file except in compliance
13   with the License.  You may obtain a copy of the License at
14 
15       http://www.apache.org/licenses/LICENSE-2.0
16 
17   Unless required by applicable law or agreed to in writing, software
18   distributed under the License is distributed on an "AS IS" BASIS,
19   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20   See the License for the specific language governing permissions and
21   limitations under the License.
22  */
23 
24 /***************************************************************************
25  LogFormat.cc
26 
27 
28  ***************************************************************************/
29 #include "tscore/ink_config.h"
30 
31 #include <cstdio>
32 #include <cstring>
33 #include <cstdlib>
34 
35 #include "tscore/SimpleTokenizer.h"
36 #include "tscore/CryptoHash.h"
37 
38 #include "LogUtils.h"
39 #include "LogFile.h"
40 #include "LogField.h"
41 #include "LogFilter.h"
42 #include "LogFormat.h"
43 #include "LogBuffer.h"
44 #include "LogObject.h"
45 #include "LogConfig.h"
46 #include "Log.h"
47 
48 // class variables
49 //
50 bool LogFormat::m_tagging_on = false;
51 
52 /*-------------------------------------------------------------------------
53   LogFormat::setup
54   -------------------------------------------------------------------------*/
55 
56 bool
setup(const char * name,const char * format_str,unsigned interval_sec)57 LogFormat::setup(const char *name, const char *format_str, unsigned interval_sec)
58 {
59   if (name == nullptr) {
60     Note("missing log format name");
61     return false;
62   }
63 
64   if (format_str) {
65     const char *tag                = " %<phn>";
66     const size_t m_format_str_size = strlen(format_str) + (m_tagging_on ? strlen(tag) : 0) + 1;
67     m_format_str                   = static_cast<char *>(ats_malloc(m_format_str_size));
68     ink_strlcpy(m_format_str, format_str, m_format_str_size);
69     if (m_tagging_on) {
70       Note("Log tagging enabled, adding %%<phn> field at the end of "
71            "format %s",
72            name);
73       ink_strlcat(m_format_str, tag, m_format_str_size);
74     };
75 
76     char *printf_str    = nullptr;
77     char *fieldlist_str = nullptr;
78     int nfields         = parse_format_string(m_format_str, &printf_str, &fieldlist_str);
79     if (nfields > (m_tagging_on ? 1 : 0)) {
80       init_variables(name, fieldlist_str, printf_str, interval_sec);
81     } else {
82       Note("Format %s encountered an error parsing the symbol string "
83            "\"%s\", symbol string contains no fields",
84            ((name) ? name : "no-name"), format_str);
85       m_valid = false;
86     }
87 
88     ats_free(fieldlist_str);
89     ats_free(printf_str);
90 
91     // We are only valid if init_variables() says we are.
92     return true;
93   }
94 
95   // We don't have a format string (ie. this will be a raw text log, so we are always valid.
96   m_valid = true;
97   return true;
98 }
99 
100 /*-------------------------------------------------------------------------
101   LogFormat::id_from_name
102   -------------------------------------------------------------------------*/
103 
104 int32_t
id_from_name(const char * name)105 LogFormat::id_from_name(const char *name)
106 {
107   int32_t id = 0;
108   if (name) {
109     CryptoHash hash;
110     CryptoContext().hash_immediate(hash, name, static_cast<int>(strlen(name)));
111 #if defined(linux)
112     /* Mask most significant bit so that return value of this function
113      * is not sign extended to be a negative number.
114      * This problem is only known to occur on Linux which
115      * is a 32-bit OS.
116      */
117     id = static_cast<int32_t>(hash.fold()) & 0x7fffffff;
118 #else
119     id = (int32_t)hash.fold();
120 #endif
121   }
122   return id;
123 }
124 
125 /*-------------------------------------------------------------------------
126   LogFormat::init_variables
127   -------------------------------------------------------------------------*/
128 
129 void
init_variables(const char * name,const char * fieldlist_str,const char * printf_str,unsigned interval_sec)130 LogFormat::init_variables(const char *name, const char *fieldlist_str, const char *printf_str, unsigned interval_sec)
131 {
132   m_field_count = parse_symbol_string(fieldlist_str, &m_field_list, &m_aggregate);
133 
134   if (m_field_count == 0) {
135     m_valid = false;
136   } else if (m_aggregate && !interval_sec) {
137     Note("Format for aggregate operators but no interval "
138          "was specified");
139     m_valid = false;
140   } else {
141     if (m_aggregate) {
142       m_agg_marshal_space = static_cast<char *>(ats_malloc(m_field_count * INK_MIN_ALIGN));
143     }
144 
145     if (m_name_str) {
146       ats_free(m_name_str);
147       m_name_str = nullptr;
148       m_name_id  = 0;
149     }
150     if (name) {
151       m_name_str = ats_strdup(name);
152       m_name_id  = id_from_name(m_name_str);
153     }
154 
155     if (m_fieldlist_str) {
156       ats_free(m_fieldlist_str);
157       m_fieldlist_str = nullptr;
158       m_fieldlist_id  = 0;
159     }
160     if (fieldlist_str) {
161       m_fieldlist_str = ats_strdup(fieldlist_str);
162       m_fieldlist_id  = id_from_name(m_fieldlist_str);
163     }
164 
165     m_printf_str    = ats_strdup(printf_str);
166     m_interval_sec  = interval_sec;
167     m_interval_next = LogUtils::timestamp();
168 
169     m_valid = true;
170   }
171 }
172 
173 /*-------------------------------------------------------------------------
174   LogFormat::LogFormat
175 
176   This is the general ctor that builds a LogFormat object from the data
177   provided.  In this case, the "fields" character string is a printf-style
178   string where the field symbols are represented within the string in the
179   form %<symbol>.
180   -------------------------------------------------------------------------*/
181 
LogFormat(const char * name,const char * format_str,unsigned interval_sec)182 LogFormat::LogFormat(const char *name, const char *format_str, unsigned interval_sec)
183   : m_interval_sec(0),
184     m_interval_next(0),
185     m_agg_marshal_space(nullptr),
186     m_valid(false),
187     m_name_str(nullptr),
188     m_name_id(0),
189     m_fieldlist_str(nullptr),
190     m_fieldlist_id(0),
191     m_field_count(0),
192     m_printf_str(nullptr),
193     m_aggregate(false),
194     m_format_str(nullptr)
195 {
196   setup(name, format_str, interval_sec);
197 
198   // A LOG_FORMAT_TEXT is a log without a format string, everything else is a LOG_FORMAT_CUSTOM. It's possible that we could get
199   // rid of log types altogether, but LogFile currently tests whether a format is a LOG_FORMAT_TEXT format ...
200   m_format_type = format_str ? LOG_FORMAT_CUSTOM : LOG_FORMAT_TEXT;
201 }
202 
203 /*-------------------------------------------------------------------------
204   LogFormat::LogFormat
205 
206   This is the copy ctor, needed for copying lists of Format objects.
207   -------------------------------------------------------------------------*/
208 
LogFormat(const LogFormat & rhs)209 LogFormat::LogFormat(const LogFormat &rhs)
210   : RefCountObj(rhs),
211     m_interval_sec(0),
212     m_interval_next(0),
213     m_agg_marshal_space(nullptr),
214     m_valid(rhs.m_valid),
215     m_name_str(nullptr),
216     m_name_id(0),
217     m_fieldlist_str(nullptr),
218     m_fieldlist_id(0),
219     m_field_count(0),
220     m_printf_str(nullptr),
221     m_aggregate(false),
222     m_format_str(nullptr),
223     m_format_type(rhs.m_format_type)
224 {
225   if (m_valid) {
226     if (m_format_type == LOG_FORMAT_TEXT) {
227       m_name_str = ats_strdup(rhs.m_name_str);
228     } else {
229       m_format_str = rhs.m_format_str ? ats_strdup(rhs.m_format_str) : nullptr;
230       init_variables(rhs.m_name_str, rhs.m_fieldlist_str, rhs.m_printf_str, rhs.m_interval_sec);
231     }
232   }
233 }
234 
235 /*-------------------------------------------------------------------------
236   LogFormat::~LogFormat
237   -------------------------------------------------------------------------*/
238 
~LogFormat()239 LogFormat::~LogFormat()
240 {
241   ats_free(m_name_str);
242   ats_free(m_fieldlist_str);
243   ats_free(m_printf_str);
244   ats_free(m_agg_marshal_space);
245   ats_free(m_format_str);
246   m_valid = false;
247 }
248 
249 /*-------------------------------------------------------------------------
250   LogFormat::format_from_specification
251 
252   This routine is obsolete as of 3.1, but will be kept around to preserve
253   the old log config file option.
254 
255   This (static) function examines the given log format specification string
256   and builds a new LogFormat object if the format specification is valid.
257   On success, a pointer to a LogFormat object allocated from the heap (with
258   new) will be returned.  On error, NULL is returned.
259   -------------------------------------------------------------------------*/
260 
261 LogFormat *
format_from_specification(char * spec,char ** file_name,char ** file_header,LogFileFormat * file_type)262 LogFormat::format_from_specification(char *spec, char **file_name, char **file_header, LogFileFormat *file_type)
263 {
264   LogFormat *format;
265   char *token;
266   int format_id;
267   char *format_name, *format_str;
268 
269   ink_assert(file_name != nullptr);
270   ink_assert(file_header != nullptr);
271   ink_assert(file_type != nullptr);
272 
273   SimpleTokenizer tok(spec, ':');
274 
275   //
276   // Divide the specification string into tokens using the ':' as a
277   // field separator.  There are currently eight (8) tokens that comprise
278   // a format specification.  Verify each of the token values and if
279   // everything looks ok, then build the LogFormat object.
280   //
281   // First should be the "format" keyword that says this is a format spec.
282   //
283   token = tok.getNext();
284   if (token == nullptr) {
285     Debug("log-format", "token expected");
286     return nullptr;
287   }
288   if (strcasecmp(token, "format") == 0) {
289     Debug("log-format", "this is a format");
290   } else {
291     Debug("log-format", "should be 'format'");
292     return nullptr;
293   }
294 
295   //
296   // Next should be the word "enabled" or "disabled", which indicates
297   // whether we should care about this format or not.
298   //
299   token = tok.getNext();
300   if (token == nullptr) {
301     Debug("log-format", "token expected");
302     return nullptr;
303   }
304   if (!strcasecmp(token, "disabled")) {
305     Debug("log-format", "format not enabled, skipping ...");
306     return nullptr;
307   } else if (!strcasecmp(token, "enabled")) {
308     Debug("log-format", "enabled format");
309   } else {
310     Debug("log-format", "should be 'enabled' or 'disabled', not %s", token);
311     return nullptr;
312   }
313 
314   //
315   // Next should be the numeric format identifier
316   //
317   token = tok.getNext();
318   if (token == nullptr) {
319     Debug("log-format", "token expected");
320     return nullptr;
321   }
322   format_id = atoi(token);
323   // NOW UNUSED !!!
324 
325   //
326   // Next should be the format name
327   //
328   token = tok.getNext();
329   if (token == nullptr) {
330     Debug("log-format", "token expected");
331     return nullptr;
332   }
333   format_name = token;
334 
335   //
336   // Next should be the printf-stlye format symbol string
337   //
338   token = tok.getNext();
339   if (token == nullptr) {
340     Debug("log-format", "token expected");
341     return nullptr;
342   }
343   format_str = token;
344 
345   //
346   // Next should be the file name for the log
347   //
348   token = tok.getNext();
349   if (token == nullptr) {
350     Debug("log-format", "token expected");
351     return nullptr;
352   }
353   *file_name = ats_strdup(token);
354 
355   //
356   // Next should be the file type, either "ASCII" or "BINARY"
357   //
358   token = tok.getNext();
359   if (token == nullptr) {
360     Debug("log-format", "token expected");
361     return nullptr;
362   }
363   if (!strcasecmp(token, "ASCII")) {
364     *file_type = LOG_FILE_ASCII;
365   } else if (!strcasecmp(token, "BINARY")) {
366     *file_type = LOG_FILE_BINARY;
367   } else {
368     Debug("log-format", "%s is not a valid file format (ASCII or BINARY)", token);
369     return nullptr;
370   }
371 
372   //
373   // the rest should be the file header
374   //
375   token = tok.getRest();
376   if (token == nullptr) {
377     Debug("log-format", "token expected");
378     return nullptr;
379   }
380   // set header to NULL if "none" was specified (a NULL header means
381   // "write no header" to the rest of the logging system)
382   //
383   *file_header = strcmp(token, "none") == 0 ? nullptr : ats_strdup(token);
384 
385   Debug("log-format", "custom:%d:%s:%s:%s:%d:%s", format_id, format_name, format_str, *file_name, *file_type, token);
386 
387   format = new LogFormat(format_name, format_str);
388   ink_assert(format != nullptr);
389   if (!format->valid()) {
390     delete format;
391     return nullptr;
392   }
393 
394   return format;
395 }
396 
397 /*-------------------------------------------------------------------------
398   LogFormat::parse_symbol_string
399 
400   This function does the work of parsing a comma-separated symbol list and
401   adding them to the LogFieldList that is provided.  The total number of
402   fields added to the list is returned.
403   -------------------------------------------------------------------------*/
404 
405 int
parse_symbol_string(const char * symbol_string,LogFieldList * field_list,bool * contains_aggregates)406 LogFormat::parse_symbol_string(const char *symbol_string, LogFieldList *field_list, bool *contains_aggregates)
407 {
408   char *sym_str;
409   int field_count = 0;
410   LogField *f;
411   char *symbol, *name, *sym, *saveptr;
412   LogField::Container container;
413   LogField::Aggregate aggregate;
414 
415   if (symbol_string == nullptr) {
416     return 0;
417   }
418   ink_assert(field_list != nullptr);
419   ink_assert(contains_aggregates != nullptr);
420 
421   *contains_aggregates = false; // we'll change if it does
422 
423   //
424   // strtok_r will mangle the input string; we'll make a copy for that.
425   //
426   sym_str = ats_strdup(symbol_string);
427   symbol  = strtok_r(sym_str, ",", &saveptr);
428 
429   while (symbol != nullptr) {
430     //
431     // See if there is an aggregate operator, which will contain "()"
432     //
433     char *begin_paren = strchr(symbol, '(');
434     if (begin_paren) {
435       char *end_paren = strchr(symbol, ')');
436       if (end_paren) {
437         Debug("log-agg", "Aggregate symbol: %s", symbol);
438         *begin_paren = '\0';
439         *end_paren   = '\0';
440         name         = begin_paren + 1;
441         sym          = symbol;
442         Debug("log-agg", "Aggregate = %s, field = %s", sym, name);
443         aggregate = LogField::valid_aggregate_name(sym);
444         if (aggregate == LogField::NO_AGGREGATE) {
445           Note("Invalid aggregate specification: %s", sym);
446         } else {
447           if (aggregate == LogField::eCOUNT && strcmp(name, "*") == 0) {
448             f = Log::global_field_list.find_by_symbol("psql");
449           } else {
450             f = Log::global_field_list.find_by_symbol(name);
451           }
452           if (!f) {
453             Note("Invalid field symbol %s used in aggregate "
454                  "operation",
455                  name);
456           } else if (f->type() != LogField::sINT) {
457             Note("Only single integer field types may be aggregated");
458           } else {
459             LogField *new_f = new LogField(*f);
460             new_f->set_aggregate_op(aggregate);
461             field_list->add(new_f, false);
462             field_count++;
463             *contains_aggregates = true;
464             Debug("log-agg", "Aggregate field %s(%s) added", sym, name);
465           }
466         }
467       } else {
468         Note("Invalid aggregate field specification: no trailing "
469              "')' in %s",
470              symbol);
471       }
472     }
473     //
474     // Now check for a container field, which starts with '{'
475     //
476     else if (*symbol == '{') {
477       Debug("log-format", "Container symbol: %s", symbol);
478       f              = nullptr;
479       char *name_end = strchr(symbol, '}');
480       if (name_end != nullptr) {
481         name      = symbol + 1;
482         *name_end = 0;            // changes '}' to '\0'
483         sym       = name_end + 1; // start of container symbol
484         LogSlice slice(sym);
485         Debug("log-format", "Name = %s, symbol = %s", name, sym);
486         container = LogField::valid_container_name(sym);
487         if (container == LogField::NO_CONTAINER) {
488           Note("Invalid container specification: %s", sym);
489         } else {
490           f = new LogField(name, container);
491           ink_assert(f != nullptr);
492           if (slice.m_enable) {
493             f->m_slice = slice;
494             Debug("log-slice", "symbol = %s, [%d:%d]", sym, f->m_slice.m_start, f->m_slice.m_end);
495           }
496           field_list->add(f, false);
497           field_count++;
498           Debug("log-format", "Container field {%s}%s added", name, sym);
499         }
500       } else {
501         Note("Invalid container field specification: no trailing "
502              "'}' in %s",
503              symbol);
504       }
505     }
506     //
507     // treat this like a regular field symbol
508     //
509     else {
510       LogSlice slice(symbol);
511       Debug("log-format", "Regular field symbol: %s", symbol);
512       f = Log::global_field_list.find_by_symbol(symbol);
513       if (f != nullptr) {
514         LogField *cpy = new LogField(*f);
515         if (slice.m_enable) {
516           cpy->m_slice = slice;
517           Debug("log-slice", "symbol = %s, [%d:%d]", symbol, cpy->m_slice.m_start, cpy->m_slice.m_end);
518         }
519         field_list->add(cpy, false);
520         field_count++;
521         Debug("log-format", "Regular field %s added", symbol);
522       } else {
523         Note("The log format symbol %s was not found in the "
524              "list of known symbols.",
525              symbol);
526         field_list->addBadSymbol(symbol);
527       }
528     }
529 
530     //
531     // Get the next symbol
532     //
533     symbol = strtok_r(nullptr, ",", &saveptr);
534   }
535 
536   ats_free(sym_str);
537   return field_count;
538 }
539 
540 //
541 // Parse escape string, supports two forms:
542 //
543 // 1) Octal representation: '\abc', for example: '\060'
544 //    0 < (a*8^2 + b*8 + c) < 255
545 //
546 // 2) Hex representation: '\xab', for example: '\x3A'
547 //    0 < (a*16 + b) < 255
548 //
549 // Return -1 if the beginning four characters are not valid
550 // escape sequence, otherwise return unsigned char value of the
551 // escape sequence in the string.
552 //
553 // NOTE: The value of escape sequence should be greater than 0
554 //       and less than 255, since:
555 //       - 0 is terminator of string, and
556 //       - 255('\377') has been used as LOG_FIELD_MARKER.
557 //
558 int
parse_escape_string(const char * str,int len)559 LogFormat::parse_escape_string(const char *str, int len)
560 {
561   int sum, start = 0;
562   unsigned char a, b, c;
563 
564   if (str[start] != '\\' || len < 2) {
565     return -1;
566   }
567 
568   if (str[start + 1] == '\\') {
569     return '\\';
570   }
571   if (len < 4) {
572     return -1;
573   }
574 
575   a = static_cast<unsigned char>(str[start + 1]);
576   b = static_cast<unsigned char>(str[start + 2]);
577   c = static_cast<unsigned char>(str[start + 3]);
578 
579   if (isdigit(a) && isdigit(b)) {
580     sum = (a - '0') * 64 + (b - '0') * 8 + (c - '0');
581 
582     if (sum == 0 || sum >= 255) {
583       Warning("Octal escape sequence out of range: \\%c%c%c, treat it as normal string\n", a, b, c);
584       return -1;
585     } else {
586       return sum;
587     }
588 
589   } else if (tolower(a) == 'x' && isxdigit(b) && isxdigit(c)) {
590     int i, j;
591     if (isdigit(b)) {
592       i = b - '0';
593     } else {
594       i = toupper(b) - 'A' + 10;
595     }
596 
597     if (isdigit(c)) {
598       j = c - '0';
599     } else {
600       j = toupper(c) - 'A' + 10;
601     }
602 
603     sum = i * 16 + j;
604 
605     if (sum == 0 || sum >= 255) {
606       Warning("Hex escape sequence out of range: \\%c%c%c, treat it as normal string\n", a, b, c);
607       return -1;
608     } else {
609       return sum;
610     }
611   }
612 
613   return -1;
614 }
615 
616 /*-------------------------------------------------------------------------
617   LogFormat::parse_format_string
618 
619   This function will parse a custom log format string, which is a
620   combination of printf characters and logging field names, separating this
621   combined format string into a normal printf string and a fieldlist.  The
622   number of logging fields parsed will be returned.  The two strings
623   returned are allocated with ats_malloc, and should be released by the
624   caller.  The function returns -1 on error.
625 
626   For 3.1, I've added the ability to log summary information using
627   aggregate operators SUM, COUNT, AVG, ...
628   -------------------------------------------------------------------------*/
629 
630 int
parse_format_string(const char * format_str,char ** printf_str,char ** fields_str)631 LogFormat::parse_format_string(const char *format_str, char **printf_str, char **fields_str)
632 {
633   ink_assert(printf_str != nullptr);
634   ink_assert(fields_str != nullptr);
635 
636   if (format_str == nullptr) {
637     *printf_str = *fields_str = nullptr;
638     return 0;
639   }
640   //
641   // Since the given format string is a combination of the printf-string
642   // and the field symbols, when we break it up into these two components
643   // each is guaranteed to be smaller (or the same size) as the format
644   // string.
645   //
646   unsigned len = static_cast<unsigned>(::strlen(format_str));
647   *printf_str  = static_cast<char *>(ats_malloc(len + 1));
648   *fields_str  = static_cast<char *>(ats_malloc(len + 1));
649 
650   unsigned printf_pos  = 0;
651   unsigned fields_pos  = 0;
652   unsigned field_count = 0;
653   unsigned field_len;
654   unsigned start, stop;
655   int escape_char;
656 
657   for (start = 0; start < len; start++) {
658     //
659     // Look for logging fields: %<field>
660     //
661     if ((format_str[start] == '%') && (start + 1 < len) && (format_str[start + 1] == '<')) {
662       //
663       // this is a field symbol designation; look for the
664       // trailing '>'.
665       //
666       if (fields_pos > 0) {
667         (*fields_str)[fields_pos++] = ',';
668       }
669       for (stop = start + 2; stop < len; stop++) {
670         if (format_str[stop] == '>') {
671           break;
672         }
673       }
674       if (format_str[stop] == '>') {
675         //
676         // We found the termination for this field spec;
677         // copy the field symbol to the symbol string and place a
678         // LOG_FIELD_MARKER in the printf string.
679         //
680         field_len = stop - start - 2;
681         memcpy(&(*fields_str)[fields_pos], &format_str[start + 2], field_len);
682         fields_pos += field_len;
683         (*printf_str)[printf_pos++] = LOG_FIELD_MARKER;
684         ++field_count;
685         start = stop;
686       } else {
687         //
688         // This was not a logging field spec after all,
689         // then try to detect and parse escape string.
690         //
691         escape_char = parse_escape_string(&format_str[start], (len - start));
692 
693         if (escape_char == '\\') {
694           start += 1;
695           (*printf_str)[printf_pos++] = static_cast<char>(escape_char);
696         } else if (escape_char >= 0) {
697           start += 3;
698           (*printf_str)[printf_pos++] = static_cast<char>(escape_char);
699         } else {
700           memcpy(&(*printf_str)[printf_pos], &format_str[start], stop - start + 1);
701           printf_pos += stop - start + 1;
702         }
703       }
704     } else {
705       //
706       // This was not the start of a logging field spec,
707       // then try to detect and parse escape string.
708       //
709       escape_char = parse_escape_string(&format_str[start], (len - start));
710 
711       if (escape_char == '\\') {
712         start += 1;
713         (*printf_str)[printf_pos++] = static_cast<char>(escape_char);
714       } else if (escape_char >= 0) {
715         start += 3;
716         (*printf_str)[printf_pos++] = static_cast<char>(escape_char);
717       } else {
718         (*printf_str)[printf_pos++] = format_str[start];
719       }
720     }
721   }
722 
723   //
724   // Ok, now NULL terminate the strings and return the number of fields
725   // actually found.
726   //
727   (*fields_str)[fields_pos] = '\0';
728   (*printf_str)[printf_pos] = '\0';
729 
730   Debug("log-format", "LogFormat::parse_format_string: field_count=%d, \"%s\", \"%s\"", field_count, *fields_str, *printf_str);
731   return field_count;
732 }
733 
734 /*-------------------------------------------------------------------------
735   LogFormat::display
736 
737   Print out some info about this object.
738   -------------------------------------------------------------------------*/
739 
740 void
display(FILE * fd)741 LogFormat::display(FILE *fd)
742 {
743   static const char *types[] = {"SQUID_LOG", "COMMON_LOG", "EXTENDED_LOG", "EXTENDED2_LOG", "LOG_FORMAT_CUSTOM", "LOG_FORMAT_TEXT"};
744 
745   fprintf(fd, "--------------------------------------------------------\n");
746   fprintf(fd, "Format : %s (%s) (%p), %u fields.\n", m_name_str, types[m_format_type], this, m_field_count);
747   if (m_fieldlist_str) {
748     fprintf(fd, "Symbols: %s\n", m_fieldlist_str);
749     fprintf(fd, "Fields :\n");
750     m_field_list.display(fd);
751   } else {
752     fprintf(fd, "Fields : None\n");
753   }
754   fprintf(fd, "--------------------------------------------------------\n");
755 }
756 
757 /*-------------------------------------------------------------------------
758   LogFormatList
759   -------------------------------------------------------------------------*/
760 
761 LogFormatList::LogFormatList() = default;
762 
~LogFormatList()763 LogFormatList::~LogFormatList()
764 {
765   clear();
766 }
767 
768 void
clear()769 LogFormatList::clear()
770 {
771   LogFormat *f;
772   while ((f = m_format_list.dequeue())) {
773     delete f;
774   }
775 }
776 
777 void
add(LogFormat * format,bool copy)778 LogFormatList::add(LogFormat *format, bool copy)
779 {
780   ink_assert(format != nullptr);
781 
782   if (copy) {
783     m_format_list.enqueue(new LogFormat(*format));
784   } else {
785     m_format_list.enqueue(format);
786   }
787 }
788 
789 LogFormat *
find_by_name(const char * name) const790 LogFormatList::find_by_name(const char *name) const
791 {
792   for (LogFormat *f = first(); f; f = next(f)) {
793     if (!strcmp(f->name(), name)) {
794       return f;
795     }
796   }
797   return nullptr;
798 }
799 
800 unsigned
count()801 LogFormatList::count()
802 {
803   unsigned cnt = 0;
804   for (LogFormat *f = first(); f; f = next(f)) {
805     cnt++;
806   }
807   return cnt;
808 }
809 
810 void
display(FILE * fd)811 LogFormatList::display(FILE *fd)
812 {
813   for (LogFormat *f = first(); f; f = next(f)) {
814     f->display(fd);
815   }
816 }
817