.../hadoop-common/src/main/conf/log4j.properties | 10 ++ .../ApplicationHistoryAuditLogger.java | 155 +++++++++++++++++++++ .../ApplicationHistoryServer.java | 23 +++ .../yarn/server/timeline/TimelineDataManager.java | 8 ++ .../timeline/webapp/TimelineWebServices.java | 97 ++++++++++++- 5 files changed, 290 insertions(+), 3 deletions(-) diff --git a/hadoop-common-project/hadoop-common/src/main/conf/log4j.properties b/hadoop-common-project/hadoop-common/src/main/conf/log4j.properties index 52d2c1ff038..2c54635d9a4 100644 --- a/hadoop-common-project/hadoop-common/src/main/conf/log4j.properties +++ b/hadoop-common-project/hadoop-common/src/main/conf/log4j.properties @@ -256,6 +256,16 @@ log4j.appender.NMAUDIT.MaxBackupIndex=${nm.audit.log.maxbackupindex} #log4j.appender.HSAUDIT.layout.ConversionPattern=%d{ISO8601} %p %c{2}: %m%n #log4j.appender.HSAUDIT.DatePattern=.yyyy-MM-dd +# AHS audit log configs +#yarn.ahs.audit.logger=INFO,AHSAUDIT +#log4j.logger.org.apache.hadoop.yarn.server.applicationhistoryservice.ApplicationHistoryAuditLogger=${yarn.ahs.audit.logger} +#log4j.additivity.org.apache.hadoop.yarn.server.applicationhistoryservice.ApplicationHistoryAuditLogger=false +#log4j.appender.AHSAUDIT=org.apache.log4j.DailyRollingFileAppender +#log4j.appender.AHSAUDIT.File=${hadoop.log.dir}/ahs-audit.log +#log4j.appender.AHSAUDIT.layout=org.apache.log4j.PatternLayout +#log4j.appender.AHSAUDIT.layout.ConversionPattern=%d{ISO8601} %p %c{2}: %m%n +#log4j.appender.AHSAUDIT.DatePattern=.yyyy-MM-dd + # Http Server Request Logs #log4j.logger.http.requests.namenode=INFO,namenoderequestlog #log4j.appender.namenoderequestlog=org.apache.hadoop.http.HttpRequestLogAppender diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/applicationhistoryservice/ApplicationHistoryAuditLogger.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/applicationhistoryservice/ApplicationHistoryAuditLogger.java new file mode 100644 index 00000000000..192e68acb3c --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/applicationhistoryservice/ApplicationHistoryAuditLogger.java @@ -0,0 +1,155 @@ +/** + * 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.hadoop.yarn.server.applicationhistoryservice; + +import java.net.InetAddress; + +import org.apache.hadoop.classification.InterfaceAudience.Private; +import org.apache.hadoop.ipc.Server; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Private +public class ApplicationHistoryAuditLogger { + private static final Logger LOG = LoggerFactory + .getLogger(ApplicationHistoryAuditLogger.class); + + public static final String UNKNOWN_USER = "UNKNOWN"; + + enum Keys { + USER, OPERATION, TARGET, RESULT, IP, PERMISSIONS, DESCRIPTION + } + + public static class AuditConstants { + static final String SUCCESS = "SUCCESS"; + static final String FAILURE = "FAILURE"; + static final String KEY_VAL_SEPARATOR = "="; + static final char PAIR_SEPARATOR = '\t'; + + // Some commonly used descriptions + public static final String UNAUTHORIZED_USER = "Unauthorized user"; + } + + /** + * Create a readable and parseable audit log string for a successful event. + * + * @param user + * User who made the service request. + * @param operation + * Operation requested by the user. + * @param target + * The target on which the operation is being performed. + * + *
+ *
+ * Note that the {@link ApplicationHistoryAuditLogger} uses tabs + * ('\t') as a key-val delimiter and hence the value fields should + * not contains tabs ('\t'). + */ + public static void logSuccess(String user, String operation, String target) { + if (LOG.isInfoEnabled()) { + LOG.info(createSuccessLog(user, operation, target)); + } + } + + /** + * A helper api for creating an audit log for a successful event. + */ + static String createSuccessLog(String user, String operation, String target) { + StringBuilder b = new StringBuilder(); + start(Keys.USER, user, b); + addRemoteIP(b); + add(Keys.OPERATION, operation, b); + add(Keys.TARGET, target, b); + add(Keys.RESULT, AuditConstants.SUCCESS, b); + return b.toString(); + } + + /** + * A helper api to add remote IP address. + */ + static void addRemoteIP(StringBuilder b) { + InetAddress ip = Server.getRemoteIp(); + // ip address can be null for testcases + if (ip != null) { + add(Keys.IP, ip.getHostAddress(), b); + } + } + + /** + * Appends the key-val pair to the passed builder in the following format. + * key=value + */ + static void add(Keys key, String value, StringBuilder b) { + b.append(AuditConstants.PAIR_SEPARATOR).append(key.name()) + .append(AuditConstants.KEY_VAL_SEPARATOR).append(value); + } + + /** + * Adds the first key-val pair to the passed builder in the following format. + * key=value + */ + static void start(Keys key, String value, StringBuilder b) { + b.append(key.name()).append(AuditConstants.KEY_VAL_SEPARATOR).append(value); + } + + /** + * Create a readable and parseable audit log string for a failed event. + * + * @param user + * User who made the service request. + * @param operation + * Operation requested by the user. + * @param perm + * Target permissions. + * @param target + * The target on which the operation is being performed. + * @param description + * Some additional information as to why the operation failed. + * + *
+ *
+ * Note that the {@link ApplicationHistoryAuditLogger} uses tabs + * ('\t') as a key-val delimiter and hence the value fields should + * not contains tabs ('\t'). + */ + public static void logFailure(String user, String operation, String perm, + String target, String description) { + if (LOG.isWarnEnabled()) { + LOG.warn(createFailureLog(user, operation, perm, target, description)); + } + } + + /** + * A helper api for creating an audit log for a failure event. + */ + static String createFailureLog(String user, String operation, String perm, + String target, String description) { + StringBuilder b = new StringBuilder(); + start(Keys.USER, user, b); + addRemoteIP(b); + add(Keys.OPERATION, operation, b); + add(Keys.TARGET, target, b); + add(Keys.RESULT, AuditConstants.FAILURE, b); + add(Keys.DESCRIPTION, description, b); + add(Keys.PERMISSIONS, perm, b); + + return b.toString(); + } +} \ No newline at end of file diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/applicationhistoryservice/ApplicationHistoryServer.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/applicationhistoryservice/ApplicationHistoryServer.java index 5f1d7c29de2..04046515e58 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/applicationhistoryservice/ApplicationHistoryServer.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/applicationhistoryservice/ApplicationHistoryServer.java @@ -30,6 +30,7 @@ import org.apache.hadoop.metrics2.source.JvmMetrics; import org.apache.hadoop.security.HttpCrossOriginFilterInitializer; import org.apache.hadoop.security.SecurityUtil; +import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.service.CompositeService; import org.apache.hadoop.service.Service; import org.apache.hadoop.util.ExitUtil; @@ -128,6 +129,8 @@ protected void serviceStop() throws Exception { } DefaultMetricsSystem.shutdown(); super.serviceStop(); + ApplicationHistoryAuditLogger.logSuccess(getUserName(), + "ApplicationHistoryServerShutdown", "ApplicationHistoryServer"); } @Private @@ -176,8 +179,13 @@ static ApplicationHistoryServer launchAppHistoryServer(String[] args) { new GenericOptionsParser(conf, args); appHistoryServer.init(conf); appHistoryServer.start(); + ApplicationHistoryAuditLogger.logSuccess(getUserName(), + "ApplicationHistoryServerStartup", "ApplicationHistoryServer"); } catch (Throwable t) { LOG.error("Error starting ApplicationHistoryServer", t); + ApplicationHistoryAuditLogger.logFailure(getUserName(), + "ApplicationHistoryServerStartup", "", "ApplicationHistoryServer", + "Exception during startup"); ExitUtil.terminate(-1, "Error starting ApplicationHistoryServer"); } return appHistoryServer; @@ -340,4 +348,19 @@ private static InetSocketAddress getBindAddress(Configuration conf) { YarnConfiguration.DEFAULT_TIMELINE_SERVICE_ADDRESS, YarnConfiguration.DEFAULT_TIMELINE_SERVICE_PORT); } + + /** + * Return user name + * @return + */ + public static String getUserName() { + String userName = ApplicationHistoryAuditLogger.UNKNOWN_USER; + try { + UserGroupInformation user = UserGroupInformation.getCurrentUser(); + userName = user.getShortUserName(); + } catch (IOException ioe) { + LOG.warn("Couldn't get current user", ioe); + } + return userName; + } } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/TimelineDataManager.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/TimelineDataManager.java index bf40d41c8ff..20f49bdb46d 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/TimelineDataManager.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/TimelineDataManager.java @@ -38,6 +38,7 @@ import org.apache.hadoop.yarn.api.records.timeline.TimelineDomains; import org.apache.hadoop.yarn.api.records.timeline.TimelinePutResponse; import org.apache.hadoop.yarn.exceptions.YarnException; +import org.apache.hadoop.yarn.server.applicationhistoryservice.ApplicationHistoryAuditLogger; import org.apache.hadoop.yarn.server.timeline.TimelineReader.Field; import org.apache.hadoop.yarn.server.timeline.security.TimelineACLsManager; import org.apache.hadoop.yarn.webapp.BadRequestException; @@ -350,6 +351,10 @@ private TimelinePutResponse doPostEntities( } // check if there is existing entity TimelineEntity existingEntity = null; + String user = (null != callerUGI) ? callerUGI.getUserName() + : ApplicationHistoryAuditLogger.UNKNOWN_USER; + String url = "/postEntities (EntityID=" + entity.getEntityId() + + ";EntityType=" + entity.getEntityType() + ")"; try { existingEntity = store.getEntity(entity.getEntityId(), entity.getEntityType(), @@ -371,6 +376,7 @@ private TimelinePutResponse doPostEntities( + entity.getEntityType() + " } into the domain " + entity.getDomainId() + "."); } + ApplicationHistoryAuditLogger.logSuccess(user, "/postEntities", url); } catch (Exception e) { // Skip the entity which already exists and was put by others LOG.warn("Skip the timeline entity: { id: " + entity.getEntityId() @@ -382,6 +388,8 @@ private TimelinePutResponse doPostEntities( error.setErrorCode( TimelinePutResponse.TimelinePutError.ACCESS_DENIED); errors.add(error); + ApplicationHistoryAuditLogger.logFailure(user, "/postEntities", "", url, + "Exception while handling request"); continue; } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/webapp/TimelineWebServices.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/webapp/TimelineWebServices.java index 9423e7f71b5..0f8bd9c22e9 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/webapp/TimelineWebServices.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/webapp/TimelineWebServices.java @@ -53,6 +53,7 @@ import org.apache.hadoop.yarn.api.records.timeline.TimelineEvents; import org.apache.hadoop.yarn.api.records.timeline.TimelinePutResponse; import org.apache.hadoop.yarn.exceptions.YarnException; +import org.apache.hadoop.yarn.server.applicationhistoryservice.ApplicationHistoryAuditLogger; import org.apache.hadoop.yarn.server.timeline.EntityIdentifier; import org.apache.hadoop.yarn.server.timeline.GenericObjectMapper; import org.apache.hadoop.yarn.server.timeline.NameValuePair; @@ -78,6 +79,14 @@ .getLogger(TimelineWebServices.class); private TimelineDataManager timelineDataManager; + private static final String AHS_REST_ABOUT = "/about"; + private static final String AHS_REST_GETENTITIES = "/getEntities"; + private static final String AHS_REST_GETENTITY = "/getEntity"; + private static final String AHS_REST_GETEVENTS = "/getEvents"; + private static final String AHS_REST_POSTENTITIES = "/postEntities"; + private static final String AHS_REST_PUTDOMAIN = "/putDomain"; + private static final String AHS_REST_GETDOMAIN = "/getDomain"; + private static final String AHS_REST_GETDOMAINS = "/getDomains"; @Inject public TimelineWebServices(TimelineDataManager timelineDataManager) { @@ -94,6 +103,11 @@ public TimelineAbout about( @Context HttpServletRequest req, @Context HttpServletResponse res) { init(res); + UserGroupInformation callerUGI = getUser(req); + String user = (null != callerUGI) ? callerUGI.getUserName() + : ApplicationHistoryAuditLogger.UNKNOWN_USER; + ApplicationHistoryAuditLogger.logSuccess(user, AHS_REST_ABOUT, + AHS_REST_ABOUT); return TimelineUtils.createTimelineAbout("Timeline API"); } @@ -117,8 +131,12 @@ public TimelineEntities getEntities( @QueryParam("limit") String limit, @QueryParam("fields") String fields) { init(res); + UserGroupInformation callerUGI = getUser(req); + String user = (null != callerUGI) ? callerUGI.getUserName() + : ApplicationHistoryAuditLogger.UNKNOWN_USER; + String url = "/" + entityType; try { - return timelineDataManager.getEntities( + TimelineEntities entities = timelineDataManager.getEntities( parseStr(entityType), parsePairStr(primaryFilter, ":"), parsePairsStr(secondaryFilter, ",", ":"), @@ -129,13 +147,21 @@ public TimelineEntities getEntities( parseLongStr(limit), parseFieldsStr(fields, ","), getUser(req)); + ApplicationHistoryAuditLogger.logSuccess(user, AHS_REST_GETENTITIES, url); + return entities; } catch (NumberFormatException e) { + ApplicationHistoryAuditLogger.logFailure(user, AHS_REST_GETENTITIES, "", + url, "NumberFormatException while processing request"); throw new BadRequestException( "windowStart, windowEnd, fromTs or limit is not a numeric value: " + e); } catch (IllegalArgumentException e) { + ApplicationHistoryAuditLogger.logFailure(user, AHS_REST_GETENTITIES, "", + url, "IllegalArgumentException while processing request"); throw new BadRequestException("requested invalid field: " + e); } catch (Exception e) { LOG.error("Error getting entities", e); + ApplicationHistoryAuditLogger.logFailure(user, AHS_REST_GETENTITIES, "", + url, "Exception while handling request"); throw new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR); } @@ -156,24 +182,37 @@ public TimelineEntity getEntity( @QueryParam("fields") String fields) { init(res); TimelineEntity entity = null; + UserGroupInformation callerUGI = getUser(req); + String user = (null != callerUGI) ? callerUGI.getUserName() + : ApplicationHistoryAuditLogger.UNKNOWN_USER; + String url = "/" + entityType + "/" + entityId; try { entity = timelineDataManager.getEntity( parseStr(entityType), parseStr(entityId), parseFieldsStr(fields, ","), getUser(req)); + ApplicationHistoryAuditLogger.logSuccess(user, AHS_REST_GETENTITY, url); } catch (YarnException e) { // The user doesn't have the access to override the existing domain. LOG.info(e.getMessage(), e); + ApplicationHistoryAuditLogger.logFailure(user, AHS_REST_GETENTITY, "", + url, "YarnException while processing request"); throw new ForbiddenException(e); } catch (IllegalArgumentException e) { + ApplicationHistoryAuditLogger.logFailure(user, AHS_REST_GETENTITY, "", + url, "IllegalArgumentException while processing request"); throw new BadRequestException(e); } catch (Exception e) { LOG.error("Error getting entity", e); + ApplicationHistoryAuditLogger.logFailure(user, AHS_REST_GETENTITY, "", + url, "Exception while handling request"); throw new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR); } if (entity == null) { + ApplicationHistoryAuditLogger.logFailure(user, AHS_REST_GETENTITY, "", + url, url + " not found"); throw new NotFoundException("Timeline entity " + new EntityIdentifier(parseStr(entityId), parseStr(entityType)) + " is not found"); @@ -198,8 +237,12 @@ public TimelineEvents getEvents( @QueryParam("windowEnd") String windowEnd, @QueryParam("limit") String limit) { init(res); + UserGroupInformation callerUGI = getUser(req); + String user = (null != callerUGI) ? callerUGI.getUserName() + : ApplicationHistoryAuditLogger.UNKNOWN_USER; + String url = "/" + entityType + "/events"; try { - return timelineDataManager.getEvents( + TimelineEvents events = timelineDataManager.getEvents( parseStr(entityType), parseArrayStr(entityId, ","), parseArrayStr(eventType, ","), @@ -207,12 +250,18 @@ public TimelineEvents getEvents( parseLongStr(windowEnd), parseLongStr(limit), getUser(req)); + ApplicationHistoryAuditLogger.logSuccess(user, AHS_REST_GETEVENTS, url); + return events; } catch (NumberFormatException e) { + ApplicationHistoryAuditLogger.logFailure(user, AHS_REST_GETEVENTS, "", + url, "BadRequestException while processing request"); throw (BadRequestException)new BadRequestException( "windowStart, windowEnd or limit is not a numeric value.") .initCause(e); } catch (Exception e) { LOG.error("Error getting entity timelines", e); + ApplicationHistoryAuditLogger.logFailure(user, AHS_REST_GETEVENTS, "", + url, "Exception while handling request"); throw new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR); } @@ -237,12 +286,19 @@ public TimelinePutResponse postEntities( LOG.error(msg); throw new ForbiddenException(msg); } + String user = (null != callerUGI) ? callerUGI.getUserName() + : ApplicationHistoryAuditLogger.UNKNOWN_USER; + String url = "/postEntities"; try { return timelineDataManager.postEntities(entities, callerUGI); } catch (BadRequestException bre) { + ApplicationHistoryAuditLogger.logFailure(user, AHS_REST_POSTENTITIES, "", + url, "BadRequestException while handling request"); throw bre; } catch (Exception e) { LOG.error("Error putting entities", e); + ApplicationHistoryAuditLogger.logFailure(user, AHS_REST_POSTENTITIES, "", + url, "Error putting entities"); throw new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR); } @@ -266,21 +322,34 @@ public TimelinePutResponse putDomain( if (callerUGI == null) { String msg = "The owner of the posted timeline domain is not set"; LOG.error(msg); + ApplicationHistoryAuditLogger.logFailure( + ApplicationHistoryAuditLogger.UNKNOWN_USER, AHS_REST_PUTDOMAIN, "", + "/domain", "The owner of the posted timeline domain is not set"); throw new ForbiddenException(msg); } domain.setOwner(callerUGI.getShortUserName()); + String user = (null != callerUGI) ? callerUGI.getUserName() + : ApplicationHistoryAuditLogger.UNKNOWN_USER; + String url = "/domain"; try { timelineDataManager.putDomain(domain, callerUGI); + ApplicationHistoryAuditLogger.logSuccess(user, AHS_REST_PUTDOMAIN, url); } catch (YarnException e) { // The user doesn't have the access to override the existing domain. LOG.error(e.getMessage(), e); + ApplicationHistoryAuditLogger.logFailure(user, AHS_REST_PUTDOMAIN, "", + url, "YarnException while processing request"); throw new ForbiddenException(e); } catch (RuntimeException e) { LOG.error("Error putting domain", e); + ApplicationHistoryAuditLogger.logFailure(user, AHS_REST_PUTDOMAIN, "", + url, "RuntimeException while processing request"); throw new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR); } catch (IOException e) { LOG.error("Error putting domain", e); + ApplicationHistoryAuditLogger.logFailure(user, AHS_REST_PUTDOMAIN, "", + url, "Exception while processing request"); throw new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR); } @@ -301,21 +370,32 @@ public TimelineDomain getDomain( init(res); domainId = parseStr(domainId); if (domainId == null || domainId.length() == 0) { + ApplicationHistoryAuditLogger.logFailure("", AHS_REST_GETDOMAIN, "", + "/domain", "Domain ID is not specified."); throw new BadRequestException("Domain ID is not specified."); } TimelineDomain domain = null; + UserGroupInformation callerUGI = getUser(req); + String user = (null != callerUGI) ? callerUGI.getUserName() + : ApplicationHistoryAuditLogger.UNKNOWN_USER; + String url = "/domain/" + domainId; try { domain = timelineDataManager.getDomain( parseStr(domainId), getUser(req)); } catch (Exception e) { LOG.error("Error getting domain", e); + ApplicationHistoryAuditLogger.logFailure(user, AHS_REST_GETDOMAIN, "", + url, "Error getting domain"); throw new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR); } if (domain == null) { + ApplicationHistoryAuditLogger.logFailure(user, AHS_REST_GETDOMAIN, "", + url, "Timeline domain [" + domainId + "] not found"); throw new NotFoundException("Timeline domain [" + domainId + "] is not found"); } + ApplicationHistoryAuditLogger.logSuccess(user, AHS_REST_GETDOMAIN, url); return domain; } @@ -335,16 +415,27 @@ public TimelineDomains getDomains( UserGroupInformation callerUGI = getUser(req); if (owner == null || owner.length() == 0) { if (callerUGI == null) { + ApplicationHistoryAuditLogger.logFailure( + ApplicationHistoryAuditLogger.UNKNOWN_USER, AHS_REST_GETDOMAINS, "", + "/domain", "Domain owner is not specified."); throw new BadRequestException("Domain owner is not specified."); } else { // By default it's going to list the caller's domains owner = callerUGI.getShortUserName(); } } + String user = (null != callerUGI) ? callerUGI.getUserName() + : ApplicationHistoryAuditLogger.UNKNOWN_USER; + String url = "/domain"; try { - return timelineDataManager.getDomains(owner, callerUGI); + TimelineDomains timelineDomains = + timelineDataManager.getDomains(owner, callerUGI); + ApplicationHistoryAuditLogger.logSuccess(user, AHS_REST_GETDOMAINS, url); + return timelineDomains; } catch (Exception e) { LOG.error("Error getting domains", e); + ApplicationHistoryAuditLogger.logFailure(user, AHS_REST_GETDOMAINS, "", + url, "Error getting domains"); throw new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR); }