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

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

1、问题场景

  1. 待埋点的信息为一个复杂对象中的比较深层次的字段,不希望做太多的非空判断
  2. 处理的主流程为一条日志,主流程中会多次调用第三方服务,需要将调用信息埋点生成子的日志,并进行主流程日志和子的调用日志的串联。
  3. 日志埋点通过cat写入es。
  4. 支持同步or异步的埋点。
  5. 建立一套规范的错误码的体系,并能够自动防重(多人协同开发时候防止另一个人也建立了一个相同的错误码),并且能够自动生成错误码的映射关系提供给产品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("%70s  %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中的痛点问题,但是仍有些许的美中不足。欢迎各位大佬前来拍砖和给出优化建议。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以为您设计出这四个页面,下面是我的设计方案: 1. 引导页 在这个页面中,我会设置一个“开始使用”按钮,让用户可以进入到主界面。同时,我会添加一些引人注目的图片和动画,让用户感到愉悦和惊喜。引导页的整体风格可以采用简洁、明快的设计,使用白色和橙色作为主色调,让用户感到清新、轻松。 2. 减脂餐厅页 在这个页面中,我会设置一个搜索框,让用户可以输入关键词进行搜索。搜索结果将以列表的形式呈现,包括餐厅的图片、名称、地址、评分和用户的评论等信息。用户可以通过点击列表项进入到餐厅详情页,了解更多关于这个餐厅的信息。减脂餐厅页的整体风格可以采用清新、健康的设计,使用绿色和白色作为主色调,让用户感到舒适、自然。 3. 景点活动页 在这个页面中,我会设置一个搜索框和一个筛选功能,让用户可以按照不同的条件进行搜索和筛选。搜索结果将以列表的形式呈现,包括景点的图片、名称、地址、评分、开放时间等信息。用户可以通过点击列表项进入到景点详情页,了解更多关于这个景点的信息。景点活动页的整体风格可以采用清新、活力的设计,使用蓝色和白色作为主色调,让用户感到自由、畅快。 4. 周边游页 在这个页面中,我会设置一个搜索框和一个筛选功能,让用户可以按照不同的条件进行搜索和筛选。搜索结果将以列表的形式呈现,包括游玩的图片、名称、路线、费用、难度等信息。用户可以通过点击列表项进入到游玩详情页,了解更多关于这个游玩的信息。周边游页的整体风格可以采用清新、自然的设计,使用绿色和白色作为主色调,让用户感到放松、愉悦。 以上就是我为您设计的四个页面,请您参考。如果您有其他的要求或者建议,欢迎随时告诉我。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值