blob: b573dac1ed1bc78da5c71e30ea6e02ee368ac1d7 [file] [log] [blame]
/*
* 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.logging.log4j.audit;
import org.apache.logging.log4j.ThreadContext;
import org.apache.logging.log4j.audit.catalog.CatalogManager;
import org.apache.logging.log4j.audit.exception.AuditException;
import org.apache.logging.log4j.audit.exception.ConstraintValidationException;
import org.apache.logging.log4j.catalog.api.Attribute;
import org.apache.logging.log4j.catalog.api.Constraint;
import org.apache.logging.log4j.catalog.api.Event;
import org.apache.logging.log4j.catalog.api.EventAttribute;
import org.apache.logging.log4j.catalog.api.plugins.ConstraintPlugins;
import org.apache.logging.log4j.message.StructuredDataMessage;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
/**
* This class is used to log events generated remotely.
*/
public abstract class AbstractEventLogger {
private static final int DEFAULT_MAX_LENGTH = 32;
private static final ConstraintPlugins constraintPlugins = ConstraintPlugins.getInstance();
public CatalogManager catalogManager;
private static final AuditExceptionHandler DEFAULT_EXCEPTION_HANDLER = (message, ex) -> {
throw new AuditException("Error logging event " + message.getId().getName(), ex);
};
private static final AuditExceptionHandler NOOP_EXCEPTION_HANDLER = (message, ex) -> {
};
private AuditExceptionHandler defaultAuditExceptionHandler = DEFAULT_EXCEPTION_HANDLER;
private final int maxLength;
protected AbstractEventLogger() {
maxLength = DEFAULT_MAX_LENGTH;
}
protected AbstractEventLogger(int maxLength) {
this.maxLength = maxLength;
}
public void setCatalogManager(CatalogManager catalogManager) {
this.catalogManager = catalogManager;
}
public List<String> getAttributeNames(String eventId) {
return catalogManager.getAttributeNames(eventId);
}
public void setDefaultAuditExceptionHandler(AuditExceptionHandler auditExceptionHandler) {
defaultAuditExceptionHandler = auditExceptionHandler == null ? NOOP_EXCEPTION_HANDLER : auditExceptionHandler;
}
public void logEvent(String eventName, Map<String, String> attributes) {
logEvent(eventName, null, attributes, defaultAuditExceptionHandler);
}
public void logEvent(String eventName, String catalogId, Map<String, String> attributes) {
logEvent(eventName, catalogId, attributes, defaultAuditExceptionHandler);
}
public void logEvent(String eventName, Map<String, String> attributes, AuditExceptionHandler exceptionHandler) {
logEvent(eventName, null, attributes, exceptionHandler);
}
private void logEvent(String eventName, String catalogId, Map<String, String> attributes, AuditExceptionHandler exceptionHandler) {
Event event = catalogId == null ? catalogManager.getEvent(eventName) : catalogManager.getEvent(eventName, catalogId);
if (event == null) {
throw new AuditException("Unable to locate definition of audit event " + eventName);
}
logEvent(eventName, attributes, event, exceptionHandler);
}
protected abstract void logEvent(StructuredDataMessage message);
private void logEvent(String eventName, Map<String, String> attributes, Event event,
AuditExceptionHandler exceptionHandler) {
AuditMessage msg = new AuditMessage(eventName, maxLength);
if (attributes == null) {
attributes = emptyMap();
}
StringBuilder missingAttributes = new StringBuilder();
StringBuilder errors = new StringBuilder();
List<EventAttribute> eventAttributes = event.getAttributes() == null ? emptyList() : event.getAttributes();
for (EventAttribute eventAttribute : eventAttributes) {
Attribute attr = catalogManager.getAttribute(eventAttribute.getName(), event.getCatalogId());
if ((!attr.isRequestContext() && (attr.isRequired()) ||
(eventAttribute.isRequired() != null && eventAttribute.isRequired()))) {
String name = attr.getName();
if (!attributes.containsKey(name)) {
if (missingAttributes.length() > 0) {
missingAttributes.append(", ");
}
missingAttributes.append(name);
} else {
if (attr.getConstraints() != null && attr.getConstraints().size() > 0) {
validateConstraints(false, attr.getConstraints(), name, attributes.get(name), errors);
}
}
}
}
Map<String, Attribute> attributeMap = catalogManager.getAttributes(eventName, event.getCatalogId());
for (String name : attributes.keySet()) {
if (!attributeMap.containsKey(name) && !name.equals("completionStatus")) {
if (errors.length() > 0) {
errors.append("\n");
}
errors.append("Attribute ").append(name).append(" is not defined for ").append(eventName);
}
}
if (missingAttributes.length() > 0) {
if (errors.length() > 0) {
errors.append("\n");
}
errors.append("Event ").append(eventName).append(" is missing required attribute(s) ").append(missingAttributes.toString());
}
if (errors.length() > 0) {
throw new ConstraintValidationException(errors.toString());
}
List<String> attributeNames = catalogManager.getAttributeNames(eventName, event.getCatalogId());
StringBuilder buf = new StringBuilder();
for (String attribute : attributes.keySet()) {
if (!attributeNames.contains(attribute)) {
if (buf.length() > 0) {
buf.append(", ");
}
buf.append(attribute);
}
}
if (buf.length() > 0) {
throw new ConstraintValidationException("Event " + eventName + " contains invalid attribute(s) " + buf.toString());
}
List<String> reqCtxAttrs = catalogManager.getRequiredContextAttributes(eventName, event.getCatalogId());
if (reqCtxAttrs != null && !reqCtxAttrs.isEmpty()) {
StringBuilder sb = new StringBuilder();
for (String attr : reqCtxAttrs) {
if (!ThreadContext.containsKey(attr)) {
if (sb.length() > 0) {
sb.append(", ");
}
sb.append(attr);
}
}
if (sb.length() > 0) {
throw new ConstraintValidationException("Event " + msg.getId().getName() +
" is missing required RequestContextMapping values for " + sb.toString());
}
}
Map<String, Attribute> reqCtxAttributes = catalogManager.getRequestContextAttributes();
for (Map.Entry<String, Attribute> entry : reqCtxAttributes.entrySet()) {
Attribute attribute = entry.getValue();
String attr = entry.getKey();
if (attribute.isRequired() && !ThreadContext.containsKey(attr)) {
if (errors.length() > 0) {
errors.append(", ");
}
errors.append(attr);
}
}
if (errors.length() > 0) {
throw new ConstraintValidationException("Event " + eventName +
" is missing required Thread Context values for " + errors.toString());
}
for (Map.Entry<String, Attribute> entry : reqCtxAttributes.entrySet()) {
Attribute attribute = reqCtxAttributes.get(entry.getKey());
if (!ThreadContext.containsKey(entry.getKey())) {
continue;
}
Set<Constraint> constraintList = attribute.getConstraints();
if (constraintList != null && constraintList.size() > 0) {
validateConstraints(true, constraintList, entry.getKey(), ThreadContext.get(entry.getKey()), errors);
}
}
if (errors.length() > 0) {
throw new ConstraintValidationException("Event " + eventName + " has incorrect data in the Thread Context: " + errors.toString());
}
msg.putAll(attributes);
try {
logEvent(msg);
} catch (Throwable ex) {
if (exceptionHandler == null) {
defaultAuditExceptionHandler.handleException(msg, ex);
} else {
exceptionHandler.handleException(msg, ex);
}
}
}
private static void validateConstraints(boolean isRequestContext, Collection<Constraint> constraints, String name,
String value, StringBuilder errors) {
for (Constraint constraint : constraints) {
constraintPlugins.validateConstraint(isRequestContext, constraint.getConstraintType().getName(), name, value,
constraint.getValue(), errors);
}
}
}