001/*
002 * Trident - A Multithreaded Server Alternative
003 * Copyright 2014 The TridentSDK Team
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *    http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package net.tridentsdk.util;
019
020import com.google.common.collect.ForwardingCollection;
021import com.google.common.collect.ImmutableList;
022import net.tridentsdk.Console;
023import net.tridentsdk.Trident;
024import net.tridentsdk.docs.InternalUseOnly;
025import net.tridentsdk.docs.Policy;
026import net.tridentsdk.registry.Registry;
027import org.apache.log4j.*;
028import org.slf4j.LoggerFactory;
029
030import javax.annotation.concurrent.ThreadSafe;
031import java.io.*;
032import java.nio.file.Files;
033import java.nio.file.Path;
034import java.util.Collection;
035import java.util.Map;
036import java.util.concurrent.ConcurrentHashMap;
037
038/**
039 * Logger for Trident, automatically obtains the correct logger for the class
040 *
041 * @author The TridentSDK Team
042 * @since 0.3-alpha-DP
043 */
044@ThreadSafe
045@Policy("Ensure initialization ASAP in main class")
046public final class TridentLogger extends ForwardingCollection<TridentLogger> implements Registry<TridentLogger> {
047    private static final Map<String, TridentLogger> LOGGERS = new ConcurrentHashMap<>();
048
049    @InternalUseOnly
050    public static void init(Level level) {
051        ConsoleAppender console = new ConsoleAppender(); //create appender
052        console.setLayout(new PatternLayout("%d{dd MMM HH:mm} [%p] %m%n"));
053        console.setThreshold(level);
054        console.activateOptions();
055        console.setWriter(new Writer() {
056            BufferedWriter writer = new BufferedWriter(new PrintWriter(System.out));
057
058            @Override
059            public void write(char[] cbuf, int off, int len) throws IOException {
060                writer.write(("\r" + new String(cbuf)).toCharArray(), off, len + 1);
061                writer.write("$ ".toCharArray());
062            }
063
064            @Override
065            public void flush() throws IOException {
066                writer.flush();
067            }
068
069            @Override
070            public void close() throws IOException {
071                writer.close();
072            }
073        });
074
075        Logger.getRootLogger().addAppender(console);
076
077        // We will copy the current log to the logs directory to prevent the file
078        // from destroying the text editor if it needs to be opened after a week
079        // of continuous errors
080        Path logs = Trident.fileContainer().resolve("logs");
081        Path path = Trident.fileContainer().resolve("trident.log");
082        try {
083            if (Files.exists(path)) {
084                if (!Files.exists(logs))
085                    Files.createDirectory(logs);
086
087                // Half a gigabyte
088                if (Files.size(path) >= 2_560_000) {
089                    File[] list = logs.toFile().listFiles();
090                    copyLog(path, logs, list.length);
091
092                    Files.delete(path);
093                }
094            }
095        } catch (IOException e) {
096            // Well we can't really do anything about it :/
097            e.printStackTrace();
098        }
099
100        FileAppender fa = new FileAppender();
101
102        fa.setName("FileLogger");
103        fa.setFile("trident.log");
104        fa.setLayout(new PatternLayout("%d{dd MMM HH:mm} [%p][%c{1}][%t] %m%n"));
105        fa.setThreshold(Level.DEBUG);
106        fa.setAppend(true);
107        fa.activateOptions();
108        try {
109            fa.setWriter(new Writer() {
110                BufferedWriter writer = new BufferedWriter(new FileWriter(fa.getFile()));
111
112                @Override
113                public void write(char[] cbuf, int off, int len) throws IOException {
114                    String s = new String(cbuf, off, len);
115                    String[] ss = s.split("\n");                   // Prevents losing newlines
116                    for (int i = 0; i < ss.length; i++) {
117                        String sss = ss[i];
118                        ss[i] = sss.replaceAll("\\P{Print}", "")   // Replaces Unicode escapes
119                                .replaceAll("\\[[\\d]{1,2}m", ""); // Replaces colors
120                    }
121                    StringBuilder finalS = new StringBuilder();
122                    for (String sss : ss) {
123                        finalS.append(sss).append("\n");
124                    }
125                    s = finalS.toString();
126
127                    writer.write(s.toCharArray());
128                }
129
130                @Override
131                public void flush() throws IOException {
132                    writer.flush();
133                }
134
135                @Override
136                public void close() throws IOException {
137                    writer.close();
138                }
139            });
140        } catch (IOException e) {
141            e.printStackTrace();
142        }
143
144        Logger.getRootLogger().addAppender(fa);
145    }
146
147    private static void copyLog(Path original, Path directory, int index) throws IOException {
148        Path newPath = directory.resolve("old." + index + ".log");
149        Files.copy(original, newPath);
150    }
151
152    private final LoggerDelegate logger;
153
154    private TridentLogger(String name) {
155        this.logger = new LoggerDelegate(LoggerFactory.getLogger(name));
156    }
157
158    /**
159     * Obtains the internal underlying representation of the logger
160     *
161     * @return the SLF4J raw logger
162     */
163    public LoggerDelegate internal() {
164        return this.logger;
165    }
166
167    @Override
168    protected Collection<TridentLogger> delegate() {
169        return ImmutableList.copyOf(LOGGERS.values());
170    }
171
172    /**
173     * Obtains the logger for the class that calls this method
174     *
175     * @return the logger for that class
176     */
177    public static TridentLogger get() {
178        return get(Trident.findCaller(3));
179    }
180
181    /**
182     * Obtains the logger for the class that is specified
183     *
184     * @param cls the specified class
185     * @return the logger for that class
186     */
187    public static TridentLogger get(Class<?> cls) {
188        return get(cls.getName());
189    }
190
191    /**
192     * Wraps the logger with a TridentLogger instance
193     *
194     * @param logger the logger to wrap
195     * @return the cached, or a new instance of a logger wrapper
196     */
197    public static TridentLogger get(org.slf4j.Logger logger) {
198        return get(logger.getName());
199    }
200
201    /**
202     * Obtains a logger, creating a new one if it does not exist in the logger registry
203     *
204     * @param name the logger name
205     * @return the new logger
206     */
207    public static TridentLogger get(String name) {
208        return LOGGERS.computeIfAbsent(name, k -> new TridentLogger(name));
209    }
210
211    /**
212     * Logs a message to the class logger
213     *
214     * @param item the item to log
215     */
216    public void log(String item) {
217        internal().info(item + Console.RESET);
218    }
219
220    /**
221     * Logs debug to the logger output
222     *
223     * @param message the message to log
224     */
225    public void debug(String message) {
226        internal().debug(message);
227    }
228
229    /**
230     * Logs an error message to the class logger with a red escape
231     *
232     * @param message the message to log
233     */
234    public void error(String message) {
235        internal().error(Console.RED + message + Console.RESET);
236    }
237
238    /**
239     * Warns the console with a yellow escape
240     *
241     * @param item the item to log
242     */
243    public void warn(String item) {
244        internal().warn(Console.YELLOW + item + Console.RESET);
245    }
246
247    /**
248     * Logs to the logger with a green escape
249     *
250     * @param item the item to log
251     */
252    public void success(String item) {
253        log(Console.GREEN + item);
254    }
255
256    /**
257     * Formats a throwable to be logged to the console
258     *
259     * <p>By default this is not needed as the concurrent which runs almost all operations by default have a set
260     * uncaught exception handler</p>
261     *
262     * @param throwable the error to log
263     */
264    public void error(Throwable throwable) {
265        internal().error(throwable);
266    }
267}