001/* 002 * Copyright 2012-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.boot.actuate.autoconfigure.metrics; 018 019import java.util.Arrays; 020import java.util.Map; 021import java.util.Objects; 022import java.util.function.Supplier; 023import java.util.stream.Collectors; 024 025import io.micrometer.core.instrument.Meter; 026import io.micrometer.core.instrument.Meter.Id; 027import io.micrometer.core.instrument.Tag; 028import io.micrometer.core.instrument.Tags; 029import io.micrometer.core.instrument.config.MeterFilter; 030import io.micrometer.core.instrument.config.MeterFilterReply; 031import io.micrometer.core.instrument.distribution.DistributionStatisticConfig; 032 033import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties.Distribution; 034import org.springframework.util.Assert; 035import org.springframework.util.StringUtils; 036 037/** 038 * {@link MeterFilter} to apply settings from {@link MetricsProperties}. 039 * 040 * @author Jon Schneider 041 * @author Phillip Webb 042 * @author Stephane Nicoll 043 * @author Artsiom Yudovin 044 * @author Alexander Abramov 045 * @since 2.0.0 046 */ 047public class PropertiesMeterFilter implements MeterFilter { 048 049 private final MetricsProperties properties; 050 051 private final MeterFilter mapFilter; 052 053 public PropertiesMeterFilter(MetricsProperties properties) { 054 Assert.notNull(properties, "Properties must not be null"); 055 this.properties = properties; 056 this.mapFilter = createMapFilter(properties.getTags()); 057 } 058 059 private static MeterFilter createMapFilter(Map<String, String> tags) { 060 if (tags.isEmpty()) { 061 return new MeterFilter() { 062 }; 063 } 064 Tags commonTags = Tags.of(tags.entrySet().stream() 065 .map((entry) -> Tag.of(entry.getKey(), entry.getValue())) 066 .collect(Collectors.toList())); 067 return MeterFilter.commonTags(commonTags); 068 } 069 070 @Override 071 public MeterFilterReply accept(Meter.Id id) { 072 boolean enabled = lookupWithFallbackToAll(this.properties.getEnable(), id, true); 073 return enabled ? MeterFilterReply.NEUTRAL : MeterFilterReply.DENY; 074 } 075 076 @Override 077 public Id map(Id id) { 078 return this.mapFilter.map(id); 079 } 080 081 @Override 082 public DistributionStatisticConfig configure(Meter.Id id, 083 DistributionStatisticConfig config) { 084 Distribution distribution = this.properties.getDistribution(); 085 return DistributionStatisticConfig.builder() 086 .percentilesHistogram(lookupWithFallbackToAll( 087 distribution.getPercentilesHistogram(), id, null)) 088 .percentiles( 089 lookupWithFallbackToAll(distribution.getPercentiles(), id, null)) 090 .sla(convertSla(id.getType(), lookup(distribution.getSla(), id, null))) 091 .minimumExpectedValue(convertMeterValue(id.getType(), 092 lookup(distribution.getMinimumExpectedValue(), id, null))) 093 .maximumExpectedValue(convertMeterValue(id.getType(), 094 lookup(distribution.getMaximumExpectedValue(), id, null))) 095 .build().merge(config); 096 } 097 098 private long[] convertSla(Meter.Type meterType, ServiceLevelAgreementBoundary[] sla) { 099 if (sla == null) { 100 return null; 101 } 102 long[] converted = Arrays.stream(sla) 103 .map((candidate) -> candidate.getValue(meterType)) 104 .filter(Objects::nonNull).mapToLong(Long::longValue).toArray(); 105 return (converted.length != 0) ? converted : null; 106 } 107 108 private Long convertMeterValue(Meter.Type meterType, String value) { 109 return (value != null) ? MeterValue.valueOf(value).getValue(meterType) : null; 110 } 111 112 private <T> T lookup(Map<String, T> values, Id id, T defaultValue) { 113 if (values.isEmpty()) { 114 return defaultValue; 115 } 116 return doLookup(values, id, () -> defaultValue); 117 } 118 119 private <T> T lookupWithFallbackToAll(Map<String, T> values, Id id, T defaultValue) { 120 if (values.isEmpty()) { 121 return defaultValue; 122 } 123 return doLookup(values, id, () -> values.getOrDefault("all", defaultValue)); 124 } 125 126 private <T> T doLookup(Map<String, T> values, Id id, Supplier<T> defaultValue) { 127 String name = id.getName(); 128 while (StringUtils.hasLength(name)) { 129 T result = values.get(name); 130 if (result != null) { 131 return result; 132 } 133 int lastDot = name.lastIndexOf('.'); 134 name = (lastDot != -1) ? name.substring(0, lastDot) : ""; 135 } 136 137 return defaultValue.get(); 138 } 139 140}