com.google.common.flogger.context.LogLevelMap Maven / Gradle / Ivy
/*
* Copyright (C) 2019 The Flogger Authors.
*
* 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.common.flogger.context;
import static com.google.common.flogger.util.Checks.checkNotNull;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
/**
* A hierarchical mapping from logger name to {@link Level} used to override the configured log
* level during debugging. This class is designed to allow efficient (i.e. zero-allocation)
* resolution of the log level for a given logger.
*
* This class is immutable and thread safe.
*/
public final class LogLevelMap {
/**
* Builder for log level map which uses type safe class/package keys (but requires that they be
* present in the JVM at the time the map is created). To set up a {@code LogLevelMap} with only
* class/package names, use {@link LogLevelMap#create(java.util.Map,Level)} or {@link
* LogLevelMap#create(java.util.Map)}.
*/
public static final class Builder {
private final Map map = new HashMap();
private Level defaultLevel = Level.OFF;
private Builder() {}
private void put(String name, Level level) {
if (map.put(name, level) != null) {
throw new IllegalArgumentException("duplicate entry for class/package: " + name);
}
}
/** Adds the given classes at the specified log level. */
@CanIgnoreReturnValue
public Builder add(Level level, Class>... classes) {
for (Class> cls : classes) {
put(cls.getName(), level);
}
return this;
}
/** Adds the given packages at the specified log level. */
@CanIgnoreReturnValue
public Builder add(Level level, Package... packages) {
for (Package pkg : packages) {
put(pkg.getName(), level);
}
return this;
}
/** Sets the default log level (use {@link Level#OFF} to disable. */
@CanIgnoreReturnValue
public Builder setDefault(Level level) {
checkNotNull(defaultLevel, "default log level must not be null");
this.defaultLevel = level;
return this;
}
public LogLevelMap build() {
return LogLevelMap.create(map, defaultLevel);
}
}
/** Returns a new builder for constructing a {@code LogLevelMap}. */
public static Builder builder() {
return new Builder();
}
/**
* Returns an empty {@code LogLevelMap} with a single default level which will apply to all
* loggers.
*/
public static LogLevelMap create(Level level) {
return create(Collections.emptyMap(), level);
}
/**
* Returns a {@code LogLevelMap} whose entries correspond to the given map, and with the default
* value of {@code Level.OFF}. The keys of the map must all be valid dot-separated logger names,
* and the values cannot be {@code null}.
*/
public static LogLevelMap create(Map map) {
return create(map, Level.OFF);
}
/**
* Returns a {@code LogLevelMap} whose entries correspond to the given map. The keys of the map
* must all be valid dot-separated logger names, and neither the values, nor the default value,
* can be {@code null}.
*/
public static LogLevelMap create(Map map, Level defaultLevel) {
checkNotNull(defaultLevel, "default log level must not be null");
for (Map.Entry e : map.entrySet()) {
String name = e.getKey();
if (name.startsWith(".") || name.endsWith(".") || name.contains("..")) {
throw new IllegalArgumentException("invalid logger name: " + name);
}
if (e.getValue() == null) {
throw new IllegalArgumentException("log levels must not be null; logger=" + name);
}
}
return new LogLevelMap(map, defaultLevel);
}
private final SegmentTrie trie;
private LogLevelMap(Map map, Level defaultLevel) {
this.trie = SegmentTrie.create(map, '.', defaultLevel);
}
/**
* Returns the log level for the specified logger, matching the {@code loggerName} to an entry in
* the map, or the nearest parent in the naming hierarchy. If the given {@code loggerName} is
* invalid, the default value is returned.
*/
public Level getLevel(String loggerName) {
return trie.find(loggerName);
}
/**
* Returns the union of this map with the given map. Logging is enabled in the merged map
* if-and-only-if it was enabled in one of the maps it was created from.
*/
public LogLevelMap merge(LogLevelMap other) {
Map thisMap = trie.getEntryMap();
Map otherMap = other.trie.getEntryMap();
// HashMap/HashSet is fine because iteration order is unimportant for creating a SegmentTrie.
Map mergedMap = new HashMap();
Set allKeys = new HashSet(thisMap.keySet());
allKeys.addAll(otherMap.keySet());
for (String key : allKeys) {
if (!otherMap.containsKey(key)) {
mergedMap.put(key, thisMap.get(key));
} else if (!thisMap.containsKey(key)) {
mergedMap.put(key, otherMap.get(key));
} else {
mergedMap.put(key, min(thisMap.get(key), otherMap.get(key)));
}
}
Level defaultLevel = min(trie.getDefaultValue(), other.trie.getDefaultValue());
return create(mergedMap, defaultLevel);
}
private static Level min(Level a, Level b) {
return a.intValue() <= b.intValue() ? a : b;
}
}