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(null, null, null);
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