/*
* Copyright 2011 Sonian Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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 com.sonian.elasticsearch.http.filter.logging;
import com.sonian.elasticsearch.http.filter.FilterChain;
import com.sonian.elasticsearch.http.filter.FilterHttpServerAdapter;
import com.sonian.elasticsearch.http.jetty.JettyHttpServerRestRequest;
import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.common.Classes;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.assistedinject.Assisted;
import org.elasticsearch.common.joda.time.DateTime;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.http.HttpChannel;
import org.elasticsearch.http.HttpRequest;
import org.elasticsearch.rest.RestResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Map;
/**
* @author imotov
*/
public class LoggingFilterHttpServerAdapter implements FilterHttpServerAdapter {
protected volatile ESLogger logger;
private final RequestLoggingLevelSettings requestLoggingLevelSettings;
private final String logFormat;
private final String clusterName;
@Inject
public LoggingFilterHttpServerAdapter(Settings settings, @Assisted String name, @Assisted Settings filterSettings,
RequestLoggingLevelSettings requestLoggingLevelSettings, ClusterName clusterName) {
String loggerName = filterSettings.get("logger", Classes.getPackageName(getClass()));
this.logFormat = filterSettings.get("format", "text");
if (logFormat.equals("json")) {
this.logger = Loggers.getLogger(loggerName);
} else {
this.logger = Loggers.getLogger(loggerName, settings);
}
this.clusterName = clusterName.value();
this.requestLoggingLevelSettings = requestLoggingLevelSettings;
requestLoggingLevelSettings.updateSettings(filterSettings);
}
public RequestLoggingLevelSettings requestLoggingLevelSettings() {
return requestLoggingLevelSettings;
}
@Override
public void doFilter(HttpRequest request, HttpChannel channel, FilterChain filterChain) {
RequestLoggingLevel level = requestLoggingLevelSettings.getLoggingLevel(request.method(), request.path());
if (level.shouldLog(logger)) {
filterChain.doFilter(request, new LoggingHttpChannel(request, channel, this.logFormat, level.logBody()));
} else {
filterChain.doFilter(request, channel);
}
}
private StringBuilder mapToString(Map<String, String> params) {
StringBuilder buf = new StringBuilder();
boolean first = true;
for (Map.Entry<String, String> param : params.entrySet()) {
if (first) {
first = false;
} else {
buf.append("&");
}
buf.append(param.getKey());
buf.append("=");
try {
buf.append(URLEncoder.encode(param.getValue(), "UTF-8"));
} catch (UnsupportedEncodingException ex) {
logger.error("UnsupportedEncodingException", ex);
}
}
if (first) {
buf.append("-");
}
return buf;
}
private class LoggingHttpChannel extends HttpChannel {
private final HttpRequest request;
private final HttpChannel channel;
private final String method;
private final String path;
private final StringBuilder params;
private final long timestamp;
private final String format;
private final String content;
private final String localaddr;
private final long localport;
private final String remoteaddr;
private final long remoteport;
private final String scheme;
private final String remoteuser;
private final String opaqueId;
public LoggingHttpChannel(HttpRequest request, HttpChannel channel, String format, boolean logBody) {
super(request);
this.channel = channel;
this.request = request;
this.format = format;
method = request.method().name();
path = request.rawPath();
params = mapToString(request.params());
timestamp = System.currentTimeMillis();
if(logBody) {
content = request.content().toUtf8();
} else {
content = null;
}
JettyHttpServerRestRequest req = (JettyHttpServerRestRequest) request;
localaddr = req.localAddr();
localport = req.localPort();
remoteaddr = req.remoteAddr();
remoteport = req.remotePort();
scheme = req.scheme();
remoteuser = req.remoteUser();
opaqueId = req.opaqueId();
}
public void logText(RestResponse response, long contentLength, long now) {
long latency = now - timestamp;
if(content != null) {
logger.info("{} {} {} {} {} {} {} [{}]",
method,
path,
params,
response.status().getStatus(),
response.status(),
contentLength >= 0 ? contentLength : "-",
latency,
content);
} else {
logger.info("{} {} {} {} {} {} {}",
method,
path,
params,
response.status().getStatus(),
response.status(),
contentLength >= 0 ? contentLength : "-",
latency);
}
}
public void logJson(RestResponse response, long contentLength, long now) {
long latency = now - timestamp;
DateTime nowdt = new DateTime(now);
DateTime startdt = new DateTime(timestamp);
try {
XContentBuilder json = XContentFactory.jsonBuilder().startObject();
json.field("time", nowdt.toDateTimeISO().toString());
json.field("starttime", startdt.toDateTimeISO().toString());
json.field("localaddr", localaddr);
json.field("localport", localport);
json.field("remoteaddr", remoteaddr);
json.field("remoteport", remoteport);
json.field("scheme", scheme);
json.field("method", method);
json.field("path", path);
json.field("querystr", params);
json.field("code", response.status().getStatus());
json.field("status", response.status());
json.field("size", contentLength);
json.field("duration", latency);
json.field("year", nowdt.toString("yyyy"));
json.field("month", nowdt.toString("MM"));
json.field("day", nowdt.toString("dd"));
json.field("hour", nowdt.toString("HH"));
json.field("minute", nowdt.toString("mm"));
json.field("dow", nowdt.toString("EEE"));
json.field("cluster", clusterName);
if (remoteuser != null) {
json.field("user", remoteuser);
}
if (opaqueId != null) {
json.field("opaque-id", opaqueId);
}
if (content != null) {
json.field("data", content);
}
json.endObject();
logger.info(json.string());
} catch (IOException e) {
logger.info("## Could not serialize to json: {} {} {} {} {} {} {} {} [{}]",
nowdt.toDateTimeISO().toString(),
method,
path,
params,
response.status().getStatus(),
response.status(),
contentLength >= 0 ? contentLength : "-",
latency,
content);
}
}
@Override
public void sendResponse(RestResponse response) {
int contentLength = -1;
try {
contentLength = response.content().length();
} catch (RuntimeException ex) {
// Ignore
}
channel.sendResponse(response);
long now = System.currentTimeMillis();
if (this.format.equals("json")) {
logJson(response, contentLength, now);
} else {
logText(response, contentLength, now);
}
}
}
}