LoggerMiddleware.java

// Copyright (c) ZeroC, Inc.

package com.zeroc.Ice;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.concurrent.CompletionStage;

final class LoggerMiddleware implements Object {
    private final Object _next;
    private final Logger _logger;
    private final int _traceLevel;
    private final String _traceCat;
    private final int _warningLevel;
    private final ToStringMode _toStringMode;

    public LoggerMiddleware(
            Object next,
            Logger logger,
            int traceLevel,
            String traceCat,
            int warningLevel,
            ToStringMode toStringMode) {
        _next = next;
        _logger = logger;
        _traceLevel = traceLevel;
        _traceCat = traceCat;
        _warningLevel = warningLevel;
        _toStringMode = toStringMode;

        assert _traceLevel > 0 || _warningLevel > 0;
    }

    @Override
    public CompletionStage<OutgoingResponse> dispatch(IncomingRequest request)
        throws UserException {
        try {
            return _next.dispatch(request)
                .handle(
                    (response, exception) -> {
                        if (exception != null) {
                            // Convert to response for further processing
                            response = request.current.createOutgoingResponse(exception);
                        }

                        var replyStatus = ReplyStatus.valueOf(response.replyStatus);
                        if (replyStatus != null) {
                            switch (replyStatus) {
                                case Ok:
                                case UserException:
                                    if (_traceLevel > 0) {
                                        logDispatch(replyStatus, request.current);
                                    }
                                    break;

                                case UnknownException:
                                case UnknownUserException:
                                case UnknownLocalException:
                                    // always log when middleware installed
                                    logDispatchFailed(
                                        response.exceptionDetails, request.current);
                                    break;

                                default:
                                    if (_traceLevel > 0 || _warningLevel > 1) {
                                        logDispatchFailed(
                                            response.exceptionDetails, request.current);
                                    }
                                    break;
                            }
                        } else {
                            // Unknown reply status, like default case above.
                            if (_traceLevel > 0 || _warningLevel > 1) {
                                logDispatchFailed(
                                    response.exceptionDetails, request.current);
                            }
                        }
                        return response;
                    });
        } catch (UserException ex) {
            if (_traceLevel > 0) {
                logDispatch(ReplyStatus.UserException, request.current);
            }
            throw ex;
        } catch (UnknownException ex) {
            logDispatchFailed(
                ex.toString(), request.current); // always log when middleware installed
            throw ex;
        } catch (DispatchException ex) {
            if (_traceLevel > 0 || _warningLevel > 1) {
                logDispatchFailed(ex.toString(), request.current);
            }
            throw ex;
        } catch (RuntimeException | Error ex) {
            logDispatchFailed(ex.toString(), request.current);
            throw ex;
        }
    }

    private void logDispatch(ReplyStatus replyStatus, Current current) {
        var sw = new StringWriter();
        var pw = new PrintWriter(sw);
        var out = new OutputBase(pw);
        out.setUseTab(false);
        out.print("dispatch of ");
        out.print(current.operation);
        out.print(" to ");
        printTarget(out, current);
        out.print(" returned a response with reply status ");
        out.print(replyStatus.toString());

        _logger.trace(_traceCat, sw.toString());
    }

    private void logDispatchFailed(String exceptionDetails, Current current) {
        var sw = new StringWriter();
        var pw = new PrintWriter(sw);
        var out = new OutputBase(pw);
        out.setUseTab(false);
        out.print("failed to dispatch ");
        out.print(current.operation);
        out.print(" to ");
        printTarget(out, current);

        if (exceptionDetails != null) {
            out.print(":\n");
            out.print(exceptionDetails);
        }

        _logger.warning(sw.toString());
    }

    private void printTarget(OutputBase out, Current current) {
        out.print(Util.identityToString(current.id, _toStringMode));
        if (!current.facet.isEmpty()) {
            out.print(" -f ");
            out.print(StringUtil.escapeString(current.facet, "", _toStringMode));
        }
        out.print(" over ");

        if (current.con != null) {
            ConnectionInfo connInfo = null;
            try {
                connInfo = current.con.getInfo();
                while (connInfo.underlying != null) {
                    connInfo = connInfo.underlying;
                }
            } catch (Exception e) {
                // Thrown by getInfo() when the connection is closed.
            }

            if (connInfo instanceof IPConnectionInfo ipConnInfo) {
                out.print(ipConnInfo.localAddress);
                out.print(":" + ipConnInfo.localPort);
                out.print("<->");
                out.print(ipConnInfo.remoteAddress);
                out.print(":" + ipConnInfo.remotePort);
            } else {
                // Connection.toString() returns a multiline string, so we just use type() here
                // for bt and similar.
                out.print(current.con.type());
            }
        } else {
            out.print("colloc");
        }
    }
}