How do I programmatically set conversionRule for logback? - java

I have a logback XML configuration file where I use my custom tag converter to tag my log messages.
<configuration scan="true">
<conversionRule conversionWord="tag"
converterClass="com.foo.MyCustomTagConverter" />
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are by default assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<encoder>
<pattern>%tag - %m%n</pattern>
</encoder>
</appender>
...
</configuration>
How can I do this programmatically via Java?

public class loggerutils {
static Logger foo = createLoggerFor("foo", "foo.log");
public static void main(String[] args) {
foo.info("test");
}
private static Logger createLoggerFor(String string, String file) {
String conversionWord = "tag";
String converterClass = "com.foo.MyCustomTagConverter";
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
Map<String, String> ruleRegistry = (Map) lc
.getObject(CoreConstants.PATTERN_RULE_REGISTRY);
if (ruleRegistry == null) {
ruleRegistry = new HashMap<String, String>();
}
lc.putObject(CoreConstants.PATTERN_RULE_REGISTRY, ruleRegistry);
ruleRegistry.put(conversionWord, converterClass);
PatternLayoutEncoder ple = new PatternLayoutEncoder();
ple.setPattern("%-20tag %-30(%d{HH:mm:ss.SSS} [%thread]) - %-15(%M) - %-5level -%logger{32} - %msg%n ");
ple.setContext(lc);
ple.start();
ConsoleAppender<ILoggingEvent> consoleAppender = new ConsoleAppender<ILoggingEvent>();
consoleAppender.setEncoder(ple);
consoleAppender.setContext(lc);
consoleAppender.start();
Logger logger = (Logger) LoggerFactory.getLogger(string);
logger.addAppender(fileAppender);
logger.setLevel(Level.DEBUG);
logger.setAdditive(false); /* set to true if root should log too */
logger.addAppender(consoleAppender);
logger.setLevel(Level.DEBUG);
logger.setAdditive(false);
return logger;
}
}

Related

How to convert Log4j.xml to Log4j2.properties (Rewrite Polici)

i want to use Rewrite Policy for some condition in logging like masking, and then I found the solution but in log4j2.xml, however we use log4j2.properties for base config, I already try to config but the rewrite still not working.
here other config compare to my config
Other Config :
<Configuration status="warn" name="MyApp" packages="">
<Properties>
<Property name="LOG_DIR">/logs</Property>
</Properties>
<Appenders>
<RollingFile
name="rollingFile"
fileName="D:/logs/omnichannel/application.log"
filePattern="D:/logs/omnichannel/application.%i.log.gz"
ignoreExceptions="false">
<PatternLayout>
<Pattern>%d{yyyy-MM-dd HH:mm:ss} %-5p %m%n</Pattern>
</PatternLayout>
<Policies>
<SizeBasedTriggeringPolicy size="10MB" />
</Policies>
<DefaultRolloverStrategy max="5">
<Delete basePath="D:/logs/" maxDepth="2">
<IfFileName glob="*/app-*.log.gz" />
<IfLastModified age="P30D" />
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
<Console name="STDOUT" target="SYSTEM_OUT" ignoreExceptions="false">
<PatternLayout pattern="%m%n"/>
</Console>
<Rewrite name="Rewrite">
<LogInterceptor />
<AppenderRef ref="rollingFile"/>
</Rewrite>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Rewrite"/>
</Root>
</Loggers>
</Configuration>
And my config here :
status = warn
name= PropertiesConfig
# Give directory path where log files should get stored
property.basePath = D://logs//
appenders = rolling, console, Rewrite
appender.Rewrite.type = Rewrite
appender.Rewrite.name = Rewrite
appender.Rewrite.layout.type = LogInterceptor
appender.Rewrite.layout.RewriteAppender = fileLogger
# ConsoleAppender will print logs on console
appender.console.type = Console
appender.console.name = consoleLogger
appender.console.target = SYSTEM_OUT
appender.console.layout.type = PatternLayout
# Specify the pattern of the logs
appender.console.layout.pattern = %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %highlight{[%-5level]}{TRACE=magenta} %mask %logger{36}.%M(%L) : %msg%n%throwable
appender.console.layout.disableAnsi=false
# RollingFileAppender will print logs in file which can be rotated based on time or size
appender.rolling.type = RollingFile
appender.rolling.name = fileLogger
appender.rolling.fileName = ${basePath}${hostName}.log
appender.rolling.filePattern = ${basePath}${hostName}_%d{yyyy-MM-dd}-%i.log.gz
appender.rolling.layout.type = PatternLayout
appender.rolling.layout.pattern = %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5level] %logger{36}.%M(%L) : %msg%n%throwable
appender.rolling.policies.type = Policies
# Rotate log file each day and keep 30 days worth
appender.rolling.policies.time.type = TimeBasedTriggeringPolicy
appender.rolling.policies.time.interval = 1
appender.rolling.strategy.type = DefaultRolloverStrategy
appender.rolling.strategy.action.type = Delete
appender.rolling.strategy.action.basePath = ${basePath}
appender.rolling.strategy.action.maxDepth = 1
appender.rolling.strategy.action.ifLastModified.type = IfLastModified
appender.rolling.strategy.fileIndex = nomax
# For hibernate tracing query parameter, make this default not opened
#loggers = application, hibernate
loggers = application, httpclient, Client
# Mention package name here. Classes in this package or sub packages will use ConsoleAppender and RollingFileAppender for logging
logger.application.name = com.project
logger.application.level = info
logger.application.appenderRefs = rolling
logger.application.appenderRef.rolling.ref = fileLogger
logger.httpclient.name = org.apache.http
logger.httpclient.level = info
logger.httpclient.appenderRefs = rolling
logger.httpclient.appenderRef.rolling.ref = fileLogger
logger.Client.name = com.sun.jersey.api
logger.Client.level = info
logger.Client.appenderRefs = rolling
logger.Client.appenderRef.rolling.ref = fileLogger
# For hibernate tracing query parameter, make this default not opened
#logger.hibernate.name = org.hibernate.type
#logger.hibernate.level = trace
#Logger.hibernate.Additivity = false
# Configure root logger for logging error logs in classes which are in package other than above specified package
rootLogger.level = info
rootLogger.appenderRefs = Rewrite
rootLogger.appenderRef.Rewrite.ref = Rewrite
my function rewrite :
#Plugin(name = "LogInterceptor", category = "Core", elementType = "rewritePolicy", printObject = true)
public class LoggingMaskingConverter implements RewritePolicy {
protected static final Logger log = LogManager.getLogger(LoggingMaskingConverter.class);
private static final List<String> loggingSetting = Arrays.asList("pin,password".split(","));
private static final String loggingMasking = "1";
private static final List<String> excludePath = Collections.emptyList();
private static final String STR_BODY = "body=[";
Properties prop = new Properties();
#PluginFactory
public static LoggingMaskingConverter createPolicy() {
return new LoggingMaskingConverter();
}
#Override
public LogEvent rewrite(LogEvent event) {
if (event.getLoggerName().contains("com.project")) {
Message outputMessage = logInfoMasking(event.getMessage());
return new Log4jLogEvent.Builder(event).setMessage(outputMessage).build();
} else {
return new Log4jLogEvent.Builder(event).build();
}
}
public Message logInfoMasking(Message message) {
StringBuilder stringBuilder = new StringBuilder();
try {
String body = message.getFormattedMessage();
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
if (StringUtils.isNotBlank(loggingMasking) && loggingMasking.equalsIgnoreCase("1")) {
if (body != null) {
Collection<String> whitelist = loggingSetting;
Collection<String> excludeKeys = excludePath;
JsonMasker masker = new JsonMasker(whitelist, excludeKeys);
JsonNode jsonNode = new ObjectMapper().readTree(body);
JsonNode masked = masker.mask(jsonNode);
stringBuilder.append(STR_BODY + masked.toString() + "]");
}
} else {
String json = ow.writeValueAsString(body);
stringBuilder.append(STR_BODY + json + "]");
}
} catch (JsonProcessingException e) {
log.error(e, e);
}
return new ObjectMessage(stringBuilder.toString());
}
}
i spend my time around one day to solve this. until this day. the rewrite still not working in logs file but in my function rewrite going well
The general format to configure a Log4j2 component via properties is:
<parent_component_prefix>.<arbitrary_id>.type = ComponentType
<parent_component_prefix>.<arbitrary_id>.property1 = value1
...
So in your case you need:
appender.$0.type = Rewrite
appender.$0.name = Rewrite
appender.$0.$1.type = LogInterceptor
appender.$0.$2.type = AppenderRef
appender.$0.$2.ref = fileLogger

Masking sensitive data at SOAP envelop with logback-spring.xml

I am currently struggling with masking the data available in the logs intercepted at the SOAP client. I have taken the approach to writing customized PatternLayout:
public class PatternMaskingLayout extends ch.qos.logback.classic.PatternLayout {
private Pattern multilinePattern;
private final List<String> maskPatterns = new ArrayList<>();
public void addMaskPattern(String maskPattern) {
maskPatterns.add(maskPattern);
multilinePattern = Pattern.compile(
String.join("|", maskPatterns),
Pattern.MULTILINE
);
}
#Override
public String doLayout(ILoggingEvent event) {
return maskMessage(super.doLayout(event)); // calling superclass method is required
}
private String maskMessage(String message) {
if (multilinePattern == null) {
return message;
}
StringBuilder sb = new StringBuilder(message);
Matcher matcher = multilinePattern.matcher(sb);
while (matcher.find()) {
IntStream.rangeClosed(1, matcher.groupCount()).forEach(group -> {
if (matcher.group(group) != null) {
IntStream.range(matcher.start(group), matcher.end(group))
.forEach(i -> sb.setCharAt(i, '*')); // replace each character with asterisk
}
});
}
return sb.toString();
}
}
My logback-spring.xml appenders looks like:
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<layout class="app.example.monitoring.tracing.PatternMaskingLayout">
<maskPattern>\"username\"\s*:\s*\"(.*?)\"</maskPattern>
<pattern>
${LOGBACK_LOGGING_PATTERN:-%d{yyyy-MM-dd HH:mm:ss.SSS} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%15.15t] %logger{36} : %msg %replace(%ex){'\n','\\u000a'}%nopex%n}
</pattern>
</layout>
</appender>
I still can not get my username masked. The XML field looks like <xa2:username>John</xa2:username>|
Have anyone have some experience with this?

Using SLF4J in Akka

