public static void log(
            ClientRequest request,
            ServiceRequest serviceRequest,
            long requestSize,
            long requestTime,
            ServiceResponse response,
            long processingTime) {
        TurmsNotification.Data dataForRequester = response.dataForRequester();
        String responseType = dataForRequester == null
                ? ""
                : dataForRequester.getKindCase()
                        .name();
        ByteBuf buffer = ByteBufUtil.join(64,
                LOG_FIELD_DELIMITER,
                // session information
                NumberFormatter.toCharBytes(request.userId()),
                request.deviceType()
                        .name(),
                serviceRequest.getIpStr(),
                // request information
                NumberFormatter.toCharBytes(request.requestId()),
                request.turmsRequest()
                        .getKindCase()
                        .name(),
                NumberFormatter.toCharBytes(requestSize),
                DateTimeUtil.toBytes(requestTime),
                // response information
                NumberFormatter.toCharBytes(response.code()
                        .getBusinessCode()),
                responseType,
                NumberFormatter.toCharBytes(processingTime));
        if (response.code()
                .isServerError()) {
            CLIENT_API_LOGGER.error(buffer);
        } else {
            CLIENT_API_LOGGER.info(buffer);
        }
    }
@implNote 1. We don't accept an object like ServiceRequestMessage to collect all data to
          avoid creating unnecessary intermediate objects. 2. We use the common log pattern
          (including the trace ID) so that our users don't need to write different parsers
          for them.
turms-service/src/main/java/im/turms/service/infra/logging/ClientApiLogging.java