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 18package java.util.logging; 19 20/** 21 * A {@code Handler} put the description of log events into a cycled memory 22 * buffer. 23 * <p> 24 * Mostly this {@code MemoryHandler} just puts the given {@code LogRecord} into 25 * the internal buffer and doesn't perform any formatting or any other process. 26 * When the buffer is full, the earliest buffered records will be discarded. 27 * <p> 28 * Every {@code MemoryHandler} has a target handler, and push action can be 29 * triggered so that all buffered records will be output to the target handler 30 * and normally the latter will publish the records. After the push action, the 31 * buffer will be cleared. 32 * <p> 33 * The push method can be called directly, but will also be called automatically 34 * if a new <code>LogRecord</code> is added that has a level greater than or 35 * equal to than the value defined for the property 36 * java.util.logging.MemoryHandler.push. 37 * <p> 38 * {@code MemoryHandler} will read following {@code LogManager} properties for 39 * initialization, if given properties are not defined or has invalid values, 40 * default value will be used. 41 * <ul> 42 * <li>java.util.logging.MemoryHandler.filter specifies the {@code Filter} 43 * class name, defaults to no {@code Filter}.</li> 44 * <li>java.util.logging.MemoryHandler.level specifies the level for this 45 * {@code Handler}, defaults to {@code Level.ALL}.</li> 46 * <li>java.util.logging.MemoryHandler.push specifies the push level, defaults 47 * to level.SEVERE.</li> 48 * <li>java.util.logging.MemoryHandler.size specifies the buffer size in number 49 * of {@code LogRecord}, defaults to 1000.</li> 50 * <li>java.util.logging.MemoryHandler.target specifies the class of the target 51 * {@code Handler}, no default value, which means this property must be 52 * specified either by property setting or by constructor.</li> 53 * </ul> 54 */ 55public class MemoryHandler extends Handler { 56 57 // default maximum buffered number of LogRecord 58 private static final int DEFAULT_SIZE = 1000; 59 60 // target handler 61 private Handler target; 62 63 // buffer size 64 private int size = DEFAULT_SIZE; 65 66 // push level 67 private Level push = Level.SEVERE; 68 69 // LogManager instance for convenience 70 private final LogManager manager = LogManager.getLogManager(); 71 72 // buffer 73 private LogRecord[] buffer; 74 75 // current position in buffer 76 private int cursor; 77 78 /** 79 * Default constructor, construct and init a {@code MemoryHandler} using 80 * {@code LogManager} properties or default values. 81 * 82 * @throws RuntimeException 83 * if property value are invalid and no default value could be 84 * used. 85 */ 86 public MemoryHandler() { 87 String className = this.getClass().getName(); 88 // init target 89 final String targetName = manager.getProperty(className + ".target"); 90 try { 91 ClassLoader loader = Thread.currentThread().getContextClassLoader(); 92 if (loader == null) { 93 loader = ClassLoader.getSystemClassLoader(); 94 } 95 Class<?> targetClass = loader.loadClass(targetName); 96 target = (Handler) targetClass.newInstance(); 97 } catch (Exception e) { 98 throw new RuntimeException("Cannot load target handler '" + targetName + "'"); 99 } 100 // init size 101 String sizeString = manager.getProperty(className + ".size"); 102 if (sizeString != null) { 103 try { 104 size = Integer.parseInt(sizeString); 105 if (size <= 0) { 106 size = DEFAULT_SIZE; 107 } 108 } catch (Exception e) { 109 printInvalidPropMessage(className + ".size", sizeString, e); 110 } 111 } 112 // init push level 113 String pushName = manager.getProperty(className + ".push"); 114 if (pushName != null) { 115 try { 116 push = Level.parse(pushName); 117 } catch (Exception e) { 118 printInvalidPropMessage(className + ".push", pushName, e); 119 } 120 } 121 // init other properties which are common for all Handler 122 initProperties("ALL", null, "java.util.logging.SimpleFormatter", null); 123 buffer = new LogRecord[size]; 124 } 125 126 /** 127 * Construct and init a {@code MemoryHandler} using given target, size and 128 * push level, other properties using {@code LogManager} properties or 129 * default values. 130 * 131 * @param target 132 * the given {@code Handler} to output 133 * @param size 134 * the maximum number of buffered {@code LogRecord}, greater than 135 * zero 136 * @param pushLevel 137 * the push level 138 * @throws IllegalArgumentException 139 * if {@code size <= 0} 140 * @throws RuntimeException 141 * if property value are invalid and no default value could be 142 * used. 143 */ 144 public MemoryHandler(Handler target, int size, Level pushLevel) { 145 if (size <= 0) { 146 throw new IllegalArgumentException("size <= 0"); 147 } 148 target.getLevel(); 149 pushLevel.intValue(); 150 this.target = target; 151 this.size = size; 152 this.push = pushLevel; 153 initProperties("ALL", null, "java.util.logging.SimpleFormatter", null); 154 buffer = new LogRecord[size]; 155 } 156 157 /** 158 * Close this handler and target handler, free all associated resources. 159 */ 160 @Override 161 public void close() { 162 manager.checkAccess(); 163 target.close(); 164 setLevel(Level.OFF); 165 } 166 167 /** 168 * Call target handler to flush any buffered output. Note that this doesn't 169 * cause this {@code MemoryHandler} to push. 170 */ 171 @Override 172 public void flush() { 173 target.flush(); 174 } 175 176 /** 177 * Put a given {@code LogRecord} into internal buffer. If given record is 178 * not loggable, just return. Otherwise it is stored in the buffer. 179 * Furthermore if the record's level is not less than the push level, the 180 * push action is triggered to output all the buffered records to the target 181 * handler, and the target handler will publish them. 182 * 183 * @param record 184 * the log record 185 */ 186 @Override public synchronized void publish(LogRecord record) { 187 if (!isLoggable(record)) { 188 return; 189 } 190 if (cursor >= size) { 191 cursor = 0; 192 } 193 buffer[cursor++] = record; 194 if (record.getLevel().intValue() >= push.intValue()) { 195 push(); 196 } 197 } 198 199 /** 200 * Return the push level. 201 * 202 * @return the push level 203 */ 204 public Level getPushLevel() { 205 return push; 206 } 207 208 /** 209 * Check if given {@code LogRecord} would be put into this 210 * {@code MemoryHandler}'s internal buffer. 211 * <p> 212 * The given {@code LogRecord} is loggable if and only if it has appropriate 213 * level and it pass any associated filter's check. 214 * <p> 215 * Note that the push level is not used for this check. 216 * 217 * @param record 218 * the given {@code LogRecord} 219 * @return the given {@code LogRecord} if it should be logged, {@code false} 220 * if {@code LogRecord} is {@code null}. 221 */ 222 @Override 223 public boolean isLoggable(LogRecord record) { 224 return super.isLoggable(record); 225 } 226 227 /** 228 * Triggers a push action to output all buffered records to the target handler, 229 * and the target handler will publish them. Then the buffer is cleared. 230 */ 231 public void push() { 232 for (int i = cursor; i < size; i++) { 233 if (buffer[i] != null) { 234 target.publish(buffer[i]); 235 } 236 buffer[i] = null; 237 } 238 for (int i = 0; i < cursor; i++) { 239 if (buffer[i] != null) { 240 target.publish(buffer[i]); 241 } 242 buffer[i] = null; 243 } 244 cursor = 0; 245 } 246 247 /** 248 * Set the push level. The push level is used to check the push action 249 * triggering. When a new {@code LogRecord} is put into the internal 250 * buffer and its level is not less than the push level, the push action 251 * will be triggered. Note that set new push level won't trigger push action. 252 * 253 * @param newLevel 254 * the new level to set. 255 */ 256 public void setPushLevel(Level newLevel) { 257 manager.checkAccess(); 258 newLevel.intValue(); 259 this.push = newLevel; 260 } 261} 262