All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.netflix.spectator.ipc.IpcLogEntry Maven / Gradle / Ivy

There is a newer version: 1.8.2
Show newest version
/*
 * Copyright 2014-2023 Netflix, 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.netflix.spectator.ipc;

import com.netflix.spectator.api.Clock;
import com.netflix.spectator.api.Id;
import com.netflix.spectator.api.Registry;
import com.netflix.spectator.api.Tag;
import com.netflix.spectator.api.histogram.PercentileTimer;
import com.netflix.spectator.ipc.http.PathSanitizer;
import org.slf4j.MDC;
import org.slf4j.Marker;
import org.slf4j.event.Level;

import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * Builder used to fill in and submit a log entry associated with an IPC request.
 */
@SuppressWarnings({"PMD.ExcessiveClassLength", "PMD.AvoidStringBufferField"})
public final class IpcLogEntry {

  private final Clock clock;

  private Registry registry;
  private IpcLogger logger;
  private Level level;
  private Marker marker;

  private long startNanos;
  private long startTime;
  private long latency;

  private String owner;
  private IpcResult result;

  private String protocol;

  private IpcStatus status;
  private String statusDetail;
  private Throwable exception;

  private IpcAttempt attempt;
  private IpcAttemptFinal attemptFinal;

  private String vip;
  private String endpoint;

  private String clientRegion;
  private String clientZone;
  private String clientApp;
  private String clientCluster;
  private String clientAsg;
  private String clientNode;

  private String serverRegion;
  private String serverZone;
  private String serverApp;
  private String serverCluster;
  private String serverAsg;
  private String serverNode;

  private String httpMethod;
  private int httpStatus;

  private String uri;
  private String path;
  private long requestContentLength = -1L;
  private long responseContentLength = -1L;

  private final List
requestHeaders = new ArrayList<>(); private final List
responseHeaders = new ArrayList<>(); private String remoteAddress; private int remotePort; private boolean disableMetrics; private final Map additionalTags = new HashMap<>(); private final StringBuilder builder = new StringBuilder(); private Id inflightId; /** Create a new instance. */ IpcLogEntry(Clock clock) { this.clock = clock; reset(); } /** Set the registry to use for recording metrics. */ IpcLogEntry withRegistry(Registry registry) { this.registry = registry; return this; } /** * Set the logger instance to use for tracking state such as the number of inflight * requests. */ IpcLogEntry withLogger(IpcLogger logger) { this.logger = logger; return this; } /** * Set the marker indicating whether it is a client or server request. */ IpcLogEntry withMarker(Marker marker) { this.marker = marker; return this; } /** * Set the log level to use when sending to SLF4j. The default level is DEBUG. For * high volume use-cases it is recommended to set the level to TRACE to avoid excessive * logging. */ public IpcLogEntry withLogLevel(Level level) { this.level = level; return this; } /** * Set the latency for the request. This will typically be set automatically using * {@link #markStart()} and {@link #markEnd()}. Use this method if the latency value * is provided by the implementation rather than measured using this entry. */ public IpcLogEntry withLatency(long latency, TimeUnit unit) { this.latency = unit.toNanos(latency); return this; } /** * Record the starting time for the request and update the number of inflight requests. * This should be called just before starting the execution of the request. As soon as * the request completes it is recommended to call {@link #markEnd()}. */ public IpcLogEntry markStart() { if (registry != null && !disableMetrics) { inflightId = getInflightId(); int n = logger.inflightRequests(inflightId).incrementAndGet(); registry.distributionSummary(inflightId).record(n); } startTime = clock.wallTime(); startNanos = clock.monotonicTime(); return this; } /** * Record the latency for the request based on the completion time. This will be * implicitly called when the request is logged, but it is advisable to call as soon * as the response is received to minimize the amount of response processing that is * counted as part of the request latency. */ public IpcLogEntry markEnd() { return withLatency(clock.monotonicTime() - startNanos, TimeUnit.NANOSECONDS); } /** * Set the library that produced the metric. */ public IpcLogEntry withOwner(String owner) { this.owner = owner; return this; } /** * Set the protocol used for this request. See {@link IpcProtocol} for more information. */ public IpcLogEntry withProtocol(IpcProtocol protocol) { return withProtocol(protocol.value()); } /** * Set the protocol used for this request. See {@link IpcProtocol} for more information. */ public IpcLogEntry withProtocol(String protocol) { this.protocol = protocol; return this; } /** * Set the result for this request. See {@link IpcResult} for more information. */ public IpcLogEntry withResult(IpcResult result) { this.result = result; return this; } /** * Set the high level status for the request. See {@link IpcStatus} for more * information. */ public IpcLogEntry withStatus(IpcStatus status) { this.status = status; return this; } /** * Set the detailed implementation specific status for the request. In most cases it * is preferable to use {@link #withException(Throwable)} or {@link #withHttpStatus(int)} * instead of calling this directly. */ public IpcLogEntry withStatusDetail(String statusDetail) { this.statusDetail = statusDetail; return this; } /** * Set the high level cause for a request failure. See {@link IpcErrorGroup} for more * information. * * @deprecated Use {@link #withStatus(IpcStatus)} instead. This method is scheduled for * removal in a future release. */ @Deprecated public IpcLogEntry withErrorGroup(IpcErrorGroup errorGroup) { return this; } /** * Set the implementation specific reason for the request failure. In most cases it * is preferable to use {@link #withException(Throwable)} or {@link #withHttpStatus(int)} * instead of calling this directly. * * @deprecated Use {@link #withStatusDetail(String)} instead. This method is scheduled for * removal in a future release. */ @Deprecated public IpcLogEntry withErrorReason(String errorReason) { return this; } /** * Set the exception that was thrown while trying to execute the request. This will be * logged and can be used to fill in the error reason. */ public IpcLogEntry withException(Throwable exception) { this.exception = exception; if (statusDetail == null) { statusDetail = exception.getClass().getSimpleName(); } if (status == null) { status = IpcStatus.forException(exception); } return this; } /** * Set the attempt number for the request. */ public IpcLogEntry withAttempt(IpcAttempt attempt) { this.attempt = attempt; return this; } /** * Set the attempt number for the request. */ public IpcLogEntry withAttempt(int attempt) { return withAttempt(IpcAttempt.forAttemptNumber(attempt)); } /** * Set whether or not this is the final attempt for the request. */ public IpcLogEntry withAttemptFinal(boolean isFinal) { this.attemptFinal = IpcAttemptFinal.forValue(isFinal); return this; } /** * Set the vip that was used to determine which server to contact. This will only be * present if using client side load balancing via Eureka. */ public IpcLogEntry withVip(String vip) { this.vip = vip; return this; } /** * Set the endpoint for this request. */ public IpcLogEntry withEndpoint(String endpoint) { this.endpoint = endpoint; return this; } /** * Set the client region for the request. In the case of the server side this will be * automatically filled in if the {@link NetflixHeader#Zone} is specified on the client * request. */ public IpcLogEntry withClientRegion(String region) { this.clientRegion = region; return this; } /** * Set the client zone for the request. In the case of the server side this will be * automatically filled in if the {@link NetflixHeader#Zone} is specified on the client * request. */ public IpcLogEntry withClientZone(String zone) { this.clientZone = zone; if (clientRegion == null) { clientRegion = extractRegionFromZone(zone); } return this; } /** * Set the client app for the request. In the case of the server side this will be * automatically filled in if the {@link NetflixHeader#ASG} is specified on the client * request. The ASG value must follow the * * Frigga server group naming conventions. */ public IpcLogEntry withClientApp(String app) { this.clientApp = app; return this; } /** * Set the client cluster for the request. In the case of the server side this will be * automatically filled in if the {@link NetflixHeader#ASG} is specified on the client * request. The ASG value must follow the * * Frigga server group naming conventions. */ public IpcLogEntry withClientCluster(String cluster) { this.clientCluster = cluster; return this; } /** * Set the client ASG for the request. In the case of the server side this will be * automatically filled in if the {@link NetflixHeader#ASG} is specified on the client * request. The ASG value must follow the * * Frigga server group naming conventions. */ public IpcLogEntry withClientAsg(String asg) { this.clientAsg = asg; if (clientApp == null || clientCluster == null) { ServerGroup group = ServerGroup.parse(asg); clientApp = (clientApp == null) ? group.app() : clientApp; clientCluster = (clientCluster == null) ? group.cluster() : clientCluster; } return this; } /** * Set the client node for the request. This will be used for access logging only and will * not be present on metrics. The server will log this value if the {@link NetflixHeader#Node} * header is present on the client request. */ public IpcLogEntry withClientNode(String node) { this.clientNode = node; return this; } /** * Set the server region for the request. In the case of the client side this will be * automatically filled in if the {@link NetflixHeader#Zone} is specified on the server * response. */ public IpcLogEntry withServerRegion(String region) { this.serverRegion = region; return this; } /** * Set the server zone for the request. In the case of the client side this will be * automatically filled in if the {@link NetflixHeader#Zone} is specified on the server * response. */ public IpcLogEntry withServerZone(String zone) { this.serverZone = zone; if (serverRegion == null) { serverRegion = extractRegionFromZone(zone); } return this; } /** * Set the server app for the request. In the case of the client side this will be * automatically filled in if the {@link NetflixHeader#ASG} is specified on the server * response. The ASG value must follow the * * Frigga server group naming conventions. */ public IpcLogEntry withServerApp(String app) { this.serverApp = app; return this; } /** * Set the server cluster for the request. In the case of the client side this will be * automatically filled in if the {@link NetflixHeader#ASG} is specified on the server * response. The ASG value must follow the * * Frigga server group naming conventions. */ public IpcLogEntry withServerCluster(String cluster) { this.serverCluster = cluster; return this; } /** * Set the server ASG for the request. In the case of the client side this will be * automatically filled in if the {@link NetflixHeader#ASG} is specified on the server * response. The ASG value must follow the * * Frigga server group naming conventions. */ public IpcLogEntry withServerAsg(String asg) { this.serverAsg = asg; if (serverApp == null || serverCluster == null) { ServerGroup group = ServerGroup.parse(asg); serverApp = (serverApp == null) ? group.app() : serverApp; serverCluster = (serverCluster == null) ? group.cluster() : serverCluster; } return this; } /** * Set the server node for the request. This will be used for access logging only and will * not be present on metrics. The client will log this value if the {@link NetflixHeader#Node} * header is present on the server response. */ public IpcLogEntry withServerNode(String node) { this.serverNode = node; return this; } /** * Set the HTTP method used for this request. */ public IpcLogEntry withHttpMethod(String method) { this.httpMethod = method; return this; } /** * Set the HTTP status code for this request. If not already set, then this will set an * appropriate value for {@link #withStatus(IpcStatus)} and {@link #withResult(IpcResult)} * based on the status code. */ public IpcLogEntry withHttpStatus(int httpStatus) { if (httpStatus >= 100 && httpStatus < 600) { this.httpStatus = httpStatus; if (status == null) { status = IpcStatus.forHttpStatus(httpStatus); } if (result == null) { result = status.result(); } } else { // If an invalid HTTP status code is passed in this.httpStatus = -1; } return this; } /** * Set the URI and path for the request. */ public IpcLogEntry withUri(String uri, String path) { this.uri = uri; this.path = path; return this; } /** * Set the URI and path for the request. The path will get extracted from the URI. If the * URI is non-strict and cannot be parsed with the java URI class, then use * {@link #withUri(String, String)} instead. */ public IpcLogEntry withUri(URI uri) { return withUri(uri.toString(), uri.getPath()); } /** * Set the length for the request entity if it is known at the time of logging. If the size * is not known, e.g. a chunked HTTP entity, then a negative value can be used and the length * will be ignored. */ public IpcLogEntry withRequestContentLength(long length) { this.requestContentLength = length; return this; } /** * Set the length for the request entity if it is known at the time of logging. If the size * is not known, e.g. a chunked HTTP entity, then a negative value can be used and the length * will be ignored. */ public IpcLogEntry withResponseContentLength(long length) { this.responseContentLength = length; return this; } /** * Add a request header value. For special headers in {@link NetflixHeader} it will * automatically fill in the more specific fields based on the header values. */ public IpcLogEntry addRequestHeader(String name, String value) { if (clientAsg == null && name.equalsIgnoreCase(NetflixHeader.ASG.headerName())) { withClientAsg(value); } else if (clientZone == null && name.equalsIgnoreCase(NetflixHeader.Zone.headerName())) { withClientZone(value); } else if (clientNode == null && name.equalsIgnoreCase(NetflixHeader.Node.headerName())) { withClientNode(value); } else if (vip == null && name.equalsIgnoreCase(NetflixHeader.Vip.headerName())) { withVip(value); } else if (isMeshRequest(name, value)) { disableMetrics(); } this.requestHeaders.add(new Header(name, value)); return this; } private boolean isMeshRequest(String name, String value) { return name.equalsIgnoreCase(NetflixHeader.IngressCommonIpcMetrics.headerName()) && "true".equalsIgnoreCase(value); } /** * Add a response header value. For special headers in {@link NetflixHeader} it will * automatically fill in the more specific fields based on the header values. */ public IpcLogEntry addResponseHeader(String name, String value) { if (serverAsg == null && name.equalsIgnoreCase(NetflixHeader.ASG.headerName())) { withServerAsg(value); } else if (serverZone == null && name.equalsIgnoreCase(NetflixHeader.Zone.headerName())) { withServerZone(value); } else if (serverNode == null && name.equalsIgnoreCase(NetflixHeader.Node.headerName())) { withServerNode(value); } else if (endpoint == null && name.equalsIgnoreCase(NetflixHeader.Endpoint.headerName())) { withEndpoint(value); } this.responseHeaders.add(new Header(name, value)); return this; } /** * Set the remote address for the request. */ public IpcLogEntry withRemoteAddress(String remoteAddress) { this.remoteAddress = remoteAddress; return this; } /** * Set the remote port for the request. */ public IpcLogEntry withRemotePort(int remotePort) { this.remotePort = remotePort; return this; } /** * Add custom tags to the request metrics. Note, IPC metrics already have many tags and it * is not recommended for users to tack on additional context. In particular, any additional * tags should have a guaranteed low cardinality. If additional tagging causes these * metrics to exceed limits, then you may lose all visibility into requests. */ public IpcLogEntry addTag(Tag tag) { this.additionalTags.put(tag.key(), tag.value()); return this; } /** * Add custom tags to the request metrics. Note, IPC metrics already have many tags and it * is not recommended for users to tack on additional context. In particular, any additional * tags should have a guaranteed low cardinality. If additional tagging causes these * metrics to exceed limits, then you may lose all visibility into requests. */ public IpcLogEntry addTag(String k, String v) { this.additionalTags.put(k, v); return this; } /** * Disable the metrics. The log will still get written, but none of the metrics will get * updated. */ public IpcLogEntry disableMetrics() { this.disableMetrics = true; return this; } private void putTag(Map tags, Tag tag) { if (tag != null) { tags.put(tag.key(), tag.value()); } } private void putTag(Map tags, String k, String v) { if (notNullOrEmpty(v)) { String value = logger.limiterForKey(k).apply(v); tags.put(k, value); } } private void finalizeFields() { // Do final checks and update fields that haven't been explicitly set if needed // before logging. if (result == null) { result = status == null ? IpcResult.success : status.result(); } if (status == null) { status = (result == IpcResult.success) ? IpcStatus.success : IpcStatus.unexpected_error; } if (attempt == null) { attempt = IpcAttempt.forAttemptNumber(1); } if (attemptFinal == null) { attemptFinal = IpcAttemptFinal.is_true; } if (endpoint == null) { endpoint = (path == null || httpStatus == 404) ? "unknown" : PathSanitizer.sanitize(path); } } private boolean isClient() { return marker != null && "ipc-client".equals(marker.getName()); } private Id createCallId(String name) { Map tags = new HashMap<>(); // User specified custom tags, add individually to ensure that limiter is applied // to the values for (Map.Entry entry : additionalTags.entrySet()) { putTag(tags, entry.getKey(), entry.getValue()); } // Required for both client and server putTag(tags, IpcTagKey.owner.key(), owner); putTag(tags, result); putTag(tags, status); if (isClient()) { // Required for client, should be null on server putTag(tags, attempt); putTag(tags, attemptFinal); // Optional for client putTag(tags, IpcTagKey.serverApp.key(), serverApp); putTag(tags, IpcTagKey.serverCluster.key(), serverCluster); putTag(tags, IpcTagKey.serverAsg.key(), serverAsg); } else { // Optional for server putTag(tags, IpcTagKey.clientApp.key(), clientApp); putTag(tags, IpcTagKey.clientCluster.key(), clientCluster); putTag(tags, IpcTagKey.clientAsg.key(), clientAsg); } // Optional for both client and server putTag(tags, IpcTagKey.endpoint.key(), endpoint); putTag(tags, IpcTagKey.vip.key(), vip); putTag(tags, IpcTagKey.protocol.key(), protocol); putTag(tags, IpcTagKey.statusDetail.key(), statusDetail); putTag(tags, IpcTagKey.httpStatus.key(), Integer.toString(httpStatus)); putTag(tags, IpcTagKey.httpMethod.key(), httpMethod); return registry.createId(name, tags); } private Id getInflightId() { if (inflightId == null) { Map tags = new HashMap<>(); // Required for both client and server putTag(tags, IpcTagKey.owner.key(), owner); // Optional for both client and server putTag(tags, IpcTagKey.vip.key(), vip); String name = isClient() ? IpcMetric.clientInflight.metricName() : IpcMetric.serverInflight.metricName(); inflightId = registry.createId(name, tags); } return inflightId; } private void recordClientMetrics() { if (disableMetrics) { return; } Id clientCall = createCallId(IpcMetric.clientCall.metricName()); PercentileTimer.builder(registry) .withId(clientCall) .build() .record(getLatency(), TimeUnit.NANOSECONDS); if (responseContentLength >= 0L) { Id clientCallSizeInbound = registry.createId( IpcMetric.clientCallSizeInbound.metricName(), clientCall.tags()); registry.distributionSummary(clientCallSizeInbound).record(responseContentLength); } if (requestContentLength >= 0L) { Id clientCallSizeOutbound = registry.createId( IpcMetric.clientCallSizeOutbound.metricName(), clientCall.tags()); registry.distributionSummary(clientCallSizeOutbound).record(requestContentLength); } } private void recordServerMetrics() { if (disableMetrics) { return; } Id serverCall = createCallId(IpcMetric.serverCall.metricName()); PercentileTimer.builder(registry) .withId(serverCall) .build() .record(getLatency(), TimeUnit.NANOSECONDS); if (requestContentLength >= 0L) { Id serverCallSizeInbound = registry.createId( IpcMetric.serverCallSizeInbound.metricName(), serverCall.tags()); registry.distributionSummary(serverCallSizeInbound).record(requestContentLength); } if (responseContentLength >= 0L) { Id serverCallSizeOutbound = registry.createId( IpcMetric.serverCallSizeOutbound.metricName(), serverCall.tags()); registry.distributionSummary(serverCallSizeOutbound).record(responseContentLength); } } /** * Log the request. This entry will potentially be reused after this is called. The user * should not attempt any further modifications to the state of this entry. */ public void log() { if (logger != null) { finalizeFields(); if (registry != null) { if (isClient()) { recordClientMetrics(); } else { recordServerMetrics(); } } if (inflightId != null) { logger.inflightRequests(inflightId).decrementAndGet(); } logger.log(this); } else { reset(); } } /** Return the log level set for this log entry. */ Level getLevel() { return level; } /** Return the marker set for this log entry. */ Marker getMarker() { return marker; } /** Return true if the request is successful and the entry can be reused. */ boolean isSuccessful() { return result == IpcResult.success; } private String extractRegionFromZone(String zone) { int n = zone.length(); if (n < 4) { return null; } else { char c = zone.charAt(n - 2); if (Character.isDigit(c) && zone.charAt(n - 3) == '-') { // AWS zones have a pattern of `${region}[a-f]`, for example: `us-east-1a` return zone.substring(0, n - 1); } else if (c == '-') { // GCE zones have a pattern of `${region}-[a-f]`, for example: `us-east1-c` // https://cloud.google.com/compute/docs/regions-zones/ return zone.substring(0, n - 2); } else { // Pattern doesn't look familiar return null; } } } private long getLatency() { if (startNanos >= 0L && latency < 0L) { // If latency was not explicitly set but the start time was, then compute the // time since the start. The field is updated so subsequent calls will return // a consistent value for the latency. latency = clock.monotonicTime() - startNanos; } return latency; } private String getExceptionClass() { return (exception == null) ? null : exception.getClass().getName(); } private String getExceptionMessage() { return (exception == null) ? null : exception.getMessage(); } private void putInMDC(String key, String value) { if (value != null && !value.isEmpty()) { MDC.put(key, value); } } private void putInMDC(Tag tag) { if (tag != null) { putInMDC(tag.key(), tag.value()); } } void populateMDC() { putInMDC("marker", marker.getName()); putInMDC("uri", uri); putInMDC("path", path); putInMDC(IpcTagKey.endpoint.key(), endpoint); putInMDC(IpcTagKey.owner.key(), owner); putInMDC(IpcTagKey.protocol.key(), protocol); putInMDC(IpcTagKey.vip.key(), vip); putInMDC("ipc.client.region", clientRegion); putInMDC("ipc.client.zone", clientZone); putInMDC("ipc.client.node", clientNode); putInMDC(IpcTagKey.clientApp.key(), clientApp); putInMDC(IpcTagKey.clientCluster.key(), clientCluster); putInMDC(IpcTagKey.clientAsg.key(), clientAsg); putInMDC("ipc.server.region", serverRegion); putInMDC("ipc.server.zone", serverZone); putInMDC("ipc.server.node", serverNode); putInMDC(IpcTagKey.serverApp.key(), serverApp); putInMDC(IpcTagKey.serverCluster.key(), serverCluster); putInMDC(IpcTagKey.serverAsg.key(), serverAsg); putInMDC("ipc.remote.address", remoteAddress); putInMDC("ipc.remote.port", Integer.toString(remotePort)); putInMDC(attempt); putInMDC(attemptFinal); putInMDC(result); putInMDC(status); putInMDC(IpcTagKey.statusDetail.key(), statusDetail); putInMDC(IpcTagKey.httpMethod.key(), httpMethod); putInMDC(IpcTagKey.httpStatus.key(), Integer.toString(httpStatus)); } @Override public String toString() { finalizeFields(); return new JsonStringBuilder(builder) .startObject() .addField("owner", owner) .addField("start", startTime) .addField("latency", getLatency() / 1e9) .addField("protocol", protocol) .addField("uri", uri) .addField("path", path) .addField("endpoint", endpoint) .addField("vip", vip) .addField("clientRegion", clientRegion) .addField("clientZone", clientZone) .addField("clientApp", clientApp) .addField("clientCluster", clientCluster) .addField("clientAsg", clientAsg) .addField("clientNode", clientNode) .addField("serverRegion", serverRegion) .addField("serverZone", serverZone) .addField("serverApp", serverApp) .addField("serverCluster", serverCluster) .addField("serverAsg", serverAsg) .addField("serverNode", serverNode) .addField("remoteAddress", remoteAddress) .addField("remotePort", remotePort) .addField("attempt", attempt) .addField("attemptFinal", attemptFinal) .addField("result", result) .addField("status", status) .addField("statusDetail", statusDetail) .addField("exceptionClass", getExceptionClass()) .addField("exceptionMessage", getExceptionMessage()) .addField("httpMethod", httpMethod) .addField("httpStatus", httpStatus) .addField("requestContentLength", requestContentLength) .addField("responseContentLength", responseContentLength) .addField("requestHeaders", requestHeaders) .addField("responseHeaders", responseHeaders) .addField("additionalTags", additionalTags) .endObject() .toString(); } /** * Resets this log entry so the instance can be reused. This helps to reduce allocations. */ void reset() { logger = null; level = Level.DEBUG; marker = null; startTime = -1L; startNanos = -1L; latency = -1L; owner = null; result = null; protocol = null; status = null; statusDetail = null; exception = null; attempt = null; attemptFinal = null; vip = null; endpoint = null; clientRegion = null; clientZone = null; clientApp = null; clientCluster = null; clientAsg = null; clientNode = null; serverRegion = null; serverZone = null; serverApp = null; serverCluster = null; serverAsg = null; serverNode = null; httpMethod = null; httpStatus = -1; uri = null; path = null; requestContentLength = -1L; responseContentLength = -1L; requestHeaders.clear(); responseHeaders.clear(); remoteAddress = null; remotePort = -1; disableMetrics = false; additionalTags.clear(); builder.delete(0, builder.length()); inflightId = null; } /** * Partially reset this log entry so it can be used for another request attempt. Any * attributes that can change for a given request need to be cleared. */ void resetForRetry() { startTime = -1L; startNanos = -1L; latency = -1L; result = null; status = null; statusDetail = null; exception = null; attempt = null; attemptFinal = null; vip = null; serverRegion = null; serverZone = null; serverApp = null; serverCluster = null; serverAsg = null; serverNode = null; httpStatus = -1; requestContentLength = -1L; responseContentLength = -1L; requestHeaders.clear(); responseHeaders.clear(); remoteAddress = null; remotePort = -1; builder.delete(0, builder.length()); inflightId = null; } /** * Apply a mapping function to this log entry. This method is mostly used to allow the * final mapping to be applied to the entry without breaking the operator chaining. */ public T convert(Function mapper) { return mapper.apply(this); } private static boolean notNullOrEmpty(String s) { return s != null && !s.isEmpty(); } private static class Header { private final String name; private final String value; Header(String name, String value) { this.name = name; this.value = value; } String name() { return name; } String value() { return value; } } private static class JsonStringBuilder { private final StringBuilder builder; private boolean firstEntry = true; JsonStringBuilder(StringBuilder builder) { this.builder = builder; } JsonStringBuilder startObject() { builder.append('{'); return this; } JsonStringBuilder endObject() { builder.append('}'); return this; } private void addSep() { if (firstEntry) { firstEntry = false; } else { builder.append(','); } } JsonStringBuilder addField(String k, String v) { if (notNullOrEmpty(v)) { addSep(); builder.append('"'); escapeAndAppend(builder, k); builder.append("\":\""); escapeAndAppend(builder, v); builder.append('"'); } return this; } JsonStringBuilder addField(String k, Tag tag) { if (tag != null) { addField(k, tag.value()); } return this; } JsonStringBuilder addField(String k, int v) { if (v >= 0) { addSep(); builder.append('"'); escapeAndAppend(builder, k); builder.append("\":").append(v); } return this; } JsonStringBuilder addField(String k, long v) { if (v >= 0L) { addSep(); builder.append('"'); escapeAndAppend(builder, k); builder.append("\":").append(v); } return this; } JsonStringBuilder addField(String k, double v) { if (v >= 0.0) { addSep(); builder.append('"'); escapeAndAppend(builder, k); builder.append("\":").append(v); } return this; } JsonStringBuilder addField(String k, List
headers) { if (!headers.isEmpty()) { addSep(); builder.append('"'); escapeAndAppend(builder, k); builder.append("\":["); boolean first = true; for (Header h : headers) { if (first) { first = false; } else { builder.append(','); } builder.append("{\"name\":\""); escapeAndAppend(builder, h.name()); builder.append("\",\"value\":\""); escapeAndAppend(builder, h.value()); builder.append("\"}"); } builder.append(']'); } return this; } JsonStringBuilder addField(String k, Map tags) { if (!tags.isEmpty()) { addSep(); builder.append('"'); escapeAndAppend(builder, k); builder.append("\":{"); boolean first = true; for (Map.Entry entry : tags.entrySet()) { if (first) { first = false; } else { builder.append(','); } builder.append('"'); escapeAndAppend(builder, entry.getKey()); builder.append("\":\""); escapeAndAppend(builder, entry.getValue()); builder.append('"'); } builder.append('}'); } return this; } private void escapeAndAppend(StringBuilder builder, String str) { int length = str.length(); for (int i = 0; i < length; ++i) { char c = str.charAt(i); switch (c) { case '"': builder.append("\\\""); break; case '\\': builder.append("\\\\"); break; case '\b': builder.append("\\b"); break; case '\f': builder.append("\\f"); break; case '\n': builder.append("\\n"); break; case '\r': builder.append("\\r"); break; case '\t': builder.append("\\t"); break; default: // Ignore control characters that are not matched explicitly above if (!Character.isISOControl(c)) { builder.append(c); } break; } } } @Override public String toString() { return builder.toString(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy