/*
* Copyright (c) 2017 Spotify AB.
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.spotify.heroic.querylogging;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.spotify.heroic.Query;
import com.spotify.heroic.metric.FullQuery;
import com.spotify.heroic.metric.QueryMetrics;
import com.spotify.heroic.metric.QueryMetricsResponse;
import java.time.Instant;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Consumer;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@QueryLoggingScope
@Slf4j
@RequiredArgsConstructor
public class Slf4jQueryLogger implements QueryLogger {
private final Consumer<String> queryLog;
private final ObjectMapper objectMapper;
private final String component;
@Override
public void logHttpQueryText(
final QueryContext context, final String query
) {
serializeAndLog(context, "http-query-text", query);
}
@Override
public void logHttpQueryJson(
final QueryContext context, final QueryMetrics query
) {
serializeAndLog(context, "http-query-json", query);
}
@Override
public void logQuery(final QueryContext context, final Query query) {
serializeAndLog(context, "query", query);
}
@Override
public void logOutgoingRequestToShards(
final QueryContext context, final FullQuery.Request request
) {
performAndCatch(() -> {
final FullQuery.Request.Summary summary = request.summarize();
serializeAndLog(context, "outgoing-request-to-shards", summary);
});
}
@Override
public void logIncomingRequestAtNode(
final QueryContext context, final FullQuery.Request request
) {
performAndCatch(() -> {
final FullQuery.Request.Summary summary = request.summarize();
serializeAndLog(context, "incoming-request-at-node", summary);
});
}
@Override
public void logOutgoingResponseAtNode(final QueryContext context, final FullQuery response) {
performAndCatch(() -> {
final FullQuery.Summary summary = response.summarize();
serializeAndLog(context, "outgoing-response-at-node", summary);
});
}
@Override
public void logIncomingResponseFromShard(
final QueryContext context, final FullQuery response
) {
performAndCatch(() -> {
final FullQuery.Summary summary = response.summarize();
serializeAndLog(context, "incoming-response-from-shard", summary);
});
}
@Override
public void logFinalResponse(
final QueryContext context, final QueryMetricsResponse queryMetricsResponse
) {
performAndCatch(() -> {
final QueryMetricsResponse.Summary summary = queryMetricsResponse.summarize();
serializeAndLog(context, "final-response", summary);
});
}
private <T> void serializeAndLog(
final QueryContext context, final String type, final T data
) {
performAndCatch(() -> {
final MessageFormat<T> message =
new MessageFormat<>(component, context.getQueryId(), context.getClientContext(),
context.getHttpContext(), type, data);
final String timestamp = Instant.now().toString();
final LogFormat logFormat = new LogFormat(timestamp, message);
try {
queryLog.accept(objectMapper.writeValueAsString(logFormat));
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
});
}
private void performAndCatch(Runnable toRun) {
try {
toRun.run();
} catch (Exception e) {
log.error("Failed while trying to log query", e);
}
}
@Data
public class LogFormat {
@JsonProperty("@timestamp")
private final String timestamp;
@JsonProperty("@message")
private final MessageFormat message;
}
@Data
public class MessageFormat<T> {
private final String component;
private final UUID queryId;
private final Optional<JsonNode> clientContext;
private final Optional<HttpContext> httpContext;
private final String type;
private final T data;
}
}