使用面向方面编程时的自定义参数记录
Customized parameter logging when using aspect oriented programing
我见过的所有使用面向方面的编程来记录日志的示例都只记录 class、方法名称和持续时间,如果它们记录参数和 return 值,则它们只需使用 ToString( ).我需要对记录的内容有更多的控制。例如,我想跳过密码,或者在某些情况下记录对象的所有属性,但在其他情况下只记录 id 属性。
有什么建议么?看了Java中的AspectJ和C#中的Unity拦截,没找到解决方法
您可以尝试引入参数注释以使用一些属性来扩充您的参数。其中一个属性可以指示跳过记录参数,另一个属性可以用于为字符串表示指定转换器class。
带有以下注释:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Log {
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface SkipLogging {
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface ToStringWith {
Class<? extends Function<?, String>> value();
}
方面可能如下所示:
import java.lang.reflect.Parameter;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public aspect LoggingAspect {
private final static Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
pointcut loggableMethod(): execution(@Log * *..*.*(..));
before(): loggableMethod() {
MethodSignature signature = (MethodSignature) thisJoinPoint.getSignature();
Parameter[] parameters = signature.getMethod()
.getParameters();
String message = IntStream.range(0, parameters.length)
.filter(i -> this.isLoggable(parameters[i]))
.<String>mapToObj(i -> toString(parameters[i], thisJoinPoint.getArgs()[i]))
.collect(Collectors.joining(", ",
"method execution " + signature.getName() + "(", ")"));
Logger methodLogger = LoggerFactory.getLogger(
thisJoinPointStaticPart.getSignature().getDeclaringType());
methodLogger.debug(message);
}
private boolean isLoggable(Parameter parameter) {
return parameter.getAnnotation(SkipLogging.class) == null;
}
private String toString(Parameter parameter, Object value) {
ToStringWith toStringWith = parameter.getAnnotation(ToStringWith.class);
if (toStringWith != null) {
Class<? extends Function<?, String>> converterClass =
toStringWith.value();
try {
@SuppressWarnings("unchecked")
Function<Object, String> converter = (Function<Object, String>)
converterClass.newInstance();
String str = converter.apply(value);
return String.format("%s='%s'", parameter.getName(), str);
} catch (Exception e) {
logger.error("Couldn't instantiate toString converter for logging "
+ converterClass.getName(), e);
return String.format("%s=<error converting to string>",
parameter.getName());
}
} else {
return String.format("%s='%s'", parameter.getName(), String.valueOf(value));
}
}
}
测试代码:
public static class SomethingToStringConverter implements Function<Something, String> {
@Override
public String apply(Something something) {
return "Something nice";
}
}
@Log
public void test(
@ToStringWith(SomethingToStringConverter.class) Something something,
String string,
@SkipLogging Class<?> cls,
Object object) {
}
public static void main(String[] args) {
// execution of this method should log the following message:
// method execution test(something='Something nice', string='some string', object='null')
test(new Something(), "some string", Object.class, null);
}
我在我的回答中使用了 Java 8 Streams API 因为它很紧凑,如果你不使用 Java 你可以将代码转换为普通的 Java 代码8 功能还是需要更好的效率。只是给大家一个思路。
我见过的所有使用面向方面的编程来记录日志的示例都只记录 class、方法名称和持续时间,如果它们记录参数和 return 值,则它们只需使用 ToString( ).我需要对记录的内容有更多的控制。例如,我想跳过密码,或者在某些情况下记录对象的所有属性,但在其他情况下只记录 id 属性。 有什么建议么?看了Java中的AspectJ和C#中的Unity拦截,没找到解决方法
您可以尝试引入参数注释以使用一些属性来扩充您的参数。其中一个属性可以指示跳过记录参数,另一个属性可以用于为字符串表示指定转换器class。
带有以下注释:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Log {
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface SkipLogging {
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface ToStringWith {
Class<? extends Function<?, String>> value();
}
方面可能如下所示:
import java.lang.reflect.Parameter;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public aspect LoggingAspect {
private final static Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
pointcut loggableMethod(): execution(@Log * *..*.*(..));
before(): loggableMethod() {
MethodSignature signature = (MethodSignature) thisJoinPoint.getSignature();
Parameter[] parameters = signature.getMethod()
.getParameters();
String message = IntStream.range(0, parameters.length)
.filter(i -> this.isLoggable(parameters[i]))
.<String>mapToObj(i -> toString(parameters[i], thisJoinPoint.getArgs()[i]))
.collect(Collectors.joining(", ",
"method execution " + signature.getName() + "(", ")"));
Logger methodLogger = LoggerFactory.getLogger(
thisJoinPointStaticPart.getSignature().getDeclaringType());
methodLogger.debug(message);
}
private boolean isLoggable(Parameter parameter) {
return parameter.getAnnotation(SkipLogging.class) == null;
}
private String toString(Parameter parameter, Object value) {
ToStringWith toStringWith = parameter.getAnnotation(ToStringWith.class);
if (toStringWith != null) {
Class<? extends Function<?, String>> converterClass =
toStringWith.value();
try {
@SuppressWarnings("unchecked")
Function<Object, String> converter = (Function<Object, String>)
converterClass.newInstance();
String str = converter.apply(value);
return String.format("%s='%s'", parameter.getName(), str);
} catch (Exception e) {
logger.error("Couldn't instantiate toString converter for logging "
+ converterClass.getName(), e);
return String.format("%s=<error converting to string>",
parameter.getName());
}
} else {
return String.format("%s='%s'", parameter.getName(), String.valueOf(value));
}
}
}
测试代码:
public static class SomethingToStringConverter implements Function<Something, String> {
@Override
public String apply(Something something) {
return "Something nice";
}
}
@Log
public void test(
@ToStringWith(SomethingToStringConverter.class) Something something,
String string,
@SkipLogging Class<?> cls,
Object object) {
}
public static void main(String[] args) {
// execution of this method should log the following message:
// method execution test(something='Something nice', string='some string', object='null')
test(new Something(), "some string", Object.class, null);
}
我在我的回答中使用了 Java 8 Streams API 因为它很紧凑,如果你不使用 Java 你可以将代码转换为普通的 Java 代码8 功能还是需要更好的效率。只是给大家一个思路。