logFileOutput.cpp revision 11011:d142a959d2ae
1/*
2 * Copyright (c) 2015, 2016, 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 "logging/log.hpp"
26#include "logging/logConfiguration.hpp"
27#include "logging/logFileOutput.hpp"
28#include "memory/allocation.inline.hpp"
29#include "runtime/os.inline.hpp"
30#include "utilities/globalDefinitions.hpp"
31#include "utilities/defaultStream.hpp"
32
33const char* LogFileOutput::FileOpenMode = "a";
34const char* LogFileOutput::PidFilenamePlaceholder = "%p";
35const char* LogFileOutput::TimestampFilenamePlaceholder = "%t";
36const char* LogFileOutput::TimestampFormat = "%Y-%m-%d_%H-%M-%S";
37const char* LogFileOutput::FileSizeOptionKey = "filesize";
38const char* LogFileOutput::FileCountOptionKey = "filecount";
39char        LogFileOutput::_pid_str[PidBufferSize];
40char        LogFileOutput::_vm_start_time_str[StartTimeBufferSize];
41
42LogFileOutput::LogFileOutput(const char* name)
43    : LogFileStreamOutput(NULL), _name(os::strdup_check_oom(name, mtLogging)),
44      _file_name(NULL), _archive_name(NULL), _archive_name_len(0),
45      _rotate_size(DefaultFileSize), _file_count(DefaultFileCount),
46      _current_size(0), _current_file(0), _rotation_semaphore(1) {
47  _file_name = make_file_name(name, _pid_str, _vm_start_time_str);
48}
49
50void LogFileOutput::set_file_name_parameters(jlong vm_start_time) {
51  int res = jio_snprintf(_pid_str, sizeof(_pid_str), "%d", os::current_process_id());
52  assert(res > 0, "PID buffer too small");
53
54  struct tm local_time;
55  time_t utc_time = vm_start_time / 1000;
56  os::localtime_pd(&utc_time, &local_time);
57  res = (int)strftime(_vm_start_time_str, sizeof(_vm_start_time_str), TimestampFormat, &local_time);
58  assert(res > 0, "VM start time buffer too small.");
59}
60
61LogFileOutput::~LogFileOutput() {
62  if (_stream != NULL) {
63    if (fclose(_stream) != 0) {
64      jio_fprintf(defaultStream::error_stream(), "Could not close log file '%s' (%s).\n",
65                  _file_name, os::strerror(errno));
66    }
67  }
68  os::free(_archive_name);
69  os::free(_file_name);
70  os::free(const_cast<char*>(_name));
71}
72
73static size_t parse_value(const char* value_str) {
74  char* end;
75  unsigned long long value = strtoull(value_str, &end, 10);
76  if (!isdigit(*value_str) || end != value_str + strlen(value_str) || value >= SIZE_MAX) {
77    return SIZE_MAX;
78  }
79  return value;
80}
81
82static bool file_exists(const char* filename) {
83  struct stat dummy_stat;
84  return os::stat(filename, &dummy_stat) == 0;
85}
86
87static uint number_of_digits(uint number) {
88  return number < 10 ? 1 : (number < 100 ? 2 : 3);
89}
90
91static bool is_regular_file(const char* filename) {
92  struct stat st;
93  int ret = os::stat(filename, &st);
94  if (ret != 0) {
95    return false;
96  }
97#ifdef _WINDOWS
98  return (st.st_mode & S_IFMT) == _S_IFREG;
99#else
100  return S_ISREG(st.st_mode);
101#endif
102}
103
104// Try to find the next number that should be used for file rotation.
105// Return UINT_MAX on error.
106static uint next_file_number(const char* filename,
107                             uint number_of_digits,
108                             uint filecount,
109                             outputStream* errstream) {
110  bool found = false;
111  uint next_num = 0;
112
113  // len is filename + dot + digits + null char
114  size_t len = strlen(filename) + number_of_digits + 2;
115  char* archive_name = NEW_C_HEAP_ARRAY(char, len, mtLogging);
116  char* oldest_name = NEW_C_HEAP_ARRAY(char, len, mtLogging);
117
118  for (uint i = 0; i < filecount; i++) {
119    int ret = jio_snprintf(archive_name, len, "%s.%0*u",
120                           filename, number_of_digits, i);
121    assert(ret > 0 && static_cast<size_t>(ret) == len - 1,
122           "incorrect buffer length calculation");
123
124    if (file_exists(archive_name) && !is_regular_file(archive_name)) {
125      // We've encountered something that's not a regular file among the
126      // possible file rotation targets. Fail immediately to prevent
127      // problems later.
128      errstream->print_cr("Possible rotation target file '%s' already exists "
129                          "but is not a regular file.", archive_name);
130      next_num = UINT_MAX;
131      break;
132    }
133
134    // Stop looking if we find an unused file name
135    if (!file_exists(archive_name)) {
136      next_num = i;
137      found = true;
138      break;
139    }
140
141    // Keep track of oldest existing log file
142    if (!found
143        || os::compare_file_modified_times(oldest_name, archive_name) > 0) {
144      strcpy(oldest_name, archive_name);
145      next_num = i;
146      found = true;
147    }
148  }
149
150  FREE_C_HEAP_ARRAY(char, oldest_name);
151  FREE_C_HEAP_ARRAY(char, archive_name);
152  return next_num;
153}
154
155bool LogFileOutput::parse_options(const char* options, outputStream* errstream) {
156  if (options == NULL || strlen(options) == 0) {
157    return true;
158  }
159  bool success = true;
160  char* opts = os::strdup_check_oom(options, mtLogging);
161
162  char* comma_pos;
163  char* pos = opts;
164  do {
165    comma_pos = strchr(pos, ',');
166    if (comma_pos != NULL) {
167      *comma_pos = '\0';
168    }
169
170    char* equals_pos = strchr(pos, '=');
171    if (equals_pos == NULL) {
172      success = false;
173      break;
174    }
175    char* key = pos;
176    char* value_str = equals_pos + 1;
177    *equals_pos = '\0';
178
179    if (strcmp(FileCountOptionKey, key) == 0) {
180      size_t value = parse_value(value_str);
181      if (value > MaxRotationFileCount) {
182        errstream->print_cr("Invalid option: %s must be in range [0, %u]",
183                            FileCountOptionKey,
184                            MaxRotationFileCount);
185        success = false;
186        break;
187      }
188      _file_count = static_cast<uint>(value);
189    } else if (strcmp(FileSizeOptionKey, key) == 0) {
190      size_t value = parse_value(value_str);
191      if (value == SIZE_MAX || value > SIZE_MAX / K) {
192        errstream->print_cr("Invalid option: %s must be in range [0, "
193                            SIZE_FORMAT "]", FileSizeOptionKey, SIZE_MAX / K);
194        success = false;
195        break;
196      }
197      _rotate_size = value * K;
198    } else {
199      errstream->print_cr("Invalid option '%s' for log file output.", key);
200      success = false;
201      break;
202    }
203    pos = comma_pos + 1;
204  } while (comma_pos != NULL);
205
206  os::free(opts);
207  return success;
208}
209
210bool LogFileOutput::initialize(const char* options, outputStream* errstream) {
211  if (!parse_options(options, errstream)) {
212    return false;
213  }
214
215  if (_file_count > 0) {
216    // compute digits with filecount - 1 since numbers will start from 0
217    _file_count_max_digits = number_of_digits(_file_count - 1);
218    _archive_name_len = 2 + strlen(_file_name) + _file_count_max_digits;
219    _archive_name = NEW_C_HEAP_ARRAY(char, _archive_name_len, mtLogging);
220  }
221
222  log_trace(logging)("Initializing logging to file '%s' (filecount: %u"
223                     ", filesize: " SIZE_FORMAT " KiB).",
224                     _file_name, _file_count, _rotate_size / K);
225
226  if (_file_count > 0 && file_exists(_file_name)) {
227    if (!is_regular_file(_file_name)) {
228      errstream->print_cr("Unable to log to file %s with log file rotation: "
229                          "%s is not a regular file",
230                          _file_name, _file_name);
231      return false;
232    }
233    _current_file = next_file_number(_file_name,
234                                     _file_count_max_digits,
235                                     _file_count,
236                                     errstream);
237    if (_current_file == UINT_MAX) {
238      return false;
239    }
240    log_trace(logging)("Existing log file found, saving it as '%s.%0*u'",
241                       _file_name, _file_count_max_digits, _current_file);
242    archive();
243    increment_file_count();
244  }
245
246  _stream = fopen(_file_name, FileOpenMode);
247  if (_stream == NULL) {
248    errstream->print_cr("Error opening log file '%s': %s",
249                        _file_name, strerror(errno));
250    return false;
251  }
252
253  if (_file_count == 0 && is_regular_file(_file_name)) {
254    log_trace(logging)("Truncating log file");
255    os::ftruncate(os::get_fileno(_stream), 0);
256  }
257
258  return true;
259}
260
261int LogFileOutput::write(const LogDecorations& decorations, const char* msg) {
262  if (_stream == NULL) {
263    // An error has occurred with this output, avoid writing to it.
264    return 0;
265  }
266
267  _rotation_semaphore.wait();
268  int written = LogFileStreamOutput::write(decorations, msg);
269  _current_size += written;
270
271  if (should_rotate()) {
272    rotate();
273  }
274  _rotation_semaphore.signal();
275
276  return written;
277}
278
279void LogFileOutput::archive() {
280  assert(_archive_name != NULL && _archive_name_len > 0, "Rotation must be configured before using this function.");
281  int ret = jio_snprintf(_archive_name, _archive_name_len, "%s.%0*u",
282                         _file_name, _file_count_max_digits, _current_file);
283  assert(ret >= 0, "Buffer should always be large enough");
284
285  // Attempt to remove possibly existing archived log file before we rename.
286  // Don't care if it fails, we really only care about the rename that follows.
287  remove(_archive_name);
288
289  // Rename the file from ex hotspot.log to hotspot.log.2
290  if (rename(_file_name, _archive_name) == -1) {
291    jio_fprintf(defaultStream::error_stream(), "Could not rename log file '%s' to '%s' (%s).\n",
292                _file_name, _archive_name, os::strerror(errno));
293  }
294}
295
296void LogFileOutput::force_rotate() {
297  if (_file_count == 0) {
298    // Rotation not possible
299    return;
300  }
301  _rotation_semaphore.wait();
302  rotate();
303  _rotation_semaphore.signal();
304}
305
306void LogFileOutput::rotate() {
307
308  if (fclose(_stream)) {
309    jio_fprintf(defaultStream::error_stream(), "Error closing file '%s' during log rotation (%s).\n",
310                _file_name, os::strerror(errno));
311  }
312
313  // Archive the current log file
314  archive();
315
316  // Open the active log file using the same stream as before
317  _stream = fopen(_file_name, FileOpenMode);
318  if (_stream == NULL) {
319    jio_fprintf(defaultStream::error_stream(), "Could not reopen file '%s' during log rotation (%s).\n",
320                _file_name, os::strerror(errno));
321    return;
322  }
323
324  // Reset accumulated size, increase current file counter, and check for file count wrap-around.
325  _current_size = 0;
326  increment_file_count();
327}
328
329char* LogFileOutput::make_file_name(const char* file_name,
330                                    const char* pid_string,
331                                    const char* timestamp_string) {
332  char* result = NULL;
333
334  // Lets start finding out if we have any %d and/or %t in the name.
335  // We will only replace the first occurrence of any placeholder
336  const char* pid = strstr(file_name, PidFilenamePlaceholder);
337  const char* timestamp = strstr(file_name, TimestampFilenamePlaceholder);
338
339  if (pid == NULL && timestamp == NULL) {
340    // We found no place-holders, return the simple filename
341    return os::strdup_check_oom(file_name, mtLogging);
342  }
343
344  // At least one of the place-holders were found in the file_name
345  const char* first = "";
346  size_t first_pos = SIZE_MAX;
347  size_t first_replace_len = 0;
348
349  const char* second = "";
350  size_t second_pos = SIZE_MAX;
351  size_t second_replace_len = 0;
352
353  // If we found a %p, then setup our variables accordingly
354  if (pid != NULL) {
355    if (timestamp == NULL || pid < timestamp) {
356      first = pid_string;
357      first_pos = pid - file_name;
358      first_replace_len = strlen(PidFilenamePlaceholder);
359    } else {
360      second = pid_string;
361      second_pos = pid - file_name;
362      second_replace_len = strlen(PidFilenamePlaceholder);
363    }
364  }
365
366  if (timestamp != NULL) {
367    if (pid == NULL || timestamp < pid) {
368      first = timestamp_string;
369      first_pos = timestamp - file_name;
370      first_replace_len = strlen(TimestampFilenamePlaceholder);
371    } else {
372      second = timestamp_string;
373      second_pos = timestamp - file_name;
374      second_replace_len = strlen(TimestampFilenamePlaceholder);
375    }
376  }
377
378  size_t first_len = strlen(first);
379  size_t second_len = strlen(second);
380
381  // Allocate the new buffer, size it to hold all we want to put in there +1.
382  size_t result_len =  strlen(file_name) + first_len - first_replace_len + second_len - second_replace_len;
383  result = NEW_C_HEAP_ARRAY(char, result_len + 1, mtLogging);
384
385  // Assemble the strings
386  size_t file_name_pos = 0;
387  size_t i = 0;
388  while (i < result_len) {
389    if (file_name_pos == first_pos) {
390      // We are in the range of the first placeholder
391      strcpy(result + i, first);
392      // Bump output buffer position with length of replacing string
393      i += first_len;
394      // Bump source buffer position to skip placeholder
395      file_name_pos += first_replace_len;
396    } else if (file_name_pos == second_pos) {
397      // We are in the range of the second placeholder
398      strcpy(result + i, second);
399      i += second_len;
400      file_name_pos += second_replace_len;
401    } else {
402      // Else, copy char by char of the original file
403      result[i] = file_name[file_name_pos++];
404      i++;
405    }
406  }
407  // Add terminating char
408  result[result_len] = '\0';
409  return result;
410}
411