001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.reef.util.logging; 020 021import java.io.IOException; 022import java.io.PrintWriter; 023import java.io.StringWriter; 024import java.util.ArrayList; 025import java.util.Date; 026import java.util.List; 027import java.util.logging.Formatter; 028import java.util.logging.LogManager; 029import java.util.logging.LogRecord; 030 031/** 032 * A denser logging format for REEF that is similar to the standard SimpleFormatter. 033 * <p> 034 * The following config properties are available: 035 * <p> 036 * * `org.apache.reef.util.logging.ThreadLogFormatter.format` 037 * is a format string for String.format() that takes same arguments and in 038 * the same order as the standard SimpleFormatter, plus the thread name: 039 * 1. date 040 * 2. class and method name 041 * 3. logger name 042 * 4. logging level 043 * 5. message 044 * 6. stack trace 045 * 7. thread name 046 * <p> 047 * * `org.apache.reef.util.logging.ThreadLogFormatter.dropPrefix` 048 * contains a comma-separated list of package name prefixes that should be 049 * removed from the class name for logging. e.g. value `com.microsoft.,org.apache.` 050 * will have the formatter write class `org.apache.reef.util.logging.Config` as 051 * `reef.util.logging.Config`. (Note the dot at the end of the prefix). 052 */ 053public final class ThreadLogFormatter extends Formatter { 054 055 private static final String DEFAULT_FORMAT = "%1$tF %1$tT,%1$tL %4$s %2$s %7$s | %5$s%6$s%n"; 056 057 private final List<String> dropPrefix = new ArrayList<>(); 058 private final Date date = new Date(); 059 private final String logFormat; 060 061 public ThreadLogFormatter() { 062 063 super(); 064 final LogManager logManager = LogManager.getLogManager(); 065 final String className = this.getClass().getName(); 066 067 final String format = logManager.getProperty(className + ".format"); 068 this.logFormat = format != null ? format : DEFAULT_FORMAT; 069 070 final String rawDropStr = logManager.getProperty(className + ".dropPrefix"); 071 if (rawDropStr != null) { 072 for (String prefix : rawDropStr.trim().split(",")) { 073 prefix = prefix.trim(); 074 if (!prefix.isEmpty()) { 075 this.dropPrefix.add(prefix); 076 } 077 } 078 } 079 } 080 081 /** 082 * Format the log string. Internally, it uses `String.format()` that takes same 083 * arguments and in the same order as the standard SimpleFormatter, plus the thread name: 084 * 1. date 085 * 2. class and method name 086 * 3. logger name 087 * 4. logging level 088 * 5. message 089 * 6. stack trace 090 * 7. thread name 091 * 092 * @return string to be written to the log. 093 */ 094 @Override 095 public String format(final LogRecord logRecord) { 096 this.date.setTime(System.currentTimeMillis()); 097 return String.format( 098 this.logFormat, 099 this.date, 100 this.trimPrefix(logRecord.getSourceClassName()) + "." + logRecord.getSourceMethodName(), 101 logRecord.getLoggerName(), 102 logRecord.getLevel().getLocalizedName(), 103 formatMessage(logRecord), 104 this.getStackTrace(logRecord.getThrown()), 105 Thread.currentThread().getName()); 106 } 107 108 /** 109 * Check if the class name starts with one of the prefixes specified in `dropPrefix`, 110 * and remove it. e.g. for class name `org.apache.reef.util.logging.Config` and 111 * prefix `com.microsoft.` (note the trailing dot), the result will be 112 * `reef.util.logging.Config` 113 */ 114 private String trimPrefix(final String className) { 115 for (final String prefix : this.dropPrefix) { 116 if (className.startsWith(prefix)) { 117 return className.substring(prefix.length()); 118 } 119 } 120 return className; 121 } 122 123 /** 124 * @return a string that contains stack trace of a given exception. 125 * if `error` is null, return an empty string. 126 */ 127 private String getStackTrace(final Throwable error) { 128 if (error != null) { 129 try (final StringWriter sw = new StringWriter(); 130 final PrintWriter pw = new PrintWriter(sw)) { 131 pw.println(); 132 error.printStackTrace(pw); 133 return sw.toString(); 134 } catch (final IOException ex) { 135 // should never happen 136 throw new RuntimeException("Unexpected error while logging stack trace", ex); 137 } 138 } 139 return ""; 140 } 141}