Optimize log output and support route mapping multi app
This commit is contained in:
@@ -109,7 +109,8 @@ public class ApiPairingController {
|
||||
}
|
||||
boolean equals = ApiPairingUtils.checkSign(appId, timestamp, app.secretkey, sign);
|
||||
if (!equals) {
|
||||
log.warn("{}request authority: app {}, timestamp {}, sign {} invalid", exchange.getLogPrefix(), appId, timestamp, sign, LogService.BIZ_ID, WebUtils.getTraceId(exchange));
|
||||
String traceId = WebUtils.getTraceId(exchange);
|
||||
log.warn("{} request authority: app {}, timestamp {}, sign {} invalid", traceId, appId, timestamp, sign, LogService.BIZ_ID, traceId);
|
||||
return Result.fail("request sign invalid");
|
||||
}
|
||||
return Result.succ();
|
||||
|
||||
@@ -142,7 +142,7 @@ public class AggregateFilter implements WebFilter {
|
||||
final String traceId = WebUtils.getTraceId(exchange);
|
||||
LogService.setBizId(traceId);
|
||||
|
||||
LOGGER.debug("matched api in aggregation: {}", path);
|
||||
LOGGER.debug("{} matched api in aggregation: {}", traceId, path);
|
||||
|
||||
// 客户端提交上来的信息
|
||||
Map<String, Object> clientInput = new HashMap<>();
|
||||
@@ -195,7 +195,7 @@ public class AggregateFilter implements WebFilter {
|
||||
jsonString = JSON.toJSONString(aggResult.getBody());
|
||||
}
|
||||
}
|
||||
LOGGER.debug("response body: {}", jsonString);
|
||||
LOGGER.debug("{} response body: {}", traceId, jsonString);
|
||||
if (aggResult.getHeaders() != null && !aggResult.getHeaders().isEmpty()) {
|
||||
serverHttpResponse.getHeaders().addAll(aggResult.getHeaders());
|
||||
serverHttpResponse.getHeaders().remove(CommonConstants.HEADER_CONTENT_LENGTH);
|
||||
@@ -211,7 +211,7 @@ public class AggregateFilter implements WebFilter {
|
||||
|
||||
long end = System.currentTimeMillis();
|
||||
pipeline.getStepContext().addElapsedTime("总耗时", end - start);
|
||||
LOGGER.info("ElapsedTimes={}", JSON.toJSONString(pipeline.getStepContext().getElapsedTimes()));
|
||||
LOGGER.info("{} ElapsedTimes={}", traceId, JSON.toJSONString(pipeline.getStepContext().getElapsedTimes()));
|
||||
|
||||
return serverHttpResponse
|
||||
.writeWith(Flux.just(exchange.getResponse().bufferFactory().wrap(jsonString.getBytes())));
|
||||
|
||||
@@ -101,7 +101,7 @@ public class FilterExceptionHandlerConfig {
|
||||
|
||||
if (t instanceof FizzRuntimeException) {
|
||||
FizzRuntimeException ex = (FizzRuntimeException) t;
|
||||
log.error(tMsg, LogService.BIZ_ID, traceId, ex);
|
||||
log.error(traceId + ' ' + tMsg, LogService.BIZ_ID, traceId, ex);
|
||||
resp.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
|
||||
RespEntity rs = null;
|
||||
if (ex.getStepContext() != null && ex.getStepContext().returnContext()) {
|
||||
|
||||
@@ -131,9 +131,9 @@ public class FlowControlFilter extends FizzWebFilter {
|
||||
if (result != null && !result.isSuccess()) {
|
||||
String blockedResourceId = result.getBlockedResourceId();
|
||||
if (BlockType.CONCURRENT_REQUEST == result.getBlockType()) {
|
||||
log.info("exceed {} flow limit, blocked by maximum concurrent requests", blockedResourceId, LogService.BIZ_ID, traceId);
|
||||
log.info("{} exceed {} flow limit, blocked by maximum concurrent requests", traceId, blockedResourceId, LogService.BIZ_ID, traceId);
|
||||
} else {
|
||||
log.info("exceed {} flow limit, blocked by maximum QPS", blockedResourceId, LogService.BIZ_ID, traceId);
|
||||
log.info("{} exceed {} flow limit, blocked by maximum QPS", traceId, blockedResourceId, LogService.BIZ_ID, traceId);
|
||||
}
|
||||
|
||||
ResourceRateLimitConfig c = resourceRateLimitConfigService.getResourceRateLimitConfig(ResourceRateLimitConfig.NODE_RESOURCE);
|
||||
|
||||
@@ -80,7 +80,7 @@ public class RouteFilter extends FizzWebFilter {
|
||||
if (resp == null) { // should not reach here
|
||||
ServerHttpRequest clientReq = exchange.getRequest();
|
||||
String traceId = WebUtils.getTraceId(exchange);
|
||||
String msg = pfr.id + " fail";
|
||||
String msg = traceId + ' ' + pfr.id + " fail";
|
||||
if (pfr.cause == null) {
|
||||
log.error(msg, LogService.BIZ_ID, traceId);
|
||||
} else {
|
||||
|
||||
@@ -52,15 +52,15 @@ public abstract class PluginFilter implements FizzPluginFilter {
|
||||
|
||||
public Mono<Void> filter(ServerWebExchange exchange, Map<String, Object> config, String fixedConfig) {
|
||||
FilterResult pfr = WebUtils.getPrevFilterResult(exchange);
|
||||
String traceId = WebUtils.getTraceId(exchange);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(this + ": " + pfr.id + " execute " + (pfr.success ? "success" : "fail"), LogService.BIZ_ID, WebUtils.getTraceId(exchange));
|
||||
log.debug(traceId + ' ' + this + ": " + pfr.id + " execute " + (pfr.success ? "success" : "fail"), LogService.BIZ_ID, traceId);
|
||||
}
|
||||
if (pfr.success) {
|
||||
return doFilter(exchange, config, fixedConfig);
|
||||
} else {
|
||||
if (WebUtils.getDirectResponse(exchange) == null) { // should not reach here
|
||||
String traceId = WebUtils.getTraceId(exchange);
|
||||
String msg = pfr.id + " fail";
|
||||
String msg = traceId + ' ' + pfr.id + " fail";
|
||||
if (pfr.cause == null) {
|
||||
log.error(msg, LogService.BIZ_ID, traceId);
|
||||
} else {
|
||||
|
||||
@@ -386,7 +386,6 @@ public class ApiConfigService implements ApplicationListener<ContextRefreshedEve
|
||||
b.append(service).append(" don't have api config matching ").append(gatewayGroups).append(" group ").append(method).append(" method ").append(path).append(" path");
|
||||
return Result.fail(b.toString());
|
||||
}
|
||||
// List<ApiConfig> appCanAccess = ThreadContext.getArrayList(macs);
|
||||
List<ApiConfig> appCanAccess = ThreadContext.getArrayList();
|
||||
for (int i = 0; i < apiConfigs.size(); i++) {
|
||||
ApiConfig ac = apiConfigs.get(i);
|
||||
@@ -428,7 +427,6 @@ public class ApiConfigService implements ApplicationListener<ContextRefreshedEve
|
||||
|
||||
public Mono<Result<ApiConfig>> auth(ServerWebExchange exchange) {
|
||||
ServerHttpRequest req = exchange.getRequest();
|
||||
HttpHeaders hdrs = req.getHeaders();
|
||||
LogService.setBizId(WebUtils.getTraceId(exchange));
|
||||
return auth(exchange, WebUtils.getAppId(exchange), WebUtils.getOriginIp(exchange), WebUtils.getTimestamp(exchange), WebUtils.getSign(exchange),
|
||||
WebUtils.getClientService(exchange), req.getMethod(), WebUtils.getClientReqPath(exchange));
|
||||
|
||||
@@ -52,7 +52,8 @@ public class AuthPluginFilter extends PluginFilter {
|
||||
return apiConfigService.auth(exchange).flatMap(
|
||||
r -> {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("req auth: {}", r, LogService.BIZ_ID, WebUtils.getTraceId(exchange));
|
||||
String traceId = WebUtils.getTraceId(exchange);
|
||||
log.debug("{} req auth: {}", traceId, r, LogService.BIZ_ID, traceId);
|
||||
}
|
||||
Map<String, Object> data = Collections.singletonMap(RESULT, r);
|
||||
return WebUtils.transmitSuccessFilterResultAndEmptyMono(exchange, AUTH_PLUGIN_FILTER, data);
|
||||
|
||||
@@ -38,7 +38,7 @@ public class ServiceConfig {
|
||||
|
||||
public Map<String/*gateway group*/,
|
||||
Map<Object/*method*/,
|
||||
Map<String/*path patten*/, ApiConfig>
|
||||
Map<String/*path patten*/, Set<ApiConfig>>
|
||||
>
|
||||
>
|
||||
apiConfigMap = new HashMap<>();
|
||||
@@ -49,34 +49,32 @@ public class ServiceConfig {
|
||||
|
||||
public void add(ApiConfig ac) {
|
||||
for (String gatewayGroup : ac.gatewayGroups) {
|
||||
Map<Object, Map<String, ApiConfig>> method2pathPattenMap = apiConfigMap.get(gatewayGroup);
|
||||
if (method2pathPattenMap == null) {
|
||||
method2pathPattenMap = new HashMap<>();
|
||||
apiConfigMap.put(gatewayGroup, method2pathPattenMap);
|
||||
}
|
||||
Map<String, ApiConfig> pathPattern2apiConfigMap = method2pathPattenMap.get(ac.fizzMethod);
|
||||
if (pathPattern2apiConfigMap == null) {
|
||||
pathPattern2apiConfigMap = new HashMap<>();
|
||||
method2pathPattenMap.put(ac.fizzMethod, pathPattern2apiConfigMap);
|
||||
}
|
||||
pathPattern2apiConfigMap.put(ac.path, ac);
|
||||
Map<Object, Map<String, Set<ApiConfig>>> method2pathPattenMap = apiConfigMap.computeIfAbsent(gatewayGroup, k -> new HashMap<>());
|
||||
Map<String, Set<ApiConfig>> pathPattern2apiConfigsMap = method2pathPattenMap.computeIfAbsent(ac.fizzMethod, k -> new HashMap<>());
|
||||
Set<ApiConfig> apiConfigs = pathPattern2apiConfigsMap.computeIfAbsent(ac.path, k -> new HashSet<>());
|
||||
apiConfigs.add(ac);
|
||||
}
|
||||
log.info("{} service add api config: {}", id, ac);
|
||||
}
|
||||
|
||||
public void remove(ApiConfig ac) {
|
||||
for (String gatewayGroup : ac.gatewayGroups) {
|
||||
Map<Object, Map<String, ApiConfig>> method2pathPattenMap = apiConfigMap.get(gatewayGroup);
|
||||
Map<Object, Map<String, Set<ApiConfig>>> method2pathPattenMap = apiConfigMap.get(gatewayGroup);
|
||||
if (method2pathPattenMap != null) {
|
||||
Map<String, ApiConfig> pathPattern2apiConfigMap = method2pathPattenMap.get(ac.fizzMethod);
|
||||
if (pathPattern2apiConfigMap != null) {
|
||||
pathPattern2apiConfigMap.remove(ac.path);
|
||||
|
||||
if (pathPattern2apiConfigMap.isEmpty()) {
|
||||
method2pathPattenMap.remove(ac.fizzMethod);
|
||||
if (method2pathPattenMap.isEmpty()) {
|
||||
apiConfigMap.remove(gatewayGroup);
|
||||
}
|
||||
Map<String, Set<ApiConfig>> pathPattern2apiConfigsMap = method2pathPattenMap.get(ac.fizzMethod);
|
||||
if (pathPattern2apiConfigsMap != null) {
|
||||
Set<ApiConfig> apiConfigs = pathPattern2apiConfigsMap.get(ac.path);
|
||||
if (apiConfigs != null) {
|
||||
apiConfigs.remove(ac);
|
||||
if (apiConfigs.isEmpty()) {
|
||||
pathPattern2apiConfigsMap.remove(ac.path);
|
||||
if (pathPattern2apiConfigsMap.isEmpty()) {
|
||||
method2pathPattenMap.remove(ac.fizzMethod);
|
||||
if (method2pathPattenMap.isEmpty()) {
|
||||
apiConfigMap.remove(gatewayGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -85,21 +83,14 @@ public class ServiceConfig {
|
||||
}
|
||||
|
||||
public void update(ApiConfig ac) {
|
||||
ApiConfig prevApiConfig = null;
|
||||
for (String gatewayGroup : ac.gatewayGroups) {
|
||||
Map<Object, Map<String, ApiConfig>> method2pathPattenMap = apiConfigMap.get(gatewayGroup);
|
||||
if (method2pathPattenMap == null) {
|
||||
method2pathPattenMap = new HashMap<>();
|
||||
apiConfigMap.put(gatewayGroup, method2pathPattenMap);
|
||||
}
|
||||
Map<String, ApiConfig> pathPattern2apiConfigMap = method2pathPattenMap.get(ac.fizzMethod);
|
||||
if (pathPattern2apiConfigMap == null) {
|
||||
pathPattern2apiConfigMap = new HashMap<>();
|
||||
method2pathPattenMap.put(ac.fizzMethod, pathPattern2apiConfigMap);
|
||||
}
|
||||
prevApiConfig = pathPattern2apiConfigMap.put(ac.path, ac);
|
||||
Map<Object, Map<String, Set<ApiConfig>>> method2pathPattenMap = apiConfigMap.computeIfAbsent(gatewayGroup, k -> new HashMap<>());
|
||||
Map<String, Set<ApiConfig>> pathPattern2apiConfigsMap = method2pathPattenMap.computeIfAbsent(ac.fizzMethod, k -> new HashMap<>());
|
||||
Set<ApiConfig> apiConfigs = pathPattern2apiConfigsMap.computeIfAbsent(ac.path, k -> new HashSet<>());
|
||||
apiConfigs.remove(ac);
|
||||
apiConfigs.add(ac);
|
||||
}
|
||||
log.info("{} service update api config {} with {}", id, prevApiConfig, ac);
|
||||
log.info("{} service update api config: {}", id, ac);
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@@ -114,37 +105,46 @@ public class ServiceConfig {
|
||||
|
||||
@JsonIgnore
|
||||
public List<ApiConfig> getApiConfigs(String gatewayGroup, HttpMethod method, String path) {
|
||||
Map<Object, Map<String, ApiConfig>> method2pathPattenMap = apiConfigMap.get(gatewayGroup);
|
||||
Map<Object, Map<String, Set<ApiConfig>>> method2pathPattenMap = apiConfigMap.get(gatewayGroup);
|
||||
if (method2pathPattenMap == null) {
|
||||
return Collections.emptyList();
|
||||
} else {
|
||||
ArrayList<ApiConfig> result = ThreadContext.getArrayList();
|
||||
Map<String, ApiConfig> pathPattern2apiConfigMap = method2pathPattenMap.get(method);
|
||||
if (pathPattern2apiConfigMap != null) {
|
||||
checkPathPattern(pathPattern2apiConfigMap, path, result);
|
||||
Map<String, Set<ApiConfig>> pathPattern2apiConfigsMap = method2pathPattenMap.get(method);
|
||||
if (pathPattern2apiConfigsMap != null) {
|
||||
checkPathPattern(pathPattern2apiConfigsMap, path, result);
|
||||
}
|
||||
pathPattern2apiConfigMap = method2pathPattenMap.get(ApiConfig.ALL_METHOD);
|
||||
if (pathPattern2apiConfigMap != null) {
|
||||
checkPathPattern(pathPattern2apiConfigMap, path, result);
|
||||
pathPattern2apiConfigsMap = method2pathPattenMap.get(ApiConfig.ALL_METHOD);
|
||||
if (pathPattern2apiConfigsMap != null) {
|
||||
checkPathPattern(pathPattern2apiConfigsMap, path, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private void checkPathPattern(Map<String, ApiConfig> pathPattern2apiConfigMap, String path, ArrayList<ApiConfig> result) {
|
||||
Set<Map.Entry<String, ApiConfig>> entries = pathPattern2apiConfigMap.entrySet();
|
||||
for (Map.Entry<String, ApiConfig> entry : entries) {
|
||||
String pathPattern = entry.getKey();
|
||||
ApiConfig apiConfig = entry.getValue();
|
||||
if (apiConfig.access == ApiConfig.ALLOW) {
|
||||
if (apiConfig.exactMatch) {
|
||||
if (pathPattern.equals(path)) {
|
||||
result.add(apiConfig);
|
||||
return;
|
||||
private void checkPathPattern(Map<String, Set<ApiConfig>> pathPattern2apiConfigMap, String path, ArrayList<ApiConfig> result) {
|
||||
Set<Map.Entry<String, Set<ApiConfig>>> entries = pathPattern2apiConfigMap.entrySet();
|
||||
boolean clear = false;
|
||||
for (Map.Entry<String, Set<ApiConfig>> entry : entries) {
|
||||
String pathPattern = entry.getKey();
|
||||
Set<ApiConfig> apiConfigs = entry.getValue();
|
||||
if (pathPattern.equals(path)) {
|
||||
for (ApiConfig ac : apiConfigs) {
|
||||
if (ac.access == ApiConfig.ALLOW) {
|
||||
if (!clear && !result.isEmpty()) {
|
||||
result.clear();
|
||||
clear = true;
|
||||
}
|
||||
result.add(ac);
|
||||
}
|
||||
} else {
|
||||
if (UrlTransformUtils.ANT_PATH_MATCHER.match(pathPattern, path)) {
|
||||
result.add(apiConfig);
|
||||
}
|
||||
if (clear && !result.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
} else if (UrlTransformUtils.ANT_PATH_MATCHER.match(pathPattern, path)) {
|
||||
for (ApiConfig ac : apiConfigs) {
|
||||
if (ac.access == ApiConfig.ALLOW) {
|
||||
result.add(ac);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,6 @@ public class RequestBodyPlugin implements FizzPluginFilter {
|
||||
String traceId = WebUtils.getTraceId(exchange);
|
||||
log.debug("{} request is decorated", traceId, LogService.BIZ_ID, traceId);
|
||||
}
|
||||
// return FizzPluginFilterChain.next(newExchange);
|
||||
return doFilter(newExchange, config);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -81,10 +81,10 @@ public class CallbackService {
|
||||
|
||||
public Mono<Void> requestBackends(ServerWebExchange exchange, HttpHeaders headers, DataBuffer body, CallbackConfig cc, Map<String, ServiceInstance> service2instMap) {
|
||||
ServerHttpRequest req = exchange.getRequest();
|
||||
String reqId = WebUtils.getTraceId(exchange);
|
||||
String traceId = WebUtils.getTraceId(exchange);
|
||||
HttpMethod method = req.getMethod();
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("service2instMap: " + JacksonUtils.writeValueAsString(service2instMap), LogService.BIZ_ID, reqId);
|
||||
log.debug(traceId + " service2instMap: " + JacksonUtils.writeValueAsString(service2instMap), LogService.BIZ_ID, traceId);
|
||||
}
|
||||
int rs = cc.receivers.size();
|
||||
Mono<Object>[] sends = new Mono[rs];
|
||||
@@ -94,11 +94,11 @@ public class CallbackService {
|
||||
if (r.type == ApiConfig.Type.SERVICE_DISCOVERY) {
|
||||
ServiceInstance si = service2instMap.get(r.service);
|
||||
if (si == null) {
|
||||
send = fizzWebClient.send2service(reqId, method, r.service, r.path, headers, body)
|
||||
send = fizzWebClient.send2service(traceId, method, r.service, r.path, headers, body)
|
||||
.onErrorResume( crError(exchange, r, method, headers, body) );
|
||||
} else {
|
||||
String uri = buildUri(req, si, r.path);
|
||||
send = fizzWebClient.send(reqId, method, uri, headers, body)
|
||||
send = fizzWebClient.send(traceId, method, uri, headers, body)
|
||||
.onErrorResume( crError(exchange, r, method, headers, body) );
|
||||
}
|
||||
} else {
|
||||
@@ -154,9 +154,9 @@ public class CallbackService {
|
||||
StringBuilder b = ThreadContext.getStringBuilder();
|
||||
WebUtils.request2stringBuilder(exchange, b);
|
||||
b.append(Consts.S.LINE_SEPARATOR).append(callback).append(Consts.S.LINE_SEPARATOR);
|
||||
String id = WebUtils.getTraceId(exchange);
|
||||
WebUtils.request2stringBuilder(id, method, r.service + Consts.S.FORWARD_SLASH + r.path, headers, body, b);
|
||||
log.error(b.toString(), LogService.BIZ_ID, id, t);
|
||||
String traceId = WebUtils.getTraceId(exchange);
|
||||
WebUtils.request2stringBuilder(traceId, method, r.service + Consts.S.FORWARD_SLASH + r.path, headers, body, b);
|
||||
log.error(b.toString(), LogService.BIZ_ID, traceId, t);
|
||||
}
|
||||
|
||||
private String buildUri(ServerHttpRequest req, ServiceInstance si, String path) {
|
||||
|
||||
@@ -422,8 +422,8 @@ public abstract class WebUtils {
|
||||
request2stringBuilder(WebUtils.getTraceId(exchange), req.getMethod(), req.getURI().toString(), req.getHeaders(), null, b);
|
||||
}
|
||||
|
||||
public static void request2stringBuilder(String reqId, HttpMethod method, String uri, HttpHeaders headers, Object body, StringBuilder b) {
|
||||
b.append(reqId).append(Consts.S.SPACE).append(method).append(Consts.S.SPACE).append(uri);
|
||||
public static void request2stringBuilder(String traceId, HttpMethod method, String uri, HttpHeaders headers, Object body, StringBuilder b) {
|
||||
b.append(traceId).append(Consts.S.SPACE).append(method).append(Consts.S.SPACE).append(uri);
|
||||
if (headers != null) {
|
||||
final boolean[] f = {false};
|
||||
LOG_HEADER_SET.forEach(
|
||||
@@ -760,12 +760,12 @@ public abstract class WebUtils {
|
||||
@Deprecated
|
||||
public static Mono<Void> responseErrorAndBindContext(ServerWebExchange exchange, String filter, HttpStatus httpStatus) {
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
String rid = getTraceId(exchange);
|
||||
String traceId = getTraceId(exchange);
|
||||
StringBuilder b = ThreadContext.getStringBuilder();
|
||||
request2stringBuilder(exchange, b);
|
||||
b.append(Consts.S.LINE_SEPARATOR);
|
||||
b.append(filter).append(Consts.S.SPACE).append(httpStatus);
|
||||
log.error(b.toString(), LogService.BIZ_ID, rid);
|
||||
log.error(b.toString(), LogService.BIZ_ID, traceId);
|
||||
transmitFailFilterResult(exchange, filter);
|
||||
return buildDirectResponseAndBindContext(exchange, httpStatus, new HttpHeaders(), Consts.S.EMPTY);
|
||||
}
|
||||
@@ -774,12 +774,12 @@ public abstract class WebUtils {
|
||||
public static Mono<Void> responseErrorAndBindContext(ServerWebExchange exchange, String filter, HttpStatus httpStatus,
|
||||
HttpHeaders headers, String content) {
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
String rid = getTraceId(exchange);
|
||||
String traceId = getTraceId(exchange);
|
||||
StringBuilder b = ThreadContext.getStringBuilder();
|
||||
request2stringBuilder(exchange, b);
|
||||
b.append(Consts.S.LINE_SEPARATOR);
|
||||
b.append(filter).append(Consts.S.SPACE).append(httpStatus);
|
||||
log.error(b.toString(), LogService.BIZ_ID, rid);
|
||||
log.error(b.toString(), LogService.BIZ_ID, traceId);
|
||||
transmitFailFilterResult(exchange, filter);
|
||||
headers = headers == null ? new HttpHeaders() : headers;
|
||||
content = StringUtils.isBlank(content) ? Consts.S.EMPTY : content;
|
||||
|
||||
Reference in New Issue
Block a user