Skip to content

Commit

Permalink
KYLO-3162: Created LoggingUtil to handle dynameic logging. Added logi…
Browse files Browse the repository at this point in the history
…n/logout logging.
  • Loading branch information
felten committed Dec 10, 2018
1 parent 0862cee commit f0b3ed5
Show file tree
Hide file tree
Showing 4 changed files with 267 additions and 70 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/**
*
*/
package com.thinkbiganalytics.logging;

/*-
* #%L
* kylo-commons-util
* %%
* Copyright (C) 2017 - 2018 ThinkBig Analytics, a Teradata Company
* %%
* Licensed 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.
* #L%
*/

import org.slf4j.Logger;

import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

/**
*
*/
public class LoggingUtil {

public enum LogLevel {
ERROR(logger -> (msg, args) -> logger.error(msg, args)),
WARN(logger -> (msg, args) -> logger.warn(msg, args)),
INFO(logger -> (msg, args) -> logger.info(msg, args)),
DEBUG(logger -> (msg, args) -> logger.debug(msg, args)),
TRACE(logger -> (msg, args) -> logger.trace(msg, args));

private final Function<Logger, BiConsumer<String, Object[]>> loggingFunc;

LogLevel(Function<Logger, BiConsumer<String, Object[]>> func) {
loggingFunc = func;
}

public static LogLevel level(String value) {
return LogLevel.valueOf(value.toUpperCase());
}

public void log(Logger logger, String message, Object... args) {
loggingFunc.apply(logger).accept(message, args);
}
}

private static final String TOKEN_REGEX = "\\{\\s*(\\w+)\\s*\\}";
private static final Pattern TOKENS_PATTERN = Pattern.compile("\\s*" + TOKEN_REGEX + "\\s*", Pattern.CASE_INSENSITIVE);

public static void log(Logger logger, LogLevel level, String message, Object... args) {
level.log(logger, message, args);
}

public static <E extends Enum<E>> void log(Logger logger, LogLevel level, Class<E> tokensType, String message, Function<E, Object> tokenValueFunct) {
log(logger, level, tokensType, message, null, tokenValueFunct);
}

public static <E extends Enum<E>> void log(Logger logger, LogLevel level, Class<E> tokensType, String message, Throwable th, Function<E, Object> tokenValueFunct) {
String logMsg = toLogMessage(message);
Object[] args = extractArguments(tokensType, message, th, tokenValueFunct);

level.log(logger, logMsg, args);
}

public static <E extends Enum<E>> Object[] extractArguments(Class<E> tokensType, String message, Function<E, Object> tokenValueFunct) {
return extractTokens(tokensType, message).stream()
.map(tokenValueFunct)
.toArray(Object[]::new);
}

public static <E extends Enum<E>> Object[] extractArguments(Class<E> tokensType, String message, Throwable th, Function<E, Object> tokenValueFunct) {
return Stream.concat(extractTokens(tokensType, message).stream().map(tokenValueFunct),
th != null ? Stream.of(th) : Stream.empty())
.toArray(Object[]::new);
}

public static <E extends Enum<E>> Object[] deriveArguments(List<E> tokens, Function<E, Object> tokenValueFunct) {
return tokens.stream()
.map(tokenValueFunct)
.toArray(Object[]::new);
}

public static <E extends Enum<E>> Object[] deriveArguments(List<E> tokens, Throwable th, Function<E, Object> tokenValueFunct) {
return Stream.concat(tokens.stream().map(tokenValueFunct),
Stream.of(th))
.toArray(Object[]::new);
}

public static <E extends Enum<E>> List<E> extractTokens(Class<E> tokensType, String message) {
List<E> tokens = new ArrayList<>();
Matcher matcher = TOKENS_PATTERN.matcher(message);

while (matcher.find()) {
String token = matcher.group(1).toUpperCase();
try {
tokens.add(Enum.valueOf(tokensType, token));
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Unsupported logging field name: " + token);
}
}

return tokens;
}

public static String toLogMessage(String message) {
return message.replaceAll(TOKEN_REGEX, "{}");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
*/
package com.thinkbiganalytics.metadata.modeshape.security;

import com.thinkbiganalytics.logging.LoggingUtil;
import com.thinkbiganalytics.logging.LoggingUtil.LogLevel;

/*-
* #%L
* thinkbig-metadata-modeshape
Expand Down Expand Up @@ -39,14 +42,10 @@

import java.security.AccessControlException;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand All @@ -61,16 +60,12 @@ public class DefaultAccessController implements AccessController {
private static final Logger log = LoggerFactory.getLogger(DefaultAccessController.class);


public enum LogFields { PERM, ENTITY, RESULT, USER, GROUPS, IP_ADDRESS };

private static final Pattern LOG_FIELD_PATTERN = Pattern.compile("\\s*\\{\\s*(\\w+)\\s*\\}\\s*", Pattern.CASE_INSENSITIVE);
// public enum LogFields { PERM, ENTITY, RESULT, USER, GROUPS, IP_ADDRESS };
public enum LogFields { PERM, ENTITY, RESULT, USER, GROUPS };

@org.springframework.beans.factory.annotation.Value("${security.entity.access.controlled:false}")
private volatile boolean entityAccessControlled;

@org.springframework.beans.factory.annotation.Value("${security.log.auth:false}")
private volatile boolean logAuthentication;

@org.springframework.beans.factory.annotation.Value("${security.log.access:false}")
private volatile boolean logAccessCheck;

Expand All @@ -92,20 +87,20 @@ public enum LogFields { PERM, ENTITY, RESULT, USER, GROUPS, IP_ADDRESS };
@Inject
private AllowedEntityActionsProvider actionsProvider;

private LogLevel logLevel;
private List<LogFields> formatFields;
private Set<UsernamePrincipal> ignoreUsers;
private Set<GroupPrincipal> ignoreGroups;
private String logMessage;
private BiConsumer<String, Object[]> loggingConsumer;

public DefaultAccessController() {
}

@PostConstruct
public void init() {
this.formatFields = generateFormatFields();
this.logMessage = generateLogMessage(this.formatFields);
this.loggingConsumer = generateLoggingConsumer();
this.logLevel = LogLevel.level(this.accessLogLevel);
this.formatFields = LoggingUtil.extractTokens(LogFields.class, this.accessLogFormat);
this.logMessage = LoggingUtil.toLogMessage(this.accessLogFormat);

if (StringUtils.isBlank(ignoreUsersCsv)) {
this.ignoreUsers = Collections.emptySet();
Expand Down Expand Up @@ -240,32 +235,34 @@ protected void logAccessCheck(String entity, Set<Action> actions, String result)
ignore |= ignoreGroups.stream().anyMatch(groups::contains);

if (! ignore) {
String[] logArgs = getFormatFields().stream()
.map(field -> {
switch (field) {
case PERM:
return actions.stream().map(action -> action.getSystemName()).collect(Collectors.joining(", "));
case ENTITY:
return entity;
case RESULT:
return result;
case USER:
return user.getName();
case GROUPS:
return groups.stream().map(Principal::getName).collect(Collectors.joining(", "));
case IP_ADDRESS:
return "";
default:
return "";
}
})
.toArray(String[]::new);
Object[] args = LoggingUtil.deriveArguments(getFormatFields(), field -> {
switch (field) {
case PERM:
return actions.stream().map(action -> action.getSystemName()).collect(Collectors.joining(", "));
case ENTITY:
return entity;
case RESULT:
return result;
case USER:
return user.getName();
case GROUPS:
return groups.stream().map(Principal::getName).collect(Collectors.joining(", "));
// case IP_ADDRESS:
// return "";
default:
return "";
}
});

getLoggingConsumer().accept(this.logMessage, logArgs);
LoggingUtil.log(log, getLogLevel(), getLogMessage(), args);
}
}
}

public LogLevel getLogLevel() {
return logLevel;
}

protected String getAccessLogFormat() {
return accessLogFormat;
}
Expand All @@ -281,36 +278,4 @@ protected String getLogMessage() {
protected Set<UsernamePrincipal> getIgnoreUsers() {
return ignoreUsers;
}

protected BiConsumer<String, Object[]> getLoggingConsumer() {
return loggingConsumer;
}

protected BiConsumer<String, Object[]> generateLoggingConsumer() {
if (this.accessLogLevel.equalsIgnoreCase("error")) return (msg, args) -> log.error(msg, args);
if (this.accessLogLevel.equalsIgnoreCase("warn")) return (msg, args) -> log.warn(msg, args);
if (this.accessLogLevel.equalsIgnoreCase("info")) return (msg, args) -> log.info(msg, args);
if (this.accessLogLevel.equalsIgnoreCase("trace")) return (msg, args) -> log.trace(msg, args);
return (msg, args) -> log.debug(msg, args);
}

protected List<LogFields> generateFormatFields() {
Matcher matcher = LOG_FIELD_PATTERN.matcher(this.accessLogFormat);
ArrayList<LogFields> fields = new ArrayList<>();

while (matcher.find()) {
String fieldStr = matcher.group(1).toUpperCase();
try {
fields.add(LogFields.valueOf(fieldStr));
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Unsupported security logging field name: " + fieldStr);
}
}

return fields;
}

protected String generateLogMessage(List<LogFields> formatFields) {
return getAccessLogFormat().replaceAll("\\{\\s*\\w+\\s*\\}", "{}");
}
}
5 changes: 5 additions & 0 deletions security/security-auth/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
<artifactId>kylo-security-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.thinkbiganalytics.kylo</groupId>
<artifactId>kylo-commons-util</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>org.apache.commons</groupId>
Expand Down
Loading

0 comments on commit f0b3ed5

Please sign in to comment.