/* * Copyright 2015 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package com.linecorp.armeria.client.logging; import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.linecorp.armeria.client.pool.DecoratingKeyedChannelPoolHandler; import com.linecorp.armeria.client.pool.KeyedChannelPoolHandler; import com.linecorp.armeria.client.pool.PoolKey; import com.linecorp.armeria.common.util.TextFormatter; import com.linecorp.armeria.common.util.Ticker; import io.netty.channel.Channel; import io.netty.util.AttributeKey; /** * Decorates a {@link KeyedChannelPoolHandler} to log the connection pool events. */ public class KeyedChannelPoolLoggingHandler extends DecoratingKeyedChannelPoolHandler<PoolKey> { private static final Logger logger = LoggerFactory.getLogger(KeyedChannelPoolLoggingHandler.class); private static final AttributeKey<ChannelStat> STAT = AttributeKey.valueOf(KeyedChannelPoolLoggingHandler.class, "STAT"); private final AtomicInteger activeChannels = new AtomicInteger(); private final Ticker ticker; private enum EventType { CREATED("Created on"), ACQUIRED("Acquired from"), RELEASED("Released from"), CLOSED("Closed on"); private final String actionDescription; EventType(String actionDescription) { this.actionDescription = actionDescription; } String actionDescription() { return actionDescription; } } private static class ChannelStat extends AtomicInteger { private static final long serialVersionUID = -851703990840809289L; private final Ticker ticker; private final long createdNanos; private long lastUsedNanos; ChannelStat(Ticker ticker) { createdNanos = ticker.read(); this.ticker = ticker; } @SuppressWarnings("checkstyle:fallthrough") void collect(EventType eventType) { switch (eventType) { case ACQUIRED: incrementAndGet(); case RELEASED: lastUsedNanos = System.nanoTime(); } } @SuppressWarnings("checkstyle:fallthrough") StringBuilder status(EventType eventType, StringBuilder buf) { long currentNanos = ticker.read(); switch (eventType) { case ACQUIRED: buf.append("was idle for "); TextFormatter.appendElapsed(buf, lastUsedNanos, currentNanos); buf.append(", "); case RELEASED: case CLOSED: buf.append("used ").append(get()).append(" time(s), "); TextFormatter.appendElapsed(buf, createdNanos, currentNanos); buf.append(" old"); } return buf; } StringBuilder collectAndStatus(EventType eventType, StringBuilder buf) { collect(eventType); return status(eventType, buf); } } private static void logFailure(String name, PoolKey key, Channel ch, Throwable cause) { logger.error("{} exception handling {}() from {}", ch, name, key, cause); } /** * Creates a new instance that decorates the specified {@code handler}. */ public KeyedChannelPoolLoggingHandler( KeyedChannelPoolHandler<PoolKey> handler) { this(handler, Ticker.systemTicker()); } /** * Creates a new instance that decorates the specified {@code handler}. * * @param ticker an alternative {@link Ticker} */ public KeyedChannelPoolLoggingHandler( KeyedChannelPoolHandler<PoolKey> handler, Ticker ticker) { super(handler); this.ticker = ticker; } @Override public void channelReleased(PoolKey key, Channel ch) throws Exception { try { super.channelReleased(key, ch); } catch (Exception e) { logFailure("channelReleased", key, ch, e); } finally { logInfo(key, ch, EventType.RELEASED); } } @Override public void channelAcquired(PoolKey key, Channel ch) throws Exception { try { super.channelAcquired(key, ch); } catch (Exception e) { logFailure("channelAcquired", key, ch, e); } finally { logInfo(key, ch, EventType.ACQUIRED); } } @Override public void channelCreated(PoolKey key, Channel ch) throws Exception { try { super.channelCreated(key, ch); } catch (Exception e) { logFailure("channelCreated", key, ch, e); } finally { logInfo(key, ch, EventType.CREATED); } } @Override public void channelClosed(PoolKey key, Channel ch) throws Exception { try { super.channelClosed(key, ch); } catch (Exception e) { logFailure("channelClosed", key, ch, e); } finally { logInfo(key, ch, EventType.CLOSED); } } private void logInfo(PoolKey key, Channel ch, EventType eventType) { if (logger.isInfoEnabled()) { logger.info("{} {} {} ({})", ch, eventType.actionDescription(), key, status(ch, eventType)); } } private StringBuilder status(Channel ch, EventType event) { final ChannelStat state; if (event == EventType.CREATED) { state = new ChannelStat(ticker); ch.attr(STAT).set(state); } else { state = ch.attr(STAT).get(); } final StringBuilder buf = new StringBuilder(16); if (state != null) { state.collectAndStatus(event, buf); activeChannelStatus(event, buf); } return buf; } @SuppressWarnings("checkstyle:fallthrough") private void activeChannelStatus(EventType event, StringBuilder buf) { switch (event) { case CREATED: activeChannels.incrementAndGet(); break; case CLOSED: activeChannels.decrementAndGet(); default: buf.append(", "); } buf.append("active channels: ").append(activeChannels.get()); } }