1 /*
2  * Copyright (c) 2017, 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.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 package test.loggerfinder;
25 
26 import java.lang.System.Logger;
27 import java.lang.System.Logger.Level;
28 import java.lang.System.LoggerFinder;
29 import java.security.AccessController;
30 import java.security.PrivilegedAction;
31 import java.util.Optional;
32 import java.util.ResourceBundle;
33 import java.util.function.Predicate;
34 import java.lang.StackWalker.StackFrame;
35 import java.text.MessageFormat;
36 import java.time.LocalDateTime;
37 import java.time.format.DateTimeFormatter;
38 
39 /**
40  * A LoggerFinder that provides System.Logger which print directly
41  * on System.err, without involving java.logging.
42  * For the purpose of the test, loggers whose name start with java.management.
43  * will log all messages, and other loggers will only log level > INFO.
44  * @author danielfuchs
45  */
46 public class TestLoggerFinder extends LoggerFinder {
47 
48     static class TestLogger implements Logger {
49 
50         final String name;
51 
TestLogger(String name)52         public TestLogger(String name) {
53             this.name = name;
54         }
55 
56 
57         @Override
getName()58         public String getName() {
59             return name;
60         }
61 
62         @Override
isLoggable(Level level)63         public boolean isLoggable(Level level) {
64             return name.equals("javax.management")
65                     || name.startsWith("javax.management.")
66                     || level.getSeverity() >= Level.INFO.getSeverity();
67         }
68 
69         @Override
log(Level level, ResourceBundle bundle, String msg, Throwable thrown)70         public void log(Level level, ResourceBundle bundle, String msg, Throwable thrown) {
71             if (!isLoggable(level)) return;
72             publish(level, bundle, msg, thrown);
73         }
74 
75         @Override
log(Level level, ResourceBundle bundle, String format, Object... params)76         public void log(Level level, ResourceBundle bundle, String format, Object... params) {
77             if (!isLoggable(level)) return;
78             publish(level, bundle, format, params);
79         }
80 
publish(Level level, ResourceBundle bundle, String msg, Throwable thrown)81         static void publish(Level level, ResourceBundle bundle, String msg, Throwable thrown) {
82             StackFrame sf = new CallerFinder().get().get();
83 
84             if (bundle != null && msg != null) {
85                 msg = bundle.getString(msg);
86             }
87             if (msg == null) msg = "";
88             LocalDateTime ldt = LocalDateTime.now();
89             String date = DateTimeFormatter.ISO_DATE_TIME.format(ldt);
90             System.err.println(date + " "
91                     + sf.getClassName() + " " + sf.getMethodName() + "\n"
92                     + String.valueOf(level) + ": " + msg);
93             thrown.printStackTrace(System.err);
94         }
95 
publish(Level level, ResourceBundle bundle, String format, Object... params)96         static void publish(Level level, ResourceBundle bundle, String format, Object... params) {
97             StackFrame sf = new CallerFinder().get().get();
98             if (bundle != null && format != null) {
99                 format = bundle.getString(format);
100             }
101             String msg = format(format, params);
102             LocalDateTime ldt = LocalDateTime.now();
103             String date = DateTimeFormatter.ISO_DATE_TIME.format(ldt);
104             System.err.println(date + " "
105                     + sf.getClassName() + " " + sf.getMethodName() + "\n"
106                     + String.valueOf(level) + ": " + msg);
107         }
108 
format(String format, Object... args)109         static String format(String format, Object... args) {
110             if (format == null) return "";
111             int index = 0, len = format.length();
112             while ((index = format.indexOf(index, '{')) >= 0) {
113                 if (index >= len - 2) break;
114                 char c = format.charAt(index+1);
115                 if (c >= '0' && c <= '9') {
116                     return MessageFormat.format(format, args);
117                 }
118                 index++;
119             }
120             return format;
121         }
122 
123     }
124 
125      /*
126      * CallerFinder is a stateful predicate.
127      */
128     static final class CallerFinder implements Predicate<StackWalker.StackFrame> {
129         private static final StackWalker WALKER;
130         static {
131             PrivilegedAction<StackWalker> pa =
132                 () -> StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
133             WALKER = AccessController.doPrivileged(pa);
134         }
135 
136         /**
137          * Returns StackFrame of the caller's frame.
138          * @return StackFrame of the caller's frame.
139          */
get()140         Optional<StackWalker.StackFrame> get() {
141             return WALKER.walk((s) -> s.filter(this).findFirst());
142         }
143 
144         private boolean lookingForLogger = true;
145         /**
146          * Returns true if we have found the caller's frame, false if the frame
147          * must be skipped.
148          *
149          * @param t The frame info.
150          * @return true if we have found the caller's frame, false if the frame
151          * must be skipped.
152          */
153         @Override
test(StackWalker.StackFrame s)154         public boolean test(StackWalker.StackFrame s) {
155             // We should skip all frames until we have found the logger,
156             // because these frames could be frames introduced by e.g. custom
157             // sub classes of Handler.
158             Class<?> c = s.getDeclaringClass();
159             boolean isLogger = System.Logger.class.isAssignableFrom(c);
160             if (lookingForLogger) {
161                 // Skip all frames until we have found the first logger frame.
162                 lookingForLogger = c != TestLogger.class;
163                 return false;
164             }
165             // Continue walking until we've found the relevant calling frame.
166             // Skips logging/logger infrastructure.
167             return !isLogger;
168         }
169     }
170 
171     @Override
getLogger(String name, Module module)172     public Logger getLogger(String name, Module module) {
173         return new TestLogger(name);
174     }
175 
176 }
177