关于CAT和ES的相关知识本文不做具体展开,各位自行去查阅资料了解吧。
借助java8之后的声明式编程的新特性来消弭PNE问题,借助日志分片来进行多条日志的埋点,同时参考行业内做的比较好的错误码的分类方式建立一套错误码机制。方案需要进行高度封装,充血方式提供全方位的能力。
错误码规范(错误码的枚举值是根据各自业务情况自定义)
/**
* 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 the specific context
*/
interface ErrorCodeManager {
// -- CHECK DUPLICATED CODE
/**
* Assert {@code ErrorCode} not duplicate
*
* @param errorContexts the {@code ErrorCode} contexts
*/
static void assertErrorCodesNoDuplicate(ErrorCodeContext[] errorContexts) {
Set 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:
*
* PARAMETER_ERROR 10000 Invalid parameter error!
*
*
* @param errorCodeContext the {@code ErrorCode} context item
*/
void showErrorCodeItem(Context errorCodeContext);
/**
* Show {@code ErrorCode} information like:
*
* PARAMETER_ERROR 10000 Invalid parameter error!
*
*
* @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 showingMsg) {
Set 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 {
@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
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 } to log exception into metric map
*
* @return the function 3
*/
Function3, Map, Void> metricException();
/**
* Using {@code } to log metric into topic
*
* @return the function 3
*/
Function3, Map, 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, Map, 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, Map, 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 the type parameter
* @param requiredType the required type
* @return the bean
*/
public static T getBean(Class 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 index = Metrics.showIndexes(key1);
checkTestValues(() -> {
Assert.assertEquals("888888888", index.get(MetricIndexes.API_NAME.getKey()));
Assert.assertNotNull(index.get(MetricIndexes.EXCEPTION_TYPE.getKey()));
});
Map 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 index2 = Metrics.showIndexes(key2);
checkTestValues(() -> {
Assert.assertEquals("readOrderDemo", index2.get(MetricIndexes.THIRD_SERVICE_NAME.getKey()));
Assert.assertNotNull(index2.get(MetricIndexes.EXCEPTION_TYPE.getKey()));
});
Map 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();
}
}
上述方案可以解决上述1中的痛点问题,但是仍有些许的美中不足。欢迎各位大佬前来拍砖和给出优化建议。