Initial implementation of process tracers

This commit is contained in:
2025-02-10 09:37:59 -06:00
parent ec713553b8
commit 9072ce2426
9 changed files with 839 additions and 1 deletions

View File

@ -32,6 +32,7 @@ import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import com.kingsrook.qqq.backend.core.actions.ActionHelper; import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.NoCodeWidgetRenderer; import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.NoCodeWidgetRenderer;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction; import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction; import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
@ -53,6 +54,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput; import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.NoCodeWidgetFrontendComponentMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.NoCodeWidgetFrontendComponentMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
@ -63,6 +65,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QStateMachineStep
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
import com.kingsrook.qqq.backend.core.model.session.QSession; import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.processes.implementations.basepull.BasepullConfiguration; import com.kingsrook.qqq.backend.core.processes.implementations.basepull.BasepullConfiguration;
import com.kingsrook.qqq.backend.core.processes.tracing.ProcessTracerInterface;
import com.kingsrook.qqq.backend.core.state.InMemoryStateProvider; import com.kingsrook.qqq.backend.core.state.InMemoryStateProvider;
import com.kingsrook.qqq.backend.core.state.StateProviderInterface; import com.kingsrook.qqq.backend.core.state.StateProviderInterface;
import com.kingsrook.qqq.backend.core.state.StateType; import com.kingsrook.qqq.backend.core.state.StateType;
@ -71,6 +74,7 @@ import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils; import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils; import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import org.apache.commons.lang.BooleanUtils; import org.apache.commons.lang.BooleanUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
/******************************************************************************* /*******************************************************************************
@ -87,12 +91,16 @@ public class RunProcessAction
public static final String BASEPULL_TIMESTAMP_FIELD = "basepullTimestampField"; public static final String BASEPULL_TIMESTAMP_FIELD = "basepullTimestampField";
public static final String BASEPULL_CONFIGURATION = "basepullConfiguration"; public static final String BASEPULL_CONFIGURATION = "basepullConfiguration";
public static final String PROCESS_TRACER_CODE_REFERENCE_FIELD = "processTracerCodeReference";
//////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////
// indicator that the timestamp field should be updated - e.g., the execute step is finished. // // indicator that the timestamp field should be updated - e.g., the execute step is finished. //
//////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////
public static final String BASEPULL_READY_TO_UPDATE_TIMESTAMP_FIELD = "basepullReadyToUpdateTimestamp"; public static final String BASEPULL_READY_TO_UPDATE_TIMESTAMP_FIELD = "basepullReadyToUpdateTimestamp";
public static final String BASEPULL_DID_QUERY_USING_TIMESTAMP_FIELD = "basepullDidQueryUsingTimestamp"; public static final String BASEPULL_DID_QUERY_USING_TIMESTAMP_FIELD = "basepullDidQueryUsingTimestamp";
private ProcessTracerInterface processTracer;
/******************************************************************************* /*******************************************************************************
@ -110,6 +118,8 @@ public class RunProcessAction
RunProcessOutput runProcessOutput = new RunProcessOutput(); RunProcessOutput runProcessOutput = new RunProcessOutput();
traceStartOrResume(runProcessInput, process);
////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////
// generate a UUID for the process, if one wasn't given // // generate a UUID for the process, if one wasn't given //
////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////
@ -166,6 +176,7 @@ public class RunProcessAction
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
// upon exception (e.g., one thrown by a step), throw it. // // upon exception (e.g., one thrown by a step), throw it. //
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
traceBreakOrFinish(runProcessInput, runProcessOutput, qe);
throw (qe); throw (qe);
} }
catch(Exception e) catch(Exception e)
@ -173,6 +184,7 @@ public class RunProcessAction
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
// upon exception (e.g., one thrown by a step), throw it. // // upon exception (e.g., one thrown by a step), throw it. //
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
traceBreakOrFinish(runProcessInput, runProcessOutput, e);
throw (new QException("Error running process", e)); throw (new QException("Error running process", e));
} }
finally finally
@ -183,6 +195,8 @@ public class RunProcessAction
runProcessOutput.setProcessState(processState); runProcessOutput.setProcessState(processState);
} }
traceBreakOrFinish(runProcessInput, runProcessOutput, null);
return (runProcessOutput); return (runProcessOutput);
} }
@ -630,6 +644,7 @@ public class RunProcessAction
runBackendStepInput.setCallback(runProcessInput.getCallback()); runBackendStepInput.setCallback(runProcessInput.getCallback());
runBackendStepInput.setFrontendStepBehavior(runProcessInput.getFrontendStepBehavior()); runBackendStepInput.setFrontendStepBehavior(runProcessInput.getFrontendStepBehavior());
runBackendStepInput.setAsyncJobCallback(runProcessInput.getAsyncJobCallback()); runBackendStepInput.setAsyncJobCallback(runProcessInput.getAsyncJobCallback());
runBackendStepInput.setProcessTracer(processTracer);
runBackendStepInput.setTableName(process.getTableName()); runBackendStepInput.setTableName(process.getTableName());
if(!StringUtils.hasContent(runBackendStepInput.getTableName())) if(!StringUtils.hasContent(runBackendStepInput.getTableName()))
@ -651,9 +666,13 @@ public class RunProcessAction
runBackendStepInput.setBasepullLastRunTime((Instant) runProcessInput.getValues().get(BASEPULL_LAST_RUNTIME_KEY)); runBackendStepInput.setBasepullLastRunTime((Instant) runProcessInput.getValues().get(BASEPULL_LAST_RUNTIME_KEY));
} }
traceStepStart(runBackendStepInput);
RunBackendStepOutput runBackendStepOutput = new RunBackendStepAction().execute(runBackendStepInput); RunBackendStepOutput runBackendStepOutput = new RunBackendStepAction().execute(runBackendStepInput);
storeState(stateKey, runBackendStepOutput.getProcessState()); storeState(stateKey, runBackendStepOutput.getProcessState());
traceStepFinish(runBackendStepInput, runBackendStepOutput);
if(runBackendStepOutput.getException() != null) if(runBackendStepOutput.getException() != null)
{ {
runProcessOutput.setException(runBackendStepOutput.getException()); runProcessOutput.setException(runBackendStepOutput.getException());
@ -931,4 +950,153 @@ public class RunProcessAction
runProcessInput.getValues().put(BASEPULL_TIMESTAMP_FIELD, basepullConfiguration.getTimestampField()); runProcessInput.getValues().put(BASEPULL_TIMESTAMP_FIELD, basepullConfiguration.getTimestampField());
runProcessInput.getValues().put(BASEPULL_CONFIGURATION, basepullConfiguration); runProcessInput.getValues().put(BASEPULL_CONFIGURATION, basepullConfiguration);
} }
/***************************************************************************
**
***************************************************************************/
private void setupProcessTracer(RunProcessInput runProcessInput, QProcessMetaData process)
{
try
{
if(process.getProcessTracerCodeReference() != null)
{
processTracer = QCodeLoader.getAdHoc(ProcessTracerInterface.class, process.getProcessTracerCodeReference());
}
Serializable processTracerCodeReference = runProcessInput.getValue(PROCESS_TRACER_CODE_REFERENCE_FIELD);
if(processTracerCodeReference != null)
{
if(processTracerCodeReference instanceof QCodeReference codeReference)
{
processTracer = QCodeLoader.getAdHoc(ProcessTracerInterface.class, codeReference);
}
}
}
catch(Exception e)
{
LOG.warn("Error setting up processTracer", e, logPair("processName", runProcessInput.getProcessName()));
}
}
/***************************************************************************
**
***************************************************************************/
private void traceStartOrResume(RunProcessInput runProcessInput, QProcessMetaData process)
{
setupProcessTracer(runProcessInput, process);
try
{
if(processTracer != null)
{
if(StringUtils.hasContent(runProcessInput.getStartAfterStep()) || StringUtils.hasContent(runProcessInput.getStartAtStep()))
{
processTracer.handleProcessResume(runProcessInput);
}
else
{
processTracer.handleProcessStart(runProcessInput);
}
}
}
catch(Exception e)
{
LOG.info("Error in traceStart", e, logPair("processName", runProcessInput.getProcessName()));
}
}
/***************************************************************************
**
***************************************************************************/
private void traceBreakOrFinish(RunProcessInput runProcessInput, RunProcessOutput runProcessOutput, Exception processException)
{
try
{
if(processTracer != null)
{
ProcessState processState = runProcessOutput.getProcessState();
boolean isBreak = true;
/////////////////////////////////////////////////////////////
// if there's no next step, that means the process is done //
/////////////////////////////////////////////////////////////
if(processState.getNextStepName().isEmpty())
{
isBreak = false;
}
else
{
/////////////////////////////////////////////////////////////////
// or if the next step is the last index, then we're also done //
/////////////////////////////////////////////////////////////////
String nextStepName = processState.getNextStepName().get();
int nextStepIndex = processState.getStepList().indexOf(nextStepName);
if(nextStepIndex == processState.getStepList().size() - 1)
{
isBreak = false;
}
}
if(isBreak)
{
processTracer.handleProcessBreak(runProcessInput, runProcessOutput, processException);
}
else
{
processTracer.handleProcessFinish(runProcessInput, runProcessOutput, processException);
}
}
}
catch(Exception e)
{
LOG.info("Error in traceProcessFinish", e, logPair("processName", runProcessInput.getProcessName()));
}
}
/***************************************************************************
**
***************************************************************************/
private void traceStepStart(RunBackendStepInput runBackendStepInput)
{
try
{
if(processTracer != null)
{
processTracer.handleStepStart(runBackendStepInput);
}
}
catch(Exception e)
{
LOG.info("Error in traceStepFinish", e, logPair("processName", runBackendStepInput.getProcessName()), logPair("stepName", runBackendStepInput.getStepName()));
}
}
/***************************************************************************
**
***************************************************************************/
private void traceStepFinish(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput)
{
try
{
if(processTracer != null)
{
processTracer.handleStepFinish(runBackendStepInput, runBackendStepOutput);
}
}
catch(Exception e)
{
LOG.info("Error in traceStepFinish", e, logPair("processName", runBackendStepInput.getProcessName()), logPair("stepName", runBackendStepInput.getStepName()));
}
}
} }

