Implement logger for workflows
Added Workflow Logger annotation and Log Store
Change-Id: I90b1e34f94083aceff3323660a708756096a0ef0
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/ImmutableListWorkflow.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/ImmutableListWorkflow.java
index 5819f11..1a77e25 100644
--- a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/ImmutableListWorkflow.java
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/ImmutableListWorkflow.java
@@ -56,6 +56,11 @@
private static StaticDataModelInjector staticDataModelInjector = new StaticDataModelInjector();
/**
+ * Workflow Logger injector.
+ */
+ private WorkflowLoggerInjector workflowLoggerInjector = new WorkflowLoggerInjector();
+
+ /**
* Constructor of ImmutableListWorkflow.
*
* @param builder builder of ImmutableListWorkflow
@@ -118,6 +123,7 @@
continue;
} else {
+ workflowLoggerInjector.inject(worklet, context);
// isNext is read only. It does not perform 'inhale'.
dataModelInjector.inject(worklet, context);
WorkletDescription workletDesc = getWorkletDesc(pc);
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowLogStore.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowLogStore.java
new file mode 100644
index 0000000..cd61da9
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowLogStore.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2022-present Open Networking Foundation
+ *
+ * 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 org.onosproject.workflow.api;
+
+import java.util.List;
+import java.util.Map;
+
+public interface WorkflowLogStore {
+
+ /**
+ * Adds log messages to store.
+ *
+ * @param contextName context name
+ * @param logMsg log message
+ * @param className class name
+ * @param level logging level
+ */
+ void addLog(String contextName, String logMsg, String className, String level);
+
+ /**
+ * Adds exception message and call stack to store.
+ *
+ * @param contextName context name
+ * @param logMsg log message
+ * @param className class name
+ * @param level logging level
+ * @param e Throwable object
+ */
+ void addException(String contextName, String logMsg, String className, String level, Throwable e);
+
+ /**
+ * Gets log messages from store.
+ *
+ * @param contextName context name
+ * @return log messages of given contextName
+ */
+ List<String> getLog(String contextName);
+
+
+ /**
+ * Get store as Java map.
+ *
+ * @return Store as Java hash map.
+ */
+ Map<String, List<String>> asJavaMap();
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowLogger.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowLogger.java
new file mode 100644
index 0000000..8622244
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowLogger.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2022-present Open Networking Foundation
+ *
+ * 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 org.onosproject.workflow.api;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+
+/*
+ Annotation for injecting logger on work-let execution context.
+ */
+public @interface WorkflowLogger {
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowLoggerFactory.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowLoggerFactory.java
new file mode 100644
index 0000000..06ca7a2
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowLoggerFactory.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2022-present Open Networking Foundation
+ *
+ * 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 org.onosproject.workflow.api;
+
+import org.onlab.osgi.DefaultServiceDirectory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.helpers.MessageFormatter;
+
+public class WorkflowLoggerFactory {
+ private static final String INFO = "INFO";
+ private static final String ERROR = "ERROR";
+ private static final String WARN = "WARN";
+
+ protected Logger log;
+ protected String contextName;
+ protected String className;
+ protected WorkflowLogStore logStore = DefaultServiceDirectory.getService(WorkflowLogStore.class);
+
+ public WorkflowLoggerFactory(String ctxtName, String cls) {
+ log = LoggerFactory.getLogger(cls);
+ contextName = ctxtName;
+ className = cls;
+ }
+
+ public void debug(String msg) {
+ log.debug(msg);
+ }
+
+ public void debug(String format, Object arg) {
+ log.debug(format, arg);
+ }
+
+ public void debug(String format, Object arg1, Object arg2) {
+ log.debug(format, arg1, arg2);
+ }
+
+ public void debug(String format, Object... arg) {
+ log.debug(format, arg);
+ }
+
+ public void debug(String format, Throwable e) {
+ log.debug(format, e);
+ }
+
+ public void info(String msg) {
+ logStore.addLog(contextName, msg, className, INFO);
+ log.info(msg);
+ }
+
+ public void info(String format, Object arg) {
+ String msg = MessageFormatter.format(format, arg).getMessage();
+ logStore.addLog(contextName, msg, className, INFO);
+ log.info(format, arg);
+ }
+
+ public void info(String format, Object arg1, Object arg2) {
+ String msg = MessageFormatter.format(format, arg1, arg2).getMessage();
+ logStore.addLog(contextName, msg, className, INFO);
+ log.info(format, arg1, arg2);
+ }
+
+ public void info(String format, Object... arg) {
+ String msg = MessageFormatter.arrayFormat(format, arg).getMessage();
+ logStore.addLog(contextName, msg, className, INFO);
+ log.info(format, arg);
+ }
+
+ public void info(String format, Throwable e) {
+ logStore.addException(contextName, format, className, INFO, e);
+ log.info(format, e);
+ }
+
+ public void error(String msg) {
+ logStore.addLog(contextName, msg, className, ERROR);
+ log.error(msg);
+ }
+
+ public void error(String format, Object arg) {
+ String msg = MessageFormatter.format(format, arg).getMessage();
+ logStore.addLog(contextName, msg, className, ERROR);
+ log.error(format, arg);
+ }
+
+ public void error(String format, Object arg1, Object arg2) {
+ String msg = MessageFormatter.format(format, arg1, arg2).getMessage();
+ logStore.addLog(contextName, msg, className, ERROR);
+ log.error(format, arg1, arg2);
+ }
+
+ public void error(String format, Object... arg) {
+ String msg = MessageFormatter.arrayFormat(format, arg).getMessage();
+ logStore.addLog(contextName, msg, className, ERROR);
+ log.error(format, arg);
+ }
+
+ public void error(String format, Throwable e) {
+ logStore.addException(contextName, format, className, ERROR, e);
+ log.error(format, e);
+ }
+
+ public void warn(String msg) {
+ logStore.addLog(contextName, msg, className, WARN);
+ log.warn(msg);
+ }
+
+ public void warn(String format, Object arg) {
+ String msg = MessageFormatter.format(format, arg).getMessage();
+ logStore.addLog(contextName, msg, className, WARN);
+ log.warn(format, arg);
+ }
+
+ public void warn(String format, Object arg1, Object arg2) {
+ String msg = MessageFormatter.format(format, arg1, arg2).getMessage();
+ logStore.addLog(contextName, msg, className, WARN);
+ log.warn(format, arg1, arg2);
+ }
+
+ public void warn(String format, Object... arg) {
+ String msg = MessageFormatter.arrayFormat(format, arg).getMessage();
+ logStore.addLog(contextName, msg, className, WARN);
+ log.warn(format, arg);
+ }
+
+ public void warn(String format, Throwable e) {
+ logStore.addException(contextName, format, className, WARN, e);
+ log.warn(format, e);
+ }
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowLoggerInjector.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowLoggerInjector.java
new file mode 100644
index 0000000..5022f96
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowLoggerInjector.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2022-present Open Networking Foundation
+ *
+ * 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 org.onosproject.workflow.api;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Class for injecting workflow logger on the work-let execution context.
+ */
+public class WorkflowLoggerInjector {
+
+ /**
+ * Injects logger to work-let.
+ *
+ * @param worklet work-let to be injected
+ * @param context workflow context
+ * @throws WorkflowException workflow exception
+ */
+ public void inject(Worklet worklet, WorkflowContext context) throws WorkflowException {
+
+ handle(worklet, context, this::injectLogger);
+ }
+
+
+ private void handle(Worklet worklet, WorkflowContext context, WorkflowLoggerInjector.ReferenceFieldBehavior func)
+ throws WorkflowException {
+ Class<?> cl = worklet.getClass();
+ List<Field> fields = getInheritedFields(cl);
+
+ for (Field field : fields) {
+ Annotation[] annotations = field.getAnnotations();
+ if (Objects.isNull(annotations)) {
+ continue;
+ }
+ for (Annotation annotation : annotations) {
+ if (!(annotation instanceof WorkflowLogger)) {
+ continue;
+ }
+
+ if (Modifier.isStatic(field.getModifiers())) {
+ throw new WorkflowException("Static field(" + field + " ) cannot use @WorkflowLogger in " + cl);
+ }
+
+ WorkflowLogger reference = (WorkflowLogger) annotation;
+ func.apply(worklet, context, field, reference);
+ }
+ }
+ }
+
+ private static List<Field> getInheritedFields(Class<?> type) {
+ List<Field> fields = new ArrayList<>();
+
+ Class<?> cl = type;
+ while (cl != null && cl != Object.class) {
+ for (Field field : cl.getDeclaredFields()) {
+ if (!field.isSynthetic()) {
+ fields.add(field);
+ }
+ }
+ cl = cl.getSuperclass();
+ }
+ return fields;
+ }
+
+ /**
+ * Functional interface for workflow logger annotated field behavior.
+ */
+ @FunctionalInterface
+ public interface ReferenceFieldBehavior {
+ void apply(Worklet worklet, WorkflowContext context, Field field, WorkflowLogger reference)
+ throws WorkflowException;
+ }
+
+ /**
+ * Injects logger on the filed of work-let.
+ *
+ * @param worklet work-let
+ * @param context workflow context
+ * @param field the field of work-let
+ * @param reference logger reference for the field
+ * @throws WorkflowException workflow exception
+ */
+ private void injectLogger(Worklet worklet, WorkflowContext context, Field field, WorkflowLogger reference)
+ throws WorkflowException {
+
+ Object obj = new WorkflowLoggerFactory(context.name(), worklet.getClass().getSimpleName());
+
+ try {
+ field.setAccessible(true);
+ field.set(worklet, obj);
+ } catch (IllegalAccessException e) {
+ throw new WorkflowException(e);
+ }
+ }
+
+}
diff --git a/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/SimpleWorkflowLogStore.java b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/SimpleWorkflowLogStore.java
new file mode 100644
index 0000000..fdc1ab1
--- /dev/null
+++ b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/SimpleWorkflowLogStore.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2022-present Open Networking Foundation
+ *
+ * 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 org.onosproject.workflow.impl;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import org.onosproject.workflow.api.WorkflowLogStore;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.sql.Timestamp;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+@Component(immediate = true)
+@Service
+public class SimpleWorkflowLogStore implements WorkflowLogStore {
+ private static final int LOG_EXPIRATION_TIME = 60;
+ private static final String SQ_OPBRACKET = "[";
+ private static final String SQ_CLBRACKET = "]";
+ private static final String SPACE = " ";
+
+ private Cache<String, List<String>> workflowLogMap;
+
+ @Activate
+ public void activate() {
+ workflowLogMap = CacheBuilder.newBuilder()
+ .expireAfterWrite(LOG_EXPIRATION_TIME, TimeUnit.MINUTES)
+ .build();
+ }
+
+ @Deactivate
+ public void deactivate() {
+ workflowLogMap.invalidateAll();
+ }
+
+ @Override
+ public void addLog(String uuid, String logMsg, String className, String level) {
+ workflowLogMap.asMap().putIfAbsent(uuid, new ArrayList<>());
+ Objects.requireNonNull(workflowLogMap.getIfPresent(uuid)).add(0, formatLog(logMsg, level, className));
+ }
+
+ @Override
+ public void addException(String uuid, String logMsg, String className, String level, Throwable e) {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ e.printStackTrace(pw);
+ String msg = logMsg + sw;
+ addLog(uuid, msg, className, level);
+ }
+
+ @Override
+ public List<String> getLog(String uuid) {
+ List<String> logs = workflowLogMap.getIfPresent(uuid);
+ if (Objects.isNull(logs)) {
+ return new ArrayList<>();
+ }
+ return logs;
+ }
+
+ @Override
+ public Map<String, List<String>> asJavaMap() {
+ return workflowLogMap.asMap();
+ }
+
+ private String formatLog(String msg, String level, String className) {
+ Timestamp timestamp = new Timestamp(System.currentTimeMillis());
+ msg = SQ_OPBRACKET + timestamp + SQ_CLBRACKET + SPACE + SQ_OPBRACKET + level + SQ_CLBRACKET + SPACE +
+ SQ_OPBRACKET + className + SQ_CLBRACKET + SPACE + msg;
+ return msg;
+ }
+}
diff --git a/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/WorkFlowEngine.java b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/WorkFlowEngine.java
index 55bf0e4..8b99bb8 100644
--- a/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/WorkFlowEngine.java
+++ b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/WorkFlowEngine.java
@@ -51,6 +51,7 @@
import org.onosproject.workflow.api.WorkflowExecutionService;
import org.onosproject.workflow.api.WorkletDescription;
import org.onosproject.workflow.api.StaticDataModelInjector;
+import org.onosproject.workflow.api.WorkflowLoggerInjector;
import org.onosproject.event.AbstractListenerManager;
import org.onosproject.event.Event;
import org.onosproject.net.intent.WorkPartitionService;
@@ -123,6 +124,7 @@
private static final int DEFAULT_WORKFLOW_THREADS = 12;
private static final int DEFAULT_EVENTTASK_THREADS = 12;
private static final int MAX_REGISTER_EVENTMAP_WAITS = 10;
+ private static final String ERROR = "ERROR";
private ScheduledExecutorService eventMapTriggerExecutor;
@@ -130,6 +132,7 @@
private JsonDataModelInjector dataModelInjector = new JsonDataModelInjector();
private StaticDataModelInjector staticDataModelInjector = new StaticDataModelInjector();
+ private WorkflowLoggerInjector workflowLoggerInjector = new WorkflowLoggerInjector();
public static final String APPID = "org.onosproject.workflow";
private ApplicationId appId;
@@ -190,6 +193,7 @@
log.info("{} worklet.process:{}", context.name(), initWorklet.tag());
log.trace("{} context: {}", context.name(), context);
+ workflowLoggerInjector.inject(initWorklet, context);
dataModelInjector.inject(initWorklet, context);
initWorklet.process(context);
dataModelInjector.inhale(initWorklet, context);
@@ -496,6 +500,7 @@
log.info("{} worklet.isCompleted:{}", latestContext.name(), worklet.tag());
log.trace("{} task:{}, context: {}", latestContext.name(), task, latestContext);
+ workflowLoggerInjector.inject(worklet, latestContext);
dataModelInjector.inject(worklet, latestContext);
boolean completed = worklet.isCompleted(latestContext, task.event());
dataModelInjector.inhale(worklet, latestContext);
@@ -589,6 +594,7 @@
log.info("{} worklet.timeout(for event):{}", latestContext.name(), worklet.tag());
log.trace("{} task:{}, context: {}", latestContext.name(), task, latestContext);
+ workflowLoggerInjector.inject(worklet, latestContext);
dataModelInjector.inject(worklet, latestContext);
WorkletDescription workletDesc = workflow.getWorkletDesc(task.programCounter());
@@ -666,6 +672,7 @@
log.info("{} worklet.timeout:{}", latestContext.name(), worklet.tag());
log.trace("{} context: {}", latestContext.name(), latestContext);
+ workflowLoggerInjector.inject(worklet, latestContext);
dataModelInjector.inject(worklet, latestContext);
WorkletDescription workletDesc = workflow.getWorkletDesc(task.programCounter());
@@ -793,6 +800,7 @@
log.trace("{} context: {}", latestContext.name(), latestContext);
+ workflowLoggerInjector.inject(worklet, latestContext);
dataModelInjector.inject(worklet, latestContext);
WorkletDescription workletDesc = workflow.getWorkletDesc(pc);