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

com.google.cloud.logging.Instrumentation Maven / Gradle / Ivy

There is a newer version: 3.21.1
Show newest version
/*
 * Copyright 2022 Google LLC
 *
 * 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.google.cloud.logging;

import com.google.api.client.util.Strings;
import com.google.api.gax.core.GaxProperties;
import com.google.cloud.Tuple;
import com.google.cloud.logging.Logging.WriteOption;
import com.google.cloud.logging.Payload.JsonPayload;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.protobuf.ListValue;
import com.google.protobuf.Struct;
import com.google.protobuf.Value;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public final class Instrumentation {
  public static final String DIAGNOSTIC_INFO_KEY = "logging.googleapis.com/diagnostic";
  public static final String INSTRUMENTATION_SOURCE_KEY = "instrumentation_source";
  public static final String INSTRUMENTATION_NAME_KEY = "name";
  public static final String INSTRUMENTATION_VERSION_KEY = "version";
  public static final String JAVA_LIBRARY_NAME_PREFIX = "java";
  // Using release-please annotations to update DEFAULT_INSTRUMENTATION_VERSION with latest version.
  // See
  // https://github.com/googleapis/release-please/blob/main/docs/customizing.md#updating-arbitrary-files
  // {x-version-update-start:google-cloud-logging:current}
  public static final String DEFAULT_INSTRUMENTATION_VERSION = "3.16.3";
  // {x-version-update-end}
  public static final String INSTRUMENTATION_LOG_NAME = "diagnostic-log";
  public static final int MAX_DIAGNOSTIC_VALUE_LENGTH = 14;
  public static final int MAX_DIAGNOSTIC_ENTIES = 3;
  private static boolean instrumentationAdded = false;
  private static final Object instrumentationLock = new Object();

  /**
   * Populates entries with instrumentation info which is added in separate log entry
   *
   * @param logEntries {@code Iterable} The list of entries to be populated
   * @return {@code Tuple>} containing a flag if instrumentation info
   *     was added or not and a modified list of log entries
   */
  public static Tuple> populateInstrumentationInfo(
      Iterable logEntries) {
    boolean isWritten = setInstrumentationStatus(true);
    if (isWritten) {
      return Tuple.of(false, logEntries);
    }
    List entries = new ArrayList<>();

    for (LogEntry logEntry : logEntries) {
      // Check if LogEntry has a proper payload and also contains a diagnostic entry
      if (!isWritten
          && logEntry.getPayload().getType() == Payload.Type.JSON
          && logEntry
              .getPayload()
              .getData()
              .containsFields(DIAGNOSTIC_INFO_KEY)) {
        try {
          ListValue infoList =
              logEntry
                  .getPayload()
                  .getData()
                  .getFieldsOrThrow(DIAGNOSTIC_INFO_KEY)
                  .getStructValue()
                  .getFieldsOrThrow(INSTRUMENTATION_SOURCE_KEY)
                  .getListValue();
          entries.add(createDiagnosticEntry(null, null, infoList));
          isWritten = true;
        } catch (RuntimeException ex) {
          System.err.println("ERROR: unexpected exception in populateInstrumentationInfo: " + ex);
        }
      } else {
        entries.add(logEntry);
      }
    }
    if (!isWritten) {
      entries.add(createDiagnosticEntry(null, null, null));
    }
    return Tuple.of(true, entries);
  }

  /**
   * Adds a partialSuccess flag option to array of WriteOption
   *
   * @param options {WriteOption[]} The options array to be extended
   * @return The new array of oprions containing WriteOption.OptionType.PARTIAL_SUCCESS flag set to
   *     true
   */
  public static WriteOption[] addPartialSuccessOption(WriteOption[] options) {
    if (options == null) {
      return options;
    }
    List writeOptions = new ArrayList<>();
    Collections.addAll(writeOptions, options);
    // Make sure we remove all partial success flags if any exist
    writeOptions.removeIf(
        option -> option.getOptionType() == WriteOption.OptionType.PARTIAL_SUCCESS);
    writeOptions.add(WriteOption.partialSuccess(true));
    return Iterables.toArray(writeOptions, WriteOption.class);
  }

  /**
   * The helper method to generate a log entry with diagnostic instrumentation data.
   *
   * @param libraryName {string} The name of the logging library to be reported. Should be prefixed
   *     with 'java'. Will be truncated if longer than 14 characters.
   * @param libraryVersion {string} The version of the logging library to be reported. Will be
   *     truncated if longer than 14 characters.
   * @return {LogEntry} The entry with diagnostic instrumentation data.
   */
  public static LogEntry createDiagnosticEntry(String libraryName, String libraryVersion) {
    return createDiagnosticEntry(libraryName, libraryVersion, null);
  }

  private static LogEntry createDiagnosticEntry(
      String libraryName, String libraryVersion, ListValue existingLibraryList) {
    Struct instrumentation =
        Struct.newBuilder()
            .putAllFields(
                ImmutableMap.of(
                    INSTRUMENTATION_SOURCE_KEY,
                    Value.newBuilder()
                        .setListValue(
                            generateLibrariesList(libraryName, libraryVersion, existingLibraryList))
                        .build()))
            .build();
    return LogEntry.newBuilder(
            JsonPayload.of(
                Struct.newBuilder()
                    .putAllFields(
                        ImmutableMap.of(
                            DIAGNOSTIC_INFO_KEY,
                            Value.newBuilder().setStructValue(instrumentation).build()))
                    .build()))
        .setLogName(INSTRUMENTATION_LOG_NAME)
        .build();
  }

  private static ListValue generateLibrariesList(
      String libraryName, String libraryVersion, ListValue existingLibraryList) {
    if (Strings.isNullOrEmpty(libraryName) || !libraryName.startsWith(JAVA_LIBRARY_NAME_PREFIX)) {
      libraryName = JAVA_LIBRARY_NAME_PREFIX;
    }
    if (Strings.isNullOrEmpty(libraryVersion)) {
      libraryVersion = getLibraryVersion(Instrumentation.class);
    }
    Struct libraryInfo = createInfoStruct(libraryName, libraryVersion);
    ListValue.Builder libraryList = ListValue.newBuilder();
    // First add instrumentation data of other libraries to a list if any
    if (existingLibraryList != null) {
      for (Value val : existingLibraryList.getValuesList()) {
        if (val.hasStructValue()) {
          try {
            String name =
                val.getStructValue().getFieldsOrThrow(INSTRUMENTATION_NAME_KEY).getStringValue();
            if (Strings.isNullOrEmpty(name) || !name.startsWith(JAVA_LIBRARY_NAME_PREFIX)) {
              continue;
            }
            String version =
                val.getStructValue().getFieldsOrThrow(INSTRUMENTATION_VERSION_KEY).getStringValue();
            if (Strings.isNullOrEmpty(version)) {
              continue;
            }
            libraryList.addValues(
                Value.newBuilder().setStructValue(createInfoStruct(name, version)).build());
            if (libraryList.getValuesCount() == MAX_DIAGNOSTIC_ENTIES) {
              break;
            }
          } catch (RuntimeException ex) {
            System.err.println("ERROR: unexpected exception in generateLibrariesList: " + ex);
          }
        }
      }
    }
    // At last, append this library info to a list
    libraryList.addValues(Value.newBuilder().setStructValue(libraryInfo).build());
    return libraryList.build();
  }

  private static Struct createInfoStruct(String libraryName, String libraryVersion) {
    return Struct.newBuilder()
        .putAllFields(
            ImmutableMap.of(
                INSTRUMENTATION_NAME_KEY,
                    Value.newBuilder().setStringValue(truncateValue(libraryName)).build(),
                INSTRUMENTATION_VERSION_KEY,
                    Value.newBuilder().setStringValue(truncateValue(libraryVersion)).build()))
        .build();
  }

  /**
   * The package-private helper method used to set the flag which indicates if instrumentation info
   * already written or not.
   *
   * @return The value of the flag before it was set.
   */
  static boolean setInstrumentationStatus(boolean value) {
    if (instrumentationAdded == value) {
      return instrumentationAdded;
    }
    synchronized (instrumentationLock) {
      boolean current = instrumentationAdded;
      instrumentationAdded = value;
      return current;
    }
  }

  /**
   * Returns a library version associated with given class
   *
   * @param libraryClass The class to be used to determine a library version
   * @return The version number string for given class or DEFAULT_INSTRUMENTATION_VERSION if class
   *     library version cannot be detected
   */
  public static String getLibraryVersion(Class libraryClass) {
    String libraryVersion = GaxProperties.getLibraryVersion(libraryClass);
    if (Strings.isNullOrEmpty(libraryVersion)) {
      libraryVersion = DEFAULT_INSTRUMENTATION_VERSION;
    }
    return libraryVersion;
  }

  /**
   * Trancates given string to MAX_DIAGNOSTIC_VALUE_LENGTH and adds "*" instead of reduced suffix
   *
   * @param value {String} Value to be truncated
   * @return The truncated string
   */
  public static String truncateValue(String value) {
    if (Strings.isNullOrEmpty(value) || value.length() < MAX_DIAGNOSTIC_VALUE_LENGTH) {
      return value;
    }
    return value.substring(0, MAX_DIAGNOSTIC_VALUE_LENGTH) + "*";
  }

  private Instrumentation() {}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy