google-nomulus/java/google/registry/proxy/GcpJsonFormatter.java
jianglai 84eab90000 Make GCP proxy log in a Stackdriver logging compliant format
When not running locally, the logging formatter is set to convert the log record to a single-line JSON string that Stackdriver logging agent running in GKE will pick up and parse correctly.

Also removed redundant logging handler in the proxy frontend connection. They have two problems: 1) it is possible to leak PII when all frontend traffic is logged, such as client IPs. Even though this is less of a concern because the GCP TCP proxy load balancer masquerade source IPs. 2) We are only logging the HTTP request/response that the frontend connection is sending to/receiving from the backend connection, but the backend already has its own logging handler to log the same message that it gets from/sends to the GAE app, so the logging in the frontend connection does not really give extra information.
Logging of some potential PII information such as the source IP of a proxied connection are also removed.

Thirdly, added a k8s autoscaling object that scales the containers based on CPU load. The default target load is 80%. This, in connection with GKE cluster VM autoscaling, means that when traffic is low, we'll only have one VM running one container of the proxy.

Fixes a bug where the MetricsComponent generates a separate ProxyConfig that does not call parse method on the command line args passed, resulting default Environment always being used in constructing the metric reporter.

Lastly a little bit of cleaning of the MOE config script, no newlines are necessary as the BUILD are formatted after string substitution.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=188029019
2018-03-06 19:23:23 -05:00

138 lines
4.8 KiB
Java

// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// 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 google.registry.proxy;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.auto.value.AutoValue;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.logging.Formatter;
import java.util.logging.Level;
import java.util.logging.LogRecord;
/**
* JUL formatter that formats log messages in a single-line JSON that Stackdriver logging can parse.
*
* <p>There is no clear documentation on how to achieve this or on the format of the JSON. This is
* much a trial and error process, plus a lot of searching. To summarize, if the logs are printed to
* {@code STDOUT} or {@code STDERR} in a single-line JSON, with the content in the {@code message}
* field and the log level in the {@code severity} field, it will be picked up by Stackdriver
* logging agent running in GKE containers and logged at correct level..
*
* @see <a
* href="https://medium.com/retailmenot-engineering/formatting-python-logs-for-stackdriver-5a5ddd80761c">
* Formatting Python Logs from Stackdriver</a> <a
* href="https://stackoverflow.com/questions/44164730/gke-stackdriver-java-logback-logging-format">
* GKE & Stackdriver: Java logback logging format?</a>
*/
class GcpJsonFormatter extends Formatter {
private static final ObjectMapper MAPPER = new ObjectMapper();
@Override
public String format(LogRecord record) {
try {
return MAPPER.writeValueAsString(LogEvent.create(record)) + "\n";
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
@AutoValue
abstract static class LogEvent {
/** Field that determines the log level. */
@JsonProperty("severity")
abstract String severity();
/**
* Field that stores the calling class and function when the log occurs.
*
* <p>This field is not used by Stackdriver, but it is useful and can be found when the log
* entries are expanded
*/
@JsonProperty("source")
abstract String source();
/** Field that contains the content, this will show up as the main entry in a log. */
@JsonProperty("message")
abstract String message();
static LogEvent create(LogRecord record) {
// Add an extra newline before the message. Stackdriver does not show newlines correctly, and
// treats them as whitespace. If you want to see correctly formatted log message, expand the
// log and look for the jsonPayload.message field. This newline makes sure that the entire
// message starts on its own line, so that indentation within the message is correct.
String message = "\n" + record.getMessage();
Level level = record.getLevel();
// See
// https://github.com/GoogleCloudPlatform/google-cloud-java/blob/master/google-cloud-logging/src/main/java/com/google/cloud/logging/Severity.java
// on how {@code Level} is mapped to severity.
String severity;
switch (level.intValue()) {
// FINEST
case 300:
// FINER
case 400:
// FINE
case 500:
severity = "DEBUG";
break;
// CONFIG
case 700:
// INFO
case 800:
severity = "INFO";
break;
// WARNING
case 900:
severity = "WARNING";
break;
// SEVERE
case 1000:
severity = "ERROR";
break;
default:
severity = "DEFAULT";
}
// The rest is mostly lifted from java.util.logging.SimpleFormatter.
String stacktrace = "";
if (record.getThrown() != null) {
StringWriter sw = new StringWriter();
try (PrintWriter pw = new PrintWriter(sw)) {
pw.println();
record.getThrown().printStackTrace(pw);
}
stacktrace = sw.toString();
}
String source;
if (record.getSourceClassName() != null) {
source = record.getSourceClassName();
if (record.getSourceMethodName() != null) {
source += " " + record.getSourceMethodName();
}
} else {
source = record.getLoggerName();
}
return new AutoValue_GcpJsonFormatter_LogEvent(severity, source, message + stacktrace);
}
}
}