The correct use of exceptions ranks among the top three in the importance of the microservice architecture, right?
Curdboys have not seen each other for a long time, I wish you all a happy Dragon Boat Festival. I want to talk about the abnormality recently. My thinking seems to have formed a closed loop. I hope this combination boxing can help your business code.
I will only discuss the best languages in the world and the most ecologically complete languages below, so there are no comments.
Unusual similarities and differences
PHP's abnormal design in PHP7 is consistent with Java Exception extends Throwable
, but there are still some subtle differences in historical reasons and design concepts. For example, the anomaly in PHP has the code
, so that there are multiple anomalies clustered into the same anomaly, and then write different business logic codes according to code
catch
The Java exception does not have code
, it cannot be designed like this, and different exceptions can only be used for different situations. Therefore, we are accustomed to encapsulating the services exposed to the outside through packaging classes instead of directly relying on the transparent transmission of exceptions.
Unified exception handling
In the Java code, the most criticized is the try catch
that is all over the country, there is no comment. Grab a piece of code
@Override
public DataResult<List<AdsDTO>> getAds(Integer liveId) {
try {
List<AdsDTO> adsDTO = new ArrayList<>();
//...业务逻辑省略
DataResult.success(adsDTO);
} catch (Exception e) {
log.error("getAds has Exception:{}", e.getMessage(), e);
DataResult.failure(ResultCode.CODE_INTERNAL_ERROR, e.getMessage()); // 将异常信息返回给服务端调用方
}
return dataResult;
}
In many cases, I just write try catch
about it, regardless of whether there are non-runtime exceptions. A better way is to use the aop method to intercept all service method calls, unify the exceptions and then handle them.
@Around("recordLog()")
public Object record(ProceedingJoinPoint joinPoint) throws Throwable {
//... 请求调用来源记录
Object result;
try {
result = joinPoint.proceed(joinPoint.getArgs());
} catch (Exception e) {
//... 记录异常日志
DataResult<Object> res = DataResult.failure(ResultCode.CODE_INTERNAL_ERROR, e.getMessage());
result = res;
}
//... 返回值日志记录
return result;
}
There is a small problem. If the abnormal information of A service is directly returned to the caller B, there may be some potential risks. The caller can never be trusted, even if he is rooted in the third generation of poor peasants. Because it is not certain what the caller will do with the error message, it may be directly returned to the front end json
RuntimeException
Exceptions in Java can be divided into runtime exceptions and non-runtime exceptions. The that does not need to be captured, and the method does not need to be marked throw Exception
. For example, we use the Preconditions
tool class in the guava package in the method. The thrown IllegalArgumentException
is also a runtime exception.
@Override
public DataResult<List<AdsDTO>> getAds(Integer liveId) {
Preconditions.checkArgument(null != liveId, "liveIds not be null");
List<AdsDTO> adsDTOS = new ArrayList<>();
//...业务逻辑省略
return DataResult.success(adsDTOS);
}
We can also use this feature to customize our own business exception class inheriting RuntimeException
XXServiceRuntimeException extends RuntimeException
For situations that do not conform to business logic, throw XXServiceRuntimeException
@Override
public DataResult<List<AdsDTO>> getAds(Integer liveId) {
if (null == liveId) {
throw new XXServiceRuntimeException("liveId can't be null");
}
List<AdsDTO> adsDTOS = new ArrayList<>();
//...业务逻辑省略
return DataResult.success(adsDTOS);
}
Then perform unified processing in aop to make corresponding optimizations. For the XXServiceRuntimeException
IllegalArgumentException
above, the abnormal internal records other than 060c8b0fa05b7c and 060c8b0fa05b7d should be no longer exposed to the outside, but you must remember to requestId
the distributed links through 060c8b0fa05b7f. Return in DataResult
to facilitate troubleshooting.
@Around("recordLog()")
public Object record(ProceedingJoinPoint joinPoint) throws Throwable {
//... 请求调用来源记录
Object result;
try {
result = joinPoint.proceed(joinPoint.getArgs());
} catch (Exception e) {
//... 记录异常日志①
log.error("{}#{}, exception:{}:", clazzSimpleName, methodName, e.getClass().getSimpleName(), e);
DataResult<Object> res = DataResult.failure(ResultCode.CODE_INTERNAL_ERROR);
if (e instanceof XXServiceRuntimeException || e instanceof IllegalArgumentException) {
res.setMessage(e.getMessage());
}
result = res;
}
if (result instanceof DataResult) {
((DataResult) result).setRequestId(EagleEye.getTraceId()); // DMC
}
//... 返回值日志记录
return result;
}
Exception monitoring
Well said closed loop, after using a custom exception class, the monitoring alarm threshold of the abnormal log can be reduced a lot, and the alarm is more accurate. Take the monitoring of Alibaba Cloud SLS as an example.
* and ERROR not XXServiceRuntimeException not IllegalArgumentException|SELECT COUNT(*) AS count
What is monitored here is the record abnormal log ①
Exceptions in PHP
The problems mentioned in Java above also exist in PHP. It is impossible to reflect that PHP is the best language in the world without using 3 methods to simulate aop.
//1. call_user_func_array
//2. 反射
//3. 直接 new
try {
$class = new $className();
$result = $class->$methodName();
} catch (\Throwable $e) {
//...略
}
The architecture logic similar to the above is no longer repeated to write pseudo-code, and it is basically the same. It also customizes its own business exception class to inherit RuntimeException
, and then do external output processing.
However, there are some historical burdens in PHP. When it was originally designed, many runtime exceptions were Notice
and Warning
errors, but the error output lacks a call stack, which is not conducive to troubleshooting.
function foo(){
return boo("xxx");
}
function boo($a){
return explode($a);
}
foo();
Warning: explode() expects at least 2 parameters, 1 given in /Users/mengkang/Downloads/ab.php on line 8
I can't see the specific parameters, nor can I see the call stack. If you use set_error_handler
+ ErrorException
, it will be very clear.
set_error_handler(function ($severity, $message, $file, $line) {
throw new ErrorException($message, 10001, $severity, $file, $line);
});
function foo(){
return boo("xxx");
}
function boo($a){
return explode($a);
}
try{
foo();
}catch(Exception $e){
echo $e->getTraceAsString();
}
The last information printed out is
Fatal error: Uncaught ErrorException: explode() expects at least 2 parameters, 1 given in /Users/mengkang/Downloads/ab.php:12
Stack trace:
#0 [internal function]: {closure}(2, 'explode() expec...', '/Users/mengkang...', 12, Array)
#1 /Users/mengkang/Downloads/ab.php(12): explode('xxx')
#2 /Users/mengkang/Downloads/ab.php(8): boo('xxx')
#3 /Users/mengkang/Downloads/ab.php(15): foo()
#4 {main}
thrown in /Users/mengkang/Downloads/ab.php on line 12
Modify the above function
function boo(array $a){
return implode(",", $a);
}
It can’t be caught, because it throws PHP Fatal error: Uncaught TypeError
, and PHP7 has addedclass Error implements Throwable
, there will be Stack in the PHP system error log, but it cannot be connected to the entire business system , here we have to talk about the log design, we expect to connect all logs in series through a traceId like Java, from Nginx logs to normal info level logs in PHP and these Uncaught TypeError
, so take over the default output to the system error log, and record it in a unified place in the catch code block. So here is simply modified to
set_error_handler(function ($severity, $message, $file, $line) {
throw new ErrorException($message, 10001, $severity, $file, $line);
});
function foo(){
return boo("xxx");
}
function boo(array $a){
return implode(",", $a);
}
try{
foo();
}catch(Throwable $e){
echo $e->getTraceAsString();
}
catch Throwable
can accept Error
and Exception
.
But set_error_handler
n't handle some errors, such as E_PARSE
errors, you can use register_shutdown_function
to find out.
It is worth noting thatregister_shutdown_function
is to execute the registered function when the script exits normally or when exit is called.
It can only be used when the script runs (run-time not parse-time) error exit. If there is a syntax error in the same file thatregister_shutdown_function
but our project is generally divided into multiple files, so that there are syntax errors in other files,
register_shutdown_function(function(){
$e = error_get_last();
if ($e){
throw new \ErrorException($e["message"], 10002, E_ERROR, $e["file"], $e["line"]);
}
});
If you want to directly use these codes (PHP) directly to the project, there may be a lot of pits, because we are used to many notices in the system. You can turn the notice errors into exceptions and then actively record them, but do not throw exceptions. can.
Come here today, next time I will talk about how to record the log.
Although the development of the PHP environment seems unclear, but because of love, let it continue. It is the best choice in some scenarios.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。