001/******************************************************************************* 002The MIT License (MIT) 003 004Copyright (c) 2024 KILLCODING.COM 005 006Permission is hereby granted, free of charge, to any person obtaining a copy 007of this software and associated documentation files (the "Software"), to deal 008in the Software without restriction, including without limitation the rights 009to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 010copies of the Software, and to permit persons to whom the Software is 011furnished to do so, subject to the following conditions: 012 013The above copyright notice and this permission notice shall be included in 014all copies or substantial portions of the Software. 015 016THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 017IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 018FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 019AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 020LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 021OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 022THE SOFTWARE. 023*****************************************************************************/ 024package com.killcoding.log; 025 026import java.util.List; 027import java.util.ArrayList; 028import java.util.Collections; 029import java.util.Map; 030import com.killcoding.tool.FileTools; 031import java.io.File; 032import java.io.IOException; 033import java.text.SimpleDateFormat; 034import java.util.Date; 035import java.text.MessageFormat; 036import java.util.HashMap; 037import java.util.Set; 038import java.nio.file.Files; 039import java.nio.file.Path; 040import java.nio.file.Paths; 041import java.nio.file.StandardCopyOption; 042import java.net.URL; 043import java.net.MalformedURLException; 044import java.net.URI; 045import com.killcoding.tool.ConfigProperties; 046import java.util.regex.Pattern; 047import java.util.regex.Matcher; 048import javax.sql.DataSource; 049import java.lang.reflect.Method; 050import com.killcoding.tool.CommonTools; 051import java.util.Arrays; 052import java.util.concurrent.ExecutorService; 053import java.util.concurrent.Executors; 054import com.killcoding.datasource.CacheDriverExecutor; 055import java.sql.Connection; 056import java.sql.SQLException; 057import javax.naming.Context; 058import javax.naming.InitialContext; 059import com.killcoding.datasource.DriverDataSource; 060import java.sql.Timestamp; 061import java.io.ByteArrayOutputStream; 062import java.io.PrintStream; 063import com.killcoding.file.BaseFile; 064import com.killcoding.log.LoggerFactory; 065import com.killcoding.log.Logger; 066 067/** 068 * The class is write application log 069 * */ 070public final class LogRecord { 071 072 protected final static String DEFAULT_MESSAGE_FORMAT = ":level :log_time_format [:log_thread_name-:log_thread_id] :log_class_name.:log_method_name(:log_file_name::log_line_number) **:message** \r\n:stack_trace"; 073 074 private final static List<Map<String, Object>> LOG_RECORD_POOL = new ArrayList<Map<String, Object>>(); 075 076 private static boolean started = false; 077 private static boolean firstRefreshConfig = false; 078 private static String level = "DEBUG"; 079 private static String hostname = null; 080 private static ConfigProperties config = null; 081 082 private static Date LAST_REFRESH_CONFIG_TIME = null; 083 084 private static String appDataSourcePath = ""; 085 private static File fileOutFolder = null; 086 private static DriverDataSource dataSource = null; 087 088 private static AlertMessage alertMesage = null; 089 private static List<String> alertMessageLevelList = null; 090 091 private static PrintStream originalOut = System.out; 092 private static PrintStream originalErr = System.err; 093 private static ByteArrayOutputStream customOutput = null; 094 private static PrintStream customPrint = null; 095 096 /** 097 * Get log time format 098 * */ 099 protected static SimpleDateFormat getLogTimeFormat() { 100 String defValue = "yyyyMMdd HH:mm:ss.SSS"; 101 if (config == null) { 102 return new SimpleDateFormat(defValue); 103 } else { 104 String logTimeFormat = config.getString("LogTimeFormat", defValue); 105 return new SimpleDateFormat(logTimeFormat); 106 } 107 } 108 109 /** 110 * Get log date format 111 * */ 112 protected static SimpleDateFormat getLogDateFormat() { 113 String defValue = "yyyyMMdd"; 114 if (config == null) { 115 return new SimpleDateFormat(defValue); 116 } else { 117 String logDateFormat = config.getString("LogDateFormat", defValue); 118 return new SimpleDateFormat(logDateFormat); 119 } 120 } 121 122 /** 123 * Is started log thread 124 * @return boolean 125 * */ 126 protected static boolean isStarted() { 127 return started; 128 } 129 130 /** 131 * Set config 132 * @param _config 133 * */ 134 protected static synchronized void setConfig(ConfigProperties _config) { 135 config = _config; 136 String fileOutFolderConfig = config.getString("FileOutFolder", null); 137 if (fileOutFolderConfig == null) { 138 try { 139 String jarPath = new File(LogRecord.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getPath(); 140 fileOutFolder = new File(String.format("%s/logs/", jarPath)); 141 } catch (Exception e) { 142 Logger.systemWarn(LogRecord.class, e); 143 } 144 } else { 145 fileOutFolder = new File(fileOutFolderConfig); 146 } 147 level = config.getString("Level", "DEBUG"); 148 Logger.setLevel(level); 149 Logger.setClassNameEnableRegex(config.getString("ClassNameEnableRegex", null)); 150 Logger.setClassNameDisableRegex(config.getString("ClassNameDisableRegex", null)); 151 hostname = CommonTools.getHostname(); 152 } 153 154 /** 155 * Set log list 156 * @param list 157 * @return List<Map<String, Object>> 158 * */ 159 protected static synchronized List<Map<String, Object>> list() { 160 return (List<Map<String, Object>>) process("LIST", null); 161 } 162 163 /** 164 * Get log list size 165 * */ 166 protected static synchronized int size() { 167 return (Integer) process("SIZE", null); 168 } 169 170 /** 171 * Add log record object 172 * */ 173 protected static synchronized void add(Map<String, Object> o) { 174 process("ADD", o); 175 if (o != null) { 176 String level = (String) o.get("level"); 177 if (level != null) { 178 if (alertMessageLevelList.contains(level)) { 179 pushAlertMessage(o); 180 } 181 } 182 } 183 } 184 185 /** 186 * Remove log record object 187 * */ 188 protected static synchronized void remove(Map<String, Object> o) { 189 process("REMOVE", o); 190 } 191 192 /** 193 * Push log record to alert message list; 194 * */ 195 protected static void pushAlertMessage(Map<String, Object> o) { 196 if (alertMesage != null) { 197 alertMesage.push(o); 198 } 199 } 200 201 private static synchronized Object process(String action, Map<String, Object> o) { 202 Integer outLogBatchLimit = config.getInteger("OutLogBatchLimit", 1000); 203 if (action.equals("ADD")) { 204 int size = LOG_RECORD_POOL.size(); 205 if (size >= outLogBatchLimit) { 206 // Logger.systemDebug(LogRecord.class, "Over OutLogBatchLimit {} >= {}", size, outLogBatchLimit); 207 LOG_RECORD_POOL.remove(0); 208 } 209 o.put("system_name",config.getString("SystemName")); 210 LOG_RECORD_POOL.add(o); 211 } 212 213 if (action.equals("SIZE")) { 214 return LOG_RECORD_POOL.size(); 215 } 216 217 if (action.equals("LIST")) { 218 List<Map<String, Object>> list = new ArrayList<Map<String, Object>>( 219 Collections.synchronizedList(LOG_RECORD_POOL)); 220 LOG_RECORD_POOL.clear(); 221 return list; 222 } 223 return null; 224 } 225 226 private static int getLogFileIndex(String prefix, String suffix) { 227 Integer fileOutMaxBackupIndex = config.getInteger("FileOutMaxBackupIndex", 10); 228 for (int i = 1; i <= fileOutMaxBackupIndex; i++) { 229 String logFileName = String.format("%s/%s.%s.%s", fileOutFolder.getAbsolutePath(), prefix, i, suffix); 230 File logFile = new File(logFileName); 231 if (!logFile.exists()) 232 return i; 233 } 234 return 0; 235 } 236 237 private static Map<String, List<String>> getLogBatch(List<Map<String, Object>> list) { 238 Map<String, List<String>> logBatch = new HashMap<String, List<String>>(); 239 for (Map<String, Object> row : list) { 240 Date logTime = (Date) row.get("log_time"); 241 String logDateStr = getLogDateFormat().format(logTime); 242 String msg = format(config.getString("MessageFormat", DEFAULT_MESSAGE_FORMAT), row); 243 List<String> batchLines = logBatch.get(logDateStr); 244 if (batchLines == null) { 245 batchLines = new ArrayList<String>(); 246 } 247 batchLines.add(msg); 248 249 if(customOutput != null){ 250 try{ 251 customOutput.flush(); 252 customPrint.flush(); 253 String outMsg = customOutput.toString(BaseFile.CHARSET); 254 if(!CommonTools.isBlank(outMsg)){ 255 customOutput = new ByteArrayOutputStream(); 256 customPrint = new PrintStream(customOutput); 257 System.setOut(customPrint); 258 System.setErr(customPrint); 259 LoggerFactory.getLogger(LogRecord.class).mark("SystemOut: {}",outMsg); 260 } 261 }catch(Exception e){ 262 Logger.systemError(LogRecord.class,e.getMessage(),e); 263 } 264 } 265 266 logBatch.put(logDateStr, batchLines); 267 } 268 return logBatch; 269 } 270 271 public static String format(String messageFormat, Map<String, Object> row) { 272 List<Object> params = new ArrayList<Object>(); 273 Pattern pattern = Pattern.compile(":[a-z0-9_]+"); 274 Matcher matcher = pattern.matcher(messageFormat); 275 while (matcher.find()) { 276 String key = matcher.group().replaceFirst(":", ""); 277 params.add(row.get(key)); 278 } 279 String mf = messageFormat.replaceAll(":[a-z0-9_]+", "%s"); 280 String msg = String.format(mf, params.toArray()).trim(); 281 return msg; 282 } 283 284 private static File getLogFile() { 285 String logFileNameFormat = config.getString("FileNameFormat", "`yyyyMMdd`.log"); 286 int startIndex = logFileNameFormat.indexOf("`"); 287 int endIndex = logFileNameFormat.lastIndexOf("`"); 288 if (startIndex > -1 && endIndex > -1) { 289 String FileNameFormatStr = logFileNameFormat.substring(startIndex + 1, endIndex); 290 SimpleDateFormat ldf = new SimpleDateFormat(FileNameFormatStr); 291 String ff = ldf.format(new java.util.Date()); 292 String ffv = logFileNameFormat.replaceFirst("`.*`", ff); 293 return new File(ffv); 294 } 295 return new File(logFileNameFormat); 296 } 297 298 public static String getLogFileName() { 299 return getLogFile().getName(); 300 } 301 302 protected static boolean isSystemOut(){ 303 return customPrint == null; 304 } 305 306 /** 307 * For write log to database 308 * */ 309 private static synchronized void writeLogBatch(Map<String, List<String>> logBatch) { 310 List<String> logDateRecord = new ArrayList<String>(logBatch.keySet()); 311 for (String logDate : logDateRecord) { 312 File logFileNameFile = getLogFile(); 313 String logFileFormatName = logFileNameFile.getName(); 314 String logFileNamePrefix = logDate; 315 String logFileNameSuffix = "log"; 316 int suffixIndex = logFileFormatName.lastIndexOf("."); 317 if (suffixIndex > -1) { 318 logFileNamePrefix = logFileFormatName.substring(0, suffixIndex); 319 logFileNameSuffix = logFileFormatName.substring(suffixIndex + 1); 320 } 321 try{ 322 if (config.getBoolean("SystemOut", true)) { 323 if(customOutput != null){ 324 customOutput.flush(); 325 customPrint.flush(); 326 String outMsg = customOutput.toString(BaseFile.CHARSET); 327 328 System.setOut(originalOut); 329 System.setErr(originalErr); 330 331 customOutput.close(); 332 customPrint.close(); 333 334 customOutput = null; 335 customPrint = null; 336 337 if(!CommonTools.isBlank(outMsg)){ 338 System.out.println(outMsg); 339 } 340 } 341 }else{ 342 if(customOutput == null){ 343 customOutput = new ByteArrayOutputStream(); 344 customPrint = new PrintStream(customOutput); 345 System.setOut(customPrint); 346 System.setErr(customPrint); 347 } 348 } 349 }catch(Exception e){ 350 Logger.systemError(LogRecord.class, e); 351 } 352 try { 353 String linesStr = String.join(System.lineSeparator(), logBatch.get(logDate)) + System.lineSeparator(); 354 if (config.getBoolean("FileOut", false)) { 355 long fileOutMaxFileSize = config.getFileSize("FileOutMaxFileSize",1024*1024*10L); 356 String logFileName = String.format("%s/%s.%s", fileOutFolder.getAbsolutePath(), logFileNamePrefix, 357 logFileNameSuffix); 358 File logFile = new File(logFileName); 359 360 if(fileOutFolder.exists()){ 361 if(!fileOutFolder.canWrite()) return; 362 }else{ 363 fileOutFolder.mkdirs(); 364 } 365 366 if(logFile.exists()){ 367 if(!logFile.canWrite()) return; 368 } 369 370 long logSize = FileTools.size(logFile); 371 if (logSize < fileOutMaxFileSize) { 372 FileTools.write(logFile, linesStr, true); 373 } else { 374 int logIndex = getLogFileIndex(logFileNamePrefix, logFileNameSuffix); 375 if (logIndex == 0) { 376 String header = config.getString("MessageHeader"); 377 if(CommonTools.isBlank(header)){ 378 FileTools.write(logFile, linesStr, false); 379 }else{ 380 FileTools.write(logFile, String.format("%s%s",header,System.lineSeparator()), false); 381 FileTools.write(logFile, linesStr, true); 382 } 383 } else { 384 String backupLogFileName = String.format("%s/%s.%s.%s", fileOutFolder.getAbsolutePath(), 385 logFileNamePrefix, logIndex, logFileNameSuffix); 386 Path source = Paths.get(logFile.getAbsolutePath()); 387 Path target = Paths.get(backupLogFileName); 388 Files.move(source, target, StandardCopyOption.REPLACE_EXISTING); 389 String header = config.getString("MessageHeader"); 390 if(CommonTools.isBlank(header)){ 391 FileTools.write(logFile, linesStr, false); 392 }else{ 393 FileTools.write(logFile, String.format("%s%s",header,System.lineSeparator()), false); 394 FileTools.write(logFile, linesStr, true); 395 } 396 } 397 } 398 } 399 } catch (Exception e) { 400 Logger.systemError(LogRecord.class, e); 401 } 402 } 403 } 404 405 /** 406 * For write log to database 407 * */ 408 private static synchronized void writeLogBatchToDatabase(List<Map<String, Object>> list) { 409 try { 410 if (config.getBoolean("DatabaseOut", false)) { 411 412 if (LAST_REFRESH_CONFIG_TIME == null) { 413 LAST_REFRESH_CONFIG_TIME = new Date(); 414 firstRefreshConfig = true; 415 } 416 long ms = new Date().getTime() - LAST_REFRESH_CONFIG_TIME.getTime(); 417 Long databaseOutRerefreshConfigTimer = config.getMilliSeconds("DatabaseOutRerefreshConfigTimer", 418 60 * 1000L); 419 String databaseOutDataSource = config.getString("DatabaseOutDataSource", ""); 420 String databaseOutArchiveExecute = config.getString("DatabaseOutArchiveExecute", null); 421 String databaseOutInsertExecute = config.getString("DatabaseOutInsertExecute", null); 422 String databaseOutConfigFrom = config.getString("DatabaseOutConfigFrom", null); 423 Integer databaseOutBatchLimit = config.getInteger("DatabaseOutBatchLimit", 100); 424 Integer databaseOutArchiveDays = config.getInteger("DatabaseOutArchiveDays",31); 425 426 initDataSource(databaseOutDataSource); 427 428 if (dataSource != null && databaseOutConfigFrom != null 429 && (ms >= databaseOutRerefreshConfigTimer || firstRefreshConfig)) { 430 firstRefreshConfig = false; 431 Map<String, Object> paramMap = new HashMap<String, Object>(); 432 paramMap.put("hostname", hostname); 433 434 Map<String, Object> remoteConfig = first(databaseOutConfigFrom, paramMap); 435 436 if (remoteConfig.containsKey("level")) { 437 String removeLevel = (String) remoteConfig.get("level"); 438 if (Logger.checkLevel(removeLevel)) { 439 level = removeLevel; 440 Logger.setLevel(level); 441 } 442 } else { 443 level = config.getString("Level", "DEBUG"); 444 Logger.setLevel(level); 445 } 446 447 if (remoteConfig.containsKey("class_name_enable_regex")) { 448 String classNameEnableRegex = (String) remoteConfig.get("class_name_enable_regex"); 449 Logger.setClassNameEnableRegex(classNameEnableRegex); 450 } else { 451 String defaultClassNameEnableRegex = config.getString("ClassNameEnableRegex", null); 452 Logger.setClassNameEnableRegex(defaultClassNameEnableRegex); 453 } 454 455 if (remoteConfig.containsKey("class_name_disable_regex")) { 456 String classNameDisableRegex = (String) remoteConfig.get("class_name_disable_regex"); 457 Logger.setClassNameDisableRegex(classNameDisableRegex); 458 } else { 459 String classNameDisableRegex = config.getString("ClassNameDisableRegex", null); 460 Logger.setClassNameDisableRegex(classNameDisableRegex); 461 } 462 463 LAST_REFRESH_CONFIG_TIME = new Date(); 464 } 465 466 if (dataSource != null && databaseOutInsertExecute != null && list.size() > 0) { 467 batch(databaseOutInsertExecute, list); 468 } 469 470 if (dataSource != null && databaseOutArchiveExecute != null) { 471 long archiveTime = System.currentTimeMillis() - databaseOutArchiveDays*24*3600*1000; 472 Map<String, Object> paramMap = new HashMap<String, Object>(); 473 paramMap.put("hostname", hostname); 474 paramMap.put("archive_time",new Timestamp(archiveTime)); 475 execute(databaseOutArchiveExecute, paramMap); 476 } 477 } 478 } catch (Exception e) { 479 Logger.systemError(LogRecord.class, e.getMessage(),e); 480 } 481 } 482 483 /** 484 * For write log to database 485 * */ 486 private static synchronized void initDataSource(String loadDatabaseOutDataSource) { 487 try { 488 if(!appDataSourcePath.equals(loadDatabaseOutDataSource)){ 489 if(dataSource != null){ 490 dataSource.closeAll(); 491 dataSource = null; 492 } 493 appDataSourcePath = loadDatabaseOutDataSource; 494 } 495 if(dataSource == null){ 496 File appDataSource = new File(appDataSourcePath); 497 dataSource = new DriverDataSource(appDataSource); 498 } 499 } catch (Exception e) { 500 Logger.systemError(LogRecord.class, e.getMessage(),e); 501 } 502 } 503 504 /** 505 * Archive Log 506 * */ 507 private static void archiveLog() { 508 Integer fileOutArchiveDays = config.getInteger("FileOutArchiveDays", 30); 509 Boolean fileOut = config.getBoolean("FileOut", false); 510 if (fileOutArchiveDays > 0 && fileOut && fileOutFolder != null) { 511 try { 512 long fileOutArchiveDaysMs = new Date().getTime() - (fileOutArchiveDays * 24 * 3600000L); 513 deleteFilesOlderThan(fileOutFolder, fileOutArchiveDaysMs); 514 } catch (Exception e) { 515 Logger.systemError(LogRecord.class, e); 516 } 517 } 518 } 519 520 /** 521 * Delete old log file 522 * */ 523 private static void deleteFilesOlderThan(File directory, long fileOutArchiveDaysMs) throws IOException { 524 if (directory.isDirectory()) { 525 File[] files = directory.listFiles(); 526 if (files != null) { 527 for (File file : files) { 528 if (file.isFile()) { 529 boolean canWrite = file.canWrite(); 530 if (canWrite) { 531 long lastModified = file.lastModified(); 532 if (lastModified < fileOutArchiveDaysMs) { 533 Files.deleteIfExists(Paths.get(file.toURI())); 534 } 535 } 536 } 537 } 538 } 539 } 540 } 541 542 /** 543 * For write log to database 544 * */ 545 public static Map<String, Object> first(String sql, Map<String, Object> params) throws Exception { 546 Logger.systemDebug(LogRecord.class, "Sql={}", sql); 547 Logger.systemDebug(LogRecord.class, "Params={}", params); 548 CacheDriverExecutor driverExecutor = null; 549 try { 550 Connection conn = dataSource.getConnection(); 551 conn.setAutoCommit(true); 552 driverExecutor = new CacheDriverExecutor(conn); 553 return driverExecutor.first(sql, params); 554 } finally { 555 if (driverExecutor != null) 556 driverExecutor.close(); 557 } 558 } 559 560 /** 561 * For write log to database 562 * */ 563 private static int execute(String sql, Map<String, Object> params) throws Exception { 564 Logger.systemDebug(LogRecord.class, "Sql={}", sql); 565 Logger.systemDebug(LogRecord.class, "Params={}", params); 566 CacheDriverExecutor driverExecutor = null; 567 try { 568 Connection conn = dataSource.getConnection(); 569 conn.setAutoCommit(true); 570 driverExecutor = new CacheDriverExecutor(conn); 571 return driverExecutor.execute(sql, params); 572 } finally { 573 if (driverExecutor != null) 574 driverExecutor.close(); 575 } 576 } 577 578 /** 579 * For write log to database 580 * */ 581 private static synchronized int batchPart(String sql, List<Map<String, Object>> recordsPart) throws Exception { 582 CacheDriverExecutor driverExecutor = null; 583 try { 584 Connection conn = dataSource.getConnection(); 585 conn.setAutoCommit(true); 586 driverExecutor = new CacheDriverExecutor(conn); 587 return driverExecutor.executeBatch(sql, recordsPart); 588 } finally { 589 if (driverExecutor != null) 590 driverExecutor.close(); 591 } 592 } 593 594 /** 595 * For write log to database 596 * */ 597 private static synchronized int batch(String sql, List<Map<String, Object>> records) throws Exception { 598 Logger.systemDebug(LogRecord.class, "Sql={}", sql); 599 Logger.systemDebug(LogRecord.class, "Batch Size={}", records.size()); 600 int rows = 0; 601 Integer databaseOutBatchLimit = config.getInteger("DatabaseOutBatchLimit", 100); 602 List<List<Map<String, Object>>> recordsParts = CommonTools.averageAssign(records, databaseOutBatchLimit); 603 for (List<Map<String, Object>> recordsPart : recordsParts) { 604 int row = batchPart(sql, recordsPart); 605 rows += row; 606 } 607 return rows; 608 } 609 610 /** 611 * Get data source 612 * @return DataSource 613 * */ 614 public static DataSource getDataSource(){ 615 return dataSource; 616 } 617 618 /** 619 * Start write log thread 620 * */ 621 protected static synchronized void start() { 622 if (!started) { 623 started = true; 624 625 try { 626 String alertMessageClazz = config.getString("AlertMessage.class"); 627 String[] alertMessageLevels = config.getString("AlertMessage.level", "DEBUG,INFO,WARN,ERROR,MARK").trim() 628 .toUpperCase().split("[^\\S]*,[^\\S]*"); 629 alertMessageLevelList = Arrays.asList(alertMessageLevels); 630 if (!CommonTools.isBlank(alertMessageClazz)) { 631 Class amc = Class.forName(alertMessageClazz); 632 alertMesage = (AlertMessage) amc.newInstance(); 633 } 634 } catch (Exception e) { 635 Logger.systemError(LogRecord.class, e); 636 } 637 638 ExecutorService executor = Executors.newFixedThreadPool(1); 639 executor.execute(new Runnable() { 640 641 @Override 642 public void run() { 643 Thread.currentThread().setName(Logger.class.getSimpleName()); 644 while (true) { 645 646 if(Thread.currentThread().isInterrupted()) break; 647 648 try { 649 List<Map<String, Object>> list = list(); 650 Map<String, List<String>> logBatch = getLogBatch(list); 651 writeLogBatch(logBatch); 652 writeLogBatchToDatabase(list); 653 archiveLog(); 654 } catch (Exception e) { 655 Logger.systemError(LogRecord.class, e); 656 } 657 658 try { 659 if (Thread.currentThread().isInterrupted()) { 660 break; 661 } else { 662 Thread.sleep(config.getMilliSeconds("OutLogTimer", 1000L)); 663 } 664 } catch (InterruptedException e) { 665 Logger.systemError(LogRecord.class, e); 666 break; 667 } 668 } 669 670 } 671 672 }); 673 } 674 } 675 676}