View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.audit;
18  
19  import org.apache.logging.log4j.LogManager;
20  import org.apache.logging.log4j.Logger;
21  import org.apache.logging.log4j.ThreadContext;
22  import org.apache.logging.log4j.audit.catalog.CatalogManager;
23  import org.apache.logging.log4j.audit.exception.AuditException;
24  import org.apache.logging.log4j.audit.util.NamingUtils;
25  import org.apache.logging.log4j.catalog.api.Attribute;
26  import org.apache.logging.log4j.catalog.api.Constraint;
27  import org.apache.logging.log4j.catalog.api.Event;
28  import org.apache.logging.log4j.catalog.api.EventAttribute;
29  import org.apache.logging.log4j.audit.exception.ConstraintValidationException;
30  import org.apache.logging.log4j.catalog.api.plugins.ConstraintPlugins;
31  import org.apache.logging.log4j.message.StructuredDataMessage;
32  
33  import java.util.*;
34  
35  import static java.util.Collections.*;
36  
37  /**
38   * This class is used to log events generated remotely.
39   */
40  public abstract class AbstractEventLogger {
41  
42      private static final Logger logger = LogManager.getLogger(AbstractEventLogger.class);
43  
44      private static final int DEFAULT_MAX_LENGTH = 32;
45  
46      private static ConstraintPlugins constraintPlugins = ConstraintPlugins.getInstance();
47  
48      public CatalogManager catalogManager;
49  
50      private static final AuditExceptionHandler DEFAULT_EXCEPTION_HANDLER = (message, ex) -> {
51          throw new AuditException("Error logging event " + message.getId().getName(), ex);
52      };
53  
54      private static final AuditExceptionHandler NOOP_EXCEPTION_HANDLER = (message, ex) -> {
55      };
56  
57      private AuditExceptionHandler defaultAuditExceptionHandler = DEFAULT_EXCEPTION_HANDLER;
58  
59      private final int maxLength;
60  
61      protected AbstractEventLogger() {
62          maxLength = DEFAULT_MAX_LENGTH;
63      }
64  
65      protected AbstractEventLogger(int maxLength) {
66          this.maxLength = maxLength;
67      }
68  
69      public void setCatalogManager(CatalogManager catalogManager) {
70          this.catalogManager = catalogManager;
71      }
72  
73      public List<String> getAttributeNames(String eventId) {
74          return catalogManager.getAttributeNames(eventId);
75      }
76  
77      public void setDefaultAuditExceptionHandler(AuditExceptionHandler auditExceptionHandler) {
78          defaultAuditExceptionHandler = auditExceptionHandler == null ? NOOP_EXCEPTION_HANDLER : auditExceptionHandler;
79      }
80  
81      public void logEvent(String eventName, Map<String, String> attributes) {
82          logEvent(eventName, null, attributes, defaultAuditExceptionHandler);
83      }
84  
85      public void logEvent(String eventName, String catalogId, Map<String, String> attributes) {
86          logEvent(eventName, catalogId, attributes, defaultAuditExceptionHandler);
87      }
88  
89      public void logEvent(String eventName, Map<String, String> attributes, AuditExceptionHandler exceptionHandler) {
90          logEvent(eventName, null, attributes, exceptionHandler);
91      }
92  
93      private void logEvent(String eventName, String catalogId, Map<String, String> attributes, AuditExceptionHandler exceptionHandler) {
94          String eventId = NamingUtils.lowerFirst(eventName);
95          Event event = catalogId == null ? catalogManager.getEvent(eventId) : catalogManager.getEvent(eventId, catalogId);
96          if (event == null) {
97              throw new AuditException("Unable to locate definition of audit event " + eventId);
98          }
99          logEvent(eventId, attributes, event, exceptionHandler);
100     }
101 
102     protected abstract void logEvent(StructuredDataMessage message);
103 
104     private void logEvent(String eventName, Map<String, String> attributes, Event event,
105                           AuditExceptionHandler exceptionHandler) {
106         AuditMessage msg = new AuditMessage(eventName, maxLength);
107 
108         if (attributes == null) {
109             attributes = emptyMap();
110         }
111 
112         StringBuilder missingAttributes = new StringBuilder();
113         StringBuilder errors = new StringBuilder();
114 
115         List<EventAttribute> eventAttributes = event.getAttributes() == null ? emptyList() : event.getAttributes();
116         for (EventAttribute eventAttribute : eventAttributes) {
117             Attribute attr = catalogManager.getAttribute(eventAttribute.getName(), event.getCatalogId());
118             if ((!attr.isRequestContext() && (attr.isRequired()) ||
119                     (eventAttribute.isRequired() != null && eventAttribute.isRequired()))) {
120                 String name = attr.getName();
121                 if (!attributes.containsKey(name)) {
122                     if (missingAttributes.length() > 0) {
123                         missingAttributes.append(", ");
124                     }
125                     missingAttributes.append(name);
126                 } else {
127                     if (attr.getConstraints() != null && attr.getConstraints().size() > 0) {
128                         validateConstraints(false, attr.getConstraints(), name, attributes.get(name), errors);
129                     }
130                 }
131             }
132         }
133         Map<String, Attribute> attributeMap = catalogManager.getAttributes(eventName, event.getCatalogId());
134         for (String name : attributes.keySet()) {
135             if (!attributeMap.containsKey(name) && !name.equals("completionStatus")) {
136                 if (errors.length() > 0) {
137                     errors.append("\n");
138                 }
139                 errors.append("Attribute ").append(name).append(" is not defined for ").append(eventName);
140             }
141         }
142         if (missingAttributes.length() > 0) {
143             if (errors.length() > 0) {
144                 errors.append("\n");
145             }
146             errors.append("Event ").append(eventName).append(" is missing required attribute(s) ").append(missingAttributes.toString());
147         }
148         if (errors.length() > 0) {
149             throw new ConstraintValidationException(errors.toString());
150         }
151         List<String> attributeNames = catalogManager.getAttributeNames(eventName, event.getCatalogId());
152         StringBuilder buf = new StringBuilder();
153         for (String attribute : attributes.keySet()) {
154             if (!attributeNames.contains(attribute)) {
155                 if (buf.length() > 0) {
156                     buf.append(", ");
157                 }
158                 buf.append(attribute);
159             }
160         }
161         if (buf.length() > 0) {
162             throw new ConstraintValidationException("Event " + eventName + " contains invalid attribute(s) " + buf.toString());
163         }
164 
165         List<String> reqCtxAttrs = catalogManager.getRequiredContextAttributes(eventName, event.getCatalogId());
166         if (reqCtxAttrs != null && !reqCtxAttrs.isEmpty()) {
167             StringBuilder sb = new StringBuilder();
168             for (String attr : reqCtxAttrs) {
169                 if (!ThreadContext.containsKey(attr)) {
170                     if (sb.length() > 0) {
171                         sb.append(", ");
172                     }
173                     sb.append(attr);
174                 }
175             }
176             if (sb.length() > 0) {
177                 throw new ConstraintValidationException("Event " + msg.getId().getName() +
178                         " is missing required RequestContextMapping values for " + sb.toString());
179             }
180         }
181 
182         Map<String, Attribute> reqCtxAttributes = catalogManager.getRequestContextAttributes();
183         for (Map.Entry<String, Attribute> entry : reqCtxAttributes.entrySet()) {
184             Attribute attribute = entry.getValue();
185             String attr = entry.getKey();
186             if (attribute.isRequired() && !ThreadContext.containsKey(attr)) {
187                 if (errors.length() > 0) {
188                     errors.append(", ");
189                 }
190                 errors.append(attr);
191             }
192         }
193         if (errors.length() > 0) {
194             throw new ConstraintValidationException("Event " + eventName +
195                                              " is missing required Thread Context values for " + errors.toString());
196         }
197 
198         for (Map.Entry<String, Attribute> entry : reqCtxAttributes.entrySet()) {
199             Attribute attribute = reqCtxAttributes.get(entry.getKey());
200             if (!ThreadContext.containsKey(entry.getKey())) {
201                 continue;
202             }
203             Set<Constraint> constraintList = attribute.getConstraints();
204             if (constraintList != null && constraintList.size() > 0) {
205                 validateConstraints(true, constraintList, entry.getKey(), ThreadContext.get(entry.getKey()), errors);
206             }
207         }
208         if (errors.length() > 0) {
209             throw new ConstraintValidationException("Event " + eventName + " has incorrect data in the Thread Context: " + errors.toString());
210         }
211 
212         msg.putAll(attributes);
213         try {
214             logEvent(msg);
215         } catch (Throwable ex) {
216             if (exceptionHandler == null) {
217                 defaultAuditExceptionHandler.handleException(msg, ex);
218             } else {
219                 exceptionHandler.handleException(msg, ex);
220             }
221         }
222     }
223 
224     private static void validateConstraints(boolean isRequestContext, Collection<Constraint> constraints, String name,
225                                             String value, StringBuilder errors) {
226         for (Constraint constraint : constraints) {
227             constraintPlugins.validateConstraint(isRequestContext, constraint.getConstraintType().getName(), name, value,
228                     constraint.getValue(), errors);
229         }
230     }
231 }