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.catalog.api.Attribute;
25  import org.apache.logging.log4j.catalog.api.Constraint;
26  import org.apache.logging.log4j.catalog.api.Event;
27  import org.apache.logging.log4j.catalog.api.EventAttribute;
28  import org.apache.logging.log4j.catalog.api.plugins.ConstraintPlugins;
29  import org.apache.logging.log4j.message.StructuredDataMessage;
30  
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Set;
34  
35  import static org.apache.logging.log4j.catalog.api.constant.Constants.*;
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          Event event = catalogManager.getEvent(eventName);
83          if (event == null) {
84              throw new AuditException("Unable to locate definition of audit event " + eventName);
85          }
86          logEvent(eventName, attributes, event, defaultAuditExceptionHandler);
87      }
88  
89      public void logEvent(String eventName, String catalogId, Map<String, String> attributes) {
90          Event event = catalogManager.getEvent(eventName, catalogId);
91          if (event == null) {
92              throw new AuditException("Unable to locate definition of audit event " + eventName);
93          }
94          logEvent(eventName, attributes, event, defaultAuditExceptionHandler);
95      }
96  
97      public void logEvent(String eventName, Map<String, String> attributes, AuditExceptionHandler exceptionHandler) {
98          Event event = catalogManager.getEvent(eventName);
99  
100         if (event == null) {
101             throw new AuditException("Unable to locate definition of audit event " + eventName);
102         }
103         logEvent(eventName, attributes, event, exceptionHandler);
104     }
105 
106     protected abstract void logEvent(StructuredDataMessage message);
107 
108     private void logEvent(String eventName, Map<String, String> attributes, Event event,
109                           AuditExceptionHandler exceptionHandler) {
110         AuditMessage msg = new AuditMessage(eventName, maxLength);
111 
112         StringBuilder missingAttributes = new StringBuilder();
113         StringBuilder errors = new StringBuilder();
114 
115         for (EventAttribute eventAttribute : event.getAttributes()) {
116             Attribute attr = catalogManager.getAttribute(eventAttribute.getName(), event.getCatalogId());
117             if ((!attr.isRequestContext() && (attr.isRequired()) ||
118                     (eventAttribute.isRequired() != null && eventAttribute.isRequired()))) {
119                 String name = attr.getName();
120                 if (!attributes.containsKey(name)) {
121                     if (missingAttributes.length() > 0) {
122                         missingAttributes.append(", ");
123                     }
124                     missingAttributes.append(name);
125                 } else {
126                     if (attr.getConstraints() != null && attr.getConstraints().size() > 0) {
127                         Constraint[] constraints = attr.getConstraints().toArray(new Constraint[attr.getConstraints().size()]);
128                         validateConstraints(false, constraints, 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 AuditException(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 AuditException("Event " + eventName + " contains invalid attribute(s) " + buf.toString());
163         }
164 
165         List<String> reqCtxAttrs = catalogManager.getRequiredContextAttributes(eventName, event.getCatalogId());
166 
167         if (reqCtxAttrs != null) {
168             StringBuilder sb = new StringBuilder();
169             for (String attr : reqCtxAttrs) {
170                 if (!ThreadContext.containsKey(attr)) {
171                     if (sb.length() > 0) {
172                         sb.append(", ");
173                     }
174                     sb.append(attr);
175                 }
176             }
177             if (sb.length() > 0) {
178                 throw new IllegalStateException("Event " + msg.getId().getName() +
179                         " is missing required RequestContextMapping values for " + sb.toString());
180             }
181         }
182         Map<String, Attribute> reqCtxAttributes = catalogManager.getRequestContextAttributes();
183         for (Map.Entry<String, String> entry : ThreadContext.getImmutableContext().entrySet()) {
184             Attribute attribute = reqCtxAttributes.get(entry.getKey());
185             if (attribute == null) {
186                 continue;
187             }
188             Set<Constraint> constraintList = attribute.getConstraints();
189             if (constraintList != null && constraintList.size() > 0) {
190                 Constraint[] constraints =
191                         attribute.getConstraints().toArray(new Constraint[attribute.getConstraints().size()]);
192                 validateConstraints(true, constraints, entry.getKey(), ThreadContext.get(entry.getKey()), errors);
193             }
194         }
195         if (errors.length() > 0) {
196             throw new AuditException("Event " + eventName + " has incorrect data in the Thread Context: " + errors.toString());
197         }
198         msg.putAll(attributes);
199         try {
200             logEvent(msg);
201         } catch (Throwable ex) {
202             if (exceptionHandler == null) {
203                 defaultAuditExceptionHandler.handleException(msg, ex);
204             } else {
205                 exceptionHandler.handleException(msg, ex);
206             }
207         }
208     }
209 
210     private static void validateConstraints(boolean isRequestContext, Constraint[] constraints, String name,
211                                             String value, StringBuilder errors) {
212         for (Constraint constraint : constraints) {
213             constraintPlugins.validateConstraint(isRequestContext, constraint.getConstraintType().getName(), name, value,
214                     constraint.getValue(), errors);
215         }
216     }
217 }