/*******************************************************************************
* Copyright (c) 2011 - 2015 Wind River Systems, Inc. and others. All rights reserved.
* This program and the accompanying materials are made available under the terms
* of the Eclipse Public License v1.0 which accompanies this distribution, and is
* available at http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Wind River Systems - initial API and implementation
*******************************************************************************/
package org.eclipse.tcf.te.tcf.log.core.internal.listener;
import java.io.FileWriter;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.eclipse.core.runtime.Assert;
import org.eclipse.osgi.util.NLS;
import org.eclipse.tcf.core.AbstractChannel.TraceListener;
import org.eclipse.tcf.protocol.IChannel;
import org.eclipse.tcf.protocol.IPeer;
import org.eclipse.tcf.protocol.Protocol;
import org.eclipse.tcf.services.IDiagnostics;
import org.eclipse.tcf.te.runtime.concurrent.util.ExecutorsUtil;
import org.eclipse.tcf.te.tcf.core.util.JSONUtils;
import org.eclipse.tcf.te.tcf.log.core.activator.CoreBundleActivator;
import org.eclipse.tcf.te.tcf.log.core.events.MonitorEvent;
import org.eclipse.tcf.te.tcf.log.core.interfaces.IPreferenceKeys;
import org.eclipse.tcf.te.tcf.log.core.interfaces.ITracing;
import org.eclipse.tcf.te.tcf.log.core.internal.nls.Messages;
import org.eclipse.tcf.te.tcf.log.core.manager.LogManager;
/**
* TCF logging channel trace listener implementation.
*/
public final class ChannelTraceListener implements TraceListener {
/**
* Time format representing time with milliseconds.
*/
public final DateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss.SSS"); //$NON-NLS-1$
/**
* Time format representing date and time with milliseconds.
*/
public final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); //$NON-NLS-1$
// Reference to the channel
/* default */ final IChannel channel;
// The log name
/* default */ final String logname;
/* default */ final boolean reverseReceived;
/**
* Constructor.
*
* @param logname The log name or <code>null</code>.
* @param channel The channel. Must not be <code>null</code>.
*/
public ChannelTraceListener(String logname, IChannel channel) {
this.logname = logname;
Assert.isNotNull(channel);
this.channel = channel;
reverseReceived = channel.getRemotePeer().getName() != null && channel.getRemotePeer().getName().endsWith("Command Server"); //$NON-NLS-1$
}
/**
* Return the associated channel.
*
* @return The channel instance.
*/
protected final IChannel getChannel() {
return channel;
}
/* (non-Javadoc)
* @see org.eclipse.tcf.core.AbstractChannel.TraceListener#onChannelClosed(java.lang.Throwable)
*/
@Override
public void onChannelClosed(final Throwable error) {
Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$
if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITracing.ID_TRACE_CHANNEL_TRACE_LISTENER)) {
CoreBundleActivator.getTraceHandler().trace("TraceListener.onChannelClosed ( " + error + " )", //$NON-NLS-1$ //$NON-NLS-2$
ITracing.ID_TRACE_CHANNEL_TRACE_LISTENER, this);
}
// Determine the remote peer from the channel
final IPeer peer = channel.getRemotePeer();
if (peer == null) return;
// Determine the date and time of the message before spawning to the log thread.
final String date = DATE_FORMAT.format(new Date(System.currentTimeMillis()));
// This method is called in the TCF event dispatch thread. There
// is no need that the logging itself keeps the TCF event dispatch
// thread busy. Execute the logging itself in a separate thread but
// still maintain the order of the messages.
ExecutorsUtil.execute(new Runnable() {
@Override
public void run() {
final String message = NLS.bind(Messages.ChannelTraceListener_channelClosed_message,
new Object[] {
date,
Integer.toHexString(channel.hashCode()),
error
});
// Get the file writer
FileWriter writer = LogManager.getInstance().getWriter(logname, peer);
if (writer != null) {
try {
writer.write(message);
writer.write("\n"); //$NON-NLS-1$
writer.flush();
} catch (IOException e) {
/* ignored on purpose */
}
}
LogManager.getInstance().monitor(peer, MonitorEvent.Type.CLOSE, new MonitorEvent.Message('F', message));
}
});
}
/* (non-Javadoc)
* @see org.eclipse.tcf.core.AbstractChannel.TraceListener#onMessageReceived(char, java.lang.String, java.lang.String, java.lang.String, byte[])
*/
@Override
public void onMessageReceived(final char type, final String token, final String service, final String name, final byte[] data) {
Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$
if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITracing.ID_TRACE_CHANNEL_TRACE_LISTENER)) {
CoreBundleActivator.getTraceHandler().trace("TraceListener.onMessageReceived ( " + type //$NON-NLS-1$
+ ", " + token + ", " + service + ", " + name + ", ... )", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
ITracing.ID_TRACE_CHANNEL_TRACE_LISTENER, this);
}
// Determine the remote peer from the channel
final IPeer peer = channel.getRemotePeer();
if (peer == null) return;
// This method is called in the TCF event dispatch thread. There
// is no need that the logging itself keeps the TCF event dispatch
// thread busy. Execute the logging itself in a separate thread but
// still maintain the order of the messages.
ExecutorsUtil.execute(new Runnable() {
@Override
public void run() {
doLogMessage(peer, type, token, service, name, data, reverseReceived ? false : true);
}
});
}
/* (non-Javadoc)
* @see org.eclipse.tcf.core.AbstractChannel.TraceListener#onMessageSent(char, java.lang.String, java.lang.String, java.lang.String, byte[])
*/
@Override
public void onMessageSent(final char type, final String token, final String service, final String name, final byte[] data) {
Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$
if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITracing.ID_TRACE_CHANNEL_TRACE_LISTENER)) {
CoreBundleActivator.getTraceHandler().trace("TraceListener.onMessageSent ( " + type //$NON-NLS-1$
+ ", " + token + ", " + service + ", " + name + ", ... )", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
ITracing.ID_TRACE_CHANNEL_TRACE_LISTENER, this);
}
// Determine the remote peer from the channel
final IPeer peer = channel.getRemotePeer();
if (peer == null) return;
// This method is called in the TCF event dispatch thread. There
// is no need that the logging itself keeps the TCF event dispatch
// thread busy. Execute the logging itself in a separate thread but
// still maintain the order of the messages.
ExecutorsUtil.execute(new Runnable() {
@Override
public void run() {
doLogMessage(peer, type, token, service, name, data, reverseReceived ? true : false);
}
});
}
/**
* Helper method to output the message to the logger.
*/
/* default */ void doLogMessage(final IPeer peer, final char type, String token, String service, String name, byte[] data, boolean received) {
Assert.isNotNull(peer);
Assert.isTrue(ExecutorsUtil.isExecutorThread(), "Illegal Thread Access"); //$NON-NLS-1$
// Filter out the locator service messages
boolean locatorEvents = CoreBundleActivator.getScopedPreferences().getBoolean(IPreferenceKeys.PREF_SHOW_LOCATOR_EVENTS);
if (!locatorEvents && service != null && service.toLowerCase().equals("locator")) { //$NON-NLS-1$
return;
}
// Filter out the heart beat messages if not overwritten by the preferences
boolean showHeartbeats = CoreBundleActivator.getScopedPreferences().getBoolean(IPreferenceKeys.PREF_SHOW_HEARTBEATS);
if (!showHeartbeats && name != null && name.toLowerCase().contains("heartbeat")) { //$NON-NLS-1$
return;
}
// Filter out framework events if not overwritten by the preferences
boolean frameworkEvents = CoreBundleActivator.getScopedPreferences().getBoolean(IPreferenceKeys.PREF_SHOW_FRAMEWORK_EVENTS);
if (!frameworkEvents && type == 'F') {
return;
}
// Decode the arguments again for tracing purpose
String args = JSONUtils.decodeStringFromByteArray(data);
// Filter out 'Diagnostic echo "ping"' and response
if ((type == 'C' && IDiagnostics.NAME.equals(service) && "echo".equals(name) && "\"ping\"".equals(args)) //$NON-NLS-1$ //$NON-NLS-2$
|| (type == 'R' && service == null && name == null && "\"ping\"".equals(args))) { //$NON-NLS-1$
return;
}
// Format the message
final String message = formatMessage(type, token, service, name, args, received);
// Get the file writer
FileWriter writer = LogManager.getInstance().getWriter(logname, peer);
if (writer != null) {
try {
writer.write(message);
writer.write("\n"); //$NON-NLS-1$
writer.flush();
} catch (IOException e) {
/* ignored on purpose */
}
}
LogManager.getInstance().monitor(peer, MonitorEvent.Type.ACTIVITY, new MonitorEvent.Message(type, message));
}
/**
* Format the trace message.
*/
private String formatMessage(char type, String token, String service, String name, String args, boolean received) {
// Get the current time stamp
String time = TIME_FORMAT.format(new Date(System.currentTimeMillis()));
// Construct the full message
//
// The message format is: <time>: [<---|--->] <type> <token> <service>#<name> <args>
StringBuilder message = new StringBuilder();
message.append(time).append(":"); //$NON-NLS-1$
message.append(" [").append(Integer.toHexString(channel.hashCode())).append("]"); //$NON-NLS-1$ //$NON-NLS-2$
message.append(" ").append(received ? "<---" : "--->"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
message.append(" ").append(Character.valueOf(type)); //$NON-NLS-1$
if (token != null) message.append(" ").append(token); //$NON-NLS-1$
if (service != null) message.append(" ").append(service); //$NON-NLS-1$
if (name != null) message.append(" ").append(name); //$NON-NLS-1$
if (args != null && args.trim().length() > 0) message.append(" ").append(args.trim()); //$NON-NLS-1$
return message.toString();
}
}