fork download
  1. package test.log
  2. import java.io.*;
  3. import java.nio.charset.CharsetEncoder;
  4. import java.nio.charset.StandardCharsets;
  5. import java.time.Instant;
  6. import java.time.ZoneId;
  7. import java.time.format.DateTimeFormatter;
  8. import java.util.Locale;
  9. import java.util.Random;
  10. import java.util.concurrent.Semaphore;
  11.  
  12. public class Logger {
  13. public static final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("MMM dd, yyyy hh:mm:ss a").withZone(ZoneId.systemDefault());
  14. private final String fileName = "log4j.log";
  15. public final String dirName;
  16. private final long maxFileSizeBytes;
  17. private Level minLevel;
  18. private final int maxBackups;
  19. private final File file;
  20. private CountingWriter w;
  21. private boolean stdoutMode;
  22.  
  23. private static Logger logger;
  24. public static void setLogger(Logger logger){
  25. Logger.logger=logger;
  26. }
  27. public static Logger getLogger(String name) {
  28. if(logger==null) logger=new Logger();
  29. return logger;
  30. }
  31.  
  32. public enum Level{debug(),info(),warn(),error(),fatal();
  33. private final String nameUcase;
  34. Level() {
  35. this.nameUcase = this.name().toUpperCase(Locale.ROOT);
  36. }
  37. }
  38.  
  39. public Logger(long maxFileSizeBytes, Level minLevel, String dirName, boolean stdoutMode, int maxBackups) {
  40. this.maxFileSizeBytes = maxFileSizeBytes;
  41. this.minLevel = minLevel;
  42. this.dirName=dirName;
  43. this.stdoutMode = stdoutMode;
  44. this.file=new File(dirName,fileName);
  45. this.maxBackups = maxBackups;
  46. System.out.println("file= " + this.file);
  47. this.w=initFileWriter();
  48. }
  49.  
  50. private Logger() {
  51. this(1024*1024*20L,Level.info,"/var/log",false, 10);
  52. }
  53.  
  54.  
  55. private CountingWriter initFileWriter() {
  56. try {
  57. final long length ;
  58. final boolean exists = file.exists();
  59. if (exists) {
  60. length = file.length();
  61. }
  62. else {
  63. final File parentFile = file.getParentFile();
  64. parentFile.mkdirs();
  65. length=0;
  66. }
  67. CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder();
  68. FileOutputStream outputStream=new FileOutputStream(file,exists);
  69. final CountingWriter countingWriter = new CountingWriter(new BufferedWriter(new OutputStreamWriter(outputStream, encoder)), length);
  70. stdoutMode=false;
  71. return countingWriter;
  72. } catch (Exception e) {
  73. System.err.println("error initialising logging! Setting 'stdout' mode");
  74. e.printStackTrace();
  75. stdoutMode=true;
  76. }
  77. return null;
  78. }
  79. public void flush() {
  80. try {
  81. w.flush();
  82. } catch (IOException e) {
  83. e.printStackTrace();
  84. }
  85. }
  86.  
  87. private void log(Level level, String message, Throwable e){
  88. if(message==null && e==null) return;
  89. if(level.ordinal()<minLevel.ordinal()) return;
  90. try {
  91. if(stdoutMode){
  92. writeToStdErr(message,e,null,null);
  93. return;
  94. }
  95. if(rollLock.availablePermits()<1){
  96. writeToStdErr(message, e, "rolling", null);
  97. return;
  98. }
  99. w.write(dateFormatter.format(Instant.now()));
  100. w.write(' ');
  101. w.write(level.nameUcase);
  102. w.write(' ');
  103. w.write('[');
  104. w.write(Thread.currentThread().getName());
  105. w.write(']');
  106. w.write(' ');
  107. final boolean hasMessage = message != null;
  108. if(hasMessage){
  109. w.write(message);
  110. }
  111. if(e!=null){
  112. if(hasMessage) w.write(' ');
  113. final PrintWriter pw = new PrintWriter(w, false);
  114. e.printStackTrace(pw);
  115. }
  116. w.write('\n');
  117. if(level.ordinal()>= Level.warn.ordinal() || isDebugEnabled()) w.flush();
  118. //flush immediately (e.g. in example of fatal error
  119. //also flush immediately if debugging: since we don't care so much about perf if we're debugging
  120. if(w.getCountBytes()>=maxFileSizeBytes){
  121. if(rollLock.tryAcquire()){//if someone's already on it, just give up
  122. try {
  123. roll();
  124. } finally {
  125. rollLock.release();
  126. }
  127. }
  128. }
  129.  
  130. } catch (Exception e1) { //last thing we want is logging causing issues
  131. writeToStdErr(message, e, "exception encountered", e1);
  132. }
  133.  
  134. }
  135.  
  136. private void writeToStdErr(String message, Throwable e, String reasonCannotWriteToFile, Exception reasonError) {
  137. if (reasonCannotWriteToFile!=null){
  138. System.err.print("[");
  139. System.err.print(reasonCannotWriteToFile);
  140. System.err.print("] ");
  141. }
  142. if (reasonError!=null){
  143. System.err.print("[");
  144. reasonError.printStackTrace();
  145. System.err.print("] ");
  146. }
  147. if (message!=null) System.err.println(message);
  148. if (e!=null) e.printStackTrace();
  149. }
  150.  
  151. private final Semaphore rollLock = new Semaphore(1);
  152. private void roll() {
  153. System.out.println("LOGGER::: roll thread=" + Thread.currentThread().getName() + " t=" + System.currentTimeMillis());
  154. renameOldBackupFiles();
  155. final File newestBackup = backupFile(1);
  156. try {
  157. w.close();
  158. } catch (IOException e) {
  159. e.printStackTrace();
  160. }
  161. //rename the main log file to newest backup (which we just renamed)
  162. boolean success = file.renameTo(newestBackup);
  163. System.out.println("LOGGER::: rename " + file + " to=" + newestBackup + " success=" + success);
  164. this.w=initFileWriter();//should now be writing to file (which is empty)
  165. }
  166.  
  167. private void renameOldBackupFiles() {
  168. File oldestBackup = backupFile(maxBackups);
  169. if (oldestBackup.exists()) {
  170. boolean deleted = oldestBackup.delete();
  171. System.out.println("LOGGER::: delete " + oldestBackup + " success=" + deleted);
  172. }
  173. // - rename all other backup files <5 in desc order (so log.4 -> log.5 and so on)
  174. for (int backup = maxBackups-1; backup > 0; backup--) {
  175. final File backupFile = backupFile(backup);
  176. if (!backupFile.exists()) continue;
  177. final File destFile = backupFile(backup + 1);
  178. boolean success = backupFile.renameTo(destFile);
  179. System.out.println("LOGGER::: rename " + backupFile + " to=" + destFile + " success=" + success);
  180. }
  181. }
  182.  
  183. private File backupFile(int backupNumber) {
  184. return new File(dirName,String.format("%s.%s",fileName, backupNumber));
  185. }
  186.  
  187.  
  188. private static class CountingWriter extends Writer{
  189. private final Writer writer;
  190. private long countBytes;
  191. public CountingWriter(Writer w, long initialCount) {
  192. this.writer = w;
  193. countBytes=initialCount;
  194. }
  195. public void write(char[] cbuf, int off, int len) throws IOException {
  196. writer.write(cbuf,off,len);
  197. final long wrote = (len - off);
  198. countBytes += wrote;//this will likely be wrong if we use extended chars which take two bytes? ascii-type chars take 1 byte, but some take 2. But probably doesn't matter, unless we start logging korean strings or something?
  199. }
  200. public void flush() throws IOException { writer.flush(); }
  201. public void close() throws IOException { writer.close(); }
  202.  
  203. public long getCountBytes() { return countBytes; }
  204. }
  205.  
  206. //log4j api
  207. public void debug(String format) {debug(format,null); }
  208. public void debug(Throwable e) {debug(null,e); }
  209. public void debug(String format, Throwable e) {log(Level.debug,format,e); }
  210.  
  211. public void info(String format) {info(format,null); }
  212. public void info(Throwable e) {info(null,e); }
  213. public void info(String format, Throwable e) {log(Level.info,format,e); }
  214.  
  215. public void warn(String format) {warn(format,null);}
  216. public void warn(Throwable e) {warn(null,e); }
  217. public void warn(String format, Throwable e) {log(Level.warn,format,e); }
  218.  
  219. public void error(String format) {error(format,null); }
  220. public void error(Throwable e) { error(null,e);}
  221. public void error(String format, Throwable e) {log(Level.error,format,e); }
  222.  
  223. public void fatal(String format) {fatal(format,null); }
  224. public void fatal(Throwable e) {fatal(null,e); }
  225. public void fatal(String format, Throwable e) {
  226. log(Level.fatal,format,e);
  227. }
  228.  
  229. //new api meths
  230. public void warnf(String format, Throwable e, Object... paramters) {warn(String.format(format, (Object[]) paramters),e);}
  231. public void infof(String format, Throwable e, Object... paramters) {info(String.format(format, (Object[]) paramters),e);}
  232. public void debugf(String format, Throwable e, Object... paramters) {debug(String.format(format, (Object[]) paramters),e);}
  233.  
  234.  
  235. public boolean isDebugEnabled() {
  236. return minLevel.ordinal()<=Level.debug.ordinal();
  237. }
  238.  
  239. }
Compilation error #stdin compilation error #stdout 0s 0KB
stdin
Standard input is empty
compilation info
Main.java:11: error: class Logger is public, should be declared in a file named Logger.java
public class Logger {
       ^
1 error
stdout
Standard output is empty