Search code examples
javagrayloggelf

How can I programmatically log numeric fields into Graylog?


The situation

I write libraries to work against a computing server. I am logging computing times (start and stop of a job, for example). I want to log this values independently of the log framework configuration of the client application. I have achieved this by using Graylog programmatically. So I configure and initialize my logger from my library while the client remain oblivious.

This is my initialization code

import org.graylog2.log.GelfAppender;

void init() {
    final GelfAppender appender = new GelfAppender();

    appender.setName("MyServerAppender");
    appender.setGraylogHost("bigcpuserver");
    appender.setGraylogPort(2020);
    appender.setExtractStacktrace(true);
    appender.setAddExtendedInformation(true);
    appender.setAdditionalFields("{'environment': 'TEST',"
            + "'ip_address': '10.20.30.40',"
            + "'serverName': 'devPc',"
            + "'libVersion': '1.0.0',"
            + "'application': 'loggingTestApp',"
            + "'appversion': '2.0.7'}");
    appender.activateOptions();
    final org.apache.log4j.Logger myLog =
        org.apache.log4j.Logger.getLogger(MyHiddenLoggerClass.class);
    myLog.addAppender(appender);

}

So far so good. I can send logs to Graylog from anywhere in my code by means of the following line:

org.apache.log4j.Logger.getLogger(MyHiddenLoggerClass.class).info("message");

The problem

I want to add numeric fields to my logs so Graylog can run statistics on them. Like, for example, find which jobs took longest --might need algorithm optimization-- or shortest --might not need the computation server at all--.

I can add fields to the MDC in the following fashion:

org.apache.log4j.MDC.put("cpuTime", startTimeMillis-endTimeMillis);

And, from then on, all logs will include the cpuTime field with that value. I do remove it after the specific log entry has been sent to prevent carrying it over the subsequent log entries:

org.apache.log4j.MDC.remove("cpuTime");

But, to Graylog, those are strings, so it can only count them and see how many different there are.

The question

How can I tell Graylog that "cpuTime" will always be a long?

The attempts so far

I tried configuring the fields beforehand, at init() time.

To do that I tested other class hierarchies too, like me.moocar. For example, I have tried the following:

    final me.moocar.logbackgelf.GelfAppender appender =
        new me.moocar.logbackgelf.GelfAppender();

    appender.setName("MyServerAppender");
    appender.setIncludeFullMDC(true);
    appender.setGraylog2ServerHost("bigcpuserver");
    appender.setGraylog2ServerPort(2020);
    final Map<String, String> additionalFields = new HashMap<String, String>();
    final Map<String, String> fieldTypes = new HashMap<String, String>();
    additionalFields.put("cpuTime", "42");
    fieldTypes.put("cpuTime", "long");
    appender.setAdditionalFields(additionalFields);
    appender.setFieldTypes(fieldTypes);
    appender.start();

    final ch.qos.logback.classic.LoggerContext logCtx =
            (LoggerContext) LoggerFactory.getILoggerFactory();
    appender.setContext(logCtx);

    ch.qos.logback.classic.Logger logToConfigure =
            logCtx.getLogger(MyHiddenLoggerClass.class);
    logToConfigure.addAppender(appender);

So I can do the following in my test code:

    final ch.qos.logback.classic.Logger log =
             logCtx.getLogger(MyHiddenLoggerClass.class);
    MDC.put("cpuTime", totalTime);
    log.error("task finished.");

And in Graylog, the log "task finished." will be accompanied by a field named "cpuTime", and containing a string with the value of totalTime at the log moment. I just want the contained value to be a number. Anyone can help me with that?

I can change the whole to the me.moocar hierarchy if that helps at all. But so far, neither me.moocar nor org.graylog2.log have given me the result I want; and with the latter I get an easier task of attaching the appender to the logger.

update

me.moocar seems to be limited to String as type of the additional fields. So if I want numeric fields I have to choose other alternative.


Solution

  • You need to send the values according to their data types. There's a difference in

    {'cpuTime':'42'}
    

    and

    {'cpuTime':42}
    

    I don't know whether there's a transformer available in Graylog (you can handle field transformations in logstash via filters). If you want to solve the issue at logger level take a look at http://logging.paluch.biz/examples/logback.html, specifically additionalFieldTypes.