/*
* $Id$
*
* SARL is an general-purpose agent programming language.
* More details on http://www.sarl.io
*
* Copyright (C) 2014-2017 the original authors or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.janusproject.kernel.services.jdk.logging;
import java.text.MessageFormat;
import java.util.logging.Filter;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import com.google.common.base.Strings;
import com.google.common.util.concurrent.Service;
import com.google.inject.Inject;
import io.janusproject.JanusConfig;
import io.janusproject.services.AbstractDependentService;
import io.janusproject.services.logging.LogService;
import io.janusproject.util.ClassFinder;
/**
* This class enables to log information by ensuring that the values of the parameters are not evaluated until the information
* should be really log, according to the log level. The logger is injected.
*
* <p>The LogService considers the parameters of the functions as:<ul>
* <li>the message is the the message in the property file;</li>
* <li>the parameters are the values that will replace the strings {0}, {1}, {2}... in the text extracted from the
* resource property.</li>
* </ul>
*
* <p>If a <code>Throwable</code> is passed as parameter, the text of the exception is retrieved.
*
* <p>If a <code>Callable</code> is passed as parameter, the object is automatically called.
*
* <p>If a <code>LogParam</code> is passed as parameter, the <code>toString</code> function will be invoked.
*
* <p>For all the other objects, the {@link #toString()} function is invoked.
*
* <p>This service is thread-safe.
*
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
public class StandardLogService extends AbstractDependentService implements LogService {
private Logger logger;
private LoggerCallerProvider loggerCallerProvider = new StackTraceLoggerCallerProvider();
/**
* Construct the service.
*/
public StandardLogService() {
//
}
/**
* Replies the object that permits to determine the caller of the logger.
*
* @return the object that permits to determine the caller of the logger.
*/
public LoggerCallerProvider getLoggerCaller() {
return this.loggerCallerProvider;
}
/**
* Change the object that permits to determine the caller of the logger.
*
* @param provider - the object that permits to determine the caller of the logger.
*/
public void setLoggerCaller(LoggerCallerProvider provider) {
if (provider == null) {
this.loggerCallerProvider = new StackTraceLoggerCallerProvider();
} else {
this.loggerCallerProvider = provider;
}
}
@Override
public final Class<? extends Service> getServiceType() {
return LogService.class;
}
/**
* Replies if this service permits to log the messages.
*
* @return <code>true</code> if the messages are loggeable, <code>false</code> otherwise.
*/
protected boolean isLogEnabled() {
return state().ordinal() <= State.RUNNING.ordinal();
}
private static LogRecord createLogRecord(Level level, String text, boolean exception, Object... message) {
Throwable realException = null;
if (exception) {
for (final Object m : message) {
if (m instanceof Throwable) {
realException = (Throwable) m;
break;
}
}
}
final LogRecord record = new LogRecord(level, text);
if (realException != null) {
record.setThrown(realException);
}
if (record.getLoggerName() == null) {
record.setLoggerName(JanusConfig.JANUS_DEFAULT_PLATFORM_NAME);
}
return record;
}
private void writeInLog(Level level, boolean exception, String message, Object... params) {
if (isLogEnabled() && this.logger.isLoggable(level)) {
final LoggerCaller caller = this.loggerCallerProvider.getLoggerCaller();
final String text = MessageFormat.format(message, params);
final LogRecord record = createLogRecord(level, text, exception, params);
record.setSourceClassName(caller.getTypeName());
final String methodName = caller.getMethod();
if (!Strings.isNullOrEmpty(methodName)) {
record.setSourceMethodName(methodName);
}
this.logger.log(record);
}
}
@Override
public void log(LogRecord record) {
if (isLogEnabled()) {
this.logger.log(record);
}
}
@Override
public void log(Level level, String message, Object... params) {
writeInLog(level, true, message, params);
}
@Override
public void info(String message, Object... params) {
writeInLog(Level.INFO, false, message, params);
}
@Override
public void fineInfo(String message, Object... params) {
writeInLog(Level.FINE, false, message, params);
}
@Override
public void finerInfo(String message, Object... params) {
writeInLog(Level.FINER, false, message, params);
}
@Override
public void debug(String message, Object... params) {
writeInLog(Level.FINEST, true, message, params);
}
@Override
public void warning(String message, Object... params) {
writeInLog(Level.WARNING, true, message, params);
}
@Override
public void warning(Throwable exception) {
writeInLog(Level.WARNING, true, exception.getLocalizedMessage(), exception);
}
@Override
public void error(String message, Object... params) {
writeInLog(Level.SEVERE, true, message, params);
}
@Override
public void error(Throwable exception) {
writeInLog(Level.SEVERE, true, exception.getLocalizedMessage(), exception);
}
@Override
public Logger getLogger() {
return this.logger;
}
@Inject
@Override
public void setLogger(Logger logger) {
if (logger != null) {
this.logger = logger;
}
}
@Override
public void setFilter(Filter filter) {
this.logger.setFilter(filter);
}
@Override
public Filter getFilter() {
return this.logger.getFilter();
}
@Override
public boolean isLoggeable(Level level) {
return isLogEnabled() && this.logger.isLoggable(level);
}
@Override
public Level getLevel() {
return this.logger.getLevel();
}
@Override
public void setLevel(Level level) {
this.logger.setLevel(level);
}
@Override
protected void doStart() {
notifyStarted();
}
@Override
protected void doStop() {
notifyStopped();
}
/**
* Provides the type of the caller of the logger.
*
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
public interface LoggerCallerProvider {
/**
* Replies the logger caller.
*
* @return the logger caller.
*/
LoggerCaller getLoggerCaller();
}
/**
* Provides the type of the caller of the logger.
*
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
public static class LoggerCaller {
private final String methodName;
private final String className;
/**
* @param className - the type of the caller.
* @param methodName - the name of the called method.
*/
public LoggerCaller(String className, String methodName) {
this.className = className;
this.methodName = methodName;
}
/**
* Replies the name of the type of the logger caller.
*
* @return the name of type of the logger caller.
*/
public String getTypeName() {
return this.className;
}
/**
* Replies the name of the last method encountered in the stack trace.
*
* @return the name of the last invoked method of the logger caller.
*/
public String getMethod() {
return this.methodName;
}
}
/**
* Provider of calling function on the stack trace.
*
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
public static class StackTraceLoggerCallerProvider implements LoggerCallerProvider {
/**
* Construct.
*/
public StackTraceLoggerCallerProvider() {
//
}
private static StackTraceElement getStackTraceElement() {
final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
// Start at 1 because the top of the stack corresponds to getStackTrace.
for (int i = 1; i < stackTrace.length; ++i) {
final String className = stackTrace[i].getClassName();
final Class<?> type = ClassFinder.findClass(className);
if (type != null) {
return stackTrace[i];
}
}
return null;
}
@Override
public LoggerCaller getLoggerCaller() {
final StackTraceElement element = getStackTraceElement();
if (element != null) {
return new LoggerCaller(element.getClassName(), element.getMethodName());
}
return null;
}
}
}