I followed Logging Doc for using SLF4J with logback in my Akka application. But i have problem in logging in non-actor classes: I can not log MDCs (e.g. sourceActorSystem) in my non-actor classes by a SLF4J logger. Here is my simple application:
1- pom.xml:
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-slf4j_2.12</artifactId>
<version>2.5.18</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
2- application.conf:
akka {
loggers = ["akka.event.slf4j.Slf4jLogger"]
loglevel = "DEBUG"
logging-filter = "akka.event.slf4j.Slf4jLoggingFilter"
}
3- logback.xml which uses sourceThread, akkaSource, and sourceActorSystem MDCs:
<configuration scan="true" scanPeriod="30 seconds" >
<contextName>akka-sample</contextName>
<jmxConfigurator />
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss} %-5level [%contextName] [%X{sourceThread}] [%X{sourceActorSystem}] [%X{akkaSource}] %logger{40} -| %msg %rEx%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="STDOUT" />
</root>
</configuration>
4- UserActor: An actor class which logs by DiagnosticLoggingAdapter:
public class UserActor extends AbstractActor {
DiagnosticLoggingAdapter logger = Logging.getLogger(this);
Handler handler = new Handler(); // non-actor class
public static Props props() {
return Props.create(UserActor.class, UserActor::new);
}
#Override
public Receive createReceive() {
return receiveBuilder()
.match(UserMessages.Register.class, register -> {
logger.info("{} received", register);
// Delegate message-handling to a non-actor class
handler.onRegister(register, getSelf(), getSender());
})
.build();
}
}
5- Handler: A non-actor class which logs by SLF4J logger. Each UserActor instance has a Handler field for delegating message-handling to it:
public class Handler {
org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(getClass());
public void onRegister(UserMessages.Register register, ActorRef self, ActorRef sender) {
logger.info("Handling {}", register);
sender.tell(new UserMessages.Registered(), self);
}
}
6- Simple application:
public class Application {
public static void main(String [] args) throws InterruptedException {
ActorSystem system = null;
try {
system = ActorSystem.create("akka-system");
ActorRef user1 = system.actorOf(UserActor.props(), "user-1");
ActorRef user2 = system.actorOf(UserActor.props(), "user-2");
user2.tell(UserMessages.Register.builder().userId("1").build(), user1);
Thread.sleep(2000);
} finally {
system.terminate();
}
}
}
Log Output:
18:07:30 INFO [akka-sample] [] [] [] akka.event.slf4j.Slf4jLogger -| Slf4jLogger started
18:07:30 INFO [akka-sample] [akka-system-akka.actor.default-dispatcher-4] [akka-system] [akka://akka-system/user/user-2] x.y.akka.UserActor -| UserMessages.Register(userId=1) received
18:07:30 INFO [akka-sample] [] [] [] x.y.akka.Handler -| Handling UserMessages.Register(userId=1)
As you can see, only log in UserActor (actor class) has sourceThread, akkaSource, and sourceActorSystem MDCs; But in Handler (non-actor) they are empty. Also if i change akka.event.DiagnosticLoggingAdapter in UserActor to org.slf4j.Logger, these MDCs become empty.

collecting logs with log4j2 v2.9

i have a question regarding log4j2 with version 2.9.
Basically I want to do the same as described here (log4j), only with 2.9:
Sample log4j v1.x
I need a logger that can be called in any method in the class. This is to collect all subsequent logs recursively from a certain starting point. The collection should be able to be read out in any form later on.
Logger logger = LogManager.getLogger();
public void meth1(){
StringWriter/ List/ String or whatever
logger.add(Collector);
logger.info("Start");
this.meth2();
this.meht3();
logger.info("Stop");
=> do something with the collected logs
}
public void meht2(){
logger.info("meth2: add Collection");
}
public void meth3(){
logger.info("meth3: add Collection");
}
public void meht4(){
logger.info("foo");
}
as soon as the end is set in a form, the following logs should be included in the collection:
Start
meth2: add Collection
meth3: add Collection
Stop
thanks for your help
After a while, I came up with the following solution which works for me using Log4J 2.8. I've added some comments to the code to explain the different steps necessary.
It's important that the logger is requested with the same name (line marked with /* 1 */ as for which the configuration is stored (line marked with /* 2 */. This implies, that the class name cannot be used to get the logger, or that MyClass.class.getName() should be used at /*2*/.
public class LoggingTest {
public static void main(String[] args) {
// define the logger, could also be static in class
final String loggerName = "myCollectingLogger";
Logger logger = LogManager.getLogger(loggerName); /* 1 */
// the log message collector
StringWriter writer = new StringWriter();
// start adapting the logger configuration
LoggerContext ctx = LoggerContext.getContext(false);
Configuration config = ctx.getConfiguration();
// create our appender
PatternLayout layout = PatternLayout.newBuilder().withPattern("%d{yyyy-MM-dd HH:mm:ss.SSS} %level [%t] [%c] [%M] [%l] - %msg%n").build();
WriterAppender writerAppender = WriterAppender.newBuilder().setName("writerAppender").setTarget(writer).setLayout(layout).build();
// add the appender to a LoggerConfig
AppenderRef ref = AppenderRef.createAppenderRef("writerAppender", null, null);
AppenderRef[] refs = new AppenderRef[] { ref };
LoggerConfig loggerConfig = LoggerConfig.createLogger(false, Level.INFO, "example", null, refs, null, config, null);
loggerConfig.addAppender(writerAppender, null, null);
// enable the LoggerConfig in the LoggerContext
config.addLogger(loggerName, loggerConfig); /* 2 */
ctx.updateLoggers();
// use the logger:
logger.info("Start");
logger.warn("foo bar");
logger.error("relax, it's just a test");
logger.info("Stop");
System.out.println("--- the collected log messages: ---");
System.out.println(writer.toString());
}
}
thanks, I've adjusted it to my needs:
public static StringWriter createStringWriter(String classname){
StringWriter stringWriter = new StringWriter();
final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
final Configuration config = ctx.getConfiguration();
PatternLayout layout = PatternLayout.newBuilder()
.withPattern("%d{yyyy-MM-dd HH:mm:ss.SSS} %level [%t] [%c] [%M] [%l] - %msg%n").build();
WriterAppender writerAppender = WriterAppender.newBuilder()
.setName(classname + "writeLogger")
.setTarget(stringWriter)
.setLayout(layout)
.build();
writerAppender.start();
config.addAppender(writerAppender);
LoggerConfig loggerConfig = config.getLoggerConfig(classname);
loggerConfig.addAppender(writerAppender, null, null);
ctx.updateLoggers();
return stringWriter;
}
public static void removeStringWriter(String classname){
final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
final Configuration config = ctx.getConfiguration();
LoggerConfig loggerConfig = config.getLoggerConfig(classname);
loggerConfig.removeAppender(classname + "writeLogger");
ctx.updateLoggers();
}

How to add a PatternLayout to the root logger at runtime?

I am using logback as the backend for Slf4j. Currently, I configure the logger using a logback.xml file. My issue is that sensitive information is being logged (outside of my control) and I want to mask this sensitive information. To mask the information, I have wrote a custom PatternLayout class that essentially does:
#Override
public String doLayout(ILoggingEvent event) {
String message = super.doLayout(event);
Matcher matcher = sesnsitiveInfoPattern.matcher(message);
if (matcher.find()) {
message = matcher.replaceAll("XXX");
}
return message;
}
My issue is that I need to tell logback to use this custom pattern layout. I don't want to add this to the XML configuration however. My current configuration looks like this:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<layout class="com.my.MaskingPatternLayout"> <!-- here -->
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</layout>
</encoder>
</appender>
<root level="info">
<appender-ref ref="STDOUT" />
</root>
</configuration>
In XML, my desired configuration would look like this (but I don't want to use XML):
Hello Max I hope you are using Log4j 2.x because this solution uses the plugins approache introduced in log4j 2.x . first you should create a package where you are going to put your plugins classes and you put there these two classes :
my.log4j.pluggins.CustomConfigurationFactory :
#Plugin(name = "CustomConfigurationFactory", category = ConfigurationFactory.CATEGORY)
#Order(value = 0)
public class CustomConfigurationFactory extends ConfigurationFactory {
private Configuration createConfiguration(final String name,
ConfigurationBuilder<BuiltConfiguration> builder) {
System.out.println("init logger");
builder.setConfigurationName(name);
builder.setStatusLevel(Level.INFO);
builder.setPackages("my.log4j.pluggins");
AppenderComponentBuilder appenderBuilder = builder.newAppender(
"Stdout", "CONSOLE").addAttribute("target",
ConsoleAppender.Target.SYSTEM_OUT);
appenderBuilder
.add(builder
.newLayout("PatternLayout")
.addAttribute("pattern", "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %myMsg%n"));
builder.add(appenderBuilder);
builder.add(builder.newRootLogger(Level.TRACE).add(
builder.newAppenderRef("Stdout")));
return builder.build();
}
#Override
protected String[] getSupportedTypes() {
String[] supportedExt = { "*" };
return supportedExt;
}
#Override
public Configuration getConfiguration(ConfigurationSource source) {
ConfigurationBuilder<BuiltConfiguration> builder = newConfigurationBuilder();
return createConfiguration(source.toString(), builder);
}
#Override
public Configuration getConfiguration(String name, URI configLocation) {
ConfigurationBuilder<BuiltConfiguration> builder = newConfigurationBuilder();
return createConfiguration(name, builder);
}
}
my.log4j.pluggins.SampleLayout :
#Plugin(name = "CustomConverter", category = "Converter")
#ConverterKeys({"myMsg"})
public class SampleLayout extends LogEventPatternConverter {
protected SampleLayout(String name, String style) {
super(name, style);
}
public static SampleLayout newInstance(){
return new SampleLayout("custConv", "custConv");
}
#Override
public void format(LogEvent event, StringBuilder stringBuilder) {
//replace the %myMsg by XXXXX if sensitive
if (sensitive()){
stringBuilder.append("XXXX");}
else {
stringBuilder.append(event.getMessage().getFormattedMessage());}
}
}
the CustomConfiguration class is responsable for creating the configuration of log4j and the line 9 where 'builder.setPackages("my.log4j.pluggins")' is important in order to scan that package and pick up the converter pluggin wich is SampleLayout.
the second class will be responsible for formatting the new key '%myMsg' in the pattern that contains my sensitive message, this Converter class checks if that message is sensitive and actes accordingly.
Before you start logging you should configure your log4j like this
ConfigurationFactory.setConfigurationFactory(new CustomConfigurationFactory());

Categories