Skip to content

Commit 7f29c9b

Browse files
sguilhenpedroigor
authored andcommitted
Improve workflow logging messages
- every execution gets its own id that can be used to track all activities related to that particular workflow execution Closes #42952 Signed-off-by: Stefan Guilhen <[email protected]>
1 parent 7bcf08f commit 7f29c9b

File tree

9 files changed

+290
-174
lines changed

9 files changed

+290
-174
lines changed

core/src/main/java/org/keycloak/representations/workflows/AbstractWorkflowComponentRepresentation.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package org.keycloak.representations.workflows;
22

33
import static org.keycloak.common.util.reflections.Reflections.isArrayType;
4-
import static org.keycloak.representations.workflows.WorkflowConstants.CONFIG_IF;
4+
import static org.keycloak.representations.workflows.WorkflowConstants.CONFIG_WITH;
55

66
import java.util.Arrays;
77
import java.util.Collections;
@@ -20,7 +20,7 @@ public abstract class AbstractWorkflowComponentRepresentation {
2020
private String id;
2121
private String uses;
2222

23-
@JsonProperty(CONFIG_IF)
23+
@JsonProperty(CONFIG_WITH)
2424
private MultivaluedHashMap<String, String> config;
2525

2626
public AbstractWorkflowComponentRepresentation(String id, String uses, MultivaluedHashMap<String, String> config) {

model/jpa/src/main/java/org/keycloak/models/workflow/JpaWorkflowStateProvider.java

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -45,22 +45,23 @@ public JpaWorkflowStateProvider(KeycloakSession session) {
4545

4646
@Override
4747
public ScheduledStep getScheduledStep(String workflowId, String resourceId) {
48-
WorkflowStateEntity.PrimaryKey pk = new WorkflowStateEntity.PrimaryKey(resourceId, workflowId);
49-
WorkflowStateEntity entity = em.find(WorkflowStateEntity.class, pk);
50-
if (entity != null) {
51-
return new ScheduledStep(entity.getWorkflowId(), entity.getScheduledStepId(), entity.getResourceId());
52-
}
53-
return null;
48+
CriteriaBuilder cb = em.getCriteriaBuilder();
49+
CriteriaQuery<WorkflowStateEntity> query = cb.createQuery(WorkflowStateEntity.class);
50+
Root<WorkflowStateEntity> stateRoot = query.from(WorkflowStateEntity.class);
51+
52+
query.where(cb.and(cb.equal(stateRoot.get("workflowId"), workflowId), cb.equal(stateRoot.get("resourceId"), resourceId)));
53+
WorkflowStateEntity entity = em.createQuery(query).getSingleResultOrNull();
54+
return entity != null ? toScheduledStep(entity) : null;
5455
}
5556

5657
@Override
57-
public void scheduleStep(Workflow workflow, WorkflowStep step, String resourceId) {
58-
WorkflowStateEntity.PrimaryKey pk = new WorkflowStateEntity.PrimaryKey(resourceId, workflow.getId());
59-
WorkflowStateEntity entity = em.find(WorkflowStateEntity.class, pk);
58+
public void scheduleStep(Workflow workflow, WorkflowStep step, String resourceId, String executionId) {
59+
WorkflowStateEntity entity = em.find(WorkflowStateEntity.class, executionId);
6060
if (entity == null) {
6161
entity = new WorkflowStateEntity();
6262
entity.setResourceId(resourceId);
6363
entity.setWorkflowId(workflow.getId());
64+
entity.setExecutionId(executionId);
6465
entity.setWorkflowProviderId(workflow.getProviderId());
6566
entity.setScheduledStepId(step.getId());
6667
entity.setScheduledStepTimestamp(Time.currentTimeMillis() + step.getAfter());
@@ -83,7 +84,7 @@ public List<ScheduledStep> getDueScheduledSteps(Workflow workflow) {
8384
query.where(cb.and(byWorkflow, isExpired));
8485

8586
return em.createQuery(query).getResultStream()
86-
.map(s -> new ScheduledStep(s.getWorkflowId(), s.getScheduledStepId(), s.getResourceId()))
87+
.map(this::toScheduledStep)
8788
.toList();
8889
}
8990

@@ -101,7 +102,7 @@ public List<ScheduledStep> getScheduledStepsByWorkflow(String workflowId) {
101102
query.where(byWorkflow);
102103

103104
return em.createQuery(query).getResultStream()
104-
.map(s -> new ScheduledStep(s.getWorkflowId(), s.getScheduledStepId(), s.getResourceId()))
105+
.map(this::toScheduledStep)
105106
.toList();
106107
}
107108

@@ -115,7 +116,20 @@ public List<ScheduledStep> getScheduledStepsByResource(String resourceId) {
115116
query.where(byResource);
116117

117118
return em.createQuery(query).getResultStream()
118-
.map(s -> new ScheduledStep(s.getWorkflowId(), s.getScheduledStepId(), s.getResourceId()))
119+
.map(this::toScheduledStep)
120+
.toList();
121+
}
122+
123+
public List<ScheduledStep> getScheduledStepsByStep(String stepId) {
124+
CriteriaBuilder cb = em.getCriteriaBuilder();
125+
CriteriaQuery<WorkflowStateEntity> query = cb.createQuery(WorkflowStateEntity.class);
126+
Root<WorkflowStateEntity> stateRoot = query.from(WorkflowStateEntity.class);
127+
128+
Predicate byStep = cb.equal(stateRoot.get("scheduledStepId"), stepId);
129+
query.where(byStep);
130+
131+
return em.createQuery(query).getResultStream()
132+
.map(this::toScheduledStep)
119133
.toList();
120134
}
121135

@@ -135,16 +149,7 @@ public void removeByResource(String resourceId) {
135149
}
136150

137151
@Override
138-
public void remove(String workflowId, String resourceId) {
139-
WorkflowStateEntity.PrimaryKey pk = new WorkflowStateEntity.PrimaryKey(resourceId, workflowId);
140-
WorkflowStateEntity entity = em.find(WorkflowStateEntity.class, pk);
141-
if (entity != null) {
142-
em.remove(entity);
143-
}
144-
}
145-
146-
@Override
147-
public void remove(String workflowId) {
152+
public void removeByWorkflow(String workflowId) {
148153
CriteriaBuilder cb = em.getCriteriaBuilder();
149154
CriteriaDelete<WorkflowStateEntity> delete = cb.createCriteriaDelete(WorkflowStateEntity.class);
150155
Root<WorkflowStateEntity> root = delete.from(WorkflowStateEntity.class);
@@ -159,6 +164,14 @@ public void remove(String workflowId) {
159164
}
160165
}
161166

167+
@Override
168+
public void remove(String executionId) {
169+
WorkflowStateEntity entity = em.find(WorkflowStateEntity.class, executionId);
170+
if (entity != null) {
171+
em.remove(entity);
172+
}
173+
}
174+
162175
@Override
163176
public void removeAll() {
164177
CriteriaBuilder cb = em.getCriteriaBuilder();
@@ -177,4 +190,7 @@ public void removeAll() {
177190
public void close() {
178191
}
179192

193+
private ScheduledStep toScheduledStep(WorkflowStateEntity entity) {
194+
return new ScheduledStep(entity.getWorkflowId(), entity.getScheduledStepId(), entity.getResourceId(), entity.getExecutionId());
195+
}
180196
}

model/jpa/src/main/java/org/keycloak/models/workflow/WorkflowStateEntity.java

Lines changed: 11 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,15 @@
3030
*/
3131
@Entity
3232
@Table(name = "WORKFLOW_STATE")
33-
@IdClass(WorkflowStateEntity.PrimaryKey.class)
3433
public class WorkflowStateEntity {
3534

3635
@Id
36+
@Column(name = "EXECUTION_ID")
37+
private String executionId;
38+
3739
@Column(name = "RESOURCE_ID")
3840
private String resourceId;
3941

40-
@Id
4142
@Column(name = "WORKFLOW_ID")
4243
private String workflowId;
4344

@@ -53,6 +54,14 @@ public class WorkflowStateEntity {
5354
@Column(name = "SCHEDULED_STEP_TIMESTAMP")
5455
private long scheduledStepTimestamp;
5556

57+
public String getExecutionId() {
58+
return executionId;
59+
}
60+
61+
public void setExecutionId(String executionId) {
62+
this.executionId = executionId;
63+
}
64+
5665
public String getResourceId() {
5766
return resourceId;
5867
}
@@ -101,49 +110,6 @@ public void setScheduledStepTimestamp(long scheduledStepTimestamp) {
101110
this.scheduledStepTimestamp = scheduledStepTimestamp;
102111
}
103112

104-
public static class PrimaryKey implements Serializable {
105-
106-
private String resourceId;
107-
private String workflowId;
108-
109-
public PrimaryKey() {
110-
}
111-
112-
public PrimaryKey(String resourceId, String workflowId) {
113-
this.resourceId = resourceId;
114-
this.workflowId = workflowId;
115-
}
116-
117-
public String getResourceId() {
118-
return resourceId;
119-
}
120-
121-
public void setResourceId(String resourceId) {
122-
this.resourceId = resourceId;
123-
}
124-
125-
public String getWorkflowId() {
126-
return workflowId;
127-
}
128-
129-
public void setWorkflowId(String workflowId) {
130-
this.workflowId = workflowId;
131-
}
132-
133-
@Override
134-
public boolean equals(Object o) {
135-
if (this == o) return true;
136-
if (o == null || getClass() != o.getClass()) return false;
137-
PrimaryKey that = (PrimaryKey) o;
138-
return Objects.equals(resourceId, that.resourceId) && Objects.equals(workflowId, that.workflowId);
139-
}
140-
141-
@Override
142-
public int hashCode() {
143-
return Objects.hash(resourceId, workflowId);
144-
}
145-
}
146-
147113
@Override
148114
public boolean equals(Object o) {
149115
if (this == o) return true;

model/jpa/src/main/resources/META-INF/jpa-changelog-26.4.0.xml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@
3131

3232
<changeSet id="40343-workflow-state-table" author="keycloak">
3333
<createTable tableName="WORKFLOW_STATE">
34+
<column name="EXECUTION_ID" type="VARCHAR(255)">
35+
<constraints nullable="false" />
36+
</column>
3437
<column name="RESOURCE_ID" type="VARCHAR(255)">
3538
<constraints nullable="false" />
3639
</column>
@@ -44,9 +47,13 @@
4447
</createTable>
4548

4649
<addPrimaryKey
47-
constraintName="PK_WORKFLOW_STEP_STATE"
50+
constraintName="PK_WORKFLOW_STATE"
51+
tableName="WORKFLOW_STATE"
52+
columnNames="EXECUTION_ID" />
53+
54+
<addUniqueConstraint constraintName="UQ_WORKFLOW_RESOURCE"
4855
tableName="WORKFLOW_STATE"
49-
columnNames="RESOURCE_ID, WORKFLOW_ID" />
56+
columnNames="WORKFLOW_ID, RESOURCE_ID"/>
5057

5158
<createIndex indexName="IDX_WORKFLOW_STATE_STEP"
5259
tableName="WORKFLOW_STATE">

model/storage-private/src/main/java/org/keycloak/models/workflow/WorkflowStateProvider.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,31 +34,31 @@ public interface WorkflowStateProvider extends Provider {
3434
void removeByResource(String resourceId);
3535

3636
/**
37-
* Removes the record identified by the specified {@code workflowId} and {@code resourceId}.
37+
* Removes any record identified by the specified {@code workflowId}.
3838
* @param workflowId the id of the workflow.
39-
* @param resourceId the id of the resource.
4039
*/
41-
void remove(String workflowId, String resourceId);
40+
void removeByWorkflow(String workflowId);
4241

4342
/**
44-
* Removes any record identified by the specified {@code workflowId}.
45-
* @param workflowId the id of the workflow.
43+
* Removes the record identified by the specified {@code executionId}.
4644
*/
47-
void remove(String workflowId);
45+
void remove(String executionId);
4846

4947
/**
5048
* Deletes all state records associated with the current realm bound to the session.
5149
*/
5250
void removeAll();
5351

54-
void scheduleStep(Workflow workflow, WorkflowStep step, String resourceId);
52+
void scheduleStep(Workflow workflow, WorkflowStep step, String resourceId, String executionId);
5553

5654
ScheduledStep getScheduledStep(String workflowId, String resourceId);
5755

5856
List<ScheduledStep> getScheduledStepsByResource(String resourceId);
5957

6058
List<ScheduledStep> getScheduledStepsByWorkflow(String workflowId);
6159

60+
List<ScheduledStep> getScheduledStepsByStep(String stepId);
61+
6262
default List<ScheduledStep> getScheduledStepsByWorkflow(Workflow workflow) {
6363
if (workflow == null) {
6464
return List.of();
@@ -69,5 +69,5 @@ default List<ScheduledStep> getScheduledStepsByWorkflow(Workflow workflow) {
6969

7070
List<ScheduledStep> getDueScheduledSteps(Workflow workflow);
7171

72-
record ScheduledStep(String workflowId, String stepId, String resourceId) {}
72+
record ScheduledStep(String workflowId, String stepId, String resourceId, String executionId) {}
7373
}

server-spi-private/src/main/java/org/keycloak/models/workflow/Workflow.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import static org.keycloak.representations.workflows.WorkflowConstants.CONFIG_ENABLED;
2121
import static org.keycloak.representations.workflows.WorkflowConstants.CONFIG_ERROR;
22+
import static org.keycloak.representations.workflows.WorkflowConstants.CONFIG_NAME;
2223
import static org.keycloak.representations.workflows.WorkflowConstants.CONFIG_RECURRING;
2324

2425
import java.util.List;
@@ -59,6 +60,10 @@ public MultivaluedHashMap<String, String> getConfig() {
5960
return config;
6061
}
6162

63+
public String getName() {
64+
return config != null ? config.getFirst(CONFIG_NAME) : null;
65+
}
66+
6267
public boolean isEnabled() {
6368
return config != null && Boolean.parseBoolean(config.getFirstOrDefault(CONFIG_ENABLED, "true"));
6469
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package org.keycloak.models.workflow;
2+
3+
import org.jboss.logging.Logger;
4+
5+
import java.util.List;
6+
import java.util.UUID;
7+
8+
import static java.util.Optional.ofNullable;
9+
10+
public class WorkflowExecutionContext {
11+
12+
private static final Logger logger = Logger.getLogger(WorkflowExecutionContext.class);
13+
14+
private String executionId;
15+
private String resourceId;
16+
private Workflow workflow;
17+
private List<WorkflowStep> steps;
18+
19+
// variable that keep track of execution steps
20+
private int lastExecutedStepIndex = -1;
21+
22+
public WorkflowExecutionContext(Workflow workflow, List<WorkflowStep> steps, String resourceId) {
23+
this.workflow = workflow;
24+
this.steps = ofNullable(steps).orElse(List.of());
25+
this.resourceId = resourceId;
26+
}
27+
28+
public WorkflowExecutionContext(Workflow workflow, List<WorkflowStep> steps, String resourceId, String stepId, String executionId) {
29+
this(workflow, steps, resourceId);
30+
this.executionId = executionId;
31+
if (stepId != null) {
32+
for (int i = 0; i < steps.size(); i++) {
33+
if (steps.get(i).getId().equals(stepId)) {
34+
this.lastExecutedStepIndex = i - 1;
35+
break;
36+
}
37+
}
38+
}
39+
}
40+
41+
public void init() {
42+
if (this.executionId == null) {
43+
this.executionId = UUID.randomUUID().toString();
44+
logger.debugf("Started workflow '%s' for resource %s (execution id: %s)", this.workflow.getName(), this.resourceId, this.executionId);
45+
}
46+
}
47+
48+
public void success(WorkflowStep step) {
49+
logger.debugf("Step %s completed successfully (execution id: %s)", step.getProviderId(), executionId);
50+
}
51+
52+
public void fail(WorkflowStep step, String errorMessage) {
53+
StringBuilder sb = new StringBuilder();
54+
sb.append("Step %s failed (execution id: %s)");
55+
if (errorMessage != null) {
56+
sb.append(" - error message: %s");
57+
logger.debugf(sb.toString(), step.getProviderId(), executionId, errorMessage);
58+
}
59+
else {
60+
logger.debugf(sb.toString(), step.getProviderId(), executionId);
61+
}
62+
}
63+
64+
public void complete() {
65+
logger.debugf("Workflow '%s' completed for resource %s (execution id: %s)", workflow.getName(), resourceId, executionId);
66+
}
67+
68+
public void cancel() {
69+
logger.debugf("Workflow '%s' cancelled for resource %s (execution id: %s)", workflow.getName(), resourceId, executionId);
70+
}
71+
72+
public boolean hasNextStep() {
73+
return lastExecutedStepIndex + 1 < steps.size();
74+
}
75+
76+
public WorkflowStep getNextStep() {
77+
if (lastExecutedStepIndex + 1 < steps.size()) {
78+
return steps.get(++lastExecutedStepIndex);
79+
}
80+
return null;
81+
}
82+
83+
public void restart() {
84+
logger.debugf("Restarted workflow '%s' for resource %s (execution id: %s)",workflow.getName(), resourceId, executionId);
85+
this.lastExecutedStepIndex = -1;
86+
}
87+
88+
public String getExecutionId() {
89+
return this.executionId;
90+
}
91+
}

0 commit comments

Comments
 (0)