View File

@ -27,17 +27,22 @@ import java.time.Instant;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobCallback; import com.kingsrook.qqq.backend.core.actions.async.AsyncJobCallback;
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobStatus; import com.kingsrook.qqq.backend.core.actions.async.AsyncJobStatus;
import com.kingsrook.qqq.backend.core.actions.async.NonPersistedAsyncJobCallback; import com.kingsrook.qqq.backend.core.actions.async.NonPersistedAsyncJobCallback;
import com.kingsrook.qqq.backend.core.actions.processes.QProcessCallback; import com.kingsrook.qqq.backend.core.actions.processes.QProcessCallback;
import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput; import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.processes.tracing.ProcessTracerInterface;
import com.kingsrook.qqq.backend.core.processes.tracing.ProcessTracerMessage;
import com.kingsrook.qqq.backend.core.utils.ValueUtils; import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
/******************************************************************************* /*******************************************************************************
@ -46,6 +51,8 @@ import com.kingsrook.qqq.backend.core.utils.ValueUtils;
*******************************************************************************/ *******************************************************************************/
public class RunBackendStepInput extends AbstractActionInput public class RunBackendStepInput extends AbstractActionInput
{ {
private static final QLogger LOG = QLogger.getLogger(RunBackendStepInput.class);
private ProcessState processState; private ProcessState processState;
private String processName; private String processName;
private String tableName; private String tableName;
@ -55,12 +62,13 @@ public class RunBackendStepInput extends AbstractActionInput
private RunProcessInput.FrontendStepBehavior frontendStepBehavior; private RunProcessInput.FrontendStepBehavior frontendStepBehavior;
private Instant basepullLastRunTime; private Instant basepullLastRunTime;
private ProcessTracerInterface processTracer;
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// note - new fields should generally be added in method: cloneFieldsInto // // note - new fields should generally be added in method: cloneFieldsInto //
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@ -96,6 +104,7 @@ public class RunBackendStepInput extends AbstractActionInput
target.setAsyncJobCallback(getAsyncJobCallback()); target.setAsyncJobCallback(getAsyncJobCallback());
target.setFrontendStepBehavior(getFrontendStepBehavior()); target.setFrontendStepBehavior(getFrontendStepBehavior());
target.setValues(getValues()); target.setValues(getValues());
target.setProcessTracer(getProcessTracer().orElse(null));
} }
@ -535,4 +544,54 @@ public class RunBackendStepInput extends AbstractActionInput
return (this); return (this);
} }
/*******************************************************************************
** Setter for processTracer
*******************************************************************************/
public void setProcessTracer(ProcessTracerInterface processTracer)
{
this.processTracer = processTracer;
}
/*******************************************************************************
** Fluent setter for processTracer
*******************************************************************************/
public RunBackendStepInput withProcessTracer(ProcessTracerInterface processTracer)
{
this.processTracer = processTracer;
return (this);
}
/***************************************************************************
**
***************************************************************************/
public Optional<ProcessTracerInterface> getProcessTracer()
{
return Optional.ofNullable(processTracer);
}
/***************************************************************************
**
***************************************************************************/
public void traceMessage(ProcessTracerMessage message)
{
if(processTracer != null)
{
try
{
processTracer.handleMessage(this, message);
}
catch(Exception e)
{
LOG.warn("Error tracing message", e, logPair("message", message));
}
}
}
} }

View File

@ -31,6 +31,7 @@ import java.util.Set;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface; import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon; import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
@ -70,6 +71,8 @@ public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissi
private VariantRunStrategy variantRunStrategy; private VariantRunStrategy variantRunStrategy;
private String variantBackend; private String variantBackend;
private QCodeReference processTracerCodeReference;
private Map<String, QSupplementalProcessMetaData> supplementalMetaData; private Map<String, QSupplementalProcessMetaData> supplementalMetaData;
@ -866,4 +869,35 @@ public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissi
return (this); return (this);
} }
/*******************************************************************************
** Getter for processTracerCodeReference
*******************************************************************************/
public QCodeReference getProcessTracerCodeReference()
{
return (this.processTracerCodeReference);
}
/*******************************************************************************
** Setter for processTracerCodeReference
*******************************************************************************/
public void setProcessTracerCodeReference(QCodeReference processTracerCodeReference)
{
this.processTracerCodeReference = processTracerCodeReference;
}
/*******************************************************************************
** Fluent setter for processTracerCodeReference
*******************************************************************************/
public QProcessMetaData withProcessTracerCodeReference(QCodeReference processTracerCodeReference)
{
this.processTracerCodeReference = processTracerCodeReference;
return (this);
}
} }

View File

@ -0,0 +1,154 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.processes.tracing;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.logging.LogPair;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLine;
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLineInterface;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput;
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcess;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
/*******************************************************************************
** Implementation of ProcessTracerInterface that writes messages to the Logger.
*******************************************************************************/
public class LoggingProcessTracer implements ProcessTracerInterface
{
private static final QLogger LOG = QLogger.getLogger(LoggingProcessTracer.class);
private long startMillis;
/***************************************************************************
**
***************************************************************************/
@Override
public void handleProcessStart(RunProcessInput runProcessInput)
{
startMillis = System.currentTimeMillis();
LOG.info("Starting process", logPair("name", runProcessInput.getProcessName()), logPair("uuid", runProcessInput.getProcessUUID()));
}
/***************************************************************************
**
***************************************************************************/
@Override
public void handleProcessResume(RunProcessInput runProcessInput)
{
String atOrAfter = "after";
String atOrAfterStep = runProcessInput.getStartAfterStep();
if(StringUtils.hasContent(runProcessInput.getStartAtStep()))
{
atOrAfter = "at";
atOrAfterStep = runProcessInput.getStartAtStep();
}
LOG.info("Resuming process", logPair("name", runProcessInput.getProcessName()), logPair("uuid", runProcessInput.getProcessUUID()), logPair(atOrAfter, atOrAfterStep));
}
/***************************************************************************
**
***************************************************************************/
@Override
public void handleStepStart(RunBackendStepInput runBackendStepInput)
{
LOG.info("Starting process step", runBackendStepInput.getStepName());
}
/***************************************************************************
**
***************************************************************************/
@Override
public void handleMessage(RunBackendStepInput runBackendStepInput, ProcessTracerMessage message)
{
LOG.info("Message from process", logPair("message", message.getMessage()));
}
/***************************************************************************
**
***************************************************************************/
@Override
public void handleStepFinish(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput)
{
LOG.info("Finished process step", logPair("name", runBackendStepInput.getStepName()));
}
/***************************************************************************
**
***************************************************************************/
@Override
public void handleProcessBreak(RunProcessInput runProcessInput, RunProcessOutput runProcessOutput, Exception processException)
{
LOG.info("Breaking process", logPair("name", runProcessInput.getProcessName()), logPair("uuid", runProcessInput.getProcessUUID()));
}
/***************************************************************************
**
***************************************************************************/
@Override
public void handleProcessFinish(RunProcessInput runProcessInput, RunProcessOutput runProcessOutput, Exception processException)
{
long finishMillis = System.currentTimeMillis();
List<LogPair> summaryLogPairs = new ArrayList<>();
Serializable processSummary = runProcessOutput.getValue(StreamedETLWithFrontendProcess.FIELD_PROCESS_SUMMARY);
if(processSummary instanceof List)
{
List<? extends ProcessSummaryLineInterface> processSummaryLines = (List<? extends ProcessSummaryLineInterface>) processSummary;
for(ProcessSummaryLineInterface processSummaryLineInterface : processSummaryLines)
{
if(processSummaryLineInterface instanceof ProcessSummaryLine processSummaryLine)
{
summaryLogPairs.add(logPair(String.valueOf(summaryLogPairs.size()), logPair("status", processSummaryLine.getStatus()), logPair("count", processSummaryLine.getCount()), logPair("message", processSummaryLine.getMessage())));
}
else
{
summaryLogPairs.add(logPair(String.valueOf(summaryLogPairs.size()), logPair("message", processSummaryLineInterface.getMessage())));
}
}
}
LOG.info("Finished process", logPair("name", runProcessInput.getProcessName()), logPair("uuid", runProcessInput.getProcessUUID()), logPair("millis", finishMillis - startMillis), logPair("summary", summaryLogPairs));
}
}

View File

@ -0,0 +1,107 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.processes.tracing;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput;
/*******************************************************************************
** Implementation of ProcessTracerInterface that does nothing (no-op).
*******************************************************************************/
public class NoopProcessTracer implements ProcessTracerInterface
{
/***************************************************************************
**
***************************************************************************/
@Override
public void handleProcessStart(RunProcessInput runProcessInput)
{
}
/***************************************************************************
**
***************************************************************************/
@Override
public void handleProcessResume(RunProcessInput runProcessInput)
{
}
/***************************************************************************
**
***************************************************************************/
@Override
public void handleStepStart(RunBackendStepInput runBackendStepInput)
{
}
/***************************************************************************
**
***************************************************************************/
@Override
public void handleMessage(RunBackendStepInput runBackendStepInput, ProcessTracerMessage message)
{
}
/***************************************************************************
**
***************************************************************************/
@Override
public void handleStepFinish(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput)
{
}
/***************************************************************************
**
***************************************************************************/
@Override
public void handleProcessBreak(RunProcessInput runProcessInput, RunProcessOutput runProcessOutput, Exception processException)
{
}
/***************************************************************************
**
***************************************************************************/
@Override
public void handleProcessFinish(RunProcessInput runProcessInput, RunProcessOutput runProcessOutput, Exception processException)
{
}
}

View File

@ -0,0 +1,77 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.processes.tracing;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput;
/*******************************************************************************
** Interface that can be plugged into the execution of a QProcess, that gets
** callbacks from QQQ for events in the lifecycle of a process, which one may
** wish to log or otherwise be aware of.
*******************************************************************************/
public interface ProcessTracerInterface
{
/***************************************************************************
** Called when a new process is started.
***************************************************************************/
void handleProcessStart(RunProcessInput runProcessInput);
/***************************************************************************
** Called when a process is resumed, e.g., after a "break" occurs between
** backend steps and frontend steps.
***************************************************************************/
void handleProcessResume(RunProcessInput runProcessInput);
/***************************************************************************
** Called when a (backend) step is started.
***************************************************************************/
void handleStepStart(RunBackendStepInput runBackendStepInput);
/***************************************************************************
** Called when the (application, custom) process step code itself decides to
** trace something. We imagine various subclasses of ProcessTracerMessage
** to be created, to communicate more specific data for the tracer implementation.
***************************************************************************/
void handleMessage(RunBackendStepInput runBackendStepInput, ProcessTracerMessage message);
/***************************************************************************
** Called when a (backend) step finishes.
***************************************************************************/
void handleStepFinish(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput);
/***************************************************************************
** Called when a process break occurs, e.g., between backend and frontend
** steps (but only if there are no more backend steps in the queue).
***************************************************************************/
void handleProcessBreak(RunProcessInput runProcessInput, RunProcessOutput runProcessOutput, Exception processException);
/***************************************************************************
** Called after the last (backend) step of a process.
***************************************************************************/
void handleProcessFinish(RunProcessInput runProcessInput, RunProcessOutput runProcessOutput, Exception processException);
}

View File

@ -0,0 +1,85 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.processes.tracing;
/*******************************************************************************
** Basic class that can be passed in to ProcessTracerInterface.handleMessage.
** This class just provides for a string message. We anticipate subclasses
** that may have more specific data, that specific tracer implementations may
** be aware of.
*******************************************************************************/
public class ProcessTracerMessage
{
private String message;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public ProcessTracerMessage()
{
}
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public ProcessTracerMessage(String message)
{
this.message = message;
}
/*******************************************************************************
** Getter for message
*******************************************************************************/
public String getMessage()
{
return (this.message);
}
/*******************************************************************************
** Setter for message
*******************************************************************************/
public void setMessage(String message)
{
this.message = message;
}
/*******************************************************************************
** Fluent setter for message
*******************************************************************************/
public ProcessTracerMessage withMessage(String message)
{
this.message = message;
return (this);
}
}

View File

@ -0,0 +1,93 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.processes.tracing;
import java.util.List;
import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.actions.processes.QProcessCallbackFactory;
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QCollectingLogger;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.utils.CollectionAssert;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertNotNull;
/*******************************************************************************
** Unit test for LoggingProcessTracer
*******************************************************************************/
class LoggingProcessTracerTest extends BaseTest
{
/*******************************************************************************
**
*******************************************************************************/
@AfterEach
void afterEach()
{
QLogger.deactivateCollectingLoggerForClass(LoggingProcessTracer.class);
}
/*******************************************************************************
** this test is based on RunProcessTest#testBreakOnFrontendSteps
*******************************************************************************/
@Test
void test() throws QException
{
/////////////////////////////////////////////////////
// activate the tracer for this run of the process //
/////////////////////////////////////////////////////
RunProcessInput input = new RunProcessInput();
input.addValue(RunProcessAction.PROCESS_TRACER_CODE_REFERENCE_FIELD, new QCodeReference(LoggingProcessTracer.class));
QCollectingLogger collectingLogger = QLogger.activateCollectingLoggerForClass(LoggingProcessTracer.class);
input.setProcessName(TestUtils.PROCESS_NAME_GREET_PEOPLE_INTERACTIVE);
input.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.BREAK);
input.setCallback(QProcessCallbackFactory.forRecord(new QRecord().withValue("id", 1)));
RunProcessOutput result0 = new RunProcessAction().execute(input);
assertNotNull(result0);
CollectionAssert.assertThat(collectingLogger.getCollectedMessages())
.matchesAll(List.of("Starting process", "Breaking process"),
(s, clm) -> clm.getMessageAsJSONObject().getString("message").equals(s));
collectingLogger.clear();
///////////////////////////////////////////////////
// now re-run (resume) to the end of the process //
///////////////////////////////////////////////////
input.setStartAfterStep(result0.getProcessState().getNextStepName().get());
RunProcessOutput result1 = new RunProcessAction().execute(input);
CollectionAssert.assertThat(collectingLogger.getCollectedMessages())
.matchesAll(List.of("Resuming process", "Starting process step", "Finished process step", "Finished process"),
(s, clm) -> clm.getMessageAsJSONObject().getString("message").equals(s));
}
}

View File

@ -0,0 +1,61 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.processes.tracing;
import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.actions.processes.QProcessCallbackFactory;
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.Test;
/*******************************************************************************
** Unit test for NoopProcessTracer ... kinda a BS test, but here to prevent
** a missing-class for code coverage...
*******************************************************************************/
class NoopProcessTracerTest extends BaseTest
{
/*******************************************************************************
**
*******************************************************************************/
@Test
void test() throws QException
{
//////////////////////////////////////////////////////////
// activate the noop tracer for this run of the process //
//////////////////////////////////////////////////////////
RunProcessInput input = new RunProcessInput();
input.addValue(RunProcessAction.PROCESS_TRACER_CODE_REFERENCE_FIELD, new QCodeReference(NoopProcessTracer.class));
input.setProcessName(TestUtils.PROCESS_NAME_GREET_PEOPLE_INTERACTIVE);
input.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.BREAK);
input.setCallback(QProcessCallbackFactory.forRecord(new QRecord().withValue("id", 1)));
new RunProcessAction().execute(input);
}
}