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: 0.2.8
Show newest version
/*
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * 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 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.ImmutableSet;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
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.LogManager;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

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

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

* * * * * * * * *
Java LevelStackdriver 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 Stackdriver Logging {@link LogEntry}. * You can read entry labels using {@link LogEntry#labels()}. To use logging levels that correspond * to Stackdriver 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.flushSize} specifies the maximum size of the * log buffer. Once reached, logs are transmitted to the Stackdriver Logging service (defaults * to 1). *
  • {@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 Stackdriver Logging * service (defaults to {@link LoggingLevel#ERROR}). *
* *

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}
 * 
*/ public class LoggingHandler extends Handler { private static final String HANDLERS_PROPERTY = "handlers"; private static final String ROOT_LOGGER_NAME = ""; private static final String[] NO_HANDLERS = new String[0]; private static final Set EXCLUDED_LOGGERS = ImmutableSet.of("io.grpc", "io.netty", "com.google.api.client.http", "sun.net.www.protocol.http"); private final LoggingOptions options; private final List buffer = new LinkedList<>(); private final WriteOption[] writeOptions; private Logging logging; private Level flushLevel; private long flushSize; /** * Creates an handler that publishes messages to Stackdriver Logging. */ public LoggingHandler() { this(null, null, null); } /** * Creates a handler that publishes messages to Stackdriver 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 Stackdriver Logging. * * @param log the name of the log to which log entries are written * @param options options for the Stackdriver Logging service */ public LoggingHandler(String log, LoggingOptions options) { this(log, options, null); } /** * Creates a handler that publishes messages to Stackdriver Logging. * * @param log the name of the log to which log entries are written * @param options options for the Stackdriver Logging service * @param monitoredResource the monitored resource to which log entries refer */ public LoggingHandler(String log, LoggingOptions options, MonitoredResource monitoredResource) { LogConfigHelper helper = new LogConfigHelper(); String className = getClass().getName(); this.options = options != null ? options : LoggingOptions.defaultInstance(); this.flushLevel = helper.getLevelProperty(className + ".flushLevel", LoggingLevel.ERROR); this.flushSize = helper.getLongProperty(className + ".flushSize", 1L); setLevel(helper.getLevelProperty(className + ".level", Level.INFO)); setFilter(helper.getFilterProperty(className + ".filter", null)); setFormatter(helper.getFormatterProperty(className + ".formatter", new SimpleFormatter())); String logName = firstNonNull(log, helper.getProperty(className + ".log", "java.log")); MonitoredResource resource = firstNonNull(monitoredResource, defaultResource()); writeOptions = new WriteOption[]{WriteOption.logName(logName), WriteOption.resource(resource)}; maskLoggers(); } private static void maskLoggers() { for (String loggerName : EXCLUDED_LOGGERS) { Logger logger = Logger.getLogger(loggerName); // We remove the Clould Logging handler if it has been registered for a logger that should be // masked List loggingHandlers = getLoggingHandlers(logger); for (LoggingHandler loggingHandler : loggingHandlers) { logger.removeHandler(loggingHandler); } // We mask ancestors if they have a Stackdriver Logging Handler registered Logger currentLogger = logger; Logger ancestor = currentLogger.getParent(); boolean masked = false; while (ancestor != null && !masked) { if (hasLoggingHandler(ancestor)) { currentLogger.setUseParentHandlers(false); masked = true; } currentLogger = ancestor; ancestor = ancestor.getParent(); } } } private static List getLoggingHandlers(Logger logger) { ImmutableList.Builder builder = ImmutableList.builder(); for (Handler handler : logger.getHandlers()) { if (handler instanceof LoggingHandler) { builder.add((LoggingHandler) handler); } } return builder.build(); } private static boolean hasLoggingHandler(Logger logger) { // look for Stackdriver Logging handler registered with addHandler() for (Handler handler : logger.getHandlers()) { if (handler instanceof LoggingHandler) { return true; } } // look for Stackdriver Logging handler registered via logging.properties String loggerName = logger.getName(); String propertyName = loggerName.equals(ROOT_LOGGER_NAME) ? HANDLERS_PROPERTY : loggerName + "." + HANDLERS_PROPERTY; String handlersProperty = LogManager.getLogManager().getProperty(propertyName); String[] handlers = handlersProperty != null ? handlersProperty.split(",") : NO_HANDLERS; for (String handlerName : handlers) { if (handlerName.contains(LoggingHandler.class.getPackage().getName())) { return true; } } return false; } private MonitoredResource defaultResource() { return MonitoredResource.of("global", ImmutableMap.of("project_id", options.projectId())); } private static class LogConfigHelper { private final LogManager manager = LogManager.getLogManager(); String getProperty(String name, String defaultValue) { return firstNonNull(manager.getProperty(name), defaultValue); } long getLongProperty(String name, long defaultValue) { try { return Long.parseLong(manager.getProperty(name)); } catch (NumberFormatException ex) { // If the level does not exist we fall back to default value } return defaultValue; } Level getLevelProperty(String name, Level defaultValue) { String stringLevel = manager.getProperty(name); if (stringLevel == null) { return defaultValue; } try { return Level.parse(stringLevel); } catch (IllegalArgumentException ex) { // If the level does not exist we fall back to default value } return defaultValue; } Filter getFilterProperty(String name, Filter defaultValue) { String stringFilter = manager.getProperty(name); try { if (stringFilter != null) { Class clz = ClassLoader.getSystemClassLoader().loadClass(stringFilter); return (Filter) clz.newInstance(); } } catch (Exception ex) { // If we cannot create the filter we fall back to default value } return defaultValue; } Formatter getFormatterProperty(String name, Formatter defaultValue) { String stringFilter = manager.getProperty(name); try { if (stringFilter != null) { Class clz = ClassLoader.getSystemClassLoader().loadClass(stringFilter); return (Formatter) clz.newInstance(); } } catch (Exception ex) { // If we cannot create the filter we fall back to default value } return defaultValue; } } /** * Returns an instance of the logging service. */ Logging logging() { if (logging == null) { logging = options.service(); } return logging; } @Override public synchronized void publish(LogRecord record) { // check that the log record should be logged if (!isLoggable(record)) { return; } LogEntry entry = entryFor(record); if (entry != null) { buffer.add(entry); } if (buffer.size() >= flushSize || record.getLevel().intValue() >= flushLevel.intValue()) { flush(); } } private LogEntry entryFor(LogRecord record) { String payload; try { payload = getFormatter().format(record); } catch (Exception ex) { // Formatting can fail but we should not throw an exception, we report the error instead reportError(null, ex, ErrorManager.FORMAT_FAILURE); return null; } Level level = record.getLevel(); Map labels = ImmutableMap.of( "levelName", level.getName(), "levelValue", String.valueOf(level.intValue())); return LogEntry.builder(Payload.StringPayload.of(payload)) .labels(labels) .severity(severityFor(level)) .build(); } private static Severity severityFor(Level level) { if (level instanceof LoggingLevel) { return ((LoggingLevel) level).severity(); } switch (level.intValue()) { // FINEST case 300: return Severity.DEBUG; // FINER case 400: return Severity.DEBUG; // FINE case 500: return Severity.DEBUG; // CONFIG case 700: return Severity.INFO; // INFO case 800: return Severity.INFO; // WARNING case 900: return Severity.WARNING; // SEVERE case 1000: return Severity.ERROR; default: return Severity.DEFAULT; } } /** * Writes the provided list of log entries to Stackdriver Logging. Override this method to change * how entries should be written. */ void write(List entries, WriteOption... options) { logging().write(entries, options); } @Override public synchronized void flush() { try { write(buffer, writeOptions); } catch (Exception ex) { // writing can fail but we should not throw an exception, we report the error instead reportError(null, ex, ErrorManager.FLUSH_FAILURE); } finally { buffer.clear(); } } /** * Closes the handler and the associated {@link Logging} object. */ @Override public synchronized void close() throws SecurityException { if (logging != null) { try { logging.close(); } catch (Exception ex) { // ignore } } logging = null; } /** * Sets the flush log level. When a log with this level is published, the log buffer is * transmitted to the Stackdriver Logging service, regardless of its size. If not set, * {@link LoggingLevel#ERROR} is used. */ public synchronized Level setFlushLevel(Level flushLevel) { this.flushLevel = flushLevel; return flushLevel; } /** * Sets the maximum size of the log buffer. Once the maximum size of the buffer is reached, logs * are transmitted to the Stackdriver Logging service. If not set, a log is sent to the service as * soon as published. */ public synchronized long setFlushSize(long flushSize) { this.flushSize = flushSize; return flushSize; } /** * 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); maskLoggers(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy