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

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

There is a newer version: 3.21.1
Show newest version
/*
 * Copyright 2016 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 static com.google.common.base.MoreObjects.firstNonNull;
import static java.util.Arrays.stream;

import com.google.cloud.MonitoredResource;
import com.google.cloud.logging.Logging.WriteOption;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.logging.ErrorManager;
import java.util.logging.Filter;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

/**
 * A logging handler that outputs logs generated with {@link java.util.logging.Logger} to Cloud
 * Logging.
 *
 * 

Java logging levels (see {@link java.util.logging.Level}) are mapped to the following Google * Cloud Logging severities: * *

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Mapping of Java logging level to Cloud Logging severities
Java LevelCloud Logging Severity
SEVEREERROR
WARNINGWARNING
INFOINFO
CONFIGINFO
FINEDEBUG
FINERDEBUG
FINESTDEBUG
* *

Original Java logging levels are added as labels (with {@code levelName} and {@code * levelValue} keys, respectively) to the corresponding Cloud Logging {@link LogEntry}. You can read * entry labels using {@link LogEntry#getLabels()}. To use logging levels that correspond to Cloud * Logging severities you can use {@link LoggingLevel}. * *

Configuration: By default each {@code LoggingHandler} is initialized using the * following {@code LogManager} configuration properties (that you can set in the {@code * logging.properties} file). If properties are not defined (or have invalid values) then the * specified default values are used. * *

    *
  • {@code com.google.cloud.logging.LoggingHandler.log} the log name (defaults to {@code * java.log}). *
  • {@code com.google.cloud.logging.LoggingHandler.level} specifies the default level for the * handler (defaults to {@code Level.INFO}). *
  • {@code com.google.cloud.logging.LoggingHandler.filter} specifies the name of a {@link * Filter} class to use (defaults to no filter). *
  • {@code com.google.cloud.logging.LoggingHandler.formatter} specifies the name of a {@link * Formatter} class to use (defaults to {@link SimpleFormatter}). *
  • {@code com.google.cloud.logging.LoggingHandler.flushLevel} specifies the flush log level. * When a log with this level is published, logs are transmitted to the Cloud Logging service * (defaults to {@link LoggingLevel#ERROR}). *
  • {@code com.google.cloud.logging.LoggingHandler.enhancers} specifies a comma separated list * of {@link LoggingEnhancer} classes. This handler will call each enhancer list whenever it * builds a {@link LogEntry} instance (defaults to empty list). *
  • {@code com.google.cloud.logging.LoggingHandler.resourceType} the type name to use when * creating the default {@link MonitoredResource} (defaults to auto-detected resource type, * else "global"). *
  • {@code com.google.cloud.logging.Synchronicity} the synchronicity of the write method to use * to write logs to the Cloud Logging service (defaults to {@link Synchronicity#ASYNC}). *
  • {@code com.google.cloud.logging.LoggingHandler.autoPopulateMetadata} is a boolean flag that * opts-out the population of the log entries metadata before the logs are sent to Cloud * Logging (defaults to {@code true}). *
  • {@code com.google.cloud.logging.LoggingHandler.redirectToStdout} is a boolean flag that * opts-in redirecting the output of the handler to STDOUT instead of ingesting logs to Cloud * Logging using Logging API (defaults to {@code false}). Redirecting logs can be used in * Google Cloud environments with installed logging agent to delegate log ingestions to the * agent. Redirected logs are formatted as one line Json string following the structured * logging guidelines. This flag is deprecated; use {@code * com.google.cloud.logging.LoggingHandler.logTarget} instead. *
  • {@code com.google.cloud.logging.LoggingHandler.logTarget} is an enumeration controlling log * routing (defaults to {@code CLOUD_LOGGING}). If set to STDOUT or STDERR, logs will be * printed to the corresponding stream in the Json format that can be parsed by the logging * agent. If set to CLOUD_LOGGING, logs will be sent directly to the Google Cloud Logging API. *
* *

To add a {@code LoggingHandler} to an existing {@link Logger} and be sure to avoid infinite * recursion when logging, use the {@link #addHandler(Logger, LoggingHandler)} method. Alternatively * you can add the handler via {@code logging.properties}. For example using the following line: * *

 * {@code com.example.mypackage.handlers=com.google.cloud.logging.LoggingHandler}
 * 
* * @see Structured logging */ public class LoggingHandler extends Handler { private static final String LEVEL_NAME_KEY = "levelName"; private static final String LEVEL_VALUE_KEY = "levelValue"; /** Where to send logs. */ public enum LogTarget { /** Sends logs to the Cloud Logging API. */ CLOUD_LOGGING, /** Sends JSON-formatted logs to stdout, for use with the Google Cloud logging agent. */ STDOUT, /** Sends JSON-formatted logs to stderr, for use with the Google Cloud logging agent. */ STDERR } private final List enhancers; private final LoggingOptions loggingOptions; private volatile Logging logging; // Logs with the same severity with the base could be more efficiently sent to // Cloud. // Defaults to level of the handler or Level.FINEST if the handler is set to // Level.ALL. // Currently there is no way to modify the base level, see // https://github.com/googleapis/google-cloud-java/issues/1740 . private final Level baseLevel; private volatile Level flushLevel; private volatile Boolean autoPopulateMetadata; private volatile LogTarget logTarget; private final WriteOption[] defaultWriteOptions; /** Creates an handler that publishes messages to Cloud Logging. */ public LoggingHandler() { this(null, null, null); } /** * Creates a handler that publishes messages to Cloud Logging. * * @param log the name of the log to which log entries are written */ public LoggingHandler(String log) { this(log, null, null); } /** * Creates a handler that publishes messages to Cloud Logging. * * @param log the name of the log to which log entries are written * @param options options for the Cloud Logging service */ public LoggingHandler(String log, LoggingOptions options) { this(log, options, null); } /** * Creates a handler that publishes messages to Cloud Logging. * * @param log the name of the log to which log entries are written * @param options options for the Cloud Logging service * @param monitoredResource the monitored resource to which log entries refer. If it is null then * a default resource is created based on the project ID and deployment environment. */ public LoggingHandler(String log, LoggingOptions options, MonitoredResource monitoredResource) { this(log, options, monitoredResource, null, null); } /** * Creates a handler that publishes messages to Cloud Logging. * * @param log the name of the log to which log entries are written * @param options options for the Cloud Logging service * @param monitoredResource the monitored resource to which log entries refer. If it is null then * a default resource is created based on the project ID and deployment environment. * @param enhancers List of {@link LoggingEnhancer} instances used to enhance any{@link LogEntry} * instances built by this handler. */ public LoggingHandler( String log, LoggingOptions options, MonitoredResource monitoredResource, List enhancers) { this(log, options, monitoredResource, enhancers, null); } /** * Creates a handler that publishes messages to Cloud Logging. Auto-population of the logs * metadata can be opted-out in {@code options} argument or in the configuration file. At least * one flag {@link LoggingOptions} or {@link LoggingConfig} has to be explicitly set to {@code * false} in order to opt out the metadata auto-population. * * @param log the name of the log to which log entries are written * @param options options for the Cloud Logging service * @param monitoredResource the monitored resource to which log entries refer. If it is null then * a default resource is created based on the project ID and deployment environment. * @param enhancers List of {@link LoggingEnhancer} instances used to enhance any{@link LogEntry} * instances built by this handler. * @param destination the log destination {@link LogDestinationName} (see 'logName' parameter in * https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry) */ public LoggingHandler( String log, LoggingOptions options, MonitoredResource monitoredResource, List enhancers, LogDestinationName destination) { try { loggingOptions = options != null ? options : LoggingOptions.getDefaultInstance(); LoggingConfig config = new LoggingConfig(getClass().getName()); setFilter(config.getFilter()); setFormatter(config.getFormatter()); Level level = config.getLogLevel(); setLevel(level); baseLevel = level.equals(Level.ALL) ? Level.FINEST : level; flushLevel = config.getFlushLevel(); Boolean f1 = loggingOptions.getAutoPopulateMetadata(); Boolean f2 = config.getAutoPopulateMetadata(); autoPopulateMetadata = isTrueOrNull(f1) && isTrueOrNull(f2); logTarget = config .getLogTarget() .orElse( firstNonNull(config.getRedirectToStdout(), Boolean.FALSE) ? LogTarget.STDOUT : LogTarget.CLOUD_LOGGING); String logName = log != null ? log : config.getLogName(); MonitoredResource resource = firstNonNull( monitoredResource, config.getMonitoredResource(loggingOptions.getProjectId())); List writeOptions = new ArrayList<>(); writeOptions.add(WriteOption.logName(logName)); if (resource != null) { writeOptions.add(WriteOption.resource(resource)); } writeOptions.add( WriteOption.labels( ImmutableMap.of( LEVEL_NAME_KEY, baseLevel.getName(), LEVEL_VALUE_KEY, String.valueOf(baseLevel.intValue())))); if (destination != null) { writeOptions.add(WriteOption.destination(destination)); } defaultWriteOptions = Iterables.toArray(writeOptions, WriteOption.class); logging = loggingOptions.getService(); logging.setFlushSeverity(severityFor(flushLevel)); logging.setWriteSynchronicity(config.getSynchronicity()); this.enhancers = new ArrayList<>(); List enhancersParam = firstNonNull(enhancers, firstNonNull(config.getEnhancers(), ImmutableList.of())); this.enhancers.addAll(enhancersParam); // In the following line getResourceEnhancers() never returns null (@NotNull // attribute) List loggingEnhancers = MonitoredResourceUtil.getResourceEnhancers(); this.enhancers.addAll(loggingEnhancers); } catch (RuntimeException ex) { reportError(null, ex, ErrorManager.OPEN_FAILURE); throw ex; } } @Override public void publish(LogRecord record) { // check that the log record should be logged if (!isLoggable(record)) { return; } // HACK warning: this logger doesn't work like normal loggers; the log calls are // issued // from another class instead of by itself, so it can't be configured off like // normal // loggers. We have to check the source class name instead. if ("io.netty.handler.codec.http2.Http2FrameLogger".equals(record.getSourceClassName())) { return; } LogEntry logEntry; try { logEntry = logEntryFor(record).build(); } catch (RuntimeException ex) { getErrorManager().error(null, ex, ErrorManager.FORMAT_FAILURE); return; } if (logEntry != null) { try { Iterable logEntries = logTarget == LogTarget.CLOUD_LOGGING ? ImmutableList.of(logEntry) : Instrumentation.populateInstrumentationInfo(ImmutableList.of(logEntry)).y(); if (autoPopulateMetadata) { logEntries = logging.populateMetadata( logEntries, getMonitoredResource(), "com.google.cloud.logging", "java"); } switch (logTarget) { case STDOUT: logEntries.forEach(log -> System.out.println(log.toStructuredJsonString())); break; case STDERR: logEntries.forEach(log -> System.err.println(log.toStructuredJsonString())); break; case CLOUD_LOGGING: logging.write(logEntries, defaultWriteOptions); break; } } catch (RuntimeException ex) { getErrorManager().error(null, ex, ErrorManager.WRITE_FAILURE); } } } private MonitoredResource getMonitoredResource() { Optional resourceOption = stream(defaultWriteOptions) .filter(o -> o.getOptionType() == WriteOption.OptionType.RESOURCE) .findFirst(); if (resourceOption.isPresent()) { return (MonitoredResource) resourceOption.get().getValue(); } return null; } protected LogEntry.Builder logEntryFor(LogRecord record) { String payload = getFormatter().format(record); Level level = record.getLevel(); LogEntry.Builder builder = LogEntry.newBuilder(Payload.StringPayload.of(payload)) .setTimestamp(Instant.ofEpochMilli(record.getMillis())) .setSeverity(severityFor(level)); if (!baseLevel.equals(level)) { builder .addLabel("levelName", level.getName()) .addLabel("levelValue", String.valueOf(level.intValue())); } for (LoggingEnhancer enhancer : enhancers) { enhancer.enhanceLogEntry(builder); } return builder; } @Override public void flush() { try { logging.flush(); } catch (RuntimeException ex) { getErrorManager().error(null, ex, ErrorManager.FLUSH_FAILURE); } } /** Closes the handler and the associated {@link Logging} object. */ @Override public synchronized void close() { if (logging != null) { try { logging.close(); } catch (Exception ex) { // ignore } } logging = null; } /** Get the flush log level. */ public Level getFlushLevel() { return flushLevel; } /** * Sets minimum logging level to log immediately and flush any pending writes. * * @param flushLevel minimum log level to trigger flush */ public void setFlushLevel(Level flushLevel) { this.flushLevel = flushLevel; logging.setFlushSeverity(severityFor(flushLevel)); } /** * Sets synchronicity of logging writes. By default, writes are asynchronous. * * @param synchronicity {@link Synchronicity} */ public void setSynchronicity(Synchronicity synchronicity) { logging.setWriteSynchronicity(synchronicity); } /** Get the flush log level. */ public Synchronicity getSynchronicity() { return logging.getWriteSynchronicity(); } /** Sets the metadata auto population flag. */ public void setAutoPopulateMetadata(boolean value) { this.autoPopulateMetadata = value; } /** Gets the metadata auto population flag. */ public Boolean getAutoPopulateMetadata() { return this.autoPopulateMetadata; } /** * Enable/disable redirection to STDOUT. If set to {@code true}, logs will be printed to STDOUT in * the Json format that can be parsed by the logging agent. If set to {@code false}, logs will be * ingested to Cloud Logging by calling Logging API. * *

This method is mutually exclusive with {@link #setLogTarget(LogTarget)}. * * @deprecated Use {@link #setLogTarget(LogTarget)}. */ @Deprecated public void setRedirectToStdout(boolean value) { this.logTarget = value ? LogTarget.STDOUT : LogTarget.CLOUD_LOGGING; } /* * @deprecated Use {@link #getLogTarget()}. */ @Deprecated public Boolean getRedirectToStdout() { return this.logTarget == LogTarget.STDOUT; } /** * Configure the destination for ingested logs. If set to STDOUT or STDERR, logs will be printed * to the corresponding stream in the Json format that can be parsed by the logging agent. If set * to CLOUD_LOGGING, logs will be sent directly to the Google Cloud Logging API. * *

This method is mutually exclusive with {@link #setRedirectToStdout(boolean)}. */ public void setLogTarget(LogTarget value) { this.logTarget = value; } public LogTarget getLogTarget() { return logTarget; } /** * Adds the provided {@code LoggingHandler} to {@code logger}. Use this method to register Cloud * Logging handlers instead of {@link Logger#addHandler(Handler)} to avoid infinite recursion when * logging. */ public static void addHandler(Logger logger, LoggingHandler handler) { logger.addHandler(handler); } private static Severity severityFor(Level level) { if (level instanceof LoggingLevel) { return ((LoggingLevel) level).getSeverity(); } // Choose the severity based on Level range. // The assumption is that Level values below maintain same numeric value int value = level.intValue(); if (value <= Level.FINE.intValue()) { return Severity.DEBUG; } else if (value <= Level.INFO.intValue()) { return Severity.INFO; } else if (value <= Level.WARNING.intValue()) { return Severity.WARNING; } else if (value <= Level.SEVERE.intValue()) { return Severity.ERROR; } else if (value == Level.OFF.intValue()) { return Severity.NONE; } return Severity.DEFAULT; } private static boolean isTrueOrNull(Boolean b) { return b == null || b.equals(Boolean.TRUE); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy