我正在尝试编写日志工具,该工具将通过ssh连接到少数服务器,打开指定的日志文件并将结果打印到System.out.print
。目前,我已经实现了从一个来源获取日志的功能。从SSHManager
类开始,仅使用Jsch
来实现。
public void tailLogFile() {
System.out.println("Starting to monitor logs for " + server.getIp());
String command = "tail -f " + server.getLogFilePath();
try {
Channel channel = getSession().openChannel("exec");
((ChannelExec)channel).setCommand(command);
InputStream commandOutput = channel.getInputStream();
channel.connect();
int readByte = commandOutput.read();
while(readByte != 0xffffffff) {
readByte = commandOutput.read();
System.out.print(server.getFontColor().toString() + (char)readByte);
}
channel.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
我猜想其余的内容都没有意义,它将彩色的日志从SSH打印到我的System.out
。但是,此程序的主要目的是将多个文件记录到一个位置。所以我试着跟随
for(SSHManager sshManager : getSshManagers()) {
sshManager.tailLogFile();
}
它现在不起作用,它从print
的第一次迭代开始for-loop
个日志,并且由于while
中的SSHManager.tailLogFile()
没有终止,它会继续打印日志从第一个来源。可以想象,我希望SSHManager
的n个实例共享System.out
,并同时提供所有来源的输出。我想知道最简单的方法是什么?我需要参与并发吗?
答案 0 :(得分:1)
您必须以非阻塞方式连续读取所有输出流。
您可以使用InputStream.available()
,如下所示:
ArrayList<ChannelExec> channels = new ArrayList<ChannelExec>();
ChannelExec channel;
channel = (ChannelExec)session1.openChannel("exec");
channel.setCommand(
"echo one && sleep 2 && echo two && sleep 2 && echo three");
channel.connect();
channels.add(channel);
channel = (ChannelExec)session2.openChannel("exec");
channel.setCommand(
"sleep 1 && echo eins && sleep 2 && echo zwei && sleep 2 && echo drei");
channel.connect();
channels.add(channel);
ArrayList<InputStream> outputs = new ArrayList<InputStream>();
for (int i = 0; i < channels.size(); i++)
{
outputs.add(channels.get(i).getInputStream());
}
Boolean anyOpened = true;
while (anyOpened)
{
anyOpened = false;
for (int i = 0; i < channels.size(); i++)
{
channel = channels.get(i);
if (!channel.isClosed())
{
anyOpened = true;
InputStream output = outputs.get(i);
while (output.available() > 0)
{
int readByte = output.read();
System.out.print((char)readByte);
}
}
}
}
将帮助您(假设使用Linux服务器):
one
eins
two
zwei
three
drei
请注意,答案按字节/字符读取输出。它不能保证在切换到另一个会话之前获得完整的发言。因此,您可能最终混合了来自不同会话的部分线路。在将缓冲区打印到输出之前,您应该在缓冲区中累积字节/字符,寻找新的一行。
答案 1 :(得分:1)
对于我来说,我更喜欢为要写入的通道提供一个OutputStream,而不是从它提供给我的InputStream中读取。
我将定义如下内容:
protected class MyOutputStream extends OutputStream {
private StringBuilder stringBuilder = new StringBuilder();
private Object lock;
public MyOutputStream(Object lock) {
this.lock = lock;
}
@Override
public void write(int b) throws IOException {
this.stringBuilder.append(b);
if (b == '\n') {
this.parseOutput();
}
}
@Override
public void write(byte[] b) throws IOException {
String str = new String(b);
this.stringBuilder.append(str);
if (str.contains("\n")) {
this.parseOutput();
}
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
String str = new String(b, off, len);
this.stringBuilder.append(str);
if (str.contains("\n")) {
this.parseOutput();
}
}
@Override
public void flush() throws IOException {
}
@Override
public void close() throws IOException {
LOGGER.info("My output stream has closed");
}
private void parseOutput() throws IOException {
// we split the text but we make sure not to drop the empty strings or the trailing char
String[] lines = this.stringBuilder.toString().split("\n", -1);
int num = 0;
int last = lines.length - 1;
String trunkated = null;
// synchronize the writing
synchronized (this.lock) {
for (String line : lines) {
// Dont treat the trunkated last line
if (num == last && line.length() > 0) {
trunkated = line;
break;
}
// write a full line
System.out.print(line);
num++;
}
}
// flush the buffer and keep the last trunkated line
this.stringBuilder.setLength(0);
if (trunkated != null) {
this.stringBuilder.append(trunkated);
}
}
}
因此用法如下:
ArrayList<ChannelExec> channels = new ArrayList<ChannelExec>();
Object lock = new Object();
ChannelExec channel;
channel = (ChannelExec)session1.openChannel("exec");
channel.setCommand("echo one && sleep 2 && echo two && sleep 2 && echo three");
channel.setOutputStream(new MyOutputStream(lock));
channel.connect();
channels.add(channel);
channel = (ChannelExec)session2.openChannel("exec");
channel.setCommand("sleep 1 && echo eins && sleep 2 && echo zwei && sleep 2 && echo drei");
channel.setOutputStream(new MyOutputStream(lock));
channel.connect();
channels.add(channel);
for (ChannelExec channel : channels) {
while (!channel.isClosed()) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
好处是,您可以受益于Jsch通道中已经存在的多线程,然后避免了泛滥日志的问题,该问题不会让其他日志被打印。 使用不同的流类处理每个日志也更加容易和清晰。 StringBuilder是累积字符的好方法,直到获得完整的行为止。
还请注意,一次编写整行避免了每个char调用一个函数,并且避免了将已写char的数量乘以系统 server.getFontColor()。toString()
请确保正确锁定,我编写的代码未经测试。