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}