1 /*
2 * Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 *
23 */
24 #include "precompiled.hpp"
25 #include "jvm.h"
26 #include "logging/log.hpp"
27 #include "logging/logConfiguration.hpp"
28 #include "logging/logDecorations.hpp"
29 #include "logging/logDecorators.hpp"
30 #include "logging/logDiagnosticCommand.hpp"
31 #include "logging/logFileOutput.hpp"
32 #include "logging/logOutput.hpp"
33 #include "logging/logSelectionList.hpp"
34 #include "logging/logStream.hpp"
35 #include "logging/logTagSet.hpp"
36 #include "memory/allocation.inline.hpp"
37 #include "memory/resourceArea.hpp"
38 #include "runtime/os.inline.hpp"
39 #include "runtime/semaphore.hpp"
40 #include "utilities/globalDefinitions.hpp"
41
42 LogOutput** LogConfiguration::_outputs = NULL;
43 size_t LogConfiguration::_n_outputs = 0;
44
45 LogConfiguration::UpdateListenerFunction* LogConfiguration::_listener_callbacks = NULL;
46 size_t LogConfiguration::_n_listener_callbacks = 0;
47
48 // LogFileOutput is the default type of output, its type prefix should be used if no type was specified
49 static const char* implicit_output_prefix = LogFileOutput::Prefix;
50
51 // Stack object to take the lock for configuring the logging.
52 // Should only be held during the critical parts of the configuration
53 // (when calling configure_output or reading/modifying the outputs array).
54 // Thread must never block when holding this lock.
55 class ConfigurationLock : public StackObj {
56 private:
57 // Semaphore used as lock
58 static Semaphore _semaphore;
59 debug_only(static intx _locking_thread_id;)
60 public:
ConfigurationLock()61 ConfigurationLock() {
62 _semaphore.wait();
63 debug_only(_locking_thread_id = os::current_thread_id());
64 }
~ConfigurationLock()65 ~ConfigurationLock() {
66 debug_only(_locking_thread_id = -1);
67 _semaphore.signal();
68 }
69 debug_only(static bool current_thread_has_lock();)
70 };
71
72 Semaphore ConfigurationLock::_semaphore(1);
73 #ifdef ASSERT
74 intx ConfigurationLock::_locking_thread_id = -1;
current_thread_has_lock()75 bool ConfigurationLock::current_thread_has_lock() {
76 return _locking_thread_id == os::current_thread_id();
77 }
78 #endif
79
post_initialize()80 void LogConfiguration::post_initialize() {
81 // Reset the reconfigured status of all outputs
82 for (size_t i = 0; i < _n_outputs; i++) {
83 _outputs[i]->_reconfigured = false;
84 }
85
86 LogDiagnosticCommand::registerCommand();
87 Log(logging) log;
88 if (log.is_info()) {
89 log.info("Log configuration fully initialized.");
90 log_develop_info(logging)("Develop logging is available.");
91
92 LogStream info_stream(log.info());
93 describe_available(&info_stream);
94
95 LogStream debug_stream(log.debug());
96 LogTagSet::list_all_tagsets(&debug_stream);
97
98 ConfigurationLock cl;
99 describe_current_configuration(&info_stream);
100 }
101 }
102
initialize(jlong vm_start_time)103 void LogConfiguration::initialize(jlong vm_start_time) {
104 LogFileOutput::set_file_name_parameters(vm_start_time);
105 assert(_outputs == NULL, "Should not initialize _outputs before this function, initialize called twice?");
106 _outputs = NEW_C_HEAP_ARRAY(LogOutput*, 2, mtLogging);
107 _outputs[0] = &StdoutLog;
108 _outputs[1] = &StderrLog;
109 _n_outputs = 2;
110 }
111
finalize()112 void LogConfiguration::finalize() {
113 disable_outputs();
114 FREE_C_HEAP_ARRAY(LogOutput*, _outputs);
115 }
116
117 // Normalizes the given LogOutput name to type=name form.
118 // For example, foo, "foo", file="foo", will all be normalized to file=foo (no quotes, prefixed).
normalize_output_name(const char * full_name,char * buffer,size_t len,outputStream * errstream)119 static bool normalize_output_name(const char* full_name, char* buffer, size_t len, outputStream* errstream) {
120 const char* start_quote = strchr(full_name, '"');
121 const char* equals = strchr(full_name, '=');
122 const bool quoted = start_quote != NULL;
123 const bool is_stdout_or_stderr = (strcmp(full_name, "stdout") == 0 || strcmp(full_name, "stderr") == 0);
124
125 // ignore equals sign within quotes
126 if (quoted && equals > start_quote) {
127 equals = NULL;
128 }
129
130 const char* prefix = "";
131 size_t prefix_len = 0;
132 const char* name = full_name;
133 if (equals != NULL) {
134 // split on equals sign
135 name = equals + 1;
136 prefix = full_name;
137 prefix_len = equals - full_name + 1;
138 } else if (!is_stdout_or_stderr) {
139 prefix = implicit_output_prefix;
140 prefix_len = strlen(prefix);
141 }
142 size_t name_len = strlen(name);
143
144 if (quoted) {
145 const char* end_quote = strchr(start_quote + 1, '"');
146 if (end_quote == NULL) {
147 errstream->print_cr("Output name has opening quote but is missing a terminating quote.");
148 return false;
149 }
150 if (start_quote != name || end_quote[1] != '\0') {
151 errstream->print_cr("Output name can not be partially quoted."
152 " Either surround the whole name with quotation marks,"
153 " or do not use quotation marks at all.");
154 return false;
155 }
156 // strip start and end quote
157 name++;
158 name_len -= 2;
159 }
160
161 int ret = jio_snprintf(buffer, len, "%.*s%.*s", prefix_len, prefix, name_len, name);
162 assert(ret > 0, "buffer issue");
163 return true;
164 }
165
find_output(const char * name)166 size_t LogConfiguration::find_output(const char* name) {
167 for (size_t i = 0; i < _n_outputs; i++) {
168 if (strcmp(_outputs[i]->name(), name) == 0) {
169 return i;
170 }
171 }
172 return SIZE_MAX;
173 }
174
new_output(const char * name,const char * options,outputStream * errstream)175 LogOutput* LogConfiguration::new_output(const char* name,
176 const char* options,
177 outputStream* errstream) {
178 LogOutput* output;
179 if (strncmp(name, LogFileOutput::Prefix, strlen(LogFileOutput::Prefix)) == 0) {
180 output = new LogFileOutput(name);
181 } else {
182 errstream->print_cr("Unsupported log output type: %s", name);
183 return NULL;
184 }
185
186 bool success = output->initialize(options, errstream);
187 if (!success) {
188 errstream->print_cr("Initialization of output '%s' using options '%s' failed.", name, options);
189 delete output;
190 return NULL;
191 }
192 return output;
193 }
194
add_output(LogOutput * output)195 size_t LogConfiguration::add_output(LogOutput* output) {
196 size_t idx = _n_outputs++;
197 _outputs = REALLOC_C_HEAP_ARRAY(LogOutput*, _outputs, _n_outputs, mtLogging);
198 _outputs[idx] = output;
199 return idx;
200 }
201
delete_output(size_t idx)202 void LogConfiguration::delete_output(size_t idx) {
203 assert(idx > 1 && idx < _n_outputs,
204 "idx must be in range 1 < idx < _n_outputs, but idx = " SIZE_FORMAT
205 " and _n_outputs = " SIZE_FORMAT, idx, _n_outputs);
206 LogOutput* output = _outputs[idx];
207 // Swap places with the last output and shrink the array
208 _outputs[idx] = _outputs[--_n_outputs];
209 _outputs = REALLOC_C_HEAP_ARRAY(LogOutput*, _outputs, _n_outputs, mtLogging);
210 delete output;
211 }
212
configure_output(size_t idx,const LogSelectionList & selections,const LogDecorators & decorators)213 void LogConfiguration::configure_output(size_t idx, const LogSelectionList& selections, const LogDecorators& decorators) {
214 assert(ConfigurationLock::current_thread_has_lock(), "Must hold configuration lock to call this function.");
215 assert(idx < _n_outputs, "Invalid index, idx = " SIZE_FORMAT " and _n_outputs = " SIZE_FORMAT, idx, _n_outputs);
216 LogOutput* output = _outputs[idx];
217
218 output->_reconfigured = true;
219
220 size_t on_level[LogLevel::Count] = {0};
221
222 bool enabled = false;
223 for (LogTagSet* ts = LogTagSet::first(); ts != NULL; ts = ts->next()) {
224 LogLevelType level = selections.level_for(*ts);
225
226 // Ignore tagsets that do not, and will not log on the output
227 if (!ts->has_output(output) && (level == LogLevel::NotMentioned || level == LogLevel::Off)) {
228 on_level[LogLevel::Off]++;
229 continue;
230 }
231
232 // Update decorators before adding/updating output level,
233 // so that the tagset will have the necessary decorators when requiring them.
234 if (level != LogLevel::Off) {
235 ts->update_decorators(decorators);
236 }
237
238 // Set the new level, if it changed
239 if (level != LogLevel::NotMentioned) {
240 ts->set_output_level(output, level);
241 } else {
242 // Look up the previously set level for this output on this tagset
243 level = ts->level_for(output);
244 }
245
246 if (level != LogLevel::Off) {
247 // Keep track of whether or not the output is ever used by some tagset
248 enabled = true;
249 }
250
251 // Track of the number of tag sets on each level
252 on_level[level]++;
253 }
254
255 // It is now safe to set the new decorators for the actual output
256 output->set_decorators(decorators);
257
258 // Update the decorators on all tagsets to get rid of unused decorators
259 for (LogTagSet* ts = LogTagSet::first(); ts != NULL; ts = ts->next()) {
260 ts->update_decorators();
261 }
262
263 if (!enabled && idx > 1) {
264 // Output is unused and should be removed, unless it is stdout/stderr (idx < 2)
265 delete_output(idx);
266 return;
267 }
268
269 output->update_config_string(on_level);
270 assert(strlen(output->config_string()) > 0, "should always have a config description");
271 }
272
disable_outputs()273 void LogConfiguration::disable_outputs() {
274 size_t idx = _n_outputs;
275
276 // Remove all outputs from all tagsets.
277 for (LogTagSet* ts = LogTagSet::first(); ts != NULL; ts = ts->next()) {
278 ts->disable_outputs();
279 }
280
281 while (idx > 0) {
282 LogOutput* out = _outputs[--idx];
283 // Delete the output unless stdout or stderr (idx 0 or 1)
284 if (idx > 1) {
285 delete_output(idx);
286 } else {
287 out->set_config_string("all=off");
288 }
289 }
290 }
291
disable_logging()292 void LogConfiguration::disable_logging() {
293 ConfigurationLock cl;
294 disable_outputs();
295 // Update the decorators on all tagsets to get rid of unused decorators
296 for (LogTagSet* ts = LogTagSet::first(); ts != NULL; ts = ts->next()) {
297 ts->update_decorators();
298 }
299 notify_update_listeners();
300 }
301
configure_stdout(LogLevelType level,int exact_match,...)302 void LogConfiguration::configure_stdout(LogLevelType level, int exact_match, ...) {
303 size_t i;
304 va_list ap;
305 LogTagType tags[LogTag::MaxTags];
306 va_start(ap, exact_match);
307 for (i = 0; i < LogTag::MaxTags; i++) {
308 LogTagType tag = static_cast<LogTagType>(va_arg(ap, int));
309 tags[i] = tag;
310 if (tag == LogTag::__NO_TAG) {
311 assert(i > 0, "Must specify at least one tag!");
312 break;
313 }
314 }
315 assert(i < LogTag::MaxTags || static_cast<LogTagType>(va_arg(ap, int)) == LogTag::__NO_TAG,
316 "Too many tags specified! Can only have up to " SIZE_FORMAT " tags in a tag set.", LogTag::MaxTags);
317 va_end(ap);
318
319 LogSelection selection(tags, !exact_match, level);
320 assert(selection.tag_sets_selected() > 0,
321 "configure_stdout() called with invalid/non-existing log selection");
322 LogSelectionList list(selection);
323
324 // Apply configuration to stdout (output #0), with the same decorators as before.
325 ConfigurationLock cl;
326 configure_output(0, list, _outputs[0]->decorators());
327 notify_update_listeners();
328 }
329
parse_command_line_arguments(const char * opts)330 bool LogConfiguration::parse_command_line_arguments(const char* opts) {
331 char* copy = os::strdup_check_oom(opts, mtLogging);
332
333 // Split the option string to its colon separated components.
334 char* str = copy;
335 char* substrings[4] = {0};
336 for (int i = 0 ; i < 4; i++) {
337 substrings[i] = str;
338
339 // Find the next colon or quote
340 char* next = strpbrk(str, ":\"");
341 #ifdef _WINDOWS
342 // Skip over Windows paths such as "C:\..."
343 // Handle both C:\... and file=C:\..."
344 if (next != NULL && next[0] == ':' && next[1] == '\\') {
345 if (next == str + 1 || (strncmp(str, "file=", 5) == 0)) {
346 next = strpbrk(next + 1, ":\"");
347 }
348 }
349 #endif
350 while (next != NULL && *next == '"') {
351 char* end_quote = strchr(next + 1, '"');
352 if (end_quote == NULL) {
353 log_error(logging)("Missing terminating quote in -Xlog option '%s'", str);
354 os::free(copy);
355 return false;
356 }
357 // Keep searching after the quoted substring
358 next = strpbrk(end_quote + 1, ":\"");
359 }
360
361 if (next != NULL) {
362 *next = '\0';
363 str = next + 1;
364 } else {
365 str = NULL;
366 break;
367 }
368 }
369
370 if (str != NULL) {
371 log_warning(logging)("Ignoring excess -Xlog options: \"%s\"", str);
372 }
373
374 // Parse and apply the separated configuration options
375 char* what = substrings[0];
376 char* output = substrings[1];
377 char* decorators = substrings[2];
378 char* output_options = substrings[3];
379 char errbuf[512];
380 stringStream ss(errbuf, sizeof(errbuf));
381 bool success = parse_log_arguments(output, what, decorators, output_options, &ss);
382
383 if (ss.size() > 0) {
384 // If it failed, log the error. If it didn't fail, but something was written
385 // to the stream, log it as a warning.
386 LogLevelType level = success ? LogLevel::Warning : LogLevel::Error;
387
388 Log(logging) log;
389 char* start = errbuf;
390 char* end = strchr(start, '\n');
391 assert(end != NULL, "line must end with newline '%s'", start);
392 do {
393 assert(start < errbuf + sizeof(errbuf) &&
394 end < errbuf + sizeof(errbuf),
395 "buffer overflow");
396 *end = '\0';
397 log.write(level, "%s", start);
398 start = end + 1;
399 end = strchr(start, '\n');
400 assert(end != NULL || *start == '\0', "line must end with newline '%s'", start);
401 } while (end != NULL);
402 }
403
404 os::free(copy);
405 return success;
406 }
407
parse_log_arguments(const char * outputstr,const char * selectionstr,const char * decoratorstr,const char * output_options,outputStream * errstream)408 bool LogConfiguration::parse_log_arguments(const char* outputstr,
409 const char* selectionstr,
410 const char* decoratorstr,
411 const char* output_options,
412 outputStream* errstream) {
413 assert(errstream != NULL, "errstream can not be NULL");
414 if (outputstr == NULL || strlen(outputstr) == 0) {
415 outputstr = "stdout";
416 }
417
418 LogSelectionList selections;
419 if (!selections.parse(selectionstr, errstream)) {
420 return false;
421 }
422
423 LogDecorators decorators;
424 if (!decorators.parse(decoratorstr, errstream)) {
425 return false;
426 }
427
428 ConfigurationLock cl;
429 size_t idx;
430 if (outputstr[0] == '#') { // Output specified using index
431 int ret = sscanf(outputstr + 1, SIZE_FORMAT, &idx);
432 if (ret != 1 || idx >= _n_outputs) {
433 errstream->print_cr("Invalid output index '%s'", outputstr);
434 return false;
435 }
436 } else { // Output specified using name
437 // Normalize the name, stripping quotes and ensures it includes type prefix
438 size_t len = strlen(outputstr) + strlen(implicit_output_prefix) + 1;
439 char* normalized = NEW_C_HEAP_ARRAY(char, len, mtLogging);
440 if (!normalize_output_name(outputstr, normalized, len, errstream)) {
441 return false;
442 }
443
444 idx = find_output(normalized);
445 if (idx == SIZE_MAX) {
446 // Attempt to create and add the output
447 LogOutput* output = new_output(normalized, output_options, errstream);
448 if (output != NULL) {
449 idx = add_output(output);
450 }
451 } else if (output_options != NULL && strlen(output_options) > 0) {
452 errstream->print_cr("Output options for existing outputs are ignored.");
453 }
454
455 FREE_C_HEAP_ARRAY(char, normalized);
456 if (idx == SIZE_MAX) {
457 return false;
458 }
459 }
460 configure_output(idx, selections, decorators);
461 notify_update_listeners();
462 selections.verify_selections(errstream);
463 return true;
464 }
465
describe_available(outputStream * out)466 void LogConfiguration::describe_available(outputStream* out) {
467 out->print("Available log levels:");
468 for (size_t i = 0; i < LogLevel::Count; i++) {
469 out->print("%s %s", (i == 0 ? "" : ","), LogLevel::name(static_cast<LogLevelType>(i)));
470 }
471 out->cr();
472
473 out->print("Available log decorators:");
474 for (size_t i = 0; i < LogDecorators::Count; i++) {
475 LogDecorators::Decorator d = static_cast<LogDecorators::Decorator>(i);
476 out->print("%s %s (%s)", (i == 0 ? "" : ","), LogDecorators::name(d), LogDecorators::abbreviation(d));
477 }
478 out->cr();
479
480 out->print("Available log tags:");
481 LogTag::list_tags(out);
482
483 LogTagSet::describe_tagsets(out);
484 }
485
describe_current_configuration(outputStream * out)486 void LogConfiguration::describe_current_configuration(outputStream* out) {
487 out->print_cr("Log output configuration:");
488 for (size_t i = 0; i < _n_outputs; i++) {
489 out->print(" #" SIZE_FORMAT ": ", i);
490 _outputs[i]->describe(out);
491 if (_outputs[i]->is_reconfigured()) {
492 out->print(" (reconfigured)");
493 }
494 out->cr();
495 }
496 }
497
describe(outputStream * out)498 void LogConfiguration::describe(outputStream* out) {
499 describe_available(out);
500 ConfigurationLock cl;
501 describe_current_configuration(out);
502 }
503
print_command_line_help(outputStream * out)504 void LogConfiguration::print_command_line_help(outputStream* out) {
505 out->print_cr("-Xlog Usage: -Xlog[:[selections][:[output][:[decorators][:output-options]]]]");
506 out->print_cr("\t where 'selections' are combinations of tags and levels of the form tag1[+tag2...][*][=level][,...]");
507 out->print_cr("\t NOTE: Unless wildcard (*) is specified, only log messages tagged with exactly the tags specified will be matched.");
508 out->cr();
509
510 out->print_cr("Available log levels:");
511 for (size_t i = 0; i < LogLevel::Count; i++) {
512 out->print("%s %s", (i == 0 ? "" : ","), LogLevel::name(static_cast<LogLevelType>(i)));
513 }
514 out->cr();
515 out->cr();
516
517 out->print_cr("Available log decorators: ");
518 for (size_t i = 0; i < LogDecorators::Count; i++) {
519 LogDecorators::Decorator d = static_cast<LogDecorators::Decorator>(i);
520 out->print("%s %s (%s)", (i == 0 ? "" : ","), LogDecorators::name(d), LogDecorators::abbreviation(d));
521 }
522 out->cr();
523 out->print_cr(" Decorators can also be specified as 'none' for no decoration.");
524 out->cr();
525
526 out->print_cr("Available log tags:");
527 LogTag::list_tags(out);
528 out->print_cr(" Specifying 'all' instead of a tag combination matches all tag combinations.");
529 out->cr();
530
531 LogTagSet::describe_tagsets(out);
532
533 out->print_cr("\nAvailable log outputs:");
534 out->print_cr(" stdout/stderr");
535 out->print_cr(" file=<filename>");
536 out->print_cr(" If the filename contains %%p and/or %%t, they will expand to the JVM's PID and startup timestamp, respectively.");
537 out->print_cr(" Additional output-options for file outputs:");
538 out->print_cr(" filesize=.. - Target byte size for log rotation (supports K/M/G suffix)."
539 " If set to 0, log rotation will not trigger automatically,"
540 " but can be performed manually (see the VM.log DCMD).");
541 out->print_cr(" filecount=.. - Number of files to keep in rotation (not counting the active file)."
542 " If set to 0, log rotation is disabled."
543 " This will cause existing log files to be overwritten.");
544 out->cr();
545
546 out->print_cr("Some examples:");
547 out->print_cr(" -Xlog");
548 out->print_cr("\t Log all messages up to 'info' level to stdout with 'uptime', 'levels' and 'tags' decorations.");
549 out->print_cr("\t (Equivalent to -Xlog:all=info:stdout:uptime,levels,tags).");
550 out->cr();
551
552 out->print_cr(" -Xlog:gc");
553 out->print_cr("\t Log messages tagged with 'gc' tag up to 'info' level to stdout, with default decorations.");
554 out->cr();
555
556 out->print_cr(" -Xlog:gc,safepoint");
557 out->print_cr("\t Log messages tagged either with 'gc' or 'safepoint' tags, both up to 'info' level, to stdout, with default decorations.");
558 out->print_cr("\t (Messages tagged with both 'gc' and 'safepoint' will not be logged.)");
559 out->cr();
560
561 out->print_cr(" -Xlog:gc+ref=debug");
562 out->print_cr("\t Log messages tagged with both 'gc' and 'ref' tags, up to 'debug' level, to stdout, with default decorations.");
563 out->print_cr("\t (Messages tagged only with one of the two tags will not be logged.)");
564 out->cr();
565
566 out->print_cr(" -Xlog:gc=debug:file=gc.txt:none");
567 out->print_cr("\t Log messages tagged with 'gc' tag up to 'debug' level to file 'gc.txt' with no decorations.");
568 out->cr();
569
570 out->print_cr(" -Xlog:gc=trace:file=gctrace.txt:uptimemillis,pid:filecount=5,filesize=1m");
571 out->print_cr("\t Log messages tagged with 'gc' tag up to 'trace' level to a rotating fileset of 5 files of size 1MB,");
572 out->print_cr("\t using the base name 'gctrace.txt', with 'uptimemillis' and 'pid' decorations.");
573 out->cr();
574
575 out->print_cr(" -Xlog:gc::uptime,tid");
576 out->print_cr("\t Log messages tagged with 'gc' tag up to 'info' level to output 'stdout', using 'uptime' and 'tid' decorations.");
577 out->cr();
578
579 out->print_cr(" -Xlog:gc*=info,safepoint*=off");
580 out->print_cr("\t Log messages tagged with at least 'gc' up to 'info' level, but turn off logging of messages tagged with 'safepoint'.");
581 out->print_cr("\t (Messages tagged with both 'gc' and 'safepoint' will not be logged.)");
582 out->cr();
583
584 out->print_cr(" -Xlog:disable -Xlog:safepoint=trace:safepointtrace.txt");
585 out->print_cr("\t Turn off all logging, including warnings and errors,");
586 out->print_cr("\t and then enable messages tagged with 'safepoint' up to 'trace' level to file 'safepointtrace.txt'.");
587 }
588
rotate_all_outputs()589 void LogConfiguration::rotate_all_outputs() {
590 // Start from index 2 since neither stdout nor stderr can be rotated.
591 for (size_t idx = 2; idx < _n_outputs; idx++) {
592 _outputs[idx]->force_rotate();
593 }
594 }
595
register_update_listener(UpdateListenerFunction cb)596 void LogConfiguration::register_update_listener(UpdateListenerFunction cb) {
597 assert(cb != NULL, "Should not register NULL as listener");
598 ConfigurationLock cl;
599 size_t idx = _n_listener_callbacks++;
600 _listener_callbacks = REALLOC_C_HEAP_ARRAY(UpdateListenerFunction,
601 _listener_callbacks,
602 _n_listener_callbacks,
603 mtLogging);
604 _listener_callbacks[idx] = cb;
605 }
606
notify_update_listeners()607 void LogConfiguration::notify_update_listeners() {
608 assert(ConfigurationLock::current_thread_has_lock(), "notify_update_listeners must be called in ConfigurationLock scope (lock held)");
609 for (size_t i = 0; i < _n_listener_callbacks; i++) {
610 _listener_callbacks[i]();
611 }
612 }
613