package org.gcube.smartgears.handlers.application.request; import java.util.concurrent.TimeUnit; import javax.xml.bind.annotation.XmlRootElement; import org.gcube.accounting.datamodel.UsageRecord.OperationResult; import org.gcube.accounting.datamodel.usagerecords.ServiceUsageRecord; import org.gcube.accounting.persistence.AccountingPersistence; import org.gcube.accounting.persistence.AccountingPersistenceFactory; import org.gcube.common.security.providers.SecretManagerProvider; import org.gcube.smartgears.Constants; import org.gcube.smartgears.configuration.Mode; import org.gcube.smartgears.context.application.ApplicationContext; import org.gcube.smartgears.handlers.application.RequestEvent; import org.gcube.smartgears.handlers.application.RequestHandler; import org.gcube.smartgears.handlers.application.ResponseEvent; import org.gcube.smartgears.utils.InnerMethodName; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import io.micrometer.core.instrument.Metrics; @XmlRootElement(name = Constants.request_accounting) public class RequestAccounting extends RequestHandler { private static Logger log = LoggerFactory.getLogger(RequestAccounting.class); private static ThreadLocal startCallThreadLocal = new ThreadLocal(); private static final String UNKNOWN = "Unknown"; @Override public String getName() { return Constants.request_accounting; } @Override public void handleRequest(RequestEvent e) { ApplicationContext appContext = e.context(); String context = getContext(appContext); if (InnerMethodName.instance.get()==null) { String calledMethod = e.request().getRequestURI().substring(e.request().getContextPath().length()); if (calledMethod.isEmpty()) calledMethod = "/"; calledMethod= e.request().getMethod()+" "+calledMethod; InnerMethodName.instance.set(calledMethod); } String caller = SecretManagerProvider.instance.get()!=null ? SecretManagerProvider.instance.get().getOwner().getId(): UNKNOWN; startCallThreadLocal.set(System.currentTimeMillis()); log.info("REQUEST START ON {}:{}({}) CALLED FROM {}@{} IN SCOPE {} ", appContext.configuration().name(),appContext.configuration().serviceClass(), InnerMethodName.instance.get(), caller, e.request().getRemoteHost(), context ); } @Override public void handleResponse(ResponseEvent e) { ApplicationContext appContext = e.context(); try { String context = getContext(appContext); String caller = SecretManagerProvider.instance.get()!=null ? SecretManagerProvider.instance.get().getOwner().getId(): UNKNOWN; String callerQualifier = UNKNOWN; //retieves caller Ip when there is a proxy String callerIp = e.request().getHeader("x-forwarded-for"); if(callerIp==null) callerIp=e.request().getRemoteHost(); boolean success = e.response().getStatus()<400; if (appContext.container().configuration().mode()!=Mode.offline) generateAccounting(caller,callerQualifier,callerIp==null?UNKNOWN:callerIp , success, context, appContext); long durationInMillis = System.currentTimeMillis()-startCallThreadLocal.get(); Metrics.globalRegistry.timer("http.requests", "response",Integer.toString(e.response().getStatus()) , "context", context, "result", success?"SUCCEDED":"FAILED", "caller-ip", callerIp, "caller-username", caller, "service-class", appContext.configuration().serviceClass(), "service-name", appContext.configuration().name(), "method", InnerMethodName.instance.get()).record(durationInMillis, TimeUnit.MILLISECONDS); log.info("REQUEST SERVED ON {}:{}({}) CALLED FROM {}@{} IN SCOPE {} {}(CODE {}) IN {} millis", appContext.configuration().name(),appContext.configuration().serviceClass(), InnerMethodName.instance.get(), caller, callerIp, context, success?"SUCCEDED":"FAILED", e.response().getStatus(),durationInMillis); }catch (Exception e1) { log.error("error on accounting",e); throw e1; } finally { startCallThreadLocal.remove(); InnerMethodName.instance.reset(); } } void generateAccounting(String caller, String callerQualifier, String remoteHost, boolean success, String gcubeContext, ApplicationContext appContext){ AccountingPersistenceFactory.setFallbackLocation(appContext.container().configuration().accountingFallbackLocation()); AccountingPersistence persistence = AccountingPersistenceFactory.getPersistence(); ServiceUsageRecord serviceUsageRecord = new ServiceUsageRecord(); try{ serviceUsageRecord.setConsumerId(caller); serviceUsageRecord.setCallerQualifier(callerQualifier); serviceUsageRecord.setScope(gcubeContext); serviceUsageRecord.setServiceClass(appContext.configuration().serviceClass()); serviceUsageRecord.setServiceName(appContext.configuration().name()); serviceUsageRecord.setHost(appContext.container().configuration().hostname()+":"+appContext.container().configuration().port()); serviceUsageRecord.setCalledMethod(InnerMethodName.instance.get()); serviceUsageRecord.setCallerHost(remoteHost); serviceUsageRecord.setOperationResult(success?OperationResult.SUCCESS:OperationResult.FAILED); serviceUsageRecord.setDuration(System.currentTimeMillis()-startCallThreadLocal.get()); persistence.account(serviceUsageRecord); }catch(Exception ex){ log.warn("invalid record passed to accounting ",ex); } } private String getContext(ApplicationContext appContext) { String infrastructure = appContext.container().configuration().infrastructure(); String context= "/"+infrastructure; if (SecretManagerProvider.instance.get() != null) context = SecretManagerProvider.instance.get().getContext(); return context; } @Override public String toString() { return getName(); } }