-
Notifications
You must be signed in to change notification settings - Fork 4k
/
Copy pathlog_builtins.cc
3641 lines (2947 loc) · 120 KB
/
log_builtins.cc
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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* Copyright (c) 2017, 2025, Oracle and/or its affiliates.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License, version 2.0,
as published by the Free Software Foundation.
This program is designed to work with certain software (including
but not limited to OpenSSL) that is licensed under separate terms,
as designated in a particular file or component or in included license
documentation. The authors of MySQL hereby grant you an additional
permission to link the program and your derivative works with the
separately licensed software that they have either included with
the program or referenced in the documentation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License, version 2.0, for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
/*
NB This module has an unusual amount of failsafes, OOM checks, and
so on as it implements a public API. This makes a fair number
of minor code paths cases of "we should never get here (unless
someone's going out of their way to break to API)". :)
*/
#define LOG_SUBSYSTEM_TAG "Server"
#include "log_builtins_filter_imp.h"
#include "log_builtins_imp.h" // internal structs
// connection_events_loop_aborted()
#include "log_sink_buffer.h"
#include "log_sink_perfschema.h"
#include "log_sink_trad.h"
#include "m_string.h"
#include "mysys_err.h"
#include <mysql/components/services/log_shared.h> // data types
#include "my_time.h" // str_to_datetime()
#include "sql/current_thd.h" // current_thd
#include "sql/log.h" // make_iso8601_timestamp, log_write_errstream,
// log_get_thread_id, mysql_errno_to_symbol,
// mysql_symbol_to_errno, log_vmessage,
// error_message_for_error_log
#include "sql/mysqld.h" // opt_log_(timestamps|error_services),
#include "sql/sql_class.h" // THD
#include "sql/tztime.h" // my_tz_OFFSET0
#include "string_with_len.h"
// Must come after sql/log.h.
#include "mysql/components/services/log_builtins.h"
#ifndef _WIN32
#include <syslog.h>
#else
#include <stdio.h>
#include "my_sys.h"
#include "my_time.h" // str_to_datetime()
#endif
#include <mysql/psi/mysql_telemetry_logs_client.h>
extern PSI_logger_key key_error_logger;
PSI_memory_key key_memory_log_error_loaded_services;
PSI_memory_key key_memory_log_error_stack;
using std::string;
using std::unique_ptr;
// helper to add telemetry log record attribute from log item field
#define ADD_STR_ATTR(rec, ll, type, name) \
if (ll->seen & type) { \
const int m = log_line_index_by_type(ll, type); \
if (m >= 0 && ll->item[m].data.data_string.length > 0) { \
rec.add_attribute_string(name, ll->item[m].data.data_string.str); \
} \
}
#define ADD_INT_ATTR(rec, ll, type, name) \
if (ll->seen & type) { \
const int m = log_line_index_by_type(ll, type); \
if (m >= 0) { \
rec.add_attribute_uint64(name, \
(uint64_t)(int)ll->item[m].data.data_integer); \
} \
}
/**
Initial log-processor:
Just buffer events until we have external log-components.
@param ll the log-event to buffer
@retval true log_sink_buffer() failed
@retval false log_sink_buffer() succeeded
*/
bool log_line_buffer_event(log_line *ll) {
return (log_sink_buffer(nullptr, ll) < 0);
}
/**
The function pointed to by this hook is run when a log-event is submitted.
By default (until any sinks are set), we just buffer incoming events.
*/
log_line_processor log_line_process_hook = log_line_buffer_event;
/**
Set the log-event processor.
When a log-event is submitted, a function is applied to that event.
That function usually either buffers the event for later processing,
or filters and logs the event.
That function can be set here.
@param llp A log-processor
*/
void log_line_process_hook_set(log_line_processor llp) {
log_line_process_hook = llp;
}
/**
Get current log-event processor.
When a log-event is submitted, a function is applied to that event.
That function usually either buffers the event for later processing,
or filters and logs the event.
log_line_process_hook_get() returns a pointer to that function.
@retval a pointer to a log-event processing function
*/
log_line_processor log_line_process_hook_get(void) {
return log_line_process_hook;
}
struct log_service_cache_entry;
struct log_service_cache_entry_free {
/**
Release an entry in the hash of log services.
@param sce the entry to free
*/
void operator()(log_service_cache_entry *sce) const;
};
/**
We're caching handles to the services used in error logging
as looking them up is costly.
*/
using cache_entry_with_deleter =
unique_ptr<log_service_cache_entry, log_service_cache_entry_free>;
static collation_unordered_map<string, cache_entry_with_deleter>
*log_service_cache;
/**
Lock for the log "stack" (i.e. the list of active log-services).
X-locked while stack is changed/configured.
S-locked while stack is used.
*/
static mysql_rwlock_t THR_LOCK_log_stack;
/**
Make sure only one instance of syslog/Eventlog code runs at a time.
(The loadable log-service is a singleton, which enforces that at
most one instance of it exists. The logger-core has its own lock
that serializes access to it. That however does not prevent the
logger core and system variable updates from using Eventlog functions
concurrently. This lock guards against that. It also serializes
any other (non-error logging) users of this service.
*/
static mysql_mutex_t THR_LOCK_log_syseventlog;
/**
When the logger-core was initialized.
0: logger-core is not currently available
>0: time (micro-seconds since the epoch) the logger-core became available
*/
static ulonglong log_builtins_inited = 0;
/**
When the logger-core was initialized.
@retval 0 logger-core is not currently available
@retval >0 time (micro-seconds since the epoch) the logger became available
*/
ulonglong log_builtins_started() { return log_builtins_inited; }
/**
Name of the interface that log-services implements.
*/
#define LOG_SERVICES_PREFIX "log_service"
/**
URN-prefix used to load a log-component.
When log-components passed to log_builtins_error_stack() are neither
built-in nor have they been loaded already, this prefix will be
prepended to their name to look them up using the component framework.
Thus, "log_sink_json" will be looked up as "file://component_log_sink_json"
and so on.
*/
#define LOG_SERVICES_URN "file://component_"
/**
Chain of log-service instances.
(Each service can have no/one/several instances.)
*/
log_service_instance *log_service_instances = nullptr;
/**
The first configured writer that also has a log-reader
is the source for the "data" field in performance_schema.error_log.
*/
log_service_instance *log_sink_pfs_source = nullptr;
/**
An error-stream.
Rather than implement its own file operations, a log-service may use
convenience functions defined in this file. These functions use the
log_errstream struct to describe their log-files. These structs are
opaque to the log-services.
*/
struct log_errstream {
FILE *file{nullptr}; ///< file to log to
mysql_mutex_t LOCK_errstream; ///< lock for logging
};
/// What mode is error-logging in (e.g. are loadable services available yet)?
static enum log_error_stage log_error_stage_current = LOG_ERROR_STAGE_BUFFERING;
/// Set error-logging stage hint (e.g. are loadable services available yet?).
void log_error_stage_set(enum log_error_stage les) {
log_error_stage_current = les;
}
/// What mode is error-logging in (e.g. are loadable services available yet)?
enum log_error_stage log_error_stage_get() { return log_error_stage_current; }
/**
Test whether a given log-service name refers to a built-in
service (built-in filter or built-in sink at this point).
@param name the name -- either just the component's, or
a fully qualified service.component
@param len the length of the aforementioned name
@return flags for built-in|singleton|filter (if built-in filter)
or flags for built-in|singleton|sink (if built-in sink)
otherwise LOG_SERVICE_UNSPECIFIED
*/
static int log_service_check_if_builtin(const char *name, size_t len) {
const size_t builtin_len = sizeof(LOG_SERVICES_PREFIX) - 1;
if ((len > (builtin_len + 1)) && (name[builtin_len] == '.') &&
(0 == strncmp(name, LOG_SERVICES_PREFIX, builtin_len))) {
name += builtin_len;
len -= builtin_len;
}
if ((len == sizeof(LOG_BUILTINS_FILTER) - 1) &&
(0 == strncmp(name, LOG_BUILTINS_FILTER, len)))
return LOG_SERVICE_BUILTIN | LOG_SERVICE_FILTER | LOG_SERVICE_SINGLETON;
if ((len == sizeof(LOG_BUILTINS_SINK) - 1) &&
(0 == strncmp(name, LOG_BUILTINS_SINK, len)))
return LOG_SERVICE_BUILTIN | LOG_SERVICE_SINK | LOG_SERVICE_SINGLETON |
LOG_SERVICE_LOG_PARSER | LOG_SERVICE_PFS_SUPPORT;
return LOG_SERVICE_UNSPECIFIED;
}
/**
Test whether given service has *all* of the given characteristics.
(See log_service_chistics for a list!)
@param sce service cache entry for the service in question
@param required_flags flags we're interested in (bitwise or'd)
@retval true if all given flags are present, false otherwise
*/
static inline bool log_service_has_characteristics(log_service_cache_entry *sce,
int required_flags) {
return ((sce->chistics & required_flags) == required_flags);
}
/**
Pre-defined "well-known" keys, as opposed to ad hoc ones,
for key/value pairs in logging.
*/
typedef struct _log_item_wellknown_key {
const char *name; ///< key name
size_t name_len; ///< length of key's name
log_item_class item_class; ///< item class (float/int/string)
log_item_type item_type; ///< exact type, 1:1 relationship with name
} log_item_wellknown_key;
/**
We support a number of predefined keys, such as "error-code" or
"message". These are defined here. We also support user-defined
"ad hoc" (or "generic") keys that let users of the error stack
add values with arbitrary keys (as long as those keys don't coincide
with the wellknown ones, anyway).
The idea here is that we want the flexibility of arbitrary keys,
while being able to do certain optimizations for the common case.
This also allows us to relatively cheaply add some convenience
features, e.g. we know that error symbol ("ER_STARTUP") and
error code (1451) are related, and can supply one as the other
is submitted. Likewise of course, we can use the error code to
fetch the associated registered error message string for that
error code. Et cetera!
*/
static const log_item_wellknown_key log_item_wellknown_keys[] = {
{STRING_WITH_LEN("--ERROR--"), LOG_UNTYPED, LOG_ITEM_END},
{STRING_WITH_LEN("log_type"), LOG_INTEGER, LOG_ITEM_LOG_TYPE},
{STRING_WITH_LEN("err_code"), LOG_INTEGER, LOG_ITEM_SQL_ERRCODE},
{STRING_WITH_LEN("err_symbol"), LOG_CSTRING, LOG_ITEM_SQL_ERRSYMBOL},
{STRING_WITH_LEN("SQL_state"), LOG_CSTRING, LOG_ITEM_SQL_STATE},
{STRING_WITH_LEN("OS_errno"), LOG_INTEGER, LOG_ITEM_SYS_ERRNO},
{STRING_WITH_LEN("OS_errmsg"), LOG_CSTRING, LOG_ITEM_SYS_STRERROR},
{STRING_WITH_LEN("source_file"), LOG_CSTRING, LOG_ITEM_SRC_FILE},
{STRING_WITH_LEN("source_line"), LOG_INTEGER, LOG_ITEM_SRC_LINE},
{STRING_WITH_LEN("function"), LOG_CSTRING, LOG_ITEM_SRC_FUNC},
{STRING_WITH_LEN("subsystem"), LOG_CSTRING, LOG_ITEM_SRV_SUBSYS},
{STRING_WITH_LEN("component"), LOG_CSTRING, LOG_ITEM_SRV_COMPONENT},
{STRING_WITH_LEN("user"), LOG_LEX_STRING, LOG_ITEM_MSC_USER},
{STRING_WITH_LEN("host"), LOG_LEX_STRING, LOG_ITEM_MSC_HOST},
{STRING_WITH_LEN("thread"), LOG_INTEGER, LOG_ITEM_SRV_THREAD},
{STRING_WITH_LEN("query_id"), LOG_INTEGER, LOG_ITEM_SQL_QUERY_ID},
{STRING_WITH_LEN("table"), LOG_CSTRING, LOG_ITEM_SQL_TABLE_NAME},
{STRING_WITH_LEN("prio"), LOG_INTEGER, LOG_ITEM_LOG_PRIO},
{STRING_WITH_LEN("label"), LOG_CSTRING, LOG_ITEM_LOG_LABEL},
{STRING_WITH_LEN("verbatim"), LOG_CSTRING, LOG_ITEM_LOG_VERBATIM},
{STRING_WITH_LEN("msg"), LOG_CSTRING, LOG_ITEM_LOG_MESSAGE},
{STRING_WITH_LEN("msg_id"), LOG_INTEGER, LOG_ITEM_LOG_LOOKUP},
{STRING_WITH_LEN("time"), LOG_CSTRING, LOG_ITEM_LOG_TIMESTAMP},
{STRING_WITH_LEN("ts"), LOG_INTEGER, LOG_ITEM_LOG_TS},
{STRING_WITH_LEN("buffered"), LOG_INTEGER, LOG_ITEM_LOG_BUFFERED},
{STRING_WITH_LEN("and_n_more"), LOG_INTEGER, LOG_ITEM_LOG_SUPPRESSED},
/*
We should never see the following key names in normal operations
(but see the user-specified key instead). These have entries all
the same, covering the entirety of log_item_type, so we can use the
usual mechanisms for type-to-class mapping etc.
We could set the names to nullptr, but they're not much overhead, add
readability, and allow for easily creating debug info of the form,
"%s:%s=\"%s\"", wellknown_name, item->key, item->value
*/
{STRING_WITH_LEN("misc_float"), LOG_FLOAT, LOG_ITEM_GEN_FLOAT},
{STRING_WITH_LEN("misc_integer"), LOG_INTEGER, LOG_ITEM_GEN_INTEGER},
{STRING_WITH_LEN("misc_string"), LOG_LEX_STRING, LOG_ITEM_GEN_LEX_STRING},
{STRING_WITH_LEN("misc_cstring"), LOG_CSTRING, LOG_ITEM_GEN_CSTRING},
{STRING_WITH_LEN("misc_buffer"), LOG_BUFFER, LOG_ITEM_GEN_BUFFER}};
static uint log_item_wellknown_keys_count =
(sizeof(log_item_wellknown_keys) / sizeof(log_item_wellknown_key));
int log_string_compare(const char *a, const char *b, size_t len,
bool case_insensitive) {
if (a == nullptr) /* purecov: begin inspected */
return (b == nullptr) ? 0 : -1;
else if (b == nullptr)
return 1; /* purecov: end */
else if (len < 1) // no length limit for comparison
{
return case_insensitive ? native_strcasecmp(a, b) : strcmp(a, b);
}
return case_insensitive ? native_strncasecmp(a, b, len) : strncmp(a, b, len);
}
/*
Log-item helpers
*/
/**
Predicate used to determine whether a type is generic
(generic string, generic float, generic integer) rather
than a well-known type.
@param t log item type to examine
@retval true if generic type
@retval false if wellknown type
*/
bool log_item_generic_type(log_item_type t) {
return (t &
(LOG_ITEM_GEN_CSTRING | LOG_ITEM_GEN_LEX_STRING |
LOG_ITEM_GEN_INTEGER | LOG_ITEM_GEN_FLOAT | LOG_ITEM_GEN_BUFFER));
}
/**
Predicate used to determine whether a class is a string
class (C-string or Lex-string).
@param c log item class to examine
@retval true if of a string class
@retval false if not of a string class
*/
bool log_item_string_class(log_item_class c) {
return ((c == LOG_CSTRING) || (c == LOG_LEX_STRING));
}
/**
Predicate used to determine whether a class is a numeric
class (integer or float).
@param c log item class to examine
@retval true if of a numeric class
@retval false if not of a numeric class
*/
bool log_item_numeric_class(log_item_class c) {
return ((c == LOG_INTEGER) || (c == LOG_FLOAT));
}
void log_item_get_int(log_item *li, longlong *i) /* purecov: begin inspected */
{
if (li->item_class == LOG_FLOAT)
*i = (longlong)li->data.data_float;
else
*i = (longlong)li->data.data_integer;
} /* purecov: end */
void log_item_get_float(log_item *li, double *f) {
if (li->item_class == LOG_FLOAT)
*f = (float)li->data.data_float;
else
*f = (float)li->data.data_integer;
}
void log_item_get_string(log_item *li, char **str, size_t *len) {
if ((*str = const_cast<char *>(li->data.data_string.str)) == nullptr)
*len = 0;
else if (li->item_class & LOG_CSTRING)
*len = strlen(li->data.data_string.str);
else
*len = li->data.data_string.length;
}
/**
See whether a string is a wellknown field name.
@param key potential key starts here
@param len length of the string to examine
@retval LOG_ITEM_TYPE_RESERVED: reserved, but not "wellknown" key
@retval LOG_ITEM_TYPE_NOT_FOUND: key not found
@retval >0: index in array of wellknowns
*/
int log_item_wellknown_by_name(const char *key, size_t len) {
uint c;
// optimize and safeify lookup
for (c = 0; (c < log_item_wellknown_keys_count); c++) {
if ((log_item_wellknown_keys[c].name_len == len) &&
(0 == native_strncasecmp(log_item_wellknown_keys[c].name, key, len))) {
if (log_item_generic_type(log_item_wellknown_keys[c].item_type) ||
(log_item_wellknown_keys[c].item_type == LOG_ITEM_END))
return LOG_ITEM_TYPE_RESERVED;
return c;
}
}
return LOG_ITEM_TYPE_NOT_FOUND;
}
/**
See whether a type is wellknown.
@param t log item type to examine
@retval LOG_ITEM_TYPE_NOT_FOUND: key not found
@retval >0: index in array of wellknowns
*/
int log_item_wellknown_by_type(log_item_type t) {
uint c;
// optimize and safeify lookup
for (c = 0; (c < log_item_wellknown_keys_count); c++) {
if (log_item_wellknown_keys[c].item_type == t) return c;
}
DBUG_PRINT("warning", ("wellknown_by_type: type %d is not well-known."
" Or, you know, known.",
t));
return LOG_ITEM_TYPE_NOT_FOUND;
}
/**
Accessor: from a record describing a wellknown key, get its name
@param idx index in array of wellknowns, see log_item_wellknown_by_...()
@retval name (NTBS)
*/
const char *log_item_wellknown_get_name(uint idx) {
return log_item_wellknown_keys[idx].name;
}
/**
Accessor: from a record describing a wellknown key, get its type
@param idx index in array of wellknowns, see log_item_wellknown_by_...()
@retval the log item type for the wellknown key
*/
log_item_type log_item_wellknown_get_type(uint idx) {
return log_item_wellknown_keys[idx].item_type;
}
/**
Accessor: from a record describing a wellknown key, get its class
@param idx index in array of wellknowns, see log_item_wellknown_by_...()
@retval the log item class for the wellknown key
*/
log_item_class log_item_wellknown_get_class(uint idx) {
return log_item_wellknown_keys[idx].item_class;
}
/**
Sanity check an item.
Certain log sinks have very low requirements with regard to the data
they receive; they write keys as strings, and then data according to
the item's class (string, integer, or float), formatted to the sink's
standards (e.g. JSON, XML, ...).
Code that has higher requirements can use this check to see whether
the given item is of a known type (whether generic or wellknown),
whether the given type and class agree, and whether in case of a
well-known type, the given key is correct for that type.
If your code generates items that don't pass this check, you should
probably go meditate on it.
@param li the log_item to check
@retval LOG_ITEM_OK no problems
@retval LOG_ITEM_TYPE_NOT_FOUND unknown item type
@retval LOG_ITEM_CLASS_MISMATCH item_class derived from type isn't
what's set on the item
@retval LOG_ITEM_KEY_MISMATCH class not generic, so key should
match wellknown
@retval LOG_ITEM_STRING_NULL class is string, pointer is nullptr
@retval LOG_ITEM_KEY_NULL no key set (this is legal e.g. on aux
items of filter rules, but should not
occur in a log_line, i.e., log_sinks are
within their rights to discard such items)
*/
int log_item_inconsistent(log_item *li) {
int w, c;
// invalid type
if ((w = log_item_wellknown_by_type(li->type)) == LOG_ITEM_TYPE_NOT_FOUND)
return LOG_ITEM_TYPE_NOT_FOUND;
// fetch expected storage class for this type
if ((c = log_item_wellknown_keys[w].item_class) == LOG_CSTRING)
c = LOG_LEX_STRING;
// class and type don't match
if (c != li->item_class) return LOG_ITEM_CLASS_MISMATCH;
// no key set
if (li->key == nullptr) return LOG_ITEM_KEY_NULL;
// it's not a generic, and key and type don't match
if (!log_item_generic_type(li->type) &&
(0 != strcmp(li->key, log_item_wellknown_keys[w].name)))
return LOG_ITEM_KEY_MISMATCH;
// strings should have non-nullptr
if ((c == LOG_LEX_STRING) && (li->data.data_string.str == nullptr))
return LOG_ITEM_STRING_NULL;
return LOG_ITEM_OK;
}
/**
Release any of key and value on a log-item that were dynamically allocated.
@param li log-item to release the payload of
*/
void log_item_free(log_item *li) {
if (li->alloc & LOG_ITEM_FREE_KEY) my_free(const_cast<char *>(li->key));
if (li->alloc & LOG_ITEM_FREE_VALUE) {
if (li->item_class == LOG_LEX_STRING)
my_free(const_cast<char *>(li->data.data_string.str));
else if (li->item_class == LOG_BUFFER) /* purecov: begin inspected */
my_free(const_cast<char *>(li->data.data_buffer.str));
else // free() is only defined on string and buffer
assert(false); /* purecov: end */
}
li->alloc = LOG_ITEM_FREE_NONE;
}
log_line *log_line_init() {
log_line *ll;
if ((ll = static_cast<log_line *>(my_malloc(
key_memory_log_error_stack, sizeof(log_line), MYF(0)))) != nullptr) {
memset(ll, 0, sizeof(log_line));
ll->flags = LOG_LINE_EMIT_TELEMETRY;
}
return ll;
}
/**
Release a log_line allocated with line_init()
@param ll a log_line previously allocated with line_init()
*/
void log_line_exit(log_line *ll) {
if (ll != nullptr) my_free(ll);
}
/**
Get log-line's output buffer.
If the logger core provides this buffer, the log-service may use it
to assemble its output therein and implicitly return it to the core.
Participation is required for services that support populating
performance_schema.error_log, and optional for all others.
@param ll the log_line to examine
@retval nullptr success, an output buffer is available
@retval otherwise failure, no output buffer is available
*/
log_item *log_line_get_output_buffer(log_line *ll) {
if ((ll == nullptr) || (ll->output_buffer.item_class != LOG_BUFFER))
return nullptr;
return &ll->output_buffer;
}
/**
Predicate indicating whether a log line is "willing" to accept any more
key/value pairs.
@param ll the log-line to examine
@retval false if not full / if able to accept another log_item
@retval true if full
*/
bool log_line_full(log_line *ll) {
return ((ll == nullptr) || (ll->count >= LOG_ITEM_MAX));
}
/**
How many items are currently set on the given log_line?
@param ll the log-line to examine
@retval the number of items set
*/
int log_line_item_count(log_line *ll) { return ll->count; }
/**
Test whether a given type is presumed present on the log line.
@param ll the log_line to examine
@param m the log_type to test for
@retval 0 not present
@retval !=0 present
*/
log_item_type_mask log_line_item_types_seen(log_line *ll,
log_item_type_mask m) {
return (ll != nullptr) ? (ll->seen & m) : 0;
}
/**
Release log line item (key/value pair) with the index elem in log line ll.
This frees whichever of key and value were dynamically allocated.
This leaves a "gap" in the bag that may immediately be overwritten
with an updated element. If the intention is to remove the item without
replacing it, use log_line_item_remove() instead!
@param ll log_line
@param elem index of the key/value pair to release
*/
void log_line_item_free(log_line *ll, size_t elem) {
assert(ll->count > 0);
log_item_free(&(ll->item[elem]));
}
/**
Release all log line items (key/value pairs) in log line ll.
This frees whichever keys and values were dynamically allocated.
@param ll log_line
*/
void log_line_item_free_all(log_line *ll) {
while (ll->count > 0) log_item_free(&(ll->item[--ll->count]));
ll->seen = LOG_ITEM_END;
}
/**
Release log line item (key/value pair) with the index elem in log line ll.
This frees whichever of key and value were dynamically allocated.
This moves any trailing items to fill the "gap" and decreases the counter
of elements in the log line. If the intention is to leave a "gap" in the
bag that may immediately be overwritten with an updated element, use
log_line_item_free() instead!
@param ll log_line
@param elem index of the key/value pair to release
*/
void log_line_item_remove(log_line *ll, int elem) {
assert(ll->count > 0);
log_line_item_free(ll, elem);
// Fill the gap if needed (if there are more elements and we're not the tail)
if ((ll->count > 1) && (elem < (ll->count - 1)))
ll->item[elem] = ll->item[ll->count - 1];
ll->count--;
}
/**
Find the (index of the) last key/value pair of the given name
in the log line.
@param ll log line
@param key the key to look for
@retval -1: none found
@retval -2: invalid search-key given
@retval -3: no log_line given
@retval >=0: index of the key/value pair in the log line
*/
int log_line_index_by_name(log_line *ll, const char *key) {
uint32 count = ll->count;
if (ll == nullptr) /* purecov: begin inspected */
return -3;
else if ((key == nullptr) || (key[0] == '\0'))
return -2; /* purecov: end */
/*
As later items overwrite earlier ones, return the rightmost match!
*/
while (count > 0) {
if (0 == strcmp(ll->item[--count].key, key)) return count;
}
return -1;
}
/**
Find the last item matching the given key in the log line.
@param ll log line
@param key the key to look for
@retval nullptr item not found
@retval otherwise pointer to the item (not a copy thereof!)
*/
log_item *log_line_item_by_name(log_line *ll, const char *key) {
const int i = log_line_index_by_name(ll, key);
return (i < 0) ? nullptr : &ll->item[i];
}
int log_line_index_by_type(log_line *ll, log_item_type t) {
uint32 count = ll->count;
/*
As later items overwrite earlier ones, return the rightmost match!
*/
while (count > 0) {
if (ll->item[--count].type == t) return count;
}
return -1;
}
int log_line_index_by_item(log_line *ll, log_item *ref) {
uint32 count = ll->count;
if (log_item_generic_type(ref->type)) {
while (count > 0) {
count--;
if (log_item_generic_type(ll->item[count].type) &&
(native_strcasecmp(ref->key, ll->item[count].key) == 0))
return count;
}
} else {
while (count > 0) {
if (ll->item[--count].type == ref->type) return count;
}
}
return -1;
}
/**
Initializes a log entry for use. This simply puts it in a defined
state; if you wish to reset an existing item, see log_item_free().
@param li the log-item to initialize
*/
void log_item_init(log_item *li) { memset(li, 0, sizeof(log_item)); }
/**
Initializes an entry in a log line for use. This simply puts it in
a defined state; if you wish to reset an existing item, see
log_item_free().
This resets the element beyond the last. The element count is not
adjusted; this is for the caller to do once it sets up a valid
element to suit its needs in the cleared slot. Finally, it is up
to the caller to make sure that an element can be allocated.
@param ll the log-line to initialize a log_item in
@retval the address of the cleared log_item
*/
log_item *log_line_item_init(log_line *ll) {
log_item_init(&ll->item[ll->count]);
return &ll->item[ll->count];
}
/**
Create new log item with key name "key", and allocation flags of
"alloc" (see enum_log_item_free).
Will return a pointer to the item's log_item_data struct for
convenience.
This is mostly interesting for filters and other services that create
items that are not part of a log_line; sources etc. that intend to
create an item for a log_line (the more common case) should usually
use the below line_item_set_with_key() which creates an item (like
this function does), but also correctly inserts it into a log_line.
@param li the log_item to work on
@param t the item-type
@param key the key to set on the item.
ignored for non-generic types (may pass nullptr for those)
see alloc
@param alloc LOG_ITEM_FREE_KEY if key was allocated by caller
LOG_ITEM_FREE_NONE if key was not allocated
Allocated keys will automatically free()d when the
log_item is.
The log_item's alloc flags will be set to the
submitted value; specifically, any pre-existing
value will be clobbered. It is therefore WRONG
a) to use this on a log_item that already has a key;
it should only be used on freshly init'd log_items;
b) to use this on a log_item that already has a
value (specifically, an allocated one); the correct
order is to init a log_item, then set up type and
key, and finally to set the value. If said value is
an allocated string, the log_item's alloc should be
bitwise or'd with LOG_ITEM_FREE_VALUE.
@retval a pointer to the log_item's log_data, for easy chaining:
log_item_set_with_key(...)->data_integer= 1;
*/
log_item_data *log_item_set_with_key(log_item *li, log_item_type t,
const char *key, uint32 alloc) {
const int c = log_item_wellknown_by_type(t);
li->alloc = alloc;
if (log_item_generic_type(t)) {
li->key = key;
} else {
li->key = log_item_wellknown_keys[c].name;
assert((alloc & LOG_ITEM_FREE_KEY) == 0);
}
// If we accept a C-string as input, it'll become a Lex string internally
if ((li->item_class = log_item_wellknown_keys[c].item_class) == LOG_CSTRING)
li->item_class = LOG_LEX_STRING;
li->type = t;
assert(((alloc & LOG_ITEM_FREE_VALUE) == 0) ||
(li->item_class == LOG_CSTRING) ||
(li->item_class == LOG_LEX_STRING) || (li->item_class == LOG_BUFFER));
return &li->data;
}
log_item_data *log_line_item_set_with_key(log_line *ll, log_item_type t,
const char *key, uint32 alloc) {
log_item *li;
if (log_line_full(ll)) return nullptr;
li = &(ll->item[ll->count]);
log_item_set_with_key(li, t, key, alloc);
ll->seen |= t;
ll->count++;
return &li->data;
}
/**
As log_item_set_with_key(), except that the key is automatically
derived from the wellknown log_item_type t.
Create new log item with type "t".
Will return a pointer to the item's log_item_data struct for
convenience.
This is mostly interesting for filters and other services that create
items that are not part of a log_line; sources etc. that intend to
create an item for a log_line (the more common case) should usually
use the below line_item_set_with_key() which creates an item (like
this function does), but also correctly inserts it into a log_line.
The allocation of this item will be LOG_ITEM_FREE_NONE;
specifically, any pre-existing value will be clobbered.
It is therefore WRONG
a) to use this on a log_item that already has a key;
it should only be used on freshly init'd log_items;
b) to use this on a log_item that already has a
value (specifically, an allocated one); the correct
order is to init a log_item, then set up type and
key, and finally to set the value. If said value is
an allocated string, the log_item's alloc should be
bitwise or'd with LOG_ITEM_FREE_VALUE.
@param li the log_item to work on
@param t the item-type
@retval a pointer to the log_item's log_data, for easy chaining:
log_item_set_with_key(...)->data_integer= 1;
*/
log_item_data *log_item_set(log_item *li, log_item_type t) {
return log_item_set_with_key(li, t, nullptr, LOG_ITEM_FREE_NONE);
}
log_item_data *log_line_item_set(log_line *ll, log_item_type t) {
return log_line_item_set_with_key(ll, t, nullptr, LOG_ITEM_FREE_NONE);
}
bool log_item_set_int(log_item_data *lid, longlong i) {
if (lid != nullptr) {
lid->data_integer = i;
return false;
}
return true;
}
bool log_item_set_float(log_item_data *lid, double f) {
if (lid != nullptr) {
lid->data_float = f;
return false;
}
return true;
}
/**
Set a string buffer on a log_item.
On success, the caller should change the item_class to LOG_BUFFER.
@param lid log_item_data struct to set the value on
@param s pointer to string-buffer (non-const)
@param s_len buffer-size
@retval true could not set a valid buffer
@retval false item was assigned a buffer
*/
bool log_item_set_buffer(log_item_data *lid, char *s, size_t s_len) {
if (lid != nullptr) { // if we have an item ...
lid->data_buffer.str = s; // set the buffer on it
if (s == nullptr) { // if the buffer is NULL, zero the length
lid->data_buffer.length = 0; /* purecov: begin inspected */
return true; /* purecov: end */
}
lid->data_buffer.length = s_len; // set the given buffer-size
return false; // signal success
}
// no item => failure
return true; /* purecov: inspected */
}
bool log_item_set_lexstring(log_item_data *lid, const char *s, size_t s_len) {
if (lid != nullptr) {
lid->data_string.str = (s == nullptr) ? "" : s;
lid->data_string.length = s_len;
return false;
}
return true;
}
bool log_item_set_cstring(log_item_data *lid, const char *s) {
if (lid != nullptr) {
lid->data_string.str = (s == nullptr) ? "" : s;
lid->data_string.length = strlen(lid->data_string.str);
return false;
}
return true;