39 char LogFileOutput::_pid_str[PidBufferSize]; |
39 char LogFileOutput::_pid_str[PidBufferSize]; |
40 char LogFileOutput::_vm_start_time_str[StartTimeBufferSize]; |
40 char LogFileOutput::_vm_start_time_str[StartTimeBufferSize]; |
41 |
41 |
42 LogFileOutput::LogFileOutput(const char* name) |
42 LogFileOutput::LogFileOutput(const char* name) |
43 : LogFileStreamOutput(NULL), _name(os::strdup_check_oom(name, mtLogging)), |
43 : LogFileStreamOutput(NULL), _name(os::strdup_check_oom(name, mtLogging)), |
44 _file_name(NULL), _archive_name(NULL), _archive_name_len(0), _current_size(0), |
44 _file_name(NULL), _archive_name(NULL), _archive_name_len(0), |
45 _rotate_size(0), _current_file(1), _file_count(0), _rotation_semaphore(1) { |
45 _rotate_size(DefaultFileSize), _file_count(DefaultFileCount), |
|
46 _current_size(0), _current_file(0), _rotation_semaphore(1) { |
46 _file_name = make_file_name(name, _pid_str, _vm_start_time_str); |
47 _file_name = make_file_name(name, _pid_str, _vm_start_time_str); |
47 } |
48 } |
48 |
49 |
49 void LogFileOutput::set_file_name_parameters(jlong vm_start_time) { |
50 void LogFileOutput::set_file_name_parameters(jlong vm_start_time) { |
50 int res = jio_snprintf(_pid_str, sizeof(_pid_str), "%d", os::current_process_id()); |
51 int res = jio_snprintf(_pid_str, sizeof(_pid_str), "%d", os::current_process_id()); |
57 assert(res > 0, "VM start time buffer too small."); |
58 assert(res > 0, "VM start time buffer too small."); |
58 } |
59 } |
59 |
60 |
60 LogFileOutput::~LogFileOutput() { |
61 LogFileOutput::~LogFileOutput() { |
61 if (_stream != NULL) { |
62 if (_stream != NULL) { |
62 if (_archive_name != NULL) { |
|
63 archive(); |
|
64 } |
|
65 if (fclose(_stream) != 0) { |
63 if (fclose(_stream) != 0) { |
66 jio_fprintf(defaultStream::error_stream(), "Could not close log file '%s' (%s).\n", |
64 jio_fprintf(defaultStream::error_stream(), "Could not close log file '%s' (%s).\n", |
67 _file_name, os::strerror(errno)); |
65 _file_name, os::strerror(errno)); |
68 } |
66 } |
69 } |
67 } |
70 os::free(_archive_name); |
68 os::free(_archive_name); |
71 os::free(_file_name); |
69 os::free(_file_name); |
72 os::free(const_cast<char*>(_name)); |
70 os::free(const_cast<char*>(_name)); |
73 } |
71 } |
74 |
72 |
75 size_t LogFileOutput::parse_value(const char* value_str) { |
73 static size_t parse_value(const char* value_str) { |
76 char* end; |
74 char* end; |
77 unsigned long long value = strtoull(value_str, &end, 10); |
75 unsigned long long value = strtoull(value_str, &end, 10); |
78 if (!isdigit(*value_str) || end != value_str + strlen(value_str) || value >= SIZE_MAX) { |
76 if (!isdigit(*value_str) || end != value_str + strlen(value_str) || value >= SIZE_MAX) { |
79 return SIZE_MAX; |
77 return SIZE_MAX; |
80 } |
78 } |
81 return value; |
79 return value; |
82 } |
80 } |
83 |
81 |
84 bool LogFileOutput::configure_rotation(const char* options) { |
82 static bool file_exists(const char* filename) { |
|
83 struct stat dummy_stat; |
|
84 return os::stat(filename, &dummy_stat) == 0; |
|
85 } |
|
86 |
|
87 static uint number_of_digits(uint number) { |
|
88 return number < 10 ? 1 : (number < 100 ? 2 : 3); |
|
89 } |
|
90 |
|
91 static 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. |
|
106 static 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 |
|
155 bool LogFileOutput::parse_options(const char* options, outputStream* errstream) { |
85 if (options == NULL || strlen(options) == 0) { |
156 if (options == NULL || strlen(options) == 0) { |
86 return true; |
157 return true; |
87 } |
158 } |
88 bool success = true; |
159 bool success = true; |
89 char* opts = os::strdup_check_oom(options, mtLogging); |
160 char* opts = os::strdup_check_oom(options, mtLogging); |
105 char* value_str = equals_pos + 1; |
176 char* value_str = equals_pos + 1; |
106 *equals_pos = '\0'; |
177 *equals_pos = '\0'; |
107 |
178 |
108 if (strcmp(FileCountOptionKey, key) == 0) { |
179 if (strcmp(FileCountOptionKey, key) == 0) { |
109 size_t value = parse_value(value_str); |
180 size_t value = parse_value(value_str); |
110 if (value == SIZE_MAX || value >= UINT_MAX) { |
181 if (value > MaxRotationFileCount) { |
|
182 errstream->print_cr("Invalid option: %s must be in range [0, %u]", |
|
183 FileCountOptionKey, |
|
184 MaxRotationFileCount); |
111 success = false; |
185 success = false; |
112 break; |
186 break; |
113 } |
187 } |
114 _file_count = static_cast<uint>(value); |
188 _file_count = static_cast<uint>(value); |
115 _file_count_max_digits = static_cast<uint>(log10(static_cast<double>(_file_count)) + 1); |
|
116 _archive_name_len = 2 + strlen(_file_name) + _file_count_max_digits; |
|
117 _archive_name = NEW_C_HEAP_ARRAY(char, _archive_name_len, mtLogging); |
|
118 } else if (strcmp(FileSizeOptionKey, key) == 0) { |
189 } else if (strcmp(FileSizeOptionKey, key) == 0) { |
119 size_t value = parse_value(value_str); |
190 size_t value = parse_value(value_str); |
120 if (value == SIZE_MAX || value > SIZE_MAX / K) { |
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); |
121 success = false; |
194 success = false; |
122 break; |
195 break; |
123 } |
196 } |
124 _rotate_size = value * K; |
197 _rotate_size = value * K; |
125 } else { |
198 } else { |
|
199 errstream->print_cr("Invalid option '%s' for log file output.", key); |
126 success = false; |
200 success = false; |
127 break; |
201 break; |
128 } |
202 } |
129 pos = comma_pos + 1; |
203 pos = comma_pos + 1; |
130 } while (comma_pos != NULL); |
204 } while (comma_pos != NULL); |
131 |
205 |
132 os::free(opts); |
206 os::free(opts); |
133 return success; |
207 return success; |
134 } |
208 } |
135 |
209 |
136 bool LogFileOutput::initialize(const char* options) { |
210 bool LogFileOutput::initialize(const char* options, outputStream* errstream) { |
137 if (!configure_rotation(options)) { |
211 if (!parse_options(options, errstream)) { |
138 return false; |
212 return false; |
139 } |
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 |
140 _stream = fopen(_file_name, FileOpenMode); |
246 _stream = fopen(_file_name, FileOpenMode); |
141 if (_stream == NULL) { |
247 if (_stream == NULL) { |
142 log_error(logging)("Could not open log file '%s' (%s).\n", _file_name, os::strerror(errno)); |
248 errstream->print_cr("Error opening log file '%s': %s", |
|
249 _file_name, strerror(errno)); |
143 return false; |
250 return false; |
144 } |
251 } |
|
252 |
|
253 if (_file_count == 0 && is_regular_file(_file_name)) { |
|
254 log_trace(logging)("Truncating log file"); |
|
255 os::ftruncate(os::fileno(_stream), 0); |
|
256 } |
|
257 |
145 return true; |
258 return true; |
146 } |
259 } |
147 |
260 |
148 int LogFileOutput::write(const LogDecorations& decorations, const char* msg) { |
261 int LogFileOutput::write(const LogDecorations& decorations, const char* msg) { |
149 if (_stream == NULL) { |
262 if (_stream == NULL) { |