io.datarouter.exception.service.DefaultExceptionRecorder Maven / Gradle / Ivy
/*
* Copyright © 2009 HotPads ([email protected])
*
* 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.datarouter.exception.service;
import java.util.Optional;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.datarouter.exception.config.DatarouterExceptionSettingRoot;
import io.datarouter.exception.conveyors.DatarouterExceptionBuffers;
import io.datarouter.exception.storage.exceptionrecord.DatarouterExceptionRecordPublisherDao;
import io.datarouter.exception.storage.exceptionrecord.ExceptionRecord;
import io.datarouter.exception.storage.httprecord.DatarouterHttpRequestRecordPublisherDao;
import io.datarouter.exception.storage.httprecord.HttpRequestRecord;
import io.datarouter.instrumentation.exception.ExceptionRecordDto;
import io.datarouter.instrumentation.exception.HttpRequestRecordDto;
import io.datarouter.storage.config.properties.ServerName;
import io.datarouter.storage.exception.ExceptionCategory;
import io.datarouter.storage.exception.UnknownExceptionCategory;
import io.datarouter.web.app.WebappName;
import io.datarouter.web.config.DatarouterWebSettingRoot;
import io.datarouter.web.config.service.ServiceName;
import io.datarouter.web.dispatcher.Dispatcher;
import io.datarouter.web.exception.ExceptionCounters;
import io.datarouter.web.exception.ExceptionHandlingConfig;
import io.datarouter.web.exception.ExceptionRecorder;
import io.datarouter.web.exception.WebExceptionCategory;
import io.datarouter.web.handler.BaseHandler;
import io.datarouter.web.monitoring.GitProperties;
import io.datarouter.web.user.session.CurrentSessionInfo;
import io.datarouter.web.user.session.service.Session;
import io.datarouter.web.util.ExceptionTool;
import io.datarouter.web.util.RequestAttributeTool;
public class DefaultExceptionRecorder implements ExceptionRecorder{
private static final Logger logger = LoggerFactory.getLogger(DefaultExceptionRecorder.class);
@Inject
private ExceptionHandlingConfig exceptionHandlingConfig;
@Inject
private GitProperties gitProperties;
@Inject
private ExceptionRecordService exceptionRecordService;
@Inject
private DatarouterExceptionRecordPublisherDao exceptionRecordPublisherDao;
@Inject
private DatarouterHttpRequestRecordPublisherDao httpRequestRecordPublisherDao;
@Inject
private DatarouterWebSettingRoot datarouterWebSettingRoot;
@Inject
private CurrentSessionInfo currentSessionInfo;
@Inject
private WebappName webappName;
@Inject
private DatarouterExceptionSettingRoot settings;
@Inject
private DatarouterExceptionBuffers exceptionBuffers;
@Inject
private ServerName serverName;
@Inject
private ServiceName serviceName;
@Override
public Optional tryRecordException(Throwable exception, String callOrigin){
return tryRecordException(exception, callOrigin, UnknownExceptionCategory.UNKNOWN);
}
@Override
public Optional tryRecordException(
Throwable exception,
String callOrigin,
ExceptionCategory category){
try{
DefaultExceptionRecorderDetails exceptionDetails = detectExceptionLocation(exception);
return Optional.of(recordException(
exception,
category,
exceptionDetails.className,
exceptionDetails.methodName,
exceptionDetails.lineNumber,
callOrigin));
}catch(Exception e){
logger.warn("Exception while recording an exception", e);
}
return Optional.empty();
}
@Override
public ExceptionRecordDto recordException(
Throwable exception,
ExceptionCategory category,
String location,
String methodName,
Integer lineNumber,
String callOrigin){
if(callOrigin == null){
callOrigin = location;
}
ExceptionCounters.inc(category.name());
ExceptionCounters.inc(category.name() + " " + webappName);
ExceptionCounters.inc(exception.getClass().getName());
ExceptionCounters.inc(callOrigin);
ExceptionCounters.inc(exception.getClass().getName() + " " + callOrigin);
ExceptionRecord exceptionRecord = new ExceptionRecord(
serviceName.get(),
serverName.get(),
ExceptionTool.getStackTraceAsString(exception),
exception.getClass().getName(),
gitProperties.getIdAbbrev().orElse(GitProperties.UNKNOWN_STRING),
location,
methodName,
lineNumber,
callOrigin);
exceptionRecord.trimFields();
exceptionBuffers.exceptionRecordBuffer.offer(exceptionRecord);
logger.warn("Exception recorded ({})", exceptionRecordService.buildExceptionLinkForCurrentServer(
exceptionRecord));
if(settings.publishRecords.get()){
exceptionRecordPublisherDao.put(exceptionRecord);
}
if(exceptionHandlingConfig.shouldReportError(exceptionRecord.toDto())){
report(exceptionRecord, category);
}
return exceptionRecord.toDto();
}
@Override
public Optional tryRecordExceptionAndHttpRequest(
Throwable exception,
String callOrigin,
HttpServletRequest request){
try{
DefaultExceptionRecorderDetails exceptionDetails = detectExceptionLocation(exception);
return Optional.of(recordExceptionAndHttpRequest(
exception,
exceptionDetails.className,
exceptionDetails.methodName,
exceptionDetails.lineNumber,
request,
callOrigin));
}catch(Exception e){
logger.warn("Exception while recording an exception", e);
return Optional.empty();
}
}
@Override
public ExceptionRecordDto recordExceptionAndHttpRequest(
Throwable exception,
String location,
String methodName,
Integer lineNumber,
HttpServletRequest request,
String callOrigin){
ExceptionRecordDto exceptionRecord = recordException(
exception,
WebExceptionCategory.HTTP_REQUEST,
location,
methodName,
lineNumber,
callOrigin);
recordHttpRequest(request, exceptionRecord, true);
return exceptionRecord;
}
protected void report(@SuppressWarnings("unused") ExceptionRecord exceptionRecord,
@SuppressWarnings("unused") ExceptionCategory category){
}
@Override
public void recordHttpRequest(HttpServletRequest request){
recordHttpRequest(request, null, false);
}
private void recordHttpRequest(
HttpServletRequest request,
ExceptionRecordDto exceptionRecord,
boolean publish){
Optional userToken = currentSessionInfo.getSession(request).map(Session::getUserToken);
String userRoles = currentSessionInfo.getRoles(request).toString();
boolean omitPayload = RequestAttributeTool.get(request, Dispatcher.TRANSMITS_PII).orElse(false);
HttpRequestRecord httpRequestRecord = new HttpRequestRecord(
exceptionRecord == null ? null : exceptionRecord.id,
RequestAttributeTool.get(request, BaseHandler.TRACE_CONTEXT),
request,
userRoles,
userToken.orElse(null),
omitPayload);
httpRequestRecord.trimFields();
exceptionBuffers.httpRequestRecordBuffer.offer(httpRequestRecord);
httpRequestRecord.trimBinaryBody(HttpRequestRecordDto.BINARY_BODY_MAX_SIZE);
if(publish && settings.publishRecords.get()){
httpRequestRecordPublisherDao.put(httpRequestRecord);
}
}
private DefaultExceptionRecorderDetails detectExceptionLocation(Throwable wholeException){
Throwable rootCause = ExceptionUtils.getRootCause(wholeException);
Throwable exception = Optional.ofNullable(rootCause).orElse(wholeException);
StackTraceElement stackTraceElement = searchClassName(exception)
.orElseGet(() -> {
StackTraceElement[] stackTrace = exception.getStackTrace();
// stackTrace is often null in case of OOM
return stackTrace.length == 0 ? null : stackTrace[0];
});
if(stackTraceElement == null){
return new DefaultExceptionRecorderDetails(
"noClass",
"noMethod",
0);
}
return new DefaultExceptionRecorderDetails(
stackTraceElement.getClassName(),
stackTraceElement.getMethodName(),
stackTraceElement.getLineNumber());
}
private Optional searchClassName(Throwable cause){
for(StackTraceElement element : cause.getStackTrace()){
for(String highlight : datarouterWebSettingRoot.stackTraceHighlights.get()){
if(element.getClassName().contains(highlight)){
return Optional.of(element);
}
}
}
return Optional.empty();
}
private static class DefaultExceptionRecorderDetails{
public final String className;
public final String methodName;
public final int lineNumber;
protected DefaultExceptionRecorderDetails(String className, String methodName, int lineNumber){
this.className = className;
this.methodName = methodName;
this.lineNumber = lineNumber;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy