LCOV - code coverage report
Current view: top level - server/logging - TraceContext.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 81 84 96.4 %
Date: 2022-11-19 15:00:39 Functions: 27 29 93.1 %

          Line data    Source code
       1             : // Copyright (C) 2018 The Android Open Source Project
       2             : //
       3             : // Licensed under the Apache License, Version 2.0 (the "License");
       4             : // you may not use this file except in compliance with the License.
       5             : // You may obtain a copy of the License at
       6             : //
       7             : // http://www.apache.org/licenses/LICENSE-2.0
       8             : //
       9             : // Unless required by applicable law or agreed to in writing, software
      10             : // distributed under the License is distributed on an "AS IS" BASIS,
      11             : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12             : // See the License for the specific language governing permissions and
      13             : // limitations under the License.
      14             : 
      15             : package com.google.gerrit.server.logging;
      16             : 
      17             : import static java.util.Objects.requireNonNull;
      18             : 
      19             : import com.google.common.base.Stopwatch;
      20             : import com.google.common.base.Strings;
      21             : import com.google.common.collect.HashBasedTable;
      22             : import com.google.common.collect.ImmutableList;
      23             : import com.google.common.collect.ImmutableMap;
      24             : import com.google.common.collect.Table;
      25             : import com.google.common.flogger.FluentLogger;
      26             : import com.google.gerrit.common.Nullable;
      27             : import com.google.gerrit.server.cancellation.RequestStateContext;
      28             : import java.util.Optional;
      29             : import java.util.concurrent.TimeUnit;
      30             : import java.util.function.Consumer;
      31             : 
      32             : /**
      33             :  * TraceContext that allows to set logging tags and enforce logging.
      34             :  *
      35             :  * <p>The logging tags are attached to all log entries that are triggered while the trace context is
      36             :  * open. If force logging is enabled all logs that are triggered while the trace context is open are
      37             :  * written to the log file regardless of the configured log level.
      38             :  *
      39             :  * <pre>
      40             :  * try (TraceContext traceContext = TraceContext.open()
      41             :  *         .addTag("tag-name", "tag-value")
      42             :  *         .forceLogging()) {
      43             :  *     // This gets logged as: A log [CONTEXT forced=true tag-name="tag-value" ]
      44             :  *     // Since force logging is enabled this gets logged independently of the configured log
      45             :  *     // level.
      46             :  *     logger.atFinest().log("A log");
      47             :  *
      48             :  *     // do stuff
      49             :  * }
      50             :  * </pre>
      51             :  *
      52             :  * <p>The logging tags and the force logging flag are stored in the {@link LoggingContext}. {@link
      53             :  * LoggingContextAwareExecutorService}, {@link LoggingContextAwareScheduledExecutorService} and the
      54             :  * executor in {@link com.google.gerrit.server.git.WorkQueue} ensure that the logging context is
      55             :  * automatically copied to background threads.
      56             :  *
      57             :  * <p>On close of the trace context newly set tags are unset. Force logging is disabled on close if
      58             :  * it got enabled while the trace context was open.
      59             :  *
      60             :  * <p>Trace contexts can be nested:
      61             :  *
      62             :  * <pre>
      63             :  * // Initially there are no tags
      64             :  * logger.atSevere().log("log without tag");
      65             :  *
      66             :  * // a tag can be set by opening a trace context
      67             :  * try (TraceContext ctx = TraceContext.open().addTag("tag1", "value1")) {
      68             :  *   logger.atSevere().log("log with tag1=value1");
      69             :  *
      70             :  *   // while a trace context is open further tags can be added.
      71             :  *   ctx.addTag("tag2", "value2")
      72             :  *   logger.atSevere().log("log with tag1=value1 and tag2=value2");
      73             :  *
      74             :  *   // also by opening another trace context a another tag can be added
      75             :  *   try (TraceContext ctx2 = TraceContext.open().addTag("tag3", "value3")) {
      76             :  *     logger.atSevere().log("log with tag1=value1, tag2=value2 and tag3=value3");
      77             :  *
      78             :  *     // it's possible to have the same tag name with multiple values
      79             :  *     ctx2.addTag("tag3", "value3a")
      80             :  *     logger.atSevere().log("log with tag1=value1, tag2=value2, tag3=value3 and tag3=value3a");
      81             :  *
      82             :  *     // adding a tag with the same name and value as an existing tag has no effect
      83             :  *     try (TraceContext ctx3 = TraceContext.open().addTag("tag3", "value3a")) {
      84             :  *       logger.atSevere().log("log with tag1=value1, tag2=value2, tag3=value3 and tag3=value3a");
      85             :  *     }
      86             :  *
      87             :  *     // closing ctx3 didn't remove tag3=value3a since it was already set before opening ctx3
      88             :  *     logger.atSevere().log("log with tag1=value1, tag2=value2, tag3=value3 and tag3=value3a");
      89             :  *   }
      90             :  *
      91             :  *   // closing ctx2 removed tag3=value3 and tag3-value3a
      92             :  *   logger.atSevere().log("with tag1=value1 and tag2=value2");
      93             :  * }
      94             :  *
      95             :  * // closing ctx1 removed tag1=value1 and tag2=value2
      96             :  * logger.atSevere().log("log without tag");
      97             :  * </pre>
      98             :  */
      99             : public class TraceContext implements AutoCloseable {
     100             :   private static final String PLUGIN_TAG = "PLUGIN";
     101             : 
     102             :   public static TraceContext open() {
     103         151 :     return new TraceContext();
     104             :   }
     105             : 
     106             :   /**
     107             :    * Opens a new trace context for request tracing.
     108             :    *
     109             :    * <ul>
     110             :    *   <li>sets a tag with a trace ID
     111             :    *   <li>enables force logging
     112             :    * </ul>
     113             :    *
     114             :    * <p>if no trace ID is provided a new trace ID is only generated if request tracing was not
     115             :    * started yet. If request tracing was already started the given {@code traceIdConsumer} is
     116             :    * invoked with the existing trace ID and no new logging tag is set.
     117             :    *
     118             :    * <p>No-op if {@code trace} is {@code false}.
     119             :    *
     120             :    * @param trace whether tracing should be started
     121             :    * @param traceId trace ID that should be used for tracing, if {@code null} a trace ID is
     122             :    *     generated
     123             :    * @param traceIdConsumer consumer for the trace ID, should be used to return the generated trace
     124             :    *     ID to the client, not invoked if {@code trace} is {@code false}
     125             :    * @return the trace context
     126             :    */
     127             :   public static TraceContext newTrace(
     128             :       boolean trace, @Nullable String traceId, TraceIdConsumer traceIdConsumer) {
     129         106 :     if (!trace) {
     130             :       // Create an empty trace context.
     131         106 :       return open();
     132             :     }
     133             : 
     134           7 :     if (!Strings.isNullOrEmpty(traceId)) {
     135           7 :       traceIdConsumer.accept(RequestId.Type.TRACE_ID.name(), traceId);
     136           7 :       return open().addTag(RequestId.Type.TRACE_ID, traceId).forceLogging();
     137             :     }
     138             : 
     139             :     Optional<String> existingTraceId =
     140           3 :         LoggingContext.getInstance().getTagsAsMap().get(RequestId.Type.TRACE_ID.name()).stream()
     141           3 :             .findAny();
     142           3 :     if (existingTraceId.isPresent()) {
     143             :       // request tracing was already started, no need to generate a new trace ID
     144           1 :       traceIdConsumer.accept(RequestId.Type.TRACE_ID.name(), existingTraceId.get());
     145           1 :       return open();
     146             :     }
     147             : 
     148           3 :     RequestId newTraceId = new RequestId();
     149           3 :     traceIdConsumer.accept(RequestId.Type.TRACE_ID.name(), newTraceId.toString());
     150           3 :     return open().addTag(RequestId.Type.TRACE_ID, newTraceId).forceLogging();
     151             :   }
     152             : 
     153             :   @FunctionalInterface
     154             :   public interface TraceIdConsumer {
     155             :     void accept(String tagName, String traceId);
     156             :   }
     157             : 
     158             :   /**
     159             :    * Opens a new timer that logs the time for an operation if request tracing is enabled.
     160             :    *
     161             :    * @param operation the name of operation the is being performed
     162             :    * @return the trace timer
     163             :    */
     164             :   public static TraceTimer newTimer(String operation) {
     165         148 :     return new TraceTimer(requireNonNull(operation, "operation is required"));
     166             :   }
     167             : 
     168             :   /**
     169             :    * Opens a new timer that logs the time for an operation if request tracing is enabled.
     170             :    *
     171             :    * @param operation the name of operation the is being performed
     172             :    * @param metadata metadata
     173             :    * @return the trace timer
     174             :    */
     175             :   public static TraceTimer newTimer(String operation, Metadata metadata) {
     176         154 :     return new TraceTimer(
     177         154 :         requireNonNull(operation, "operation is required"),
     178         154 :         requireNonNull(metadata, "metadata is required"));
     179             :   }
     180             : 
     181             :   public static class TraceTimer implements AutoCloseable {
     182         154 :     private static final FluentLogger logger = FluentLogger.forEnclosingClass();
     183             : 
     184             :     private final Consumer<Long> doneLogFn;
     185             :     private final Stopwatch stopwatch;
     186             : 
     187             :     private TraceTimer(String operation) {
     188         148 :       this(
     189         148 :           () -> logger.atFine().log("Starting timer for %s", operation),
     190             :           elapsedMs -> {
     191         148 :             LoggingContext.getInstance()
     192         148 :                 .addPerformanceLogRecord(() -> PerformanceLogRecord.create(operation, elapsedMs));
     193         148 :             logger.atFine().log("%s done (%d ms)", operation, elapsedMs);
     194         148 :           });
     195         148 :     }
     196             : 
     197             :     private TraceTimer(String operation, Metadata metadata) {
     198         154 :       this(
     199             :           () ->
     200         154 :               logger.atFine().log(
     201         154 :                   "Starting timer for %s (%s)", operation, metadata.toStringForLoggingLazy()),
     202             :           elapsedMs -> {
     203         154 :             LoggingContext.getInstance()
     204         154 :                 .addPerformanceLogRecord(
     205           3 :                     () -> PerformanceLogRecord.create(operation, elapsedMs, metadata));
     206         154 :             logger.atFine().log(
     207         154 :                 "%s (%s) done (%d ms)", operation, metadata.toStringForLoggingLazy(), elapsedMs);
     208         154 :           });
     209         154 :     }
     210             : 
     211         154 :     private TraceTimer(Runnable startLogFn, Consumer<Long> doneLogFn) {
     212         154 :       RequestStateContext.abortIfCancelled();
     213         154 :       startLogFn.run();
     214         154 :       this.doneLogFn = doneLogFn;
     215         154 :       this.stopwatch = Stopwatch.createStarted();
     216         154 :     }
     217             : 
     218             :     @Override
     219             :     public void close() {
     220         154 :       stopwatch.stop();
     221         154 :       doneLogFn.accept(stopwatch.elapsed(TimeUnit.MILLISECONDS));
     222         154 :       RequestStateContext.abortIfCancelled();
     223         154 :     }
     224             :   }
     225             : 
     226             :   // Table<TAG_NAME, TAG_VALUE, REMOVE_ON_CLOSE>
     227         151 :   private final Table<String, String, Boolean> tags = HashBasedTable.create();
     228             : 
     229             :   private final boolean oldAclLogging;
     230             :   private final ImmutableList<String> oldAclLogRecords;
     231             : 
     232             :   private boolean stopForceLoggingOnClose;
     233             :   private boolean stopAclLoggingOnClose;
     234             : 
     235         151 :   private TraceContext() {
     236             :     // Just in case remember the old state and reset ACL log entries.
     237         151 :     this.oldAclLogging = LoggingContext.getInstance().isAclLogging();
     238         151 :     this.oldAclLogRecords = LoggingContext.getInstance().getAclLogRecords();
     239         151 :   }
     240             : 
     241             :   public TraceContext addTag(RequestId.Type requestId, Object tagValue) {
     242         103 :     return addTag(requireNonNull(requestId, "request ID is required").name(), tagValue);
     243             :   }
     244             : 
     245             :   public TraceContext addTag(String tagName, Object tagValue) {
     246         151 :     String name = requireNonNull(tagName, "tag name is required");
     247         151 :     String value = requireNonNull(tagValue, "tag value is required").toString();
     248         151 :     tags.put(name, value, LoggingContext.getInstance().addTag(name, value));
     249         151 :     return this;
     250             :   }
     251             : 
     252             :   public ImmutableMap<String, String> getTags() {
     253          96 :     ImmutableMap.Builder<String, String> tagMap = ImmutableMap.builder();
     254          96 :     tags.cellSet().forEach(c -> tagMap.put(c.getRowKey(), c.getColumnKey()));
     255          96 :     return tagMap.build();
     256             :   }
     257             : 
     258             :   public TraceContext addPluginTag(String pluginName) {
     259         151 :     return addTag(PLUGIN_TAG, pluginName);
     260             :   }
     261             : 
     262             :   public TraceContext forceLogging() {
     263           7 :     if (stopForceLoggingOnClose) {
     264           1 :       return this;
     265             :     }
     266             : 
     267           7 :     stopForceLoggingOnClose = !LoggingContext.getInstance().forceLogging(true);
     268           7 :     return this;
     269             :   }
     270             : 
     271             :   public static boolean isTracing() {
     272          28 :     return LoggingContext.getInstance().isLoggingForced();
     273             :   }
     274             : 
     275             :   public static Optional<String> getTraceId() {
     276           2 :     return LoggingContext.getInstance().getTagsAsMap().get(RequestId.Type.TRACE_ID.name()).stream()
     277           2 :         .findFirst();
     278             :   }
     279             : 
     280             :   public static Optional<String> getPluginTag() {
     281           0 :     return getTag(PLUGIN_TAG);
     282             :   }
     283             : 
     284             :   public static Optional<String> getTag(String tagName) {
     285           0 :     return LoggingContext.getInstance().getTagsAsMap().get(tagName).stream().findFirst();
     286             :   }
     287             : 
     288             :   public TraceContext enableAclLogging() {
     289           1 :     if (stopAclLoggingOnClose) {
     290           0 :       return this;
     291             :     }
     292             : 
     293           1 :     stopAclLoggingOnClose = !LoggingContext.getInstance().aclLogging(true);
     294           1 :     return this;
     295             :   }
     296             : 
     297             :   public static ImmutableList<String> getAclLogRecords() {
     298           1 :     return LoggingContext.getInstance().getAclLogRecords();
     299             :   }
     300             : 
     301             :   @Override
     302             :   public void close() {
     303         151 :     for (Table.Cell<String, String, Boolean> cell : tags.cellSet()) {
     304         151 :       if (cell.getValue()) {
     305         151 :         LoggingContext.getInstance().removeTag(cell.getRowKey(), cell.getColumnKey());
     306             :       }
     307         151 :     }
     308         151 :     if (stopForceLoggingOnClose) {
     309           7 :       LoggingContext.getInstance().forceLogging(false);
     310             :     }
     311             : 
     312         151 :     if (stopAclLoggingOnClose) {
     313           1 :       LoggingContext.getInstance().aclLogging(oldAclLogging);
     314           1 :       LoggingContext.getInstance().setAclLogRecords(oldAclLogRecords);
     315             :     }
     316         151 :   }
     317             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750