4

I am using logstash-logback-encoder to print logs in json format.

My logback.xml looks like below:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="net.logstash.logback.encoder.LogstashEncoder">
            <includeContext>false</includeContext>
            <fieldNames>
                <timestamp>timestamp</timestamp>
                <version>[ignore]</version>
                <levelValue>[ignore]</levelValue>
            </fieldNames>
        </encoder>
    </appender>
    <appender name="stash"
        class="ch.qos.logback.core.rolling.RollingFileAppender">        
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>/tmp/SolrUpdater.%d{yyyy-MM-dd}.log
            </fileNamePattern>
            <maxHistory>7</maxHistory>
        </rollingPolicy>
        <encoder class="net.logstash.logback.encoder.LogstashEncoder">
            <includeContext>false</includeContext>
            <fieldNames>
                <timestamp>timestamp</timestamp>
                <version>[ignore]</version>
                <levelValue>[ignore]</levelValue>
            </fieldNames>
        </encoder>
    </appender>
    <root level="error">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="stash" />
    </root>
</configuration>

Can someone let me know how can I display all level in lowercase like error, rather than the default ERROR and WARN as warning?

UPDATE - I created a CustomLogLevelJsonProvider as suggested by Zakhar:

package com.jabong.discovery.importer.solrUpdater.log;

public class CustomLogLevelJsonProvider extends LogLevelJsonProvider {
    final static String DEBUG = "debug";
    final static String ERROR = "error";
    final static String INFO = "info";
    final static String WARNING = "warning";

    @Override
    public void writeTo(JsonGenerator generator, ILoggingEvent event)
        throws IOException {
    JsonWritingUtils.writeStringField(generator, getFieldName(),
        getCustomLogLevel(event));
    }

    private String getCustomLogLevel(ILoggingEvent event) {
    if (event.getLevel() == Level.ALL) {
        return Level.ALL.toString();
    }
    if (event.getLevel() == Level.DEBUG) {
        return DEBUG;
    }
    if (event.getLevel() == Level.ERROR) {
        return ERROR;
    }
    if (event.getLevel() == Level.INFO) {
        return INFO;
    }
    if (event.getLevel() == Level.WARN) {
        return WARNING;
    }
    return "";
    }
}

Updated logback.xml:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
         <encoder class="com.jabong.discovery.importer.solrUpdater.log.CustomEncoder">
            <includeContext>false</includeContext>
            <fieldNames>
                <timestamp>timestamp</timestamp>
                <version>[ignore]</version>
                <levelValue>[ignore]</levelValue>
            </fieldNames>
        </encoder>
    </appender>
    <appender name="FILE"
        class="ch.qos.logback.core.rolling.RollingFileAppender">        
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>/tmp/SolrUpdater.%d{yyyy-MM-dd}.log
            </fileNamePattern>
            <maxHistory>7</maxHistory>
        </rollingPolicy>
        <encoder class="net.logstash.logback.encoder.LogstashEncoder">
            <includeContext>false</includeContext>
            <fieldNames>
                <timestamp>timestamp</timestamp>
                <version>[ignore]</version>
                <levelValue>[ignore]</levelValue>
            </fieldNames>
        </encoder>
    </appender>
    <root level="error">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="FILE" />
    </root>
</configuration>

Now I am seeing two level:

{"timestamp":"2015-12-26T23:41:08.818+05:30","message":"Partial Updater Initialization Failed ","logger_name":"com.jabong.discovery.importer.solrUpdater.partialupdate.PartialUpdater","thread_name":"main","level":"ERROR","stack_trace":"java.lang.Exception: Failed to Connect to SolrIndex Type:solr, Core:discovery, Host:localhost, Port:8888\n\tat com.jabong.discovery.importer.solrUpdater.partialupdate.PartialUpdater.initIndexUpdater(PartialUpdater.java:83) [classes/:na]\n\tat com.jabong.discovery.importer.solrUpdater.partialupdate.PartialUpdater.initialise(PartialUpdater.java:36) [classes/:na]\n\tat com.jabong.discovery.importer.solrUpdater.partialupdate.PartialUpdater.<init>(PartialUpdater.java:30) [classes/:na]\n\tat Updater.initialise(Updater.java:50) [classes/:na]\n\tat Updater.main(Updater.java:70) [classes/:na]\n","level":"error"}
Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190
tuk
  • 5,941
  • 14
  • 79
  • 162

3 Answers3

8

I do not know if this is somehow configurable via XML. But I just tried to override behavior you do not like. You will need to implement two new classes.

1)

public class CustomEncoder extends LogstashEncoder {
    public CustomEncoder() {
        LoggingEventJsonProviders providers = getFormatter().getProviders();

        // Remove provider that is responsible for log level appending
        removeDefaultProvider(providers);

        // Register our implementation
        providers.addLogLevel(new CustomLogLevelJsonProvider());
    }

    private void removeDefaultProvider(LoggingEventJsonProviders providers) {
        JsonProvider<ILoggingEvent> providerToDelete = null;

        for (JsonProvider<ILoggingEvent> provider : providers.getProviders()) {
            if (provider instanceof LogLevelJsonProvider) {
                providerToDelete = provider;
                break;
            }
        }

        providers.removeProvider(providerToDelete);
    }
}

2)

public class CustomLogLevelJsonProvider extends LogLevelJsonProvider {
    @Override
    public void writeTo(JsonGenerator generator, ILoggingEvent event) throws IOException {
        JsonWritingUtils.writeStringField(
                generator, getFieldName(), event.getLevel().toString().toLowerCase());
    }
}

3) Enable new Encoder in config.

<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="com.test.encoder.CustomEncoder">
        <includeContext>false</includeContext>
        <fieldNames>
            <timestamp>timestamp</timestamp>
            <version>[ignore]</version>
            <levelValue>[ignore]</levelValue>
        </fieldNames>
    </encoder>
</appender>

Update: You have updated your question. You will need some additional logic to transform WARN or warn to warning. As this will happen on each log message this will create overhead. Value that you want to change is hardcoded in class: ch.qos.logback.classic.Level

Zakhar
  • 605
  • 8
  • 17
  • Can you also let me know where I have to place these custom classes? – tuk Dec 26 '15 at 14:23
  • In your classpath. Inside your project for example. – Zakhar Dec 26 '15 at 14:24
  • I tried your solurtion but now I am seeing two `level` . One having value `ERROR` and another one `error`. – tuk Dec 26 '15 at 18:19
  • Are you using your custom encoder for both appenders? – Zakhar Dec 26 '15 at 22:19
  • tried adding custom encoder to both appenders and also adding it just to console appender but seeing the same in both the cases. Two `level` field in log. – tuk Dec 27 '15 at 05:17
1

We can use logback's replace function to replace INFO with info based on %level.

And we can apply this replace several times to handle all cases of levels:

"%replace(%replace(%replace(%replace(%level){'INFO','info'}){'WARN','warn'}){'ERROR','error'}){'DEBUG','debug'}"

which gives us our "lowercased" level:

{ "level": "info", "message": ... }
Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190
0

Better use Logstash and apply lowercase filter on "level" field.

Sergiu
  • 9
  • 2