@Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        long start = System.currentTimeMillis();
        Result result = invoker.invoke(invocation);
        long end = System.currentTimeMillis();
        if (TRACERS.size() > 0) {
            String key = invoker.getInterface().getName() + "." + RpcUtils.getMethodName(invocation);
            Set<Channel> channels = TRACERS.get(key);
            if (CollectionUtils.isEmpty(channels)) {
                key = invoker.getInterface().getName();
                channels = TRACERS.get(key);
            }
            if (CollectionUtils.isNotEmpty(channels)) {
                for (Channel channel : new ArrayList<>(channels)) {
                    if (channel.isConnected()) {
                        try {
                            int max = 1;
                            Integer m = (Integer) channel.getAttribute(TRACE_MAX);
                            if (m != null) {
                                max = m;
                            }
                            int count;
                            AtomicInteger c = (AtomicInteger) channel.getAttribute(TRACE_COUNT);
                            if (c == null) {
                                c = new AtomicInteger();
                                channel.setAttribute(TRACE_COUNT, c);
                            }
                            count = c.getAndIncrement();
                            if (count < max) {
                                String prompt =
                                        channel.getUrl().getParameter(Constants.PROMPT_KEY, Constants.DEFAULT_PROMPT);
                                channel.send(
                                        "\r\n" + RpcContext.getServiceContext().getRemoteAddress() + " -> "
                                                + invoker.getInterface().getName()
                                                + "." + RpcUtils.getMethodName(invocation)
                                                + "(" + JsonUtils.toJson(invocation.getArguments()) + ")" + " -> "
                                                + JsonUtils.toJson(result.getValue())
                                                + "\r\nelapsed: " + (end - start) + " ms."
                                                + "\r\n\r\n" + prompt);
                            }
                            if (count >= max - 1) {
                                channels.remove(channel);
                            }
                        } catch (Throwable e) {
                            channels.remove(channel);
                            logger.warn(PROTOCOL_FAILED_PARSE, "", "", e.getMessage(), e);
                        }
                    } else {
                        channels.remove(channel);
                    }
                }
            }
        }
        return result;
    }
@Test
    void testInvoke() throws Exception {
        String method = "sayHello";
        Class<?> type = DemoService.class;
        String key = type.getName() + "." + method;
        // add tracer
        TraceFilter.addTracer(type, method, mockChannel, 2);

        Invoker<DemoService> mockInvoker = mock(Invoker.class);
        Invocation mockInvocation = mock(Invocation.class);
        Result mockResult = mock(Result.class);
        TraceFilter filter = new TraceFilter();

        given(mockInvoker.getInterface()).willReturn(DemoService.class);
        given(mockInvocation.getMethodName()).willReturn(method);
        given(mockInvocation.getArguments()).willReturn(new Object[0]);
        given(mockInvoker.invoke(mockInvocation)).willReturn(mockResult);
        given(mockResult.getValue()).willReturn("result");

        // test invoke
        filter.invoke(mockInvoker, mockInvocation);
        String message = listToString(mockChannel.getReceivedObjects());
        String expectMessage = "org.apache.dubbo.rpc.protocol.dubbo.support.DemoService.sayHello([]) -> \"result\"";
        System.out.println("actual message: " + message);
        Assertions.assertTrue(message.contains(expectMessage));
        Assertions.assertTrue(message.contains("elapsed:"));
        AtomicInteger traceCount = (AtomicInteger) mockChannel.getAttribute(TRACE_COUNT);
        Assertions.assertEquals(1, traceCount.get());

        // test remove channel when count >= max - 1
        filter.invoke(mockInvoker, mockInvocation);
        Field tracers = TraceFilter.class.getDeclaredField(TRACERS_FIELD_NAME);
        tracers.setAccessible(true);
        ConcurrentHashMap<String, Set<Channel>> o =
                (ConcurrentHashMap<String, Set<Channel>>) tracers.get(new ConcurrentHashMap<String, Set<Channel>>());
        Assertions.assertTrue(o.containsKey(key));
        Set<Channel> channels = o.get(key);
        Assertions.assertNotNull(channels);
        Assertions.assertFalse(channels.contains(mockChannel));
    }