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
24package test.loggerfinder;
25
26import java.lang.System.Logger;
27import java.lang.System.Logger.Level;
28import java.lang.System.LoggerFinder;
29import java.security.AccessController;
30import java.security.PrivilegedAction;
31import java.util.Optional;
32import java.util.ResourceBundle;
33import java.util.function.Predicate;
34import java.lang.StackWalker.StackFrame;
35import java.text.MessageFormat;
36import java.time.LocalDateTime;
37import 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 */
46public class TestLoggerFinder extends LoggerFinder {
47
48    static class TestLogger implements Logger {
49
50        final String name;
51
52        public TestLogger(String name) {
53            this.name = name;
54        }
55
56
57        @Override
58        public String getName() {
59            return name;
60        }
61
62        @Override
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
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
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
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
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
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         */
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
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
172    public Logger getLogger(String name, Module module) {
173        return new TestLogger(name);
174    }
175
176}
177