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
33 const char* LogFileOutput::FileOpenMode = "a";
34 const char* LogFileOutput::PidFilenamePlaceholder = "%p";
35 const char* LogFileOutput::TimestampFilenamePlaceholder = "%t";
36 const char* LogFileOutput::TimestampFormat = "%Y-%m-%d_%H-%M-%S";
37 const char* LogFileOutput::FileSizeOptionKey = "filesize";
38 const char* LogFileOutput::FileCountOptionKey = "filecount";
39 char LogFileOutput::_pid_str[PidBufferSize];
40 char LogFileOutput::_vm_start_time_str[StartTimeBufferSize];
41
42 LogFileOutput::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), _current_size(0),
45 _rotate_size(0), _current_file(1), _file_count(0), _rotation_semaphore(1) {
46 _file_name = make_file_name(name, _pid_str, _vm_start_time_str);
47 }
48
49 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 assert(res > 0, "PID buffer too small");
52
53 struct tm local_time;
54 time_t utc_time = vm_start_time / 1000;
55 os::localtime_pd(&utc_time, &local_time);
56 res = (int)strftime(_vm_start_time_str, sizeof(_vm_start_time_str), TimestampFormat, &local_time);
57 assert(res > 0, "VM start time buffer too small.");
58 }
59
60 LogFileOutput::~LogFileOutput() {
61 if (_stream != NULL) {
62 if (_archive_name != NULL) {
63 archive();
64 }
65 if (fclose(_stream) != 0) {
66 jio_fprintf(defaultStream::error_stream(), "Could not close log file '%s' (%s).\n",
67 _file_name, strerror(errno));
68 }
69 }
70 os::free(_archive_name);
71 os::free(_file_name);
72 os::free(const_cast<char*>(_name));
73 }
74
75 size_t LogFileOutput::parse_value(const char* value_str) {
76 char* end;
77 unsigned long long value = strtoull(value_str, &end, 10);
78 if (!isdigit(*value_str) || end != value_str + strlen(value_str) || value >= SIZE_MAX) {
79 return SIZE_MAX;
80 }
81 return value;
82 }
83
84 bool LogFileOutput::configure_rotation(const char* options) {
85 if (options == NULL || strlen(options) == 0) {
86 return true;
87 }
88 bool success = true;
89 char* opts = os::strdup_check_oom(options, mtLogging);
90
91 char* comma_pos;
92 char* pos = opts;
93 do {
94 comma_pos = strchr(pos, ',');
95 if (comma_pos != NULL) {
96 *comma_pos = '\0';
97 }
98
99 char* equals_pos = strchr(pos, '=');
100 if (equals_pos == NULL) {
101 success = false;
102 break;
103 }
104 char* key = pos;
105 char* value_str = equals_pos + 1;
106 *equals_pos = '\0';
107
108 if (strcmp(FileCountOptionKey, key) == 0) {
109 size_t value = parse_value(value_str);
110 if (value == SIZE_MAX || value >= UINT_MAX) {
111 success = false;
112 break;
113 }
114 _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) {
119 size_t value = parse_value(value_str);
120 if (value == SIZE_MAX || value > SIZE_MAX / K) {
121 success = false;
122 break;
123 }
124 _rotate_size = value * K;
125 } else {
126 success = false;
127 break;
128 }
129 pos = comma_pos + 1;
130 } while (comma_pos != NULL);
131
132 os::free(opts);
133 return success;
134 }
135
136 bool LogFileOutput::initialize(const char* options) {
137 if (!configure_rotation(options)) {
138 return false;
139 }
140 _stream = fopen(_file_name, FileOpenMode);
141 if (_stream == NULL) {
142 log_error(logging)("Could not open log file '%s' (%s).\n", _file_name, strerror(errno));
143 return false;
144 }
145 return true;
146 }
147
148 int LogFileOutput::write(const LogDecorations& decorations, const char* msg) {
149 if (_stream == NULL) {
150 // An error has occurred with this output, avoid writing to it.
151 return 0;
152 }
153
154 _rotation_semaphore.wait();
155 int written = LogFileStreamOutput::write(decorations, msg);
156 _current_size += written;
157
158 if (should_rotate()) {
159 rotate();
160 }
161 _rotation_semaphore.signal();
162
163 return written;
164 }
193 void LogFileOutput::rotate() {
194
195 if (fclose(_stream)) {
196 jio_fprintf(defaultStream::error_stream(), "Error closing file '%s' during log rotation (%s).\n",
197 _file_name, strerror(errno));
198 }
199
200 // Archive the current log file
201 archive();
202
203 // Open the active log file using the same stream as before
204 _stream = fopen(_file_name, FileOpenMode);
205 if (_stream == NULL) {
206 jio_fprintf(defaultStream::error_stream(), "Could not reopen file '%s' during log rotation (%s).\n",
207 _file_name, strerror(errno));
208 return;
209 }
210
211 // Reset accumulated size, increase current file counter, and check for file count wrap-around.
212 _current_size = 0;
213 _current_file = (_current_file >= _file_count ? 1 : _current_file + 1);
214 }
215
216 char* LogFileOutput::make_file_name(const char* file_name,
217 const char* pid_string,
218 const char* timestamp_string) {
219 char* result = NULL;
220
221 // Lets start finding out if we have any %d and/or %t in the name.
222 // We will only replace the first occurrence of any placeholder
223 const char* pid = strstr(file_name, PidFilenamePlaceholder);
224 const char* timestamp = strstr(file_name, TimestampFilenamePlaceholder);
225
226 if (pid == NULL && timestamp == NULL) {
227 // We found no place-holders, return the simple filename
228 return os::strdup_check_oom(file_name, mtLogging);
229 }
230
231 // At least one of the place-holders were found in the file_name
232 const char* first = "";
233 size_t first_pos = SIZE_MAX;
|
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
33 const char* LogFileOutput::FileOpenMode = "a";
34 const char* LogFileOutput::PidFilenamePlaceholder = "%p";
35 const char* LogFileOutput::TimestampFilenamePlaceholder = "%t";
36 const char* LogFileOutput::TimestampFormat = "%Y-%m-%d_%H-%M-%S";
37 const char* LogFileOutput::FileSizeOptionKey = "filesize";
38 const char* LogFileOutput::FileCountOptionKey = "filecount";
39 char LogFileOutput::_pid_str[PidBufferSize];
40 char LogFileOutput::_vm_start_time_str[StartTimeBufferSize];
41
42 LogFileOutput::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(DefaultRotationFileSize), _file_count(DefaultRotationFileCount),
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
50 void 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
61 LogFileOutput::~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, strerror(errno));
66 }
67 }
68 os::free(_archive_name);
69 os::free(_file_name);
70 os::free(const_cast<char*>(_name));
71 }
72
73 static 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
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 double d = static_cast<double>(number);
89 uint res = 1 + log10(d);
90 return res;
91 }
92
93 static uint next_file_number(const char* filename,
94 uint number_of_digits,
95 uint filecount) {
96 bool found = false;
97 uint next_num = 0;
98
99 // len is filename + dot + digits + null char
100 size_t len = strlen(filename) + number_of_digits + 2;
101 char* archive_name = NEW_C_HEAP_ARRAY(char, len, mtLogging);
102 char* oldest_name = NEW_C_HEAP_ARRAY(char, len, mtLogging);
103
104 for (uint i = 0; i < filecount; i++) {
105 int ret = jio_snprintf(archive_name, len, "%s.%0*u",
106 filename, number_of_digits, i);
107 assert(ret > 0 && static_cast<size_t>(ret) == len - 1,
108 "incorrect buffer length calculation");
109
110 // Stop looking if we find an unused file name
111 if (!file_exists(archive_name)) {
112 next_num = i;
113 found = true;
114 break;
115 }
116
117 // Keep track of oldest existing log file
118 if (!found
119 || os::compare_file_modified_times(oldest_name, archive_name) > 0) {
120 strcpy(oldest_name, archive_name);
121 next_num = i;
122 found = true;
123 }
124 }
125
126 FREE_C_HEAP_ARRAY(char, oldest_name);
127 FREE_C_HEAP_ARRAY(char, archive_name);
128 assert(found, "should always find a next number");
129 return next_num;
130 }
131
132 bool LogFileOutput::configure(const char* options) {
133 if (options == NULL || strlen(options) == 0) {
134 return true;
135 }
136 bool success = true;
137 char* opts = os::strdup_check_oom(options, mtLogging);
138
139 char* comma_pos;
140 char* pos = opts;
141 do {
142 comma_pos = strchr(pos, ',');
143 if (comma_pos != NULL) {
144 *comma_pos = '\0';
145 }
146
147 char* equals_pos = strchr(pos, '=');
148 if (equals_pos == NULL) {
149 success = false;
150 break;
151 }
152 char* key = pos;
153 char* value_str = equals_pos + 1;
154 *equals_pos = '\0';
155
156 if (strcmp(FileCountOptionKey, key) == 0) {
157 size_t value = parse_value(value_str);
158 if (value == SIZE_MAX || value >= UINT_MAX) {
159 success = false;
160 break;
161 }
162 _file_count = static_cast<uint>(value);
163 } else if (strcmp(FileSizeOptionKey, key) == 0) {
164 size_t value = parse_value(value_str);
165 if (value == SIZE_MAX || value > SIZE_MAX / K) {
166 success = false;
167 break;
168 }
169 _rotate_size = value * K;
170 } else {
171 success = false;
172 break;
173 }
174 pos = comma_pos + 1;
175 } while (comma_pos != NULL);
176
177 os::free(opts);
178 return success;
179 }
180
181 bool LogFileOutput::initialize(const char* options) {
182 if (!configure(options)) {
183 return false;
184 }
185
186 if (_file_count > 0) {
187 _file_count_max_digits = number_of_digits(_file_count - 1); // -1 for 0-index
188 _archive_name_len = 2 + strlen(_file_name) + _file_count_max_digits;
189 _archive_name = NEW_C_HEAP_ARRAY(char, _archive_name_len, mtLogging);
190 }
191
192 log_trace(logging)("Initializing logging to file '%s' (filecount: %u"
193 ", filesize: " SIZE_FORMAT " KiB).",
194 _file_name, _file_count, _rotate_size / K);
195
196 if (_file_count > 0 && file_exists(_file_name)) {
197 _current_file = next_file_number(_file_name,
198 _file_count_max_digits,
199 _file_count);
200 log_trace(logging)("Existing log file found, saving it as '%s.%0*u'.",
201 _file_name, _file_count_max_digits, _current_file);
202 archive();
203 }
204
205 _stream = fopen(_file_name, FileOpenMode);
206 if (_stream == NULL) {
207 log_error(logging)("Could not open log file '%s' (%s).", _file_name, strerror(errno));
208 return false;
209 }
210
211 if (_file_count == 0) {
212 log_trace(logging)("Truncating log file");
213 os::ftruncate(os::fileno(_stream), 0);
214 }
215
216 return true;
217 }
218
219 int LogFileOutput::write(const LogDecorations& decorations, const char* msg) {
220 if (_stream == NULL) {
221 // An error has occurred with this output, avoid writing to it.
222 return 0;
223 }
224
225 _rotation_semaphore.wait();
226 int written = LogFileStreamOutput::write(decorations, msg);
227 _current_size += written;
228
229 if (should_rotate()) {
230 rotate();
231 }
232 _rotation_semaphore.signal();
233
234 return written;
235 }
264 void LogFileOutput::rotate() {
265
266 if (fclose(_stream)) {
267 jio_fprintf(defaultStream::error_stream(), "Error closing file '%s' during log rotation (%s).\n",
268 _file_name, strerror(errno));
269 }
270
271 // Archive the current log file
272 archive();
273
274 // Open the active log file using the same stream as before
275 _stream = fopen(_file_name, FileOpenMode);
276 if (_stream == NULL) {
277 jio_fprintf(defaultStream::error_stream(), "Could not reopen file '%s' during log rotation (%s).\n",
278 _file_name, strerror(errno));
279 return;
280 }
281
282 // Reset accumulated size, increase current file counter, and check for file count wrap-around.
283 _current_size = 0;
284 _current_file++;
285 if (_current_file == _file_count) {
286 _current_file = 0;
287 }
288 }
289
290 char* LogFileOutput::make_file_name(const char* file_name,
291 const char* pid_string,
292 const char* timestamp_string) {
293 char* result = NULL;
294
295 // Lets start finding out if we have any %d and/or %t in the name.
296 // We will only replace the first occurrence of any placeholder
297 const char* pid = strstr(file_name, PidFilenamePlaceholder);
298 const char* timestamp = strstr(file_name, TimestampFilenamePlaceholder);
299
300 if (pid == NULL && timestamp == NULL) {
301 // We found no place-holders, return the simple filename
302 return os::strdup_check_oom(file_name, mtLogging);
303 }
304
305 // At least one of the place-holders were found in the file_name
306 const char* first = "";
307 size_t first_pos = SIZE_MAX;
|