1 /* 2 * Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.rmi.runtime; 27 28 import java.io.ByteArrayOutputStream; 29 import java.io.PrintStream; 30 import java.io.OutputStream; 31 import java.rmi.server.LogStream; 32 import java.security.PrivilegedAction; 33 import java.util.logging.Handler; 34 import java.util.logging.SimpleFormatter; 35 import java.util.logging.Level; 36 import java.util.logging.Logger; 37 import java.util.logging.LogRecord; 38 import java.util.logging.StreamHandler; 39 40 /** 41 * Utility which provides an abstract "logger" like RMI internal API 42 * which can be directed to use one of two types of logging 43 * infrastructure: the java.util.logging API or the 44 * java.rmi.server.LogStream API. The default behavior is to use the 45 * java.util.logging API. The LogStream API may be used instead by 46 * setting the system property sun.rmi.log.useOld to true. 47 * 48 * For backwards compatibility, supports the RMI system logging 49 * properties which pre-1.4 comprised the only way to configure RMI 50 * logging. If the java.util.logging API is used and RMI system log 51 * properties are set, the system properties override initial RMI 52 * logger values as appropriate. If the java.util.logging API is 53 * turned off, pre-1.4 logging behavior is used. 54 * 55 * @author Laird Dornin 56 * @since 1.4 57 */ 58 @SuppressWarnings("deprecation") 59 public abstract class Log { 60 61 /** Logger re-definition of old RMI log values */ 62 public static final Level BRIEF = Level.FINE; 63 public static final Level VERBOSE = Level.FINER; 64 65 /* selects log implementation */ 66 private static final LogFactory logFactory; 67 static { 68 boolean useOld = java.security.AccessController.doPrivileged( 69 (PrivilegedAction<Boolean>) () -> Boolean.getBoolean("sun.rmi.log.useOld")); 70 71 /* set factory to select the logging facility to use */ 72 logFactory = (useOld ? (LogFactory) new LogStreamLogFactory() : 73 (LogFactory) new LoggerLogFactory()); 74 } 75 76 /** "logger like" API to be used by RMI implementation */ 77 public abstract boolean isLoggable(Level level); 78 public abstract void log(Level level, String message); 79 public abstract void log(Level level, String message, Throwable thrown); 80 81 /** get and set the RMI server call output stream */ 82 public abstract void setOutputStream(OutputStream stream); 83 public abstract PrintStream getPrintStream(); 84 85 /** factory interface enables Logger and LogStream implementations */ 86 private static interface LogFactory { 87 Log createLog(String loggerName, String oldLogName, Level level); 88 } 89 90 /* access log objects */ 91 92 /** 93 * Access log for a tri-state system property. 94 * 95 * Need to first convert override value to a log level, taking 96 * care to interpret a range of values between BRIEF, VERBOSE and 97 * SILENT. 98 * 99 * An override {@literal <} 0 is interpreted to mean that the logging 100 * configuration should not be overridden. The level passed to the 101 * factories createLog method will be null in this case. 102 * 103 * Note that if oldLogName is null and old logging is on, the 104 * returned LogStreamLog will ignore the override parameter - the 105 * log will never log messages. This permits new logs that only 106 * write to Loggers to do nothing when old logging is active. 107 * 108 * Do not call getLog multiple times on the same logger name. 109 * Since this is an internal API, no checks are made to ensure 110 * that multiple logs do not exist for the same logger. 111 */ 112 public static Log getLog(String loggerName, String oldLogName, 113 int override) 114 { 115 Level level; 116 117 if (override < 0) { 118 level = null; 119 } else if (override == LogStream.SILENT) { 120 level = Level.OFF; 121 } else if ((override > LogStream.SILENT) && 122 (override <= LogStream.BRIEF)) { 123 level = BRIEF; 124 } else if ((override > LogStream.BRIEF) && 125 (override <= LogStream.VERBOSE)) 126 { 127 level = VERBOSE; 128 } else { 129 level = Level.FINEST; 130 } 131 return logFactory.createLog(loggerName, oldLogName, level); 132 } 133 134 /** 135 * Access logs associated with boolean properties 136 * 137 * Do not call getLog multiple times on the same logger name. 138 * Since this is an internal API, no checks are made to ensure 139 * that multiple logs do not exist for the same logger. 140 */ 141 public static Log getLog(String loggerName, String oldLogName, 142 boolean override) 143 { 144 Level level = (override ? VERBOSE : null); 145 return logFactory.createLog(loggerName, oldLogName, level); 146 } 147 148 /** 149 * Factory to create Log objects which deliver log messages to the 150 * java.util.logging API. 151 */ 152 private static class LoggerLogFactory implements LogFactory { 153 LoggerLogFactory() {} 154 155 /* 156 * Accessor to obtain an arbitrary RMI logger with name 157 * loggerName. If the level of the logger is greater than the 158 * level for the system property with name, the logger level 159 * will be set to the value of system property. 160 */ 161 public Log createLog(final String loggerName, String oldLogName, 162 final Level level) 163 { 164 Logger logger = Logger.getLogger(loggerName); 165 return new LoggerLog(logger, level); 166 } 167 } 168 169 /** 170 * Class specialized to log messages to the java.util.logging API 171 */ 172 private static class LoggerLog extends Log { 173 174 /* alternate console handler for RMI loggers */ 175 private static final Handler alternateConsole = 176 java.security.AccessController.doPrivileged( 177 new java.security.PrivilegedAction<Handler>() { 178 public Handler run() { 179 InternalStreamHandler alternate = 180 new InternalStreamHandler(System.err); 181 alternate.setLevel(Level.ALL); 182 return alternate; 183 } 184 }); 185 186 /** handler to which messages are copied */ 187 private InternalStreamHandler copyHandler = null; 188 189 /* logger to which log messages are written */ 190 private final Logger logger; 191 192 /* used as return value of RemoteServer.getLog */ 193 private LoggerPrintStream loggerSandwich; 194 195 /** creates a Log which will delegate to the given logger */ 196 private LoggerLog(final Logger logger, final Level level) { 197 this.logger = logger; 198 199 if (level != null){ 200 java.security.AccessController.doPrivileged( 201 new java.security.PrivilegedAction<Void>() { 202 public Void run() { 203 if (!logger.isLoggable(level)) { 204 logger.setLevel(level); 205 } 206 logger.addHandler(alternateConsole); 207 return null; 208 } 209 } 210 ); 211 } 212 } 213 214 public boolean isLoggable(Level level) { 215 return logger.isLoggable(level); 216 } 217 218 public void log(Level level, String message) { 219 if (isLoggable(level)) { 220 String[] source = getSource(); 221 logger.logp(level, source[0], source[1], 222 Thread.currentThread().getName() + ": " + message); 223 } 224 } 225 226 public void log(Level level, String message, Throwable thrown) { 227 if (isLoggable(level)) { 228 String[] source = getSource(); 229 logger.logp(level, source[0], source[1], 230 Thread.currentThread().getName() + ": " + 231 message, thrown); 232 } 233 } 234 235 /** 236 * Set the output stream associated with the RMI server call 237 * logger. 238 * 239 * Calling code needs LoggingPermission "control". 240 */ 241 public synchronized void setOutputStream(OutputStream out) { 242 if (out != null) { 243 if (!logger.isLoggable(VERBOSE)) { 244 logger.setLevel(VERBOSE); 245 } 246 copyHandler = new InternalStreamHandler(out); 247 copyHandler.setLevel(Log.VERBOSE); 248 logger.addHandler(copyHandler); 249 } else { 250 /* ensure that messages are not logged */ 251 if (copyHandler != null) { 252 logger.removeHandler(copyHandler); 253 } 254 copyHandler = null; 255 } 256 } 257 258 public synchronized PrintStream getPrintStream() { 259 if (loggerSandwich == null) { 260 loggerSandwich = new LoggerPrintStream(logger); 261 } 262 return loggerSandwich; 263 } 264 } 265 266 /** 267 * Subclass of StreamHandler for redirecting log output. flush 268 * must be called in the publish and close methods. 269 */ 270 private static class InternalStreamHandler extends StreamHandler { 271 InternalStreamHandler(OutputStream out) { 272 super(out, new SimpleFormatter()); 273 } 274 275 public void publish(LogRecord record) { 276 super.publish(record); 277 flush(); 278 } 279 280 public void close() { 281 flush(); 282 } 283 } 284 285 /** 286 * PrintStream which forwards log messages to the logger. Class 287 * is needed to maintain backwards compatibility with 288 * RemoteServer.{set|get}Log(). 289 */ 290 private static class LoggerPrintStream extends PrintStream { 291 292 /** logger where output of this log is sent */ 293 private final Logger logger; 294 295 /** record the last character written to this stream */ 296 private int last = -1; 297 298 /** stream used for buffering lines */ 299 private final ByteArrayOutputStream bufOut; 300 301 private LoggerPrintStream(Logger logger) 302 { 303 super(new ByteArrayOutputStream()); 304 bufOut = (ByteArrayOutputStream) super.out; 305 this.logger = logger; 306 } 307 308 public void write(int b) { 309 if ((last == '\r') && (b == '\n')) { 310 last = -1; 311 return; 312 } else if ((b == '\n') || (b == '\r')) { 313 try { 314 /* write the converted bytes of the log message */ 315 String message = 316 Thread.currentThread().getName() + ": " + 317 bufOut.toString(); 318 logger.logp(Level.INFO, "LogStream", "print", message); 319 } finally { 320 bufOut.reset(); 321 } 322 } else { 323 super.write(b); 324 } 325 last = b; 326 } 327 328 public void write(byte b[], int off, int len) { 329 if (len < 0) { 330 throw new ArrayIndexOutOfBoundsException(len); 331 } 332 for (int i = 0; i < len; i++) { 333 write(b[off + i]); 334 } 335 } 336 337 public String toString() { 338 return "RMI"; 339 } 340 } 341 342 /** 343 * Factory to create Log objects which deliver log messages to the 344 * java.rmi.server.LogStream API 345 */ 346 private static class LogStreamLogFactory implements LogFactory { 347 LogStreamLogFactory() {} 348 349 /* create a new LogStreamLog for the specified log */ 350 public Log createLog(String loggerName, String oldLogName, 351 Level level) 352 { 353 LogStream stream = null; 354 if (oldLogName != null) { 355 stream = LogStream.log(oldLogName); 356 } 357 return new LogStreamLog(stream, level); 358 } 359 } 360 361 /** 362 * Class specialized to log messages to the 363 * java.rmi.server.LogStream API 364 */ 365 private static class LogStreamLog extends Log { 366 /** Log stream to which log messages are written */ 367 private final LogStream stream; 368 369 /** the level of the log as set by associated property */ 370 private int levelValue = Level.OFF.intValue(); 371 372 private LogStreamLog(LogStream stream, Level level) { 373 if ((stream != null) && (level != null)) { 374 /* if the stream or level is null, don't log any 375 * messages 376 */ 377 levelValue = level.intValue(); 378 } 379 this.stream = stream; 380 } 381 382 public synchronized boolean isLoggable(Level level) { 383 return (level.intValue() >= levelValue); 384 } 385 386 public void log(Level messageLevel, String message) { 387 if (isLoggable(messageLevel)) { 388 String[] source = getSource(); 389 stream.println(unqualifiedName(source[0]) + 390 "." + source[1] + ": " + message); 391 } 392 } 393 394 public void log(Level level, String message, Throwable thrown) { 395 if (isLoggable(level)) { 396 /* 397 * keep output contiguous and maintain the contract of 398 * RemoteServer.getLog 399 */ 400 synchronized (stream) { 401 String[] source = getSource(); 402 stream.println(unqualifiedName(source[0]) + "." + 403 source[1] + ": " + message); 404 thrown.printStackTrace(stream); 405 } 406 } 407 } 408 409 public PrintStream getPrintStream() { 410 return stream; 411 } 412 413 public synchronized void setOutputStream(OutputStream out) { 414 if (out != null) { 415 if (VERBOSE.intValue() < levelValue) { 416 levelValue = VERBOSE.intValue(); 417 } 418 stream.setOutputStream(out); 419 } else { 420 /* ensure that messages are not logged */ 421 levelValue = Level.OFF.intValue(); 422 } 423 } 424 425 /* 426 * Mimic old log messages that only contain unqualified names. 427 */ 428 private static String unqualifiedName(String name) { 429 int lastDot = name.lastIndexOf('.'); 430 if (lastDot >= 0) { 431 name = name.substring(lastDot + 1); 432 } 433 name = name.replace('$', '.'); 434 return name; 435 } 436 } 437 438 /** 439 * Obtain class and method names of code calling a log method. 440 */ 441 private static String[] getSource() { 442 StackTraceElement[] trace = (new Exception()).getStackTrace(); 443 return new String[] { 444 trace[3].getClassName(), 445 trace[3].getMethodName() 446 }; 447 } 448 }