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}