blob: 3eb8ce9cf9f72d92971936bba509302ab61ed9a9 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
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
26package sun.rmi.runtime;
27
28import java.io.ByteArrayOutputStream;
29import java.io.IOException;
30import java.io.PrintStream;
31import java.io.OutputStream;
32import java.rmi.server.LogStream;
33import java.util.logging.ConsoleHandler;
34import java.util.logging.Handler;
35import java.util.logging.Formatter;
36import java.util.logging.SimpleFormatter;
37import java.util.logging.StreamHandler;
38import java.util.logging.Level;
39import java.util.logging.Logger;
40import java.util.logging.LogManager;
41import java.util.logging.LogRecord;
42import java.util.logging.StreamHandler;
43import java.util.Map;
44import 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 */
64public 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((String) 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 = (Handler)
183 java.security.AccessController.doPrivileged(
184 new java.security.PrivilegedAction() {
185 public Object run() {
186 InternalStreamHandler alternate =
187 new InternalStreamHandler(System.err);
188 alternate.setLevel(Level.ALL);
189 return alternate;
190 }
191 }
192 );
193
194 /** handler to which messages are copied */
195 private InternalStreamHandler copyHandler = null;
196
197 /* logger to which log messages are written */
198 private final Logger logger;
199
200 /* used as return value of RemoteServer.getLog */
201 private LoggerPrintStream loggerSandwich;
202
203 /** creates a Log which will delegate to the given logger */
204 private LoggerLog(final Logger logger, final Level level) {
205 this.logger = logger;
206
207 if (level != null){
208 java.security.AccessController.doPrivileged(
209 new java.security.PrivilegedAction() {
210 public Object run() {
211 if (!logger.isLoggable(level)) {
212 logger.setLevel(level);
213 }
214 logger.addHandler(alternateConsole);
215 return null;
216 }
217 }
218 );
219 }
220 }
221
222 public boolean isLoggable(Level level) {
223 return logger.isLoggable(level);
224 }
225
226 public void log(Level level, String message) {
227 if (isLoggable(level)) {
228 String[] source = getSource();
229 logger.logp(level, source[0], source[1],
230 Thread.currentThread().getName() + ": " + message);
231 }
232 }
233
234 public void log(Level level, String message, Throwable thrown) {
235 if (isLoggable(level)) {
236 String[] source = getSource();
237 logger.logp(level, source[0], source[1],
238 Thread.currentThread().getName() + ": " +
239 message, thrown);
240 }
241 }
242
243 /**
244 * Set the output stream associated with the RMI server call
245 * logger.
246 *
247 * Calling code needs LoggingPermission "control".
248 */
249 public synchronized void setOutputStream(OutputStream out) {
250 if (out != null) {
251 if (!logger.isLoggable(VERBOSE)) {
252 logger.setLevel(VERBOSE);
253 }
254 copyHandler = new InternalStreamHandler(out);
255 copyHandler.setLevel(Log.VERBOSE);
256 logger.addHandler(copyHandler);
257 } else {
258 /* ensure that messages are not logged */
259 if (copyHandler != null) {
260 logger.removeHandler(copyHandler);
261 }
262 copyHandler = null;
263 }
264 }
265
266 public synchronized PrintStream getPrintStream() {
267 if (loggerSandwich == null) {
268 loggerSandwich = new LoggerPrintStream(logger);
269 }
270 return loggerSandwich;
271 }
272 }
273
274 /**
275 * Subclass of StreamHandler for redirecting log output. flush
276 * must be called in the publish and close methods.
277 */
278 private static class InternalStreamHandler extends StreamHandler {
279 InternalStreamHandler(OutputStream out) {
280 super(out, new SimpleFormatter());
281 }
282
283 public void publish(LogRecord record) {
284 super.publish(record);
285 flush();
286 }
287
288 public void close() {
289 flush();
290 }
291 }
292
293 /**
294 * PrintStream which forwards log messages to the logger. Class
295 * is needed to maintain backwards compatibility with
296 * RemoteServer.{set|get}Log().
297 */
298 private static class LoggerPrintStream extends PrintStream {
299
300 /** logger where output of this log is sent */
301 private final Logger logger;
302
303 /** record the last character written to this stream */
304 private int last = -1;
305
306 /** stream used for buffering lines */
307 private final ByteArrayOutputStream bufOut;
308
309 private LoggerPrintStream(Logger logger)
310 {
311 super(new ByteArrayOutputStream());
312 bufOut = (ByteArrayOutputStream) super.out;
313 this.logger = logger;
314 }
315
316 public void write(int b) {
317 if ((last == '\r') && (b == '\n')) {
318 last = -1;
319 return;
320 } else if ((b == '\n') || (b == '\r')) {
321 try {
322 /* write the converted bytes of the log message */
323 String message =
324 Thread.currentThread().getName() + ": " +
325 bufOut.toString();
326 logger.logp(Level.INFO, "LogStream", "print", message);
327 } finally {
328 bufOut.reset();
329 }
330 } else {
331 super.write(b);
332 }
333 last = b;
334 }
335
336 public void write(byte b[], int off, int len) {
337 if (len < 0) {
338 throw new ArrayIndexOutOfBoundsException(len);
339 }
340 for (int i = 0; i < len; i++) {
341 write(b[off + i]);
342 }
343 }
344
345 public String toString() {
346 return "RMI";
347 }
348 }
349
350 /**
351 * Factory to create Log objects which deliver log messages to the
352 * java.rmi.server.LogStream API
353 */
354 private static class LogStreamLogFactory implements LogFactory {
355 LogStreamLogFactory() {}
356
357 /* create a new LogStreamLog for the specified log */
358 public Log createLog(String loggerName, String oldLogName,
359 Level level)
360 {
361 LogStream stream = null;
362 if (oldLogName != null) {
363 stream = LogStream.log(oldLogName);
364 }
365 return new LogStreamLog(stream, level);
366 }
367 }
368
369 /**
370 * Class specialized to log messages to the
371 * java.rmi.server.LogStream API
372 */
373 private static class LogStreamLog extends Log {
374 /** Log stream to which log messages are written */
375 private final LogStream stream;
376
377 /** the level of the log as set by associated property */
378 private int levelValue = Level.OFF.intValue();
379
380 private LogStreamLog(LogStream stream, Level level) {
381 if ((stream != null) && (level != null)) {
382 /* if the stream or level is null, dont log any
383 * messages
384 */
385 levelValue = level.intValue();
386 }
387 this.stream = stream;
388 }
389
390 public synchronized boolean isLoggable(Level level) {
391 return (level.intValue() >= levelValue);
392 }
393
394 public void log(Level messageLevel, String message) {
395 if (isLoggable(messageLevel)) {
396 String[] source = getSource();
397 stream.println(unqualifiedName(source[0]) +
398 "." + source[1] + ": " + message);
399 }
400 }
401
402 public void log(Level level, String message, Throwable thrown) {
403 if (isLoggable(level)) {
404 /*
405 * keep output contiguous and maintain the contract of
406 * RemoteServer.getLog
407 */
408 synchronized (stream) {
409 String[] source = getSource();
410 stream.println(unqualifiedName(source[0]) + "." +
411 source[1] + ": " + message);
412 thrown.printStackTrace(stream);
413 }
414 }
415 }
416
417 public PrintStream getPrintStream() {
418 return stream;
419 }
420
421 public synchronized void setOutputStream(OutputStream out) {
422 if (out != null) {
423 if (VERBOSE.intValue() < levelValue) {
424 levelValue = VERBOSE.intValue();
425 }
426 stream.setOutputStream(out);
427 } else {
428 /* ensure that messages are not logged */
429 levelValue = Level.OFF.intValue();
430 }
431 }
432
433 /*
434 * Mimic old log messages that only contain unqualified names.
435 */
436 private static String unqualifiedName(String name) {
437 int lastDot = name.lastIndexOf(".");
438 if (lastDot >= 0) {
439 name = name.substring(lastDot + 1);
440 }
441 name = name.replace('$', '.');
442 return name;
443 }
444 }
445
446 /**
447 * Obtain class and method names of code calling a log method.
448 */
449 private static String[] getSource() {
450 StackTraceElement[] trace = (new Exception()).getStackTrace();
451 return new String[] {
452 trace[3].getClassName(),
453 trace[3].getMethodName()
454 };
455 }
456}