null

Liferay 7.x, JSF и ошибки - в логах пусто, что делать?

При переносе JSF-портлетов с Liferay 6.2 на Liferay 7.1 обнаружилось неприятное поведение - далеко не все исключения, происходящие при работе при работе приложения, фиксируются в логах. Некоторые из них отображаются на странице, другие не отображаются вообще нигде. Были обнаружены следующие ситуации:

  1. @PostConstruct методы - исключение отображается только на странице.
  2. action'ы у не-AJAX компонентов - исключение не отображается вообще.
  3. Исключения при рендере ответа (например, EL-ValueExpression бросило исключение) - ошибки отображаются на странице.

@PostConstruct

import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;

@ManagedBean
@ViewScoped
public class InitFailureBean {
    @PostConstruct
    private void init() {
        throw new RuntimeException("HELLO THERE!");
    }

    public String getProp() {
        return "Hello, cruel world!";
    }

}

view.xhtml

<f:view
        xmlns="http://www.w3.org/1999/xhtml"
        xmlns:f="http://xmlns.jcp.org/jsf/core"
        xmlns:h="http://xmlns.jcp.org/jsf/html"
>
    <h:head/>
    <h:body>
        <h:outputText value="#{initFailureBean.prop}"/>
    </h:body>
</f:view>

CommandButton action

import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;

@ManagedBean
@ViewScoped
public class AjaxFailureBean {

    public static final Log log = LogFactoryUtil.getLog(AjaxFailureBean.class);

    public void actionFailure() {
        log.info("There will be an error");
        if (true) {
            throw new RuntimeException("Invisible error!");
        }
        log.info("After exception");
    }

}
<f:view
        xmlns="http://www.w3.org/1999/xhtml"
        xmlns:f="http://xmlns.jcp.org/jsf/core"
        xmlns:h="http://xmlns.jcp.org/jsf/html"
        xmlns:p="http://primefaces.org/ui"
        xmlns:ui="http://java.sun.com/jsf/facelets"
>
    <h:body>
        <h:form>
            <p:commandButton value="Action failure!" ajax="false" action="#{ajaxFailureBean.actionFailure}" update="sampleOutput"/>
        </h:form>
    </h:body>
</f:view>

Страница после нажатия кнопки

Содержимое лога после трёх нажатий кнопки 

Исключение при рендере

import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;

@ManagedBean
@ViewScoped
public class RenderFailureBean {

    public static final Log log = LogFactoryUtil.getLog(RenderFailureBean.class);

    public String getFailureProp() {
        log.info("Before exception");
        if (true) {
            throw new RuntimeException("Exception here!");
        }
        log.info("After exception");
        return "Hello, cruel world!";
    }
}
<f:view
        xmlns="http://www.w3.org/1999/xhtml"
        xmlns:f="http://xmlns.jcp.org/jsf/core"
        xmlns:h="http://xmlns.jcp.org/jsf/html"
        xmlns:p="http://primefaces.org/ui"
        xmlns:ui="http://java.sun.com/jsf/facelets"
>
    <h:body>
        <h:form>
            <h:outputText value="#{renderFailureBean.failureProp}"/>
        </h:form>
    </h:body>
</f:view>

Страница после ошибки:

Логи после ошибки:

Решение проблемы

Для решения этой проблемы был написан простой обработчик исключений JSF, добавляющий сообщения об ошибке в логи.

import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;

import javax.faces.FacesException;
import javax.faces.context.ExceptionHandler;
import javax.faces.context.ExceptionHandlerWrapper;
import javax.faces.event.ExceptionQueuedEvent;

public class LiferayJsfExceptionLogger extends ExceptionHandlerWrapper {
    private static final Log log = LogFactoryUtil.getLog(LiferayJsfExceptionLogger.class);
    private ExceptionHandler wrapped;

    public LiferayJsfExceptionLogger(ExceptionHandler wrapped) {
        this.wrapped = wrapped;
    }

    @Override
    public ExceptionHandler getWrapped() {
        return wrapped;
    }

    @Override
    public void handle() throws FacesException {

        for (ExceptionQueuedEvent event : getUnhandledExceptionQueuedEvents()) {
            Throwable throwable = event.getContext().getException();
            log.error(throwable);
        }
        super.handle();
    }
}
import javax.faces.context.ExceptionHandler;
import javax.faces.context.ExceptionHandlerFactory;

public class LiferayJsfExceptionLoggerFactory extends ExceptionHandlerFactory {
    private final ExceptionHandlerFactory parent;

    public LiferayJsfExceptionLoggerFactory(ExceptionHandlerFactory parent) {
        this.parent = parent;
    }

    @Override
    public ExceptionHandler getExceptionHandler() {
        return new LiferayJsfExceptionLogger(parent.getExceptionHandler());
    }
}
<?xml version="1.0"?>

<faces-config
	version="2.2"
	xmlns="http://xmlns.jcp.org/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd"
>
	<factory>
		<exception-handler-factory>com.tuneit.ibox.jsf.LiferayJsfExceptionLoggerFactory</exception-handler-factory>
	</factory>
</faces-config>

Кроме того, всё это можно оформить в виде отдельной библиотеки и подключать её, как зависимость  - тогда отпадёт потребность в ручной регистрации фабрики в faces-config.xml.

Проверяем предыдущие примеры (например, с ошибкой при рендере).

Страница:

Логи:

 

Засим откланиваюсь, прощайте.