lumbermill.internal.geospatial.GeoIP Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of lumbermill-geospatial Show documentation
Show all versions of lumbermill-geospatial Show documentation
Where Logs are cut into Lumber
/*
* Copyright 2016 Sony Mobile Communications, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package lumbermill.internal.geospatial;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.maxmind.db.CHMCache;
import com.maxmind.geoip2.DatabaseReader;
import com.maxmind.geoip2.exception.GeoIp2Exception;
import com.maxmind.geoip2.model.CityResponse;
import com.sun.org.apache.bcel.internal.generic.GETFIELD;
import lumbermill.api.JsonEvent;
import lumbermill.internal.Json;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.*;
import static java.util.Arrays.asList;
/**
*
*/
public class GeoIP {
private static final Logger LOGGER = LoggerFactory.getLogger(GeoIP.class);
private static String DEFAULT_TARGET = "geoip";
private static final List DEFAULT_FIELDS = asList (
"country_code2",
"country_code3",
"country_name",
"continent_code",
"continent_name",
"city_name",
"timezone",
"locationOf",
"latitude",
"longitude"
);
/**
* Fields that will be included in geoip node, Optional
*/
private final List fields;
/**
* GeoIP database
*/
private final DatabaseReader reader;
/**
* Field where to read ip address in json
*/
private final String sourceField;
private final String targetField;
/**
*
* @param sourceField Muse
* @param reader
* @param fields
*/
private GeoIP(String sourceField, String targetField, DatabaseReader reader, List fields) {
this.sourceField = sourceField;
this.targetField = targetField;
this.fields = fields;
this.reader = reader;
}
public JsonEvent decorate(JsonEvent event) {
if (!event.has(sourceField)) {
return event;
}
String ip = event.valueAsString(sourceField);
Optional locationOptional = locationOf(ip);
if (!locationOptional.isPresent()) {
return event;
}
CityResponse location = locationOptional.get();
ObjectNode geoIpNode = Json.OBJECT_MAPPER.createObjectNode();
// Wrapper objects are never null but actual values can be null
put(geoIpNode, "country_code2", location.getCountry().getIsoCode());
put(geoIpNode, "country_code3", location.getCountry().getIsoCode());
put(geoIpNode, "country_name", location.getCountry().getName());
put(geoIpNode, "continent_code", location.getContinent().getCode());
put(geoIpNode, "continent_name", location.getContinent().getName());
put(geoIpNode, "city_name", location.getCity().getName());
put(geoIpNode, "timezone", location.getLocation().getTimeZone());
put(geoIpNode, "latitude", location.getLocation().getLatitude().doubleValue());
put(geoIpNode, "longitude", location.getLocation().getLongitude().doubleValue());
geoIpNode.set("location", Json.createArrayNode (
location.getLocation().getLongitude().doubleValue(),
location.getLocation().getLatitude().doubleValue()));
event.unsafe().set(targetField, geoIpNode);
return event;
}
private Optional locationOf(String ip) {
try {
InetAddress ipAddress = InetAddress.getByName(ip);
CityResponse response = reader.city(ipAddress);
if (!hasLocations(response)) {
return Optional.empty();
}
return Optional.of(response);
} catch (UnknownHostException e) {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("UnknownHostException: " + ip);
}
} catch (GeoIp2Exception e) {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Unexpected GeoIp2Exception: " + e.getMessage());
}
} catch (IOException e) {
LOGGER.warn("Unexpected IOException", e);
}
return Optional.empty();
}
private boolean hasLocations(CityResponse location) {
if (location == null || (location.getLocation().getLatitude() == null
|| location.getLocation().getLongitude() == null)) {
return false;
}
return true;
}
private void put(ObjectNode node, String field, Double value) {
if (fields.contains(field)) {
if (value != null) {
node.put(field, value);
}
}
}
private void put(ObjectNode node, String field, String value) {
if (fields.contains(field)) {
if (value != null) {
node.put(field, value);
}
}
}
public static class Factory {
// No need to bother about threadsafety
private static final Map CACHE = new HashMap<>();
public static GeoIP create(String source, Optional target, Optional path, Optional> fields) {
String theTarget = target.isPresent() ? target.get() : DEFAULT_TARGET;
List theFields = fields.isPresent() ? fields.get() : DEFAULT_FIELDS;
String key = key(source, theTarget, path, theFields);
if (CACHE.containsKey(key)) {
return CACHE.get(key);
}
DatabaseReader theReader = path.isPresent() ? fromFile(path.get()) : fromClasspath();
GeoIP geoIP = new GeoIP(source, theTarget, theReader, theFields);
CACHE.put(key, geoIP);
return geoIP;
}
private static DatabaseReader fromFile(File path) {
try {
LOGGER.info("Opening GeoIP database from file {}", path);
return new DatabaseReader.Builder(path).withCache(new CHMCache()).build();
} catch (IOException e) {
throw new IllegalStateException("Failed to open Geo database file" + path, e);
}
}
private static DatabaseReader fromClasspath() {
try {
LOGGER.info("Trying to open database GeoLite2-City.mmdb from classpath");
URL resource = Thread.currentThread().getContextClassLoader().getResource("GeoLite2-City.mmdb");
return new DatabaseReader.Builder(resource.openStream())
.withCache(new CHMCache()).build();
} catch (IOException e) {
throw new IllegalStateException("Failed to open GeoLite2-City.mmdb database in classpath", e);
}
}
private static String key(String source, String target, Optional path, List fields) {
return new StringBuilder()
.append(source)
.append(target)
.append(path.isPresent() ? path.get().getAbsolutePath() : "")
.append(Arrays.toString(fields.toArray())).toString();
}
}
}