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