Suppose we use Logback for logging.
It’s required to change the path to the log file every time a certain event (i.e. function call) occurs.
For example, somewhere we call a function.
startNewLogSegment("A")
After this event, the logger is expected to start writing to the logs/mySegment_A.log
file.
Then, again a call performed:
startNewLogSegment("B")
After this event, the logger is expected to finish writing to the previous file and start writing to the logs/mySegment_B.log
file.
Let's assume that a state changed by startNewLogSegment
should be visible in the whole application (all threads).
I tried to apply the approach with MDC:
logback.xml
...
<appender name="SIFTING_BY_ID" class="ch.qos.logback.classic.sift.SiftingAppender">
<discriminator>
<key>id</key>
<defaultValue>initial</defaultValue>
</discriminator>
<sift>
<appender name="FULL-${id}" class="ch.qos.logback.core.FileAppender">
<file>logs/mySegment_${id}.log</file>
<append>false</append>
<encoder >
<pattern>%d{dd-MM-yyyy HH:mm:ss.SSS} [%thread] [%-5level] %logger{36}.%M - %msg%n</pattern>
</encoder>
</appender>
</sift>
</appender>
...
and calling MDC.put("id", "A")
when a custom event appears.
But it works a different way than I need.
It’s known that the MDC manages contextual information on a per thread basis, so at least we need a control over threads creation to accomplish the goal described above.
I wonder if this approach could be used with Spring, and in particular with async operations performed by Spring Reactor. I’ve found no information about using a custom thread pool for internal Spring activities.
Possibly, I hope, there’s a more simple way to tune logging that way without abusing Spring internals.
I ended up with a cusom implementation of discriminator AbstractDiscriminator<ILoggingEvent>
allowing uasge of globally visible values.
GVC.java
/**
* Global values context.
* Allows to sift log files globally independent from a thread calling log operation.
* <p>
* Used API analogous to standard {@link org.slf4j.MDC}.
*/
public final class GVC {
private static Map<String, String> STORED = new HashMap<>();
private GVC() {
}
public static synchronized void put(String key, String value) {
STORED.put(key, value);
}
public static synchronized String get(String key) {
return STORED.get(key);
}
}
GVCBasedDiscriminator.java
/**
* Customized analogue of MDCBasedDiscriminator.
* <p>
* GVCBasedDiscriminator essentially returns the value mapped to an GVC key.
* If the value is null, then a default value is returned.
* <p>
* Both Key and the DefaultValue are user specified properties.
*/
public class GVCBasedDiscriminator extends AbstractDiscriminator<ILoggingEvent> {
private String key;
private String defaultValue;
public String getDiscriminatingValue(ILoggingEvent event) {
String value = GVC.get(key);
if (value == null) {
return defaultValue;
} else {
return value;
}
}
@Override
public String getKey() {
return key;
}
@Override
public void start() {
int errors = 0;
if (OptionHelper.isEmpty(key)) {
errors++;
addError("The \"Key\" property must be set");
}
if (OptionHelper.isEmpty(defaultValue)) {
errors++;
addError("The \"DefaultValue\" property must be set");
}
if (errors == 0) {
started = true;
}
}
/**
* Key for this discriminator instance
*
* @param key
*/
public void setKey(String key) {
this.key = key;
}
/**
* The default GVC value in case the GVC is not set for
* {@link #setKey(String) mdcKey}.
* <p/>
* <p> For example, if {@link #setKey(String) Key} is set to the value
* "someKey", and the MDC is not set for "someKey", then this appender will
* use the default value, which you can set with the help of this method.
*
* @param defaultValue
*/
public void setDefaultValue(String defaultValue) {
this.defaultValue = defaultValue;
}
}
logback.xml
<appender name="TRACES_PER_SESSION_FILE" class="ch.qos.logback.classic.sift.SiftingAppender">
<!-- here the custom discriminator implementation is applied -->
<discriminator class="internal.paxport.misc.logging.GVCBasedDiscriminator">
<key>id</key>
<defaultValue>initial</defaultValue>
</discriminator>
<sift>
<appender name="FULL-${id}" class="ch.qos.logback.core.FileAppender">
<file>logs/mySegment_${id}.log</file>
<append>false</append>
<encoder>
<pattern>%d{dd-MM-yyyy HH:mm:ss.SSS} [%thread] [%-5level] %logger{36}.%M - %msg%n</pattern>
</encoder>
</appender>
</sift>
</appender>