1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17 package org.apache.juli;
18
19 import java.util.concurrent.LinkedBlockingDeque;
20 import java.util.concurrent.TimeUnit;
21 import java.util.logging.LogRecord;
22 /**
23  * A {@link FileHandler} implementation that uses a queue of log entries.
24  *
25  * <p>Configuration properties are inherited from the {@link FileHandler}
26  * class. This class does not add its own configuration properties for the
27  * logging configuration, but relies on the following system properties
28  * instead:</p>
29  *
30  * <ul>
31  *   <li><code>org.apache.juli.AsyncOverflowDropType</code>
32  *    Default value: <code>1</code></li>
33  *   <li><code>org.apache.juli.AsyncMaxRecordCount</code>
34  *    Default value: <code>10000</code></li>
35  *   <li><code>org.apache.juli.AsyncLoggerPollInterval</code>
36  *    Default value: <code>1000</code></li>
37  * </ul>
38  *
39  * <p>See the System Properties page in the configuration reference of Tomcat.</p>
40  */

41 public class AsyncFileHandler extends FileHandler {
42
43     public static final int OVERFLOW_DROP_LAST    = 1;
44     public static final int OVERFLOW_DROP_FIRST   = 2;
45     public static final int OVERFLOW_DROP_FLUSH   = 3;
46     public static final int OVERFLOW_DROP_CURRENT = 4;
47
48     public static final int DEFAULT_OVERFLOW_DROP_TYPE = 1;
49     public static final int DEFAULT_MAX_RECORDS        = 10000;
50     public static final int DEFAULT_LOGGER_SLEEP_TIME  = 1000;
51
52     public static final int OVERFLOW_DROP_TYPE = Integer.parseInt(
53             System.getProperty("org.apache.juli.AsyncOverflowDropType",
54                                Integer.toString(DEFAULT_OVERFLOW_DROP_TYPE)));
55     public static final int MAX_RECORDS = Integer.parseInt(
56             System.getProperty("org.apache.juli.AsyncMaxRecordCount",
57                                Integer.toString(DEFAULT_MAX_RECORDS)));
58     public static final int LOGGER_SLEEP_TIME = Integer.parseInt(
59             System.getProperty("org.apache.juli.AsyncLoggerPollInterval",
60                                Integer.toString(DEFAULT_LOGGER_SLEEP_TIME)));
61
62     protected static final LinkedBlockingDeque<LogEntry> queue =
63             new LinkedBlockingDeque<>(MAX_RECORDS);
64
65     protected static final LoggerThread logger = new LoggerThread();
66
67     static {
68         logger.start();
69     }
70
71     protected volatile boolean closed = false;
72
73     public AsyncFileHandler() {
74         this(nullnullnull);
75     }
76
77     public AsyncFileHandler(String directory, String prefix, String suffix) {
78         this(directory, prefix, suffix, null);
79     }
80
81     public AsyncFileHandler(String directory, String prefix, String suffix, Integer maxDays) {
82         super(directory, prefix, suffix, maxDays);
83         open();
84     }
85
86     @Override
87     public void close() {
88         if (closed) {
89             return;
90         }
91         closed = true;
92         super.close();
93     }
94
95     @Override
96     protected void open() {
97         if (!closed) {
98             return;
99         }
100         closed = false;
101         super.open();
102     }
103
104
105     @Override
106     public void publish(LogRecord record) {
107         if (!isLoggable(record)) {
108             return;
109         }
110         // fill source entries, before we hand the record over to another
111         // thread with another class loader
112         record.getSourceMethodName();
113         LogEntry entry = new LogEntry(record, this);
114         boolean added = false;
115         try {
116             while (!added && !queue.offer(entry)) {
117                 switch (OVERFLOW_DROP_TYPE) {
118                     case OVERFLOW_DROP_LAST: {
119                         //remove the last added element
120                         queue.pollLast();
121                         break;
122                     }
123                     case OVERFLOW_DROP_FIRST: {
124                         //remove the first element in the queue
125                         queue.pollFirst();
126                         break;
127                     }
128                     case OVERFLOW_DROP_FLUSH: {
129                         added = queue.offer(entry, 1000, TimeUnit.MILLISECONDS);
130                         break;
131                     }
132                     case OVERFLOW_DROP_CURRENT: {
133                         added = true;
134                         break;
135                     }
136                 }//switch
137             }//while
138         } catch (InterruptedException x) {
139             // Allow thread to be interrupted and back out of the publish
140             // operation. No further action required.
141         }
142
143     }
144
145     protected void publishInternal(LogRecord record) {
146         super.publish(record);
147     }
148
149     protected static class LoggerThread extends Thread {
150         public LoggerThread() {
151             this.setDaemon(true);
152             this.setName("AsyncFileHandlerWriter-" + System.identityHashCode(this));
153         }
154
155         @Override
156         public void run() {
157             while (true) {
158                 try {
159                     LogEntry entry = queue.poll(LOGGER_SLEEP_TIME, TimeUnit.MILLISECONDS);
160                     if (entry != null) {
161                         entry.flush();
162                     }
163                 } catch (InterruptedException x) {
164                     // Ignore the attempt to interrupt the thread.
165                 } catch (Exception x) {
166                     x.printStackTrace();
167                 }
168             }
169         }
170     }
171
172     protected static class LogEntry {
173         private final LogRecord record;
174         private final AsyncFileHandler handler;
175         public LogEntry(LogRecord record, AsyncFileHandler handler) {
176             super();
177             this.record = record;
178             this.handler = handler;
179         }
180
181         public boolean flush() {
182             if (handler.closed) {
183                 return false;
184             } else {
185                 handler.publishInternal(record);
186                 return true;
187             }
188         }
189     }
190 }
191