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