/* * Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved. * * Licensed 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.hazelcast.internal.diagnostics; import com.hazelcast.core.HazelcastInstance; import com.hazelcast.core.LifecycleEvent; import com.hazelcast.core.LifecycleListener; import com.hazelcast.core.Member; import com.hazelcast.core.MembershipAdapter; import com.hazelcast.core.MembershipEvent; import com.hazelcast.core.MigrationEvent; import com.hazelcast.core.MigrationListener; import com.hazelcast.instance.NodeExtension; import com.hazelcast.internal.cluster.ClusterVersionListener; import com.hazelcast.logging.ILogger; import com.hazelcast.nio.Address; import com.hazelcast.nio.Connection; import com.hazelcast.nio.ConnectionListenable; import com.hazelcast.nio.ConnectionListener; import com.hazelcast.spi.impl.NodeEngineImpl; import com.hazelcast.spi.properties.HazelcastProperties; import com.hazelcast.spi.properties.HazelcastProperty; import com.hazelcast.version.Version; import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; import static java.util.concurrent.TimeUnit.SECONDS; /** * The {@link SystemLogPlugin} is responsible for: * <ol> * <li>Showing lifecycle changes like shutting down, merging etc.</li> * <li>Show connection creation and connection closing. Also the causes of closes are included.</li> * <li>Showing membership changes.</li> * <li>Optionally showing partition migration</li> * </ol> * * This plugin is very useful to get an idea what is happening inside a cluster; especially when there are connection related * problems. * * This plugin has a low overhead and is meant to run in production. */ public class SystemLogPlugin extends DiagnosticsPlugin { /** * If this plugin is enabled. */ public static final HazelcastProperty ENABLED = new HazelcastProperty(Diagnostics.PREFIX + ".systemlog.enabled", "true"); /** * If logging partition migration is enabled. Because there can be so many partitions, logging the partition migration * can be very noisy. */ public static final HazelcastProperty LOG_PARTITIONS = new HazelcastProperty(Diagnostics.PREFIX + ".systemlog.partitions", "false"); /** * Currently the Diagnostic is scheduler based, so each task gets to run as often at is has been configured. This works * fine for most plugins, but if there are outside events triggering the need to write, this scheduler based model is * not the right fit. In the future the Diagnostics need to be improved. */ private static final long PERIOD_MILLIS = SECONDS.toMillis(1); private final Queue<Object> logQueue = new ConcurrentLinkedQueue<Object>(); private final ConnectionListenable connectionObservable; private final HazelcastInstance hazelcastInstance; private final Address thisAddress; private final boolean logPartitions; private final boolean enabled; private final NodeExtension nodeExtension; public SystemLogPlugin(NodeEngineImpl nodeEngine) { this(nodeEngine.getProperties(), nodeEngine.getNode().connectionManager, nodeEngine.getHazelcastInstance(), nodeEngine.getLogger(SystemLogPlugin.class), nodeEngine.getNode().getNodeExtension()); } public SystemLogPlugin(HazelcastProperties properties, ConnectionListenable connectionObservable, HazelcastInstance hazelcastInstance, ILogger logger) { this(properties, connectionObservable, hazelcastInstance, logger, null); } public SystemLogPlugin(HazelcastProperties properties, ConnectionListenable connectionObservable, HazelcastInstance hazelcastInstance, ILogger logger, NodeExtension nodeExtension) { super(logger); this.connectionObservable = connectionObservable; this.hazelcastInstance = hazelcastInstance; this.thisAddress = getThisAddress(hazelcastInstance); this.logPartitions = properties.getBoolean(LOG_PARTITIONS); this.enabled = properties.getBoolean(ENABLED); this.nodeExtension = nodeExtension; } private Address getThisAddress(HazelcastInstance hazelcastInstance) { try { return hazelcastInstance.getCluster().getLocalMember().getAddress(); } catch (UnsupportedOperationException e) { return null; } } @Override public long getPeriodMillis() { if (!enabled) { return DiagnosticsPlugin.DISABLED; } return PERIOD_MILLIS; } @Override public void onStart() { logger.info("Plugin:active: logPartitions:" + logPartitions); connectionObservable.addConnectionListener(new ConnectionListenerImpl()); hazelcastInstance.getCluster().addMembershipListener(new MembershipListenerImpl()); if (logPartitions) { hazelcastInstance.getPartitionService().addMigrationListener(new MigrationListenerImpl()); } hazelcastInstance.getLifecycleService().addLifecycleListener(new LifecycleListenerImpl()); if (nodeExtension != null) { nodeExtension.registerListener(new ClusterVersionListenerImpl()); } } @Override public void run(DiagnosticsLogWriter writer) { for (; ; ) { Object item = logQueue.poll(); if (item == null) { return; } if (item instanceof LifecycleEvent) { render(writer, (LifecycleEvent) item); } else if (item instanceof MembershipEvent) { render(writer, (MembershipEvent) item); } else if (item instanceof MigrationEvent) { render(writer, (MigrationEvent) item); } else if (item instanceof ConnectionEvent) { ConnectionEvent event = (ConnectionEvent) item; render(writer, event); } else if (item instanceof Version) { render(writer, (Version) item); } } } private void render(DiagnosticsLogWriter writer, LifecycleEvent event) { writer.startSection("Lifecycle"); writer.writeEntry(event.getState().name()); writer.endSection(); } private void render(DiagnosticsLogWriter writer, MembershipEvent event) { switch (event.getEventType()) { case MembershipEvent.MEMBER_ADDED: writer.startSection("MemberAdded"); break; case MembershipEvent.MEMBER_REMOVED: writer.startSection("MemberRemoved"); break; default: return; } writer.writeKeyValueEntry("member", event.getMember().getAddress().toString()); // writing the members writer.startSection("Members"); Set<Member> members = event.getMembers(); if (members != null) { boolean first = true; for (Member member : members) { if (member.getAddress().equals(thisAddress)) { if (first) { writer.writeEntry(member.getAddress().toString() + ":this:master"); } else { writer.writeEntry(member.getAddress().toString() + ":this"); } } else if (first) { writer.writeEntry(member.getAddress().toString() + ":master"); } else { writer.writeEntry(member.getAddress().toString()); } first = false; } } writer.endSection(); // ending the outer section writer.endSection(); } private void render(DiagnosticsLogWriter writer, MigrationEvent event) { switch (event.getStatus()) { case STARTED: writer.startSection("MigrationStarted"); break; case COMPLETED: writer.startSection("MigrationCompleted"); break; case FAILED: writer.startSection("MigrationFailed"); break; default: return; } Member oldOwner = event.getOldOwner(); writer.writeKeyValueEntry("oldOwner", oldOwner == null ? "null" : oldOwner.getAddress().toString()); writer.writeKeyValueEntry("newOwner", event.getNewOwner().getAddress().toString()); writer.writeKeyValueEntry("partitionId", event.getPartitionId()); writer.endSection(); } @SuppressWarnings("ThrowableResultOfMethodCallIgnored") private void render(DiagnosticsLogWriter writer, ConnectionEvent event) { if (event.added) { writer.startSection("ConnectionAdded"); } else { writer.startSection("ConnectionRemoved"); } Connection connection = event.connection; writer.writeEntry(connection.toString()); writer.writeKeyValueEntry("type", connection.getType().name()); writer.writeKeyValueEntry("isAlive", connection.isAlive()); if (!event.added) { String closeReason = connection.getCloseReason(); Throwable closeCause = connection.getCloseCause(); if (closeReason == null && closeCause != null) { closeReason = closeCause.getMessage(); } writer.writeKeyValueEntry("closeReason", closeReason == null ? "Unknown" : closeReason); if (closeCause != null) { writer.startSection("CloseCause"); String s = closeCause.getClass().getName(); String message = closeCause.getMessage(); writer.writeEntry((message != null) ? (s + ": " + message) : s); for (StackTraceElement element : closeCause.getStackTrace()) { writer.writeEntry(element.toString()); } writer.endSection(); } } writer.endSection(); } private void render(DiagnosticsLogWriter writer, Version version) { writer.startSection("ClusterVersionChanged"); writer.writeEntry(version.toString()); writer.endSection(); } private class LifecycleListenerImpl implements LifecycleListener { @Override public void stateChanged(LifecycleEvent event) { logQueue.add(event); } } private static final class ConnectionEvent { final boolean added; final Connection connection; private ConnectionEvent(boolean added, Connection connection) { this.added = added; this.connection = connection; } } private class ConnectionListenerImpl implements ConnectionListener { @Override public void connectionAdded(Connection connection) { logQueue.add(new ConnectionEvent(true, connection)); } @Override public void connectionRemoved(Connection connection) { logQueue.add(new ConnectionEvent(false, connection)); } } private class MembershipListenerImpl extends MembershipAdapter { @Override public void memberAdded(MembershipEvent event) { logQueue.add(event); } @Override public void memberRemoved(MembershipEvent event) { logQueue.add(event); } } private class MigrationListenerImpl implements MigrationListener { @Override public void migrationStarted(MigrationEvent event) { logQueue.add(event); } @Override public void migrationCompleted(MigrationEvent event) { logQueue.add(event); } @Override public void migrationFailed(MigrationEvent event) { logQueue.add(event); } } private class ClusterVersionListenerImpl implements ClusterVersionListener { @Override public void onClusterVersionChange(Version newVersion) { logQueue.add(newVersion); } } }