告别恼人的NPE,日志分片,畅快的将日志信息埋入ES的设计方案

    技术2022-07-29  73

    关于CAT和ES的相关知识本文不做具体展开,各位自行去查阅资料了解吧。

    1、问题场景

    待埋点的信息为一个复杂对象中的比较深层次的字段,不希望做太多的非空判断处理的主流程为一条日志,主流程中会多次调用第三方服务,需要将调用信息埋点生成子的日志,并进行主流程日志和子的调用日志的串联。日志埋点通过cat写入es。支持同步or异步的埋点。建立一套规范的错误码的体系,并能够自动防重(多人协同开发时候防止另一个人也建立了一个相同的错误码),并且能够自动生成错误码的映射关系提供给产品or业务,便于他们也便于开发自己定期的去ES的kibanna看板上关注系统运行的健康度和错误情况。

    2、解决思路

    借助java8之后的声明式编程的新特性来消弭PNE问题,借助日志分片来进行多条日志的埋点,同时参考行业内做的比较好的错误码的分类方式建立一套错误码机制。方案需要进行高度封装,充血方式提供全方位的能力。

    3、代码实战

    错误码规范(错误码的枚举值是根据各自业务情况自定义)

    /** * Abstract metric key type. This should be implement by {@link Enum} */ public interface MetricItem { /** * For {@link Enum} type * * @return {@link String key} */ String getKey(); }

     

     

    /** * Metric {@code index} fields */ public interface MetricIndex extends MetricItem { } /** * Es index tags info. * * @Author Jason Gao * @Date: 2020/5/25 */ public enum MetricIndexes implements MetricIndex { TRACE_ID("trace-id"), ERROR_CODE("error-code"), PROCESS_STATUS("process-status"), EXECUTE_TIME("execute-time"), HTTP_STATUS_CODE("http-status-code"), API_NAME("api-name"), ORDER_KEY("order-key"), ORDER_ID("orderId"), UUID("uuid"), CHANNEL("channel"), SUB_CHANNEL("sub-channel"), THIRD_SERVICE_NAME("third-service-name"), THIRD_SERVICE_ACK_CODE("third-service-ack-code"), /** main es info or sub es info. */ MASTER_OR_SLAVE("master-or-slave"), EXCEPTION_TYPE("exception-type"), ; private String key; MetricIndexes(String key) { this.key = key; } public String getKey() { return key; } } /** * Metric {@code store} fields */ public interface MetricStore extends MetricItem { } /** * Es story tags info. * * @Author Jason Gao * @Date: 2020/5/25 */ public enum MetricStories implements MetricStore { ERROR_MESSAGE("error-message"), FULL_DATA("full-data"), CHOSE_CONFIG("chosen-config"), SEND_HTTP_METHOD("send-http-method"), SEND_HEADER("send-header"), SEND_URL("send-url"), SEND_QUERY_STRING("send-query-String"), SEND_BODY("send-body"), RESPONSE_CONTENT("response-content"), THIRD_SERVICE_ACK_VALUE("third-service-ack-value"), THIRD_SERVICE_REQUEST("third-service-request"), THIRD_SERVICE_RESPONSE("third-service-response"), THIRD_SERVICE_EXCEPTION("third-service-exception"), EXCEPTION("exception"), ; private String key; MetricStories(String key) { this.key = key; } public String getKey() { return key; } }

    错误码机制:

    import java.util.Arrays; import java.util.Comparator; import java.util.HashSet; import java.util.Set; import java.util.function.Consumer; /** * The interface {@code ErrorCode} context. */ public interface ErrorCodeContext { /** * Enum name. * * @return the name */ String name(); /** * Gets code. * * @return the code */ String getCode(); /** * Gets message. * * @return the message */ String getMessage(); /** * Generate the error code. */ interface ErrorCodeGenerator { // -- CODE PREFIX /** * The prefix of {@code order} error. */ String ORDER_ERROR_PREFIX = "0"; /** * The prefix of {@code config} error. */ String CONFIG_ERROR_PREFIX = "1"; /** * The prefix of {@code business} error. */ String BUSINESS_ERROR_PREFIX = "2"; /** * The prefix of {@code repository service}, Such as DB、Redis or ES. */ String REPOSITORY_ERROR_PREFIX = "3"; /** * The prefix of {@code dependent service}, Such as SOA or Third api. */ String DEPENDENT_SERVICE_ERROR_PREFIX = "4"; /** * The prefix of {@code system} error. */ String SYSTEM_ERROR_PREFIX = "5"; /** * The System Codes. * * @apiNote Set a unique code for your application. */ String applicationErrorCode(); /** * Creates an error code. * * @param prefix the prefix of error code. * @param code the sub-code. * @return the error code. */ default String createErrorCode(String prefix, String code) { return prefix + applicationErrorCode() + code; } /** * Creates an error code because of invalid parameter. * * @param code the sub-code. * @return 1[]xxx */ default String createParameterErrorCode(String code) { return createErrorCode(ORDER_ERROR_PREFIX, code); } /** * Creates an error code because of invalid config. * * @param code the sub-code. * @return 1[]xxx */ default String createConfigErrorCode(String code) { return createErrorCode(CONFIG_ERROR_PREFIX, code); } /** * Creates an error code because of failed to call the business process. * * @param code the sub-code. * @return 2[]xxx */ default String createBusinessProcessErrorCode(String code) { return createErrorCode(BUSINESS_ERROR_PREFIX, code); } /** * Creates an error code because of invalid repository operation. Such as the operation of DB, Redis or ES. * * @param code the sub-code. * @return 3[]xxx */ default String createRepositoryErrorCode(String code) { return createErrorCode(REPOSITORY_ERROR_PREFIX, code); } /** * Creates an error code because of failed to call the dependent services. * * @param code the sub-code. * @return 4[]xxx */ default String createDependentServiceErrorCode(String code) { return createErrorCode(DEPENDENT_SERVICE_ERROR_PREFIX, code); } /** * Creates an error code because of unknown system exception. * * @param code the sub-code. * @return 5[]xxx */ default String createSystemErrorCode(String code) { return createErrorCode(SYSTEM_ERROR_PREFIX, code); } } /** * The {@code ErrorCode} expand functionality manager. * * @param <Context> the specific context */ interface ErrorCodeManager<Context extends ErrorCodeContext> { // -- CHECK DUPLICATED CODE /** * Assert {@code ErrorCode} not duplicate * * @param errorContexts the {@code ErrorCode} contexts */ static void assertErrorCodesNoDuplicate(ErrorCodeContext[] errorContexts) { Set<String> duplicated = new HashSet<>(errorContexts.length << 1); for (ErrorCodeContext value : errorContexts) { if (duplicated.contains(value.getCode())) { throw new IllegalArgumentException("ErrorCodes can't be duplicated code"); } else { duplicated.add(value.getCode()); } } duplicated.clear(); } /** * Show {@code ErrorCode} information like: * <pre> * PARAMETER_ERROR 10000 Invalid parameter error! * </pre> * * @param errorCodeContext the {@code ErrorCode} context item */ void showErrorCodeItem(Context errorCodeContext); /** * Show {@code ErrorCode} information like: * <pre> * PARAMETER_ERROR 10000 Invalid parameter error! * </pre> * * @param errorCodeContexts the {@code ErrorCode} contexts */ default void showErrorCodes(Context[] errorCodeContexts) { showErrorCodes(errorCodeContexts, this::showErrorCodeItem); } /** * Show ErrorCode. * * @param errorCodeContexts the {@code ErrorCode} contexts * @param showingMsg the showing msg */ default void showErrorCodes(Context[] errorCodeContexts, Consumer<Context> showingMsg) { Set<String> prefixSet = new HashSet<>(8); Arrays.stream(errorCodeContexts) .sorted(Comparator.comparing(ErrorCodeContext::getCode)) .peek(value -> { String prefix = value.getCode().substring(0, 1); if (!prefixSet.contains(prefix)) { prefixSet.add(prefix); System.out.println(); } }) .forEach(value -> { showingMsg.accept(value); System.out.println(); }); } } } /** * The s2s error code context. */ public interface S2SErrorCodeContext extends ErrorCodeContext { /** * The S2SErrorCode generator. */ ErrorCodeGenerator GENERATOR = new ErrorCodeGenerator(); /** * The S2SErrorCode manager. */ ErrorCodeManager MANAGER = new ErrorCodeManager(); /** * The s2s error code generator. */ class ErrorCodeGenerator implements ErrorCodeContext.ErrorCodeGenerator { /** * The S2S-Application system code. * * @apiNote Set a unique code for your application */ private static final String DEMO_APPLICATION_CODE = "S"; @Override public String applicationErrorCode() { return DEMO_APPLICATION_CODE; } } /** * The Ctrip error code manager. */ class ErrorCodeManager implements ErrorCodeContext.ErrorCodeManager<S2SErrorCodeContext> { @Override public void showErrorCodeItem(S2SErrorCodeContext errorCodeContext) { System.out.printf("ps %5s %s", errorCodeContext.name(), errorCodeContext.getCode(), errorCodeContext.getMessage()); } } } /** * Application error info context: * [20000] SUCCESS * [1Dxxx] {@code parameter} error. Define your parameter check exception * [2Dxxx] {@code business} error. Define your business logic exception * [3Dxxx] {@code repository service}. Define repository operation exception * [4Dxxx] {@code dependent service}. Define dependency service exception * [5Dxxx] {@code system} error. Define application system exception */ public enum S2SErrorCode implements S2SErrorCodeContext { /** * The successful error code. */ SUCCESS("20000", "Success"), /*-------------------------------------------Order error as below---------------------------------------**/ /** * Invalid order error, the code starts with 0. */ ORDER_EMPTY_ERROR(GENERATOR.createParameterErrorCode("000"), "Empty order data error!"), ORDER_AID_ERROR(GENERATOR.createParameterErrorCode("001"), "Invalid order aid error!"), ORDER_ORDER_STATUS_ERROR(GENERATOR.createParameterErrorCode("002"), "Invalid order orderStatus error!"), /*-------------------------------------------Config error as below---------------------------------------**/ /** * Invalid config info error, the code starts with 0. */ CONFIG_DATASOURCE_FIELD_ERROR(GENERATOR.createConfigErrorCode("000"), "Invalid field about dataSource error!"), CONFIG_FILTER_RELATION_ERROR(GENERATOR.createConfigErrorCode("001"), "Invalid orderStatusId error!"), CONFIG_MATCH_FILTER_ERROR(GENERATOR.createConfigErrorCode("002"), "Can not match filter config error!"), CONFIG_FORMAT_FIELD_ERROR(GENERATOR.createConfigErrorCode("003"), "Invalid formatKey field error!"), CONFIG_MATCH_HTTP_METHOD_ERROR(GENERATOR.createConfigErrorCode("004"), "Can not match http method config error!"), CONFIG_URL_FIELD_ERROR(GENERATOR.createConfigErrorCode("005"), "Invalid url field error!"), CONFIG_CONTENT_TYPE_FIELD_ERROR(GENERATOR.createConfigErrorCode("006"), "Invalid content type field error!"), CONFIG_SEND_VALUE_FIELD_ERROR(GENERATOR.createConfigErrorCode("007"), "Invalid send value field error!"), /*-------------------------------------------Business error as below---------------------------------------**/ /** * Basic business error, the code starts with 0. */ BUSINESS_ERROR( GENERATOR.createBusinessProcessErrorCode("000"), "Business error!"), BUSINESS_REPLACE_URL_PARAM_ERROR( GENERATOR.createBusinessProcessErrorCode("001"), "Replace params in url error!"), BUSINESS_REPLACE_HEADER_PARAM_ERROR( GENERATOR.createBusinessProcessErrorCode("002"), "Replace params in header error!"), BUSINESS_REPLACE_BODY_PARAM_ERROR( GENERATOR.createBusinessProcessErrorCode("003"), "Replace params in body error!"), /*-------------------------------------------Repository error as below---------------------------------------**/ /** * Basic repository error, the code starts with 0. */ REPOSITORY_ERROR( GENERATOR.createRepositoryErrorCode("000"), "Repository error!"), /*-------------------------------------------Dependent service error as below---------------------------------------**/ /** * Basic dependent service error, the code starts with 0. */ DEPENDENT_SERVICE_ERROR( GENERATOR.createDependentServiceErrorCode("000"), "Failed to call the dependent service!"), PARTNER_INTERFACE_SEND_ERROR( GENERATOR.createDependentServiceErrorCode("001"), "Failed to send info to partner by partner interface!"), /*-------------------------------------------System error as below---------------------------------------**/ /** * Basic system error, the code starts with 0. */ SYSTEM_ERROR( GENERATOR.createSystemErrorCode("000"), "System error!"), SYSTEM_CONTEXT_PARAM_MAP_ERROR( GENERATOR.createSystemErrorCode("001"), "context param map error!"), ; // -- Encapsulation private String code; private String message; S2SErrorCode(String code, String message) { this.code = code; this.message = message; } @Override public String getCode() { return this.code; } @Override public String getMessage() { return this.message; } static { ErrorCodeContext.ErrorCodeManager.assertErrorCodesNoDuplicate(S2SErrorCode.values()); } /** * Show error codes. */ public static void showErrorCodes() { MANAGER.showErrorCodes(S2SErrorCode.values()); } /** * Show error codes mapping info to developer or product manager or business manager. */ public static void main(String[] args) { showErrorCodes(); } }

    埋点工具类的实现

    import com.ctrip.ibu.s2s.sender.service.common.metrics.index.MetricIndex; import com.ctrip.ibu.s2s.sender.service.common.metrics.store.MetricStore; import com.ctrip.ibu.s2s.sender.service.common.utils.CopyUtil; import com.ctrip.ibu.s2s.sender.service.config.RemoteConfig; import io.vavr.API; import io.vavr.CheckedRunnable; import io.vavr.control.Option; import lombok.extern.slf4j.Slf4j; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.function.Consumer; import java.util.function.Supplier; import static io.vavr.API.*; import static io.vavr.Predicates.instanceOf; @Slf4j public final class Metrics { /** * Recommend initial map size */ private static final int RECOMMEND_INDEX_SIZE = 32; private static final int RECOMMEND_STORE_SIZE = 8; /** * Async metrics switch */ private static boolean asyncLogging = false; /** * Async metrics executor */ private static volatile ExecutorService executor; /** * Thread local cache params map */ private static final ThreadLocal<Map<String, String>> INDEX_TAG = ThreadLocal.withInitial(HashMap::new); private static final ThreadLocal<Map<String, String>> STORED_TAG = ThreadLocal.withInitial(HashMap::new); /** * Thread local cache multi params map */ private static final ThreadLocal<Map<String, Map<String, String>>> KEY_INDEX_TAG = ThreadLocal.withInitial(HashMap::new); private static final ThreadLocal<Map<String, Map<String, String>>> KEY_STORED_TAG = ThreadLocal.withInitial(HashMap::new); /** * Metrics configurator */ private static MetricConfiguration configuration; /** * Remote config info from qconfig. */ private static volatile RemoteConfig remoteConfig; private static void initSpringContextConfig() { if (configuration == null) { synchronized (Metrics.class) { if (configuration == null) { // load metrics configuration from spring context try { configuration = SpringContextConfigurator.getBean(MetricConfiguration.class); remoteConfig = SpringContextConfigurator.getBean(RemoteConfig.class); asyncLogging = remoteConfig.getAsyncEsLog().isAsyncLogging(); if (asyncLogging && executor == null) { executor = Executors.newFixedThreadPool(2); } } catch (Exception e) { log.error("Metrics init spring context configuration failure.", e); } } } } } @Important(important = "The only way to get configuration") private static MetricConfiguration getConfiguration() { Metrics.initSpringContextConfig(); return configuration; } /** * Metric with runnable lambda * * @param runnable the runnable */ public static void metric(CheckedRunnable runnable) { call(runnable); } /** * Metric runnable with switch from configuration */ private static void call(CheckedRunnable runnable) { runChecked(runnable); } private static void runChecked(CheckedRunnable runnable) { try { runnable.run(); } catch (Throwable throwable) { log.warn("Metrics runnable exception", throwable); } } // -- Metric Index /** * Index. * * @param metric the metric * @param value the value */ public static void index(MetricIndex metric, String value) { if (metric != null) { INDEX_TAG.get().put(metric.getKey(), value); } } /** * Index. * * @param metric the metric * @param value the value */ public static void index(MetricIndex metric, Object value) { if (metric != null && value != null) { INDEX_TAG.get().put(metric.getKey(), value.toString()); } } /** * Index. * * @param key the key * @param metric the metric * @param value the value */ public static void index(String key, MetricIndex metric, Object value) { if (metric != null && value != null) { final Map<String, String> keyMap = KEY_INDEX_TAG.get().get(key); if (keyMap != null) { keyMap.put(metric.getKey(), value.toString()); } } } // -- Metric Store /** * Store. * * @param metric the metric * @param value the value */ public static void store(MetricStore metric, String value) { if (metric != null) { STORED_TAG.get().put(metric.getKey(), value); } } /** * Store. * * @param metric the metric * @param value the value */ public static void store(MetricStore metric, Object value) { if (metric != null && value != null) { STORED_TAG.get().put(metric.getKey(), value.toString()); } } /** * Store. * * @param key the key * @param metric the metric * @param value the value */ public static void store(String key, MetricStore metric, Object value) { if (metric != null && value != null) { final Map<String, String> keyMap = KEY_STORED_TAG.get().get(key); if (keyMap != null) { keyMap.put(metric.getKey(), value.toString()); } } } // -- Metric Exception /** * Exception. * * @param e the e */ public static void exception(Throwable e) { if (e != null) { getConfiguration().metricException().apply(e, INDEX_TAG.get(), STORED_TAG.get()); } } /** * Exception. * * @param key the key * @param e the e */ public static void exception(String key, Throwable e) { if (e != null) { final Map<String, String> keyMap1 = KEY_INDEX_TAG.get().get(key); final Map<String, String> keyMap2 = KEY_STORED_TAG.get().get(key); if (keyMap1 != null && keyMap2 != null) { getConfiguration().metricException().apply(e, keyMap1, keyMap2); } } } // -- Finally /** * Log tags according to the configuration * {@link MetricConfiguration#metricFinally()} */ public static void build() { try { if (asyncLogging) { Map<String, String> cloneIndexMap = new HashMap<>(); CopyUtil.deepCopyMap(INDEX_TAG.get(), cloneIndexMap); Map<String, String> cloneStoredMap = new HashMap<>(); CopyUtil.deepCopyMap(STORED_TAG.get(), cloneStoredMap); executor.submit(() -> getConfiguration().metricFinally().apply(getConfiguration().topic(), cloneIndexMap, cloneStoredMap) ); } else { getConfiguration().metricFinally().apply(getConfiguration().topic(), INDEX_TAG.get(), STORED_TAG.get()); } } finally { remove(); } } /** * Log tags according to the configuration * {@link MetricConfiguration#metricFinally()} * * @param key the key */ public static void build(String key) { try { if (asyncLogging) { Map<String, String> cloneKeyIndexMap = new HashMap<>(); CopyUtil.deepCopyMap(KEY_INDEX_TAG.get().get(key), cloneKeyIndexMap); Map<String, String> cloneKeyStoredMap = new HashMap<>(); CopyUtil.deepCopyMap(KEY_STORED_TAG.get().get(key), cloneKeyStoredMap); executor.submit(() -> getConfiguration().metricFinally().apply(getConfiguration().topic(), cloneKeyIndexMap, cloneKeyStoredMap) ); } else { getConfiguration().metricFinally().apply(getConfiguration().topic(), KEY_INDEX_TAG.get().get(key), KEY_STORED_TAG.get().get(key)); } } finally { remove(key); } } // ---------------------------------------- Local scope key --------------------------------------------------- /** * Create a new params map at thread local which marked by {@code key} * * @return the string */ public static String local() { final String key = String.valueOf(System.nanoTime()); KEY_INDEX_TAG.get().put(key, new HashMap<>(RECOMMEND_INDEX_SIZE)); KEY_STORED_TAG.get().put(key, new HashMap<>(RECOMMEND_STORE_SIZE)); return key; } // -- Entry point with lambda "() -> { scope }" /** * Entry point of the tags API. * * @param key thread local multi metric key * @return a new {@code Tags} instance */ public static Tags Log(String key) { Metrics.initSpringContextConfig(); return new Tags(Option.of(key)); } /** * Entry point of the tags thread local API. * * @return a new {@code Tags} instance */ public static Tags Log() { Metrics.initSpringContextConfig(); return new Tags(Option.none()); } // -- Entry point with "$" in the lambda scope /** * Placeholder which used in {@code Log} area * * @param pattern the pattern * @param value the value * @return the option */ public static Option<Tags.ItemCase> $(MetricItem pattern, Object value) { Objects.requireNonNull(pattern, "Metrics pattern is null"); Objects.requireNonNull(value, "Metrics value is null"); return Match(pattern).option( Case(API.$(instanceOf(MetricIndex.class)), clazz -> new Tags.IndexCase(pattern, () -> value)), Case(API.$(instanceOf(MetricStore.class)), clazz -> new Tags.StoreCase(pattern, () -> value)) ); } /** * Placeholder which used in {@code Log} area * * @param pattern the pattern * @param value the value * @return the option */ public static Option<Tags.ItemCase> $(MetricItem pattern, Supplier<?> value) { Objects.requireNonNull(pattern, "Metrics pattern is null"); Objects.requireNonNull(value, "Metrics value is null"); return Match(pattern).option( Case(API.$(instanceOf(MetricIndex.class)), clazz -> new Tags.IndexCase(pattern, value)), Case(API.$(instanceOf(MetricStore.class)), clazz -> new Tags.StoreCase(pattern, value)) ); } /** * Placeholder which used in {@code Log} area * * @param throwable the throwable * @return the option */ public static Option<Tags.ItemCase> $(Throwable throwable) { Objects.requireNonNull(throwable, "Metrics throwable is null"); return Option.of(new Tags.ExceptionCase(throwable)); } // ------------------------------------------ Function ------------------------------------------ /** * Instances are obtained via {@link Metrics#Log(String)}. */ public static final class Tags { /** * Thread local cache multi params map key */ private final Option<String> key; private Tags(Option<String> key) { this.key = key; } /** * Obtained via {@link Metrics#$(MetricItem, Supplier)} * * @param caseOps the case ops */ @SafeVarargs public final void of(Option<ItemCase>... caseOps) { for (Option<ItemCase> caseOp : caseOps) { caseOp.toJavaOptional().ifPresent(itemCase -> itemCase.accept(key)); } } /** * Obtained via {@link Metrics#$(MetricItem, Supplier)} * * @param caseOps the case ops */ @SafeVarargs public final void of(Supplier<Option<ItemCase>>... caseOps) { for (Supplier<Option<ItemCase>> caseOp : caseOps) { caseOp.get().toJavaOptional().ifPresent(itemCase -> itemCase.accept(key)); } } // ----------------------------------------------- CASES ----------------------------------------------- /** * The interface Item case. */ public interface ItemCase extends Consumer<Option<String>> { } /** * Metric {@code index} */ public static final class IndexCase implements ItemCase { private final MetricIndex pattern; private final Supplier<?> value; private IndexCase(MetricItem pattern, Supplier<?> value) { this.pattern = (MetricIndex) pattern; this.value = value; } @Override public void accept(Option<String> key) { Metrics.metric(() -> key.map(_key -> run(() -> index(_key, pattern, value.get())) ).orElse(() -> { index(pattern, value.get()); return null; })); } } /** * Metric {@code store} */ public static final class StoreCase implements ItemCase { private final MetricStore pattern; private final Supplier<?> value; private StoreCase(MetricItem pattern, Supplier<?> value) { this.pattern = (MetricStore) pattern; this.value = value; } @Override public void accept(Option<String> key) { Metrics.metric(() -> key.map(_key -> run(() -> store(_key, pattern, value.get())) ).orElse(() -> { store(pattern, value.get()); return null; })); } } /** * Metric {@code exception} */ public static final class ExceptionCase implements ItemCase { private final Throwable throwable; private ExceptionCase(Throwable throwable) { this.throwable = throwable; } @Override public void accept(Option<String> key) { Metrics.metric(() -> key.map(_key -> run(() -> exception(_key, throwable)) ).orElse(() -> { exception(throwable); return null; })); } } } // --------------------------------------- Show for check or test ------------------------------------------------ /** * Show indexs map. * * @return the map */ public static Map<String, String> showIndexes() { return INDEX_TAG.get(); } /** * Show indexs map. * * @param key the key * @return the map */ public static Map<String, String> showIndexes(String key) { return KEY_INDEX_TAG.get().get(key); } /** * Show stores map. * * @return the map */ public static Map<String, String> showStores() { return STORED_TAG.get(); } /** * Show stores map. * * @param key the key * @return the map */ public static Map<String, String> showStores(String key) { return KEY_STORED_TAG.get().get(key); } // ---------------------------------------- Remove --------------------------------------------------- /** * Remove threadLocal map and all-keys map */ public static void remove() { INDEX_TAG.remove(); KEY_INDEX_TAG.remove(); STORED_TAG.remove(); KEY_STORED_TAG.remove(); } /** * Remove the-key map * * @param key the key */ public static void remove(String key) { if (KEY_INDEX_TAG.get() != null) { KEY_INDEX_TAG.get().remove(key); } if (KEY_STORED_TAG.get() != null) { KEY_STORED_TAG.get().remove(key); } } } import io.vavr.Function3; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Map; /** * Metric configuration. * Please provide your custom {@code configuration} through * {@link org.springframework.context.annotation.Configuration} and * {@link org.springframework.context.annotation.Bean} */ public interface MetricConfiguration { /** * Metric logs topic * * @return the string */ String topic(); /** * Using {@code <Throwable, IndexMap, StoreMap>} to log exception into metric map * * @return the function 3 */ Function3<Throwable, Map<String, String>, Map<String, String>, Void> metricException(); /** * Using {@code <Topic, IndexMap, StoreMap>} to log metric into topic * * @return the function 3 */ Function3<String, Map<String, String>, Map<String, String>, Void> metricFinally(); /** * Build exception stack readable * * @param cause throwable * @return A readable stack */ default String buildExceptionStack(Throwable cause) { if (cause != null) { StringWriter stringWriter = new StringWriter(MetricModule.EXCEPTION_STACK_SIZE); cause.printStackTrace(new PrintWriter(stringWriter)); return stringWriter.toString(); } else { return MetricModule.EXCEPTION_STACK_UNKNOWN; } } } /** * The interface Metric module. */ interface MetricModule { /** * The constant EXCEPTION_STACK_SIZE. */ int EXCEPTION_STACK_SIZE = 2048; /** * The constant EXCEPTION_STACK_UNKNOWN. */ String EXCEPTION_STACK_UNKNOWN = "Unknown exception"; } import java.lang.annotation.*; @Documented @Retention(RetentionPolicy.SOURCE) @Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE}) public @interface Important { /** * Mark important reminders. * * @return important */ String important() default "important!"; } import com.ctrip.ibu.s2s.sender.service.common.constants.LogConstants; import com.ctrip.ibu.s2s.sender.service.common.constants.MessageConstants; import com.ctrip.ibu.s2s.sender.service.common.metrics.index.MetricIndexes; import com.ctrip.ibu.s2s.sender.service.common.metrics.store.MetricStories; import com.ctrip.soa.caravan.util.serializer.JacksonJsonSerializer; import com.dianping.cat.Cat; import io.vavr.Function3; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Configuration; import java.util.Map; /** * Metric configuration. * Please provide your custom {@code configuration} through * {@link org.springframework.context.annotation.Configuration} and * {@link org.springframework.context.annotation.Bean} */ @Slf4j @Configuration public class CtripMetricConfiguration implements MetricConfiguration { @Override public String topic() { return LogConstants.WORKER_ES_SCENARIO; } /** * Metric send exception info to collection. * * @return {@link Void} void empty. */ @Override public Function3<Throwable, Map<String, String>, Map<String, String>, Void> metricException() { return (throwable, indexMap, storeMap) -> { indexMap.put(MetricIndexes.EXCEPTION_TYPE.getKey(), throwable.getClass().getCanonicalName()); storeMap.put(MetricStories.EXCEPTION.getKey(), buildExceptionStack(throwable)); return null; }; } /** * Metric finally send info to es. * * @return {@link Void} void empty. */ @Override public Function3<String, Map<String, String>, Map<String, String>, Void> metricFinally() { return (topic, indexMap, storeMap) -> { log.info("[[traceId={},masterOrSlave={}]] Cat logTags,indexInfo={}", indexMap.get(MetricIndexes.TRACE_ID.getKey()), indexMap.get(MetricIndexes.MASTER_OR_SLAVE.getKey()), JacksonJsonSerializer.INSTANCE.serialize(indexMap)); Cat.logTags(this.topic(), indexMap, storeMap); return null; }; } } /** * @Author Jason Gao * @Date: 2020/5/25 */ @Configuration public class SpringContextConfigurator implements ApplicationContextAware, DisposableBean { @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { AppContext.setApplicationContext(applicationContext); } /** * Gets the application Context stored in a static variable. * * @return the application context */ public static ApplicationContext getApplicationContext() { return AppContext.getApplicationContext(); } /** * Get the Bean from the static variable applicationContext and * automatically transform it to the type of the assigned object. * * @param <T> the type parameter * @param requiredType the required type * @return the bean */ public static <T> T getBean(Class<T> requiredType) { return AppContext.getApplicationContext().getBean(requiredType); } @Override public void destroy() throws Exception { AppContext.clear(); } /** * Spring static context container */ private static class AppContext { private static ApplicationContext ctx; /** * Injected from the class "ApplicationContextProvider" which is * automatically loaded during Spring-Initialization. * * @param applicationContext the application context */ static void setApplicationContext(ApplicationContext applicationContext) { ctx = applicationContext; } /** * Get access to the Spring ApplicationContext from everywhere in your Application. * * @return the application context */ static ApplicationContext getApplicationContext() { checkApplicationContext(); return ctx; } /** * Clean up static variables when the Context is off. */ static void clear() { ctx = null; } private static void checkApplicationContext() { if (ctx == null) { throw new IllegalStateException("applicationContext not injected."); } } } }

    单元测试类

    /** * @Author Jason Gao * @Date: 2020/5/25 */ @RunWith(SpringRunner.class) @SpringBootTest(classes = ServiceInitializer.class) public class MetricsTest { @Test public void metric() { Request request = new Request(); HotelData hotelData = new HotelData(); hotelData.setCityId(1); hotelData.setCityNameEn("shanghai"); request.setHotelData(hotelData); /** 第一个分片 */ String key1 = Metrics.local(); Metrics.Log(key1).of( $(MetricIndexes.ORDER_ID, () -> "orderIdXXXX"), // index 可以统一转成es的String类型的value $(MetricIndexes.API_NAME, () -> 999999999L - 111111111L), // 可以预防空指针 $(MetricIndexes.API_NAME, () -> request.getBasicData().getOrderID()), // store $(MetricStories.CHOSE_CONFIG, "CHOSE_CONFIG_xxxx"), //TODO 注意:对于value是非基本类型的Object对象时候需要自行JSON化 $(MetricStories.FULL_DATA, JacksonJsonSerializer.INSTANCE.serialize(request)), // exception $(new RuntimeException()) ); Map<String, String> index = Metrics.showIndexes(key1); checkTestValues(() -> { Assert.assertEquals("888888888", index.get(MetricIndexes.API_NAME.getKey())); Assert.assertNotNull(index.get(MetricIndexes.EXCEPTION_TYPE.getKey())); }); Map<String, String> store = Metrics.showStores(key1); checkTestValues(() -> { Assert.assertEquals("CHOSE_CONFIG_xxxx", store.get(MetricStories.CHOSE_CONFIG.getKey())); Assert.assertNotNull(store.get(MetricStories.EXCEPTION.getKey())); }); Metrics.build(key1); /** 第二个分片 */ String key2 = Metrics.local(); // Option 2: add metric by automatic placeholder "$" Metrics.Log(key2).of( $(MetricIndexes.ORDER_ID, () -> "orderIdXXXX"), // index $(MetricIndexes.THIRD_SERVICE_NAME, () -> "readOrderDemo"), // 可以预防空指针 $(MetricStories.THIRD_SERVICE_REQUEST, () -> "third-service-request=xxxxx"), // store $(MetricStories.THIRD_SERVICE_RESPONSE, () -> "third-service-response=xxxxx"), // exception $(new RuntimeException("MMMMMMMMM")) ); Map<String, String> index2 = Metrics.showIndexes(key2); checkTestValues(() -> { Assert.assertEquals("readOrderDemo", index2.get(MetricIndexes.THIRD_SERVICE_NAME.getKey())); Assert.assertNotNull(index2.get(MetricIndexes.EXCEPTION_TYPE.getKey())); }); Map<String, String> store2 = Metrics.showStores(key2); checkTestValues(() -> { Assert.assertEquals("third-service-request=xxxxx", store2.get(MetricStories.THIRD_SERVICE_REQUEST.getKey())); Assert.assertEquals("third-service-response=xxxxx", store2.get(MetricStories.THIRD_SERVICE_RESPONSE.getKey())); Assert.assertNotNull(store2.get(MetricStories.EXCEPTION.getKey())); }); Metrics.build(key2); } private void checkTestValues(Runnable checker) { checker.run(); } }

     

    4、结语

    上述方案可以解决上述1中的痛点问题,但是仍有些许的美中不足。欢迎各位大佬前来拍砖和给出优化建议。

    Processed: 0.017, SQL: 9