package io.cattle.platform.activity.impl; import io.cattle.platform.activity.ActivityLog; import io.cattle.platform.activity.Entry; import io.cattle.platform.async.utils.ResourceTimeoutException; import io.cattle.platform.async.utils.TimeoutException; import io.cattle.platform.core.model.Instance; import io.cattle.platform.core.model.Service; import io.cattle.platform.core.model.ServiceLog; import io.cattle.platform.engine.idempotent.IdempotentRetryException; import io.cattle.platform.engine.process.ExitReason; import io.cattle.platform.engine.process.ProcessInstanceException; import io.cattle.platform.engine.process.impl.ProcessExecutionExitException; import io.cattle.platform.eventing.EventService; import io.cattle.platform.lock.exception.FailedToAcquireLockException; import io.cattle.platform.object.ObjectManager; import io.cattle.platform.object.meta.ObjectMetaDataManager; import io.cattle.platform.object.util.ObjectUtils; import io.cattle.platform.object.util.TransitioningUtils; import io.cattle.platform.util.exception.InstanceException; import io.cattle.platform.util.exception.ServiceReconcileException; import java.util.Date; import java.util.Stack; import org.apache.commons.lang3.StringUtils; import org.jooq.exception.DataChangedException; public class ActivityLogImpl implements ActivityLog { EventService eventService; ObjectManager objectManager; Stack<EntryImpl> entries = new Stack<>(); public ActivityLogImpl(ObjectManager objectManager, EventService eventService) { super(); this.objectManager = objectManager; this.eventService = eventService; } @Override public Entry start(Service service, String type, String message) { ServiceLog auditLog = newServiceLog(service); auditLog.setEventType(type); auditLog.setDescription(message); auditLog.setTransactionId(io.cattle.platform.util.resource.UUID.randomUUID().toString()); if (entries.size() > 0) { ServiceLog parentLog = entries.peek().auditLog; auditLog.setSubLog(true); auditLog.setEventType(parentLog.getEventType() + "." + type); auditLog.setTransactionId(parentLog.getTransactionId()); } EntryImpl impl = new EntryImpl(this, service, objectManager.create(auditLog)); ObjectUtils.publishChanged(eventService, objectManager, impl.auditLog); entries.push(impl); return impl; } protected ServiceLog newServiceLog(Service obj) { ServiceLog auditLog = objectManager.newRecord(ServiceLog.class); auditLog.setLevel("info"); auditLog.setCreated(new Date()); auditLog.setAccountId(obj.getAccountId()); auditLog.setServiceId(obj.getId()); return auditLog; } protected void close(EntryImpl entry) { entry.auditLog.setEndTime(new Date()); entries.pop(); objectManager.persist(entry.auditLog); ObjectUtils.publishChanged(eventService, objectManager, entry.auditLog); if (entries.size() == 0) { String transitioning = null; String message = null; if (entry.failed) { transitioning = ObjectMetaDataManager.TRANSITIONING_ERROR_OVERRIDE; message = entry.message; } else if (entry.message != null) { message = entry.message; } objectManager.reload(entry.owner); try { objectManager.setFields(entry.owner, ObjectMetaDataManager.TRANSITIONING_FIELD, transitioning, ObjectMetaDataManager.TRANSITIONING_MESSAGE_FIELD, message); } catch (DataChangedException e) { } ObjectUtils.publishChanged(eventService, objectManager, entry.owner); } } @Override public void info(String message, Object... args) { String desc = String.format(message, args); ServiceLog log = newSubEntry(entries.peek(), "info"); log.setDescription(desc); log.setEndTime(new Date()); create(log); } @Override public void instance(Instance instance, String operation, String reason, String level) { if (instance == null) { return; } ServiceLog log = newSubEntry(entries.peek(), ""); log.setEventType("service.instance." + operation); log.setEndTime(new Date()); log.setDescription(reason); log.setInstanceId(instance.getId()); log.setLevel(level); create(log); } protected ServiceLog newSubEntry(EntryImpl entryImpl, String suffix) { ServiceLog log = newServiceLog(entryImpl.owner); log.setTransactionId(entryImpl.auditLog.getTransactionId()); log.setEventType(entryImpl.auditLog.getEventType() + "." + suffix); log.setSubLog(true); log.setEndTime(new Date()); return log; } protected void exception(EntryImpl entryImpl, Throwable t) { if (t instanceof IdempotentRetryException) { return; } if (t instanceof ProcessInstanceException) { return; } entryImpl.failed = true; entryImpl.message = t.getMessage(); ServiceLog log = newSubEntry(entryImpl, "exception"); log.setInstanceId(getInstanceIdFromThrowable(t)); log.setDescription(t.getMessage()); log.setLevel("error"); if (t instanceof ServiceReconcileException) { entryImpl.failed = false; log.setLevel("info"); } if (t instanceof DataChangedException) { entryImpl.failed = false; log.setLevel("info"); log.setDescription("Database state has changed, need to re-evaluate"); } if (t instanceof FailedToAcquireLockException) { entryImpl.failed = false; log.setLevel("info"); log.setDescription("Busy processing [" + ((FailedToAcquireLockException)t).getLockId() + "] will try later"); } if (t instanceof ProcessExecutionExitException) { if (((ProcessExecutionExitException) t).getExitReason() == ExitReason.STATE_CHANGED) { entryImpl.failed = false; log.setLevel("info"); log.setDescription("State has changed, need to re-evaluate"); } } if (t instanceof TimeoutException) { entryImpl.failed = false; log.setLevel("info"); Instance instance = objectManager.loadResource(Instance.class, log.getInstanceId()); if (instance != null) { String error = TransitioningUtils.getTransitioningError(instance); if (StringUtils.isNotBlank(error)) { log.setLevel("error"); log.setDescription(log.getDescription() + ": " + error); } } } create(log); } protected void create(ServiceLog serviceLog) { objectManager.create(serviceLog); ObjectUtils.publishChanged(eventService, objectManager, serviceLog); } protected Long getInstanceIdFromThrowable(Throwable t) { Object obj = null; if (t instanceof InstanceException) { obj = ((InstanceException) t).getInstance(); } if (t instanceof ResourceTimeoutException) { obj = ((ResourceTimeoutException) t).getResource(); } if (obj instanceof Instance) { return ((Instance) obj).getId(); } return null; } }