diff --git a/agent/conf/log4j-cloud.xml.in b/agent/conf/log4j-cloud.xml.in
index 12228017adb..6bc80f08473 100644
--- a/agent/conf/log4j-cloud.xml.in
+++ b/agent/conf/log4j-cloud.xml.in
@@ -34,7 +34,7 @@ under the License.
-
+
@@ -47,7 +47,7 @@ under the License.
-
+
diff --git a/agent/src/com/cloud/agent/Agent.java b/agent/src/com/cloud/agent/Agent.java
index f39d0e78961..994e8224894 100644
--- a/agent/src/com/cloud/agent/Agent.java
+++ b/agent/src/com/cloud/agent/Agent.java
@@ -37,6 +37,7 @@ import javax.naming.ConfigurationException;
import org.apache.cloudstack.managed.context.ManagedContextTimerTask;
import org.apache.log4j.Logger;
+import org.slf4j.MDC;
import com.cloud.agent.api.AgentControlAnswer;
import com.cloud.agent.api.AgentControlCommand;
@@ -462,6 +463,9 @@ public class Agent implements HandlerFactory, IAgentControl {
final Command cmd = cmds[i];
Answer answer;
try {
+ if (cmd.getContextParam("logid") != null) {
+ MDC.put("logcontextid", cmd.getContextParam("logid"));
+ }
if (s_logger.isDebugEnabled()) {
if (!requestLogged) // ensures request is logged only once per method call
{
@@ -615,6 +619,9 @@ public class Agent implements HandlerFactory, IAgentControl {
} else if (obj instanceof Request) {
final Request req = (Request)obj;
final Command command = req.getCommand();
+ if (command.getContextParam("logid") != null) {
+ MDC.put("logcontextid", command.getContextParam("logid"));
+ }
Answer answer = null;
_inProgress.incrementAndGet();
try {
diff --git a/api/src/org/apache/cloudstack/context/LogContext.java b/api/src/org/apache/cloudstack/context/LogContext.java
new file mode 100644
index 00000000000..9bc9ed738ec
--- /dev/null
+++ b/api/src/org/apache/cloudstack/context/LogContext.java
@@ -0,0 +1,302 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.apache.cloudstack.context;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import org.apache.log4j.Logger;
+import org.slf4j.MDC;
+
+import org.apache.cloudstack.managed.threadlocal.ManagedThreadLocal;
+
+import com.cloud.exception.CloudAuthenticationException;
+import com.cloud.user.Account;
+import com.cloud.user.User;
+import com.cloud.utils.UuidUtils;
+import com.cloud.utils.db.EntityManager;
+import com.cloud.utils.exception.CloudRuntimeException;
+
+/**
+ * LogContext records information about the environment the API call is made. This
+ * class must be always be available in all CloudStack code.
+ */
+public class LogContext {
+ private static final Logger s_logger = Logger.getLogger(LogContext.class);
+ private static ManagedThreadLocal s_currentContext = new ManagedThreadLocal();
+
+ private String logContextId;
+ private Account account;
+ private long accountId;
+ private long startEventId = 0;
+ private String eventDescription;
+ private String eventDetails;
+ private String eventType;
+ private boolean isEventDisplayEnabled = true; // default to true unless specifically set
+ private User user;
+ private long userId;
+ private final Map context = new HashMap();
+
+ static EntityManager s_entityMgr;
+
+ public static void init(EntityManager entityMgr) {
+ s_entityMgr = entityMgr;
+ }
+
+ protected LogContext() {
+ }
+
+ protected LogContext(long userId, long accountId, String logContextId) {
+ this.userId = userId;
+ this.accountId = accountId;
+ this.logContextId = logContextId;
+ }
+
+ protected LogContext(User user, Account account, String logContextId) {
+ this.user = user;
+ userId = user.getId();
+ this.account = account;
+ accountId = account.getId();
+ this.logContextId = logContextId;
+ }
+
+ public void putContextParameter(String key, String value) {
+ context.put(key, value);
+ }
+
+ public String getContextParameter(String key) {
+ return context.get(key);
+ }
+
+ public long getCallingUserId() {
+ return userId;
+ }
+
+ public User getCallingUser() {
+ if (user == null) {
+ user = s_entityMgr.findById(User.class, userId);
+ }
+ return user;
+ }
+
+ public String getLogContextId() {
+ return logContextId;
+ }
+
+ public Account getCallingAccount() {
+ if (account == null) {
+ account = s_entityMgr.findById(Account.class, accountId);
+ }
+ return account;
+ }
+
+ public static LogContext current() {
+ LogContext context = s_currentContext.get();
+ if (context == null) {
+ context = registerSystemLogContextOnceOnly();
+ }
+ return context;
+ }
+
+ /**
+ * This method should only be called if you can propagate the context id
+ * from another LogContext.
+ *
+ * @param callingUser calling user
+ * @param callingAccount calling account
+ * @param contextId context id propagated from another call context
+ * @return LogContext
+ */
+ public static LogContext register(User callingUser, Account callingAccount, String contextId) {
+ return register(callingUser, callingAccount, null, null, contextId);
+ }
+
+ protected static LogContext register(User callingUser, Account callingAccount, Long userId, Long accountId, String contextId) {
+ LogContext callingContext = null;
+ if (userId == null || accountId == null) {
+ callingContext = new LogContext(callingUser, callingAccount, contextId);
+ } else {
+ callingContext = new LogContext(userId, accountId, contextId);
+ }
+ s_currentContext.set(callingContext);
+ MDC.put("logcontextid", UuidUtils.first(contextId));
+ if (s_logger.isTraceEnabled()) {
+ s_logger.trace("Registered for log: " + callingContext);
+ }
+ return callingContext;
+ }
+
+ public static LogContext registerPlaceHolderContext() {
+ LogContext context = new LogContext(0, 0, UUID.randomUUID().toString());
+ s_currentContext.set(context);
+ return context;
+ }
+
+ public static LogContext register(User callingUser, Account callingAccount) {
+ return register(callingUser, callingAccount, UUID.randomUUID().toString());
+ }
+
+ public static LogContext registerSystemLogContextOnceOnly() {
+ try {
+ LogContext context = s_currentContext.get();
+ if (context == null) {
+ return register(null, null, User.UID_SYSTEM, Account.ACCOUNT_ID_SYSTEM, UUID.randomUUID().toString());
+ }
+ assert context.getCallingUserId() == User.UID_SYSTEM : "You are calling a very specific method that registers a one time system context. This method is meant for background threads that does processing.";
+ return context;
+ } catch (Exception e) {
+ s_logger.error("Failed to register the system log context.", e);
+ throw new CloudRuntimeException("Failed to register system log context", e);
+ }
+ }
+
+ public static LogContext register(String callingUserUuid, String callingAccountUuid) {
+ Account account = s_entityMgr.findByUuid(Account.class, callingAccountUuid);
+ if (account == null) {
+ throw new CloudAuthenticationException("The account is no longer current.").add(Account.class, callingAccountUuid);
+ }
+
+ User user = s_entityMgr.findByUuid(User.class, callingUserUuid);
+ if (user == null) {
+ throw new CloudAuthenticationException("The user is no longer current.").add(User.class, callingUserUuid);
+ }
+ return register(user, account);
+ }
+
+ public static LogContext register(long callingUserId, long callingAccountId) throws CloudAuthenticationException {
+ Account account = s_entityMgr.findById(Account.class, callingAccountId);
+ if (account == null) {
+ throw new CloudAuthenticationException("The account is no longer current.").add(Account.class, Long.toString(callingAccountId));
+ }
+ User user = s_entityMgr.findById(User.class, callingUserId);
+ if (user == null) {
+ throw new CloudAuthenticationException("The user is no longer current.").add(User.class, Long.toString(callingUserId));
+ }
+ return register(user, account);
+ }
+
+ public static LogContext register(long callingUserId, long callingAccountId, String contextId) throws CloudAuthenticationException {
+ Account account = s_entityMgr.findById(Account.class, callingAccountId);
+ if (account == null) {
+ throw new CloudAuthenticationException("The account is no longer current.").add(Account.class, Long.toString(callingAccountId));
+ }
+ User user = s_entityMgr.findById(User.class, callingUserId);
+ if (user == null) {
+ throw new CloudAuthenticationException("The user is no longer current.").add(User.class, Long.toString(callingUserId));
+ }
+ return register(user, account, contextId);
+ }
+
+ public static void unregister() {
+ LogContext context = s_currentContext.get();
+ if (context != null) {
+ s_currentContext.remove();
+ if (s_logger.isTraceEnabled()) {
+ s_logger.trace("Unregistered: " + context);
+ }
+ }
+ MDC.clear();
+ }
+
+ public void setStartEventId(long startEventId) {
+ this.startEventId = startEventId;
+ }
+
+ public long getStartEventId() {
+ return startEventId;
+ }
+
+ public long getCallingAccountId() {
+ return accountId;
+ }
+
+ public String getCallingAccountUuid() {
+ return getCallingAccount().getUuid();
+ }
+
+ public String getCallingUserUuid() {
+ return getCallingUser().getUuid();
+ }
+
+ public void setEventDetails(String eventDetails) {
+ this.eventDetails = eventDetails;
+ }
+
+ public String getEventDetails() {
+ return eventDetails;
+ }
+
+ public String getEventType() {
+ return eventType;
+ }
+
+ public void setEventType(String eventType) {
+ this.eventType = eventType;
+ }
+
+ public String getEventDescription() {
+ return eventDescription;
+ }
+
+ public void setEventDescription(String eventDescription) {
+ this.eventDescription = eventDescription;
+ }
+
+ /**
+ * Whether to display the event to the end user.
+ * @return true - if the event is to be displayed to the end user, false otherwise.
+ */
+ public boolean isEventDisplayEnabled() {
+ return isEventDisplayEnabled;
+ }
+
+ public void setEventDisplayEnabled(boolean eventDisplayEnabled) {
+ isEventDisplayEnabled = eventDisplayEnabled;
+ }
+
+ public Map getContextParameters() {
+ return context;
+ }
+
+ public void putContextParameters(Map details) {
+ if (details == null)
+ return;
+ for (Map.Entry entry : details.entrySet()) {
+ putContextParameter(entry.getKey(), entry.getValue());
+ }
+ }
+
+ public static void setActionEventInfo(String eventType, String description) {
+ LogContext context = LogContext.current();
+ if (context != null) {
+ context.setEventType(eventType);
+ context.setEventDescription(description);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder("LogCtxt[acct=").append(getCallingAccountId())
+ .append("; user=")
+ .append(getCallingUserId())
+ .append("; id=")
+ .append(logContextId)
+ .append("]")
+ .toString();
+ }
+}
\ No newline at end of file
diff --git a/api/src/org/apache/cloudstack/context/LogContextListener.java b/api/src/org/apache/cloudstack/context/LogContextListener.java
new file mode 100644
index 00000000000..6fc1beb0b87
--- /dev/null
+++ b/api/src/org/apache/cloudstack/context/LogContextListener.java
@@ -0,0 +1,50 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.apache.cloudstack.context;
+
+import javax.annotation.PostConstruct;
+import javax.inject.Inject;
+
+import org.apache.cloudstack.managed.context.ManagedContextListener;
+
+import com.cloud.utils.db.EntityManager;
+
+public class LogContextListener implements ManagedContextListener