org.graylog.plugins.beats.BeatsCodec.java Source code

Java tutorial

Introduction

Here is the source code for org.graylog.plugins.beats.BeatsCodec.java

Source

/**
 * This file is part of Graylog Beats Plugin.
 *
 * Graylog Beats Plugin is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Graylog Beats Plugin is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Graylog Beats Plugin.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.graylog.plugins.beats;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.inject.assistedinject.Assisted;
import org.graylog2.plugin.Message;
import org.graylog2.plugin.Tools;
import org.graylog2.plugin.configuration.Configuration;
import org.graylog2.plugin.inputs.annotations.Codec;
import org.graylog2.plugin.inputs.annotations.ConfigClass;
import org.graylog2.plugin.inputs.annotations.FactoryClass;
import org.graylog2.plugin.inputs.codecs.AbstractCodec;
import org.graylog2.plugin.journal.RawMessage;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.inject.Inject;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import static java.util.Objects.requireNonNull;
import static org.graylog.plugins.beats.MapUtils.flatten;

@Codec(name = "beats", displayName = "Beats")
public class BeatsCodec extends AbstractCodec {
    private static final Logger LOG = LoggerFactory.getLogger(BeatsCodec.class);
    private static final String MAP_KEY_SEPARATOR = "_";

    private final ObjectMapper objectMapper;

    @Inject
    public BeatsCodec(@Assisted Configuration configuration, ObjectMapper objectMapper) {
        super(configuration);
        this.objectMapper = requireNonNull(objectMapper);
    }

    @Nullable
    @Override
    public Message decode(@Nonnull RawMessage rawMessage) {
        final byte[] payload = rawMessage.getPayload();
        final Map<String, Object> event;
        try {
            event = objectMapper.readValue(payload, new TypeReference<Map<String, Object>>() {
            });
        } catch (IOException e) {
            LOG.error("Couldn't decode raw message {}", rawMessage);
            return null;
        }

        return parseEvent(event);
    }

    @Nullable
    private Message parseEvent(Map<String, Object> event) {
        @SuppressWarnings("unchecked")
        final Map<String, String> metadata = (HashMap<String, String>) event.remove("@metadata");
        final String type;
        if (metadata == null) {
            LOG.warn("Couldn't recognize Beats type");
            type = "unknown";
        } else {
            type = metadata.get("beat");
        }
        final Message gelfMessage;
        switch (type) {
        case "filebeat":
            gelfMessage = parseFilebeat(event);
            break;
        case "topbeat":
            gelfMessage = parseTopbeat(event);
            break;
        case "packetbeat":
            gelfMessage = parsePacketbeat(event);
            break;
        case "winlogbeat":
            gelfMessage = parseWinlogbeat(event);
            break;
        default:
            LOG.debug("Unknown beats type {}. Using generic handler.", type);
            gelfMessage = parseGenericBeat(event);
            break;
        }

        return gelfMessage;
    }

    private Message createMessage(String message, Map<String, Object> event) {
        @SuppressWarnings("unchecked")
        final Map<String, Object> beat = (Map<String, Object>) event.remove("beat");
        final String hostname;
        final String name;
        if (beat == null) {
            hostname = "unknown";
            name = "unknown";
        } else {
            hostname = String.valueOf(beat.get("hostname"));
            name = String.valueOf(beat.get("name"));
        }
        final String timestampField = String.valueOf(event.remove("@timestamp"));
        final DateTime timestamp = Tools.dateTimeFromString(timestampField);
        final String type = String.valueOf(event.get("type"));
        final Object tags = event.get("tags");

        final Message result = new Message(message, hostname, timestamp);
        result.addField("name", name);
        result.addField("type", type);
        result.addField("tags", tags);

        return result;
    }

    /**
     * @see <a href="https://www.elastic.co/guide/en/beats/filebeat/1.2/exported-fields.html">Filebeat Exported Fields</a>
     */
    private Message parseFilebeat(Map<String, Object> event) {
        final String message = String.valueOf(event.get("message"));
        final Message gelfMessage = createMessage(message, event);
        gelfMessage.addField("facility", "filebeat");
        gelfMessage.addField("file", event.get("source"));
        gelfMessage.addField("input_type", event.get("input_type"));
        gelfMessage.addField("count", event.get("count"));
        gelfMessage.addField("offset", event.get("offset"));
        @SuppressWarnings("unchecked")
        final Map<String, Object> fields = (Map<String, Object>) event.get("fields");
        if (fields != null) {
            gelfMessage.addFields(fields);
        }
        return gelfMessage;
    }

    /**
     * @see <a href="https://www.elastic.co/guide/en/beats/topbeat/1.2/exported-fields.html">Topbeat Exported Fields</a>
     */
    private Message parseTopbeat(Map<String, Object> event) {
        final Message gelfMessage = createMessage("-", event);
        gelfMessage.addField("facility", "topbeat");
        final Map<String, Object> flattened = flatten(event, "topbeat", MAP_KEY_SEPARATOR);

        // Fix field names containing dots, like "cpu.name"
        final Map<String, Object> withoutDots = MapUtils.replaceKeyCharacter(flattened, '.',
                MAP_KEY_SEPARATOR.charAt(0));
        gelfMessage.addFields(withoutDots);
        return gelfMessage;
    }

    /**
     * @see <a href="https://www.elastic.co/guide/en/beats/packetbeat/1.2/exported-fields.html">Packetbeat Exported Fields</a>
     */
    private Message parsePacketbeat(Map<String, Object> event) {
        final Message gelfMessage = createMessage("-", event);
        gelfMessage.addField("facility", "packetbeat");
        final Map<String, Object> flattened = flatten(event, "packetbeat", MAP_KEY_SEPARATOR);

        // Fix field names containing dots, like "icmp.version"
        final Map<String, Object> withoutDots = MapUtils.replaceKeyCharacter(flattened, '.',
                MAP_KEY_SEPARATOR.charAt(0));
        gelfMessage.addFields(withoutDots);

        return gelfMessage;
    }

    /**
     * @see <a href="https://www.elastic.co/guide/en/beats/winlogbeat/1.2/exported-fields.html">Winlogbeat Exported Fields</a>
     */
    private Message parseWinlogbeat(Map<String, Object> event) {
        final String message = String.valueOf(event.remove("message"));
        final Message gelfMessage = createMessage(message, event);
        gelfMessage.addField("facility", "winlogbeat");
        final Map<String, Object> flattened = flatten(event, "winlogbeat", MAP_KEY_SEPARATOR);

        // Fix field names containing dots, like "user.name"
        final Map<String, Object> withoutDots = MapUtils.replaceKeyCharacter(flattened, '.',
                MAP_KEY_SEPARATOR.charAt(0));
        gelfMessage.addFields(withoutDots);
        return gelfMessage;
    }

    private Message parseGenericBeat(Map<String, Object> event) {
        final String message = String.valueOf(event.remove("message"));
        final Message gelfMessage = createMessage(message, event);
        gelfMessage.addField("facility", "genericbeat");
        final Map<String, Object> flattened = flatten(event, "beat", MAP_KEY_SEPARATOR);

        // Fix field names containing dots
        final Map<String, Object> withoutDots = MapUtils.replaceKeyCharacter(flattened, '.',
                MAP_KEY_SEPARATOR.charAt(0));
        gelfMessage.addFields(withoutDots);
        return gelfMessage;
    }

    @FactoryClass
    public interface Factory extends AbstractCodec.Factory<BeatsCodec> {
        @Override
        BeatsCodec create(Configuration configuration);

        @Override
        Config getConfig();

        @Override
        Descriptor getDescriptor();
    }

    @ConfigClass
    public static class Config extends AbstractCodec.Config {
    }

    public static class Descriptor extends AbstractCodec.Descriptor {
        @Inject
        public Descriptor() {
            super(BeatsCodec.class.getAnnotation(Codec.class).displayName());
        }
    }
}