IDEMPIERE-4072 iDempiere Monitor: Implement server and cache details for cluster node. Implement multi block streaming of log file from another server node.
This commit is contained in:
parent
d2ec523b00
commit
730d221357
|
@ -0,0 +1,134 @@
|
||||||
|
/**********************************************************************
|
||||||
|
* This file is part of iDempiere ERP Open Source *
|
||||||
|
* http://www.idempiere.org *
|
||||||
|
* *
|
||||||
|
* Copyright (C) Contributors *
|
||||||
|
* *
|
||||||
|
* This program is free software; you can redistribute it and/or *
|
||||||
|
* modify it under the terms of the GNU General Public License *
|
||||||
|
* as published by the Free Software Foundation; either version 2 *
|
||||||
|
* of the License, or (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* This program is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||||
|
* GNU General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU General Public License *
|
||||||
|
* along with this program; if not, write to the Free Software *
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, *
|
||||||
|
* MA 02110-1301, USA. *
|
||||||
|
* *
|
||||||
|
* Contributors: *
|
||||||
|
* - Trek Global Corporation *
|
||||||
|
* - Heng Sin Low *
|
||||||
|
**********************************************************************/
|
||||||
|
package org.idempiere.server.cluster.callable;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
import org.compiere.util.CLogFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author hengsin
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class GetLogInfoCallable extends LogFileCallable implements Callable<GetLogInfoCallable.LogInfo>, Serializable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* generated serial id
|
||||||
|
*/
|
||||||
|
private static final long serialVersionUID = 33969865104073117L;
|
||||||
|
|
||||||
|
private String fileName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param fileName
|
||||||
|
*/
|
||||||
|
public GetLogInfoCallable(String fileName) {
|
||||||
|
this.fileName = fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LogInfo call() throws Exception {
|
||||||
|
CLogFile fileHandler = CLogFile.get(false, null, false);
|
||||||
|
//
|
||||||
|
// Display current log File
|
||||||
|
if (fileHandler != null && fileHandler.getFileName().equals(fileName))
|
||||||
|
fileHandler.flush();
|
||||||
|
|
||||||
|
File file = new File(fileName);
|
||||||
|
if (!isAccessible(file))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
long length = file.length();
|
||||||
|
int noOfBlocks = 1;
|
||||||
|
if (length > BLOCK_SIZE) {
|
||||||
|
int v = (int) (((length - 1) / BLOCK_SIZE) + 1);
|
||||||
|
if (v == 0)
|
||||||
|
v = 1;
|
||||||
|
noOfBlocks = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new LogInfo(fileName, length, BLOCK_SIZE, noOfBlocks);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class LogInfo implements Serializable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* generated serial id
|
||||||
|
*/
|
||||||
|
private static final long serialVersionUID = -4154825003947713729L;
|
||||||
|
|
||||||
|
private String fileName;
|
||||||
|
private long length;
|
||||||
|
private int blockSize;
|
||||||
|
private int noOfBlocks;
|
||||||
|
|
||||||
|
public LogInfo(String fileName, long length, int blockSize, int noOfBlocks) {
|
||||||
|
super();
|
||||||
|
this.fileName = fileName;
|
||||||
|
this.length = length;
|
||||||
|
this.blockSize = blockSize;
|
||||||
|
this.noOfBlocks = noOfBlocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the serialversionuid
|
||||||
|
*/
|
||||||
|
public static long getSerialversionuid() {
|
||||||
|
return serialVersionUID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the fileName
|
||||||
|
*/
|
||||||
|
public String getFileName() {
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the length
|
||||||
|
*/
|
||||||
|
public long getLength() {
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the blockSize
|
||||||
|
*/
|
||||||
|
public int getBlockSize() {
|
||||||
|
return blockSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the noOfBlocks
|
||||||
|
*/
|
||||||
|
public int getNoOfBlocks() {
|
||||||
|
return noOfBlocks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
/**********************************************************************
|
||||||
|
* This file is part of iDempiere ERP Open Source *
|
||||||
|
* http://www.idempiere.org *
|
||||||
|
* *
|
||||||
|
* Copyright (C) Contributors *
|
||||||
|
* *
|
||||||
|
* This program is free software; you can redistribute it and/or *
|
||||||
|
* modify it under the terms of the GNU General Public License *
|
||||||
|
* as published by the Free Software Foundation; either version 2 *
|
||||||
|
* of the License, or (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* This program is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||||
|
* GNU General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU General Public License *
|
||||||
|
* along with this program; if not, write to the Free Software *
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, *
|
||||||
|
* MA 02110-1301, USA. *
|
||||||
|
* *
|
||||||
|
* Contributors: *
|
||||||
|
* - Trek Global Corporation *
|
||||||
|
* - Heng Sin Low *
|
||||||
|
**********************************************************************/
|
||||||
|
package org.idempiere.server.cluster.callable;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
import org.compiere.Adempiere;
|
||||||
|
import org.compiere.util.CLogFile;
|
||||||
|
import org.compiere.util.CLogger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author hengsin
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public abstract class LogFileCallable {
|
||||||
|
|
||||||
|
protected static final int BLOCK_SIZE = 1024 * 1024 * 5;
|
||||||
|
protected static final String s_dirAccessFileName = "dirAccess.txt";
|
||||||
|
|
||||||
|
protected CLogger log = CLogger.getCLogger(getClass());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* default constructor
|
||||||
|
*/
|
||||||
|
public LogFileCallable() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return list of files accessible
|
||||||
|
*/
|
||||||
|
protected ArrayList<File> getDirAcessList() {
|
||||||
|
final ArrayList<File> dirAccessList = new ArrayList<File>();
|
||||||
|
|
||||||
|
// by default has access to log directory
|
||||||
|
CLogFile fileHandler = CLogFile.get(true, null, false);
|
||||||
|
File logDir = fileHandler.getLogDirectory();
|
||||||
|
dirAccessList.add(logDir);
|
||||||
|
|
||||||
|
// load from dirAccess.properties file
|
||||||
|
String dirAccessPathName = Adempiere.getAdempiereHome() + File.separator + s_dirAccessFileName;
|
||||||
|
File dirAccessFile = new File(dirAccessPathName);
|
||||||
|
if (dirAccessFile.exists()) {
|
||||||
|
try (BufferedReader br = new BufferedReader(new FileReader(dirAccessFile))) {
|
||||||
|
while (true) {
|
||||||
|
String pathName = br.readLine();
|
||||||
|
if (pathName == null)
|
||||||
|
break;
|
||||||
|
File pathDir = new File(pathName);
|
||||||
|
if (pathDir.exists() && !dirAccessList.contains(pathDir))
|
||||||
|
dirAccessList.add(pathDir);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.log(Level.SEVERE, dirAccessPathName + " - " + e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dirAccessList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param file
|
||||||
|
* @return true if log file is accessible, false otherwise
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
protected boolean isAccessible(File file) throws IOException {
|
||||||
|
if (!file.exists() || !file.canRead()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean found = false;
|
||||||
|
ArrayList<File> dirAccessList = getDirAcessList();
|
||||||
|
|
||||||
|
for (File dir : dirAccessList) {
|
||||||
|
if (file.getCanonicalPath().startsWith(dir.getAbsolutePath())) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
log.warning("Couldn't find file in directories that allowed to access");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,42 +25,49 @@
|
||||||
**********************************************************************/
|
**********************************************************************/
|
||||||
package org.idempiere.server.cluster.callable;
|
package org.idempiere.server.cluster.callable;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.RandomAccessFile;
|
||||||
import java.io.FileReader;
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.FileChannel;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
import org.compiere.Adempiere;
|
|
||||||
import org.compiere.util.CLogFile;
|
import org.compiere.util.CLogFile;
|
||||||
import org.compiere.util.CLogger;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author hengsin
|
* @author hengsin
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class ReadLogCallable implements Callable<byte[]>, Serializable {
|
public class ReadLogCallable extends LogFileCallable implements Callable<byte[]>, Serializable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* generated serial id
|
* generated serial id
|
||||||
*/
|
*/
|
||||||
private static final long serialVersionUID = 33969865104073117L;
|
private static final long serialVersionUID = 33969865104073117L;
|
||||||
private static final String s_dirAccessFileName = "dirAccess.txt";
|
|
||||||
private static CLogger log = CLogger.getCLogger(ReadLogCallable.class);
|
|
||||||
|
|
||||||
private String fileName;
|
private String fileName;
|
||||||
|
private int blockNo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* default constructor
|
*
|
||||||
|
* @param fileName
|
||||||
*/
|
*/
|
||||||
public ReadLogCallable(String fileName) {
|
public ReadLogCallable(String fileName) {
|
||||||
this.fileName = fileName;
|
this(fileName, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param fileName
|
||||||
|
* @param blockNo 0 base block index
|
||||||
|
*/
|
||||||
|
public ReadLogCallable(String fileName, int blockNo) {
|
||||||
|
this.fileName = fileName;
|
||||||
|
this.blockNo = blockNo;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] call() throws Exception {
|
public byte[] call() throws Exception {
|
||||||
CLogFile fileHandler = CLogFile.get(false, null, false);
|
CLogFile fileHandler = CLogFile.get(false, null, false);
|
||||||
|
@ -70,73 +77,37 @@ public class ReadLogCallable implements Callable<byte[]>, Serializable {
|
||||||
fileHandler.flush();
|
fileHandler.flush();
|
||||||
|
|
||||||
File file = new File(fileName);
|
File file = new File(fileName);
|
||||||
if (!file.exists() || !file.canRead()) {
|
if (!isAccessible(file))
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
if (file.length() == 0) {
|
if (file.length() == 0) {
|
||||||
return new byte[0];
|
return new byte[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean found = false;
|
try(RandomAccessFile raf = new RandomAccessFile(file, "r"); FileChannel channel = raf.getChannel();) {
|
||||||
ArrayList<File> dirAccessList = getDirAcessList();
|
if (blockNo > 0) {
|
||||||
|
channel.position(blockNo * BLOCK_SIZE);
|
||||||
for (File dir : dirAccessList) {
|
}
|
||||||
if (file.getCanonicalPath().startsWith(dir.getAbsolutePath())) {
|
ByteBuffer buffer = ByteBuffer.allocate(1024);
|
||||||
found = true;
|
ByteArrayOutputStream baos = new ByteArrayOutputStream(2048);
|
||||||
break;
|
int bytesRead = 0;
|
||||||
|
int totalRead = 0;
|
||||||
|
while((bytesRead = channel.read(buffer)) > 0) {
|
||||||
|
totalRead += bytesRead;
|
||||||
|
if (totalRead > BLOCK_SIZE) {
|
||||||
|
int diff = BLOCK_SIZE - totalRead;
|
||||||
|
bytesRead = bytesRead - diff;
|
||||||
|
totalRead = BLOCK_SIZE;
|
||||||
|
}
|
||||||
|
baos.write(buffer.array(), 0, bytesRead);
|
||||||
|
buffer.clear();
|
||||||
|
if (totalRead == BLOCK_SIZE)
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!found) {
|
|
||||||
log.warning("Couldn't find file in directories that allowed to access");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stream Log
|
|
||||||
try (FileInputStream fis = new FileInputStream(file)) {
|
|
||||||
int bufferSize = 2048; // 2k Buffer
|
|
||||||
byte[] buffer = new byte[bufferSize];
|
|
||||||
//
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(bufferSize);
|
|
||||||
//
|
|
||||||
int read = 0;
|
|
||||||
while ((read = fis.read(buffer)) > 0)
|
|
||||||
baos.write(buffer, 0, read);
|
|
||||||
return baos.toByteArray();
|
return baos.toByteArray();
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
log.log(Level.SEVERE, "stream" + ex);
|
log.log(Level.SEVERE, "stream" + ex);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private ArrayList<File> getDirAcessList() {
|
|
||||||
final ArrayList<File> dirAccessList = new ArrayList<File>();
|
|
||||||
|
|
||||||
// by default has access to log directory
|
|
||||||
CLogFile fileHandler = CLogFile.get(true, null, false);
|
|
||||||
File logDir = fileHandler.getLogDirectory();
|
|
||||||
dirAccessList.add(logDir);
|
|
||||||
|
|
||||||
// load from dirAccess.properties file
|
|
||||||
String dirAccessPathName = Adempiere.getAdempiereHome() + File.separator + s_dirAccessFileName;
|
|
||||||
File dirAccessFile = new File(dirAccessPathName);
|
|
||||||
if (dirAccessFile.exists()) {
|
|
||||||
try {
|
|
||||||
BufferedReader br = new BufferedReader(new FileReader(dirAccessFile));
|
|
||||||
while (true) {
|
|
||||||
String pathName = br.readLine();
|
|
||||||
if (pathName == null)
|
|
||||||
break;
|
|
||||||
File pathDir = new File(pathName);
|
|
||||||
if (pathDir.exists() && !dirAccessList.contains(pathDir))
|
|
||||||
dirAccessList.add(pathDir);
|
|
||||||
}
|
|
||||||
br.close();
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.log(Level.SEVERE, dirAccessPathName + " - " + e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return dirAccessList;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,6 +88,7 @@ import org.idempiere.distributed.IClusterMember;
|
||||||
import org.idempiere.distributed.IClusterService;
|
import org.idempiere.distributed.IClusterService;
|
||||||
import org.idempiere.server.cluster.ClusterServerMgr;
|
import org.idempiere.server.cluster.ClusterServerMgr;
|
||||||
import org.idempiere.server.cluster.callable.DeleteLogsCallable;
|
import org.idempiere.server.cluster.callable.DeleteLogsCallable;
|
||||||
|
import org.idempiere.server.cluster.callable.GetLogInfoCallable;
|
||||||
import org.idempiere.server.cluster.callable.ReadLogCallable;
|
import org.idempiere.server.cluster.callable.ReadLogCallable;
|
||||||
import org.idempiere.server.cluster.callable.RotateLogCallable;
|
import org.idempiere.server.cluster.callable.RotateLogCallable;
|
||||||
import org.idempiere.server.cluster.callable.SetTraceLevelCallable;
|
import org.idempiere.server.cluster.callable.SetTraceLevelCallable;
|
||||||
|
@ -442,18 +443,27 @@ public class AdempiereMonitor extends HttpServlet
|
||||||
|
|
||||||
if (!Util.isEmpty(nodeId, true))
|
if (!Util.isEmpty(nodeId, true))
|
||||||
{
|
{
|
||||||
ReadLogCallable callable = new ReadLogCallable(traceCmd);
|
|
||||||
|
GetLogInfoCallable infoCallable = new GetLogInfoCallable(traceCmd);
|
||||||
|
IClusterService service = ClusterServerMgr.getClusterService();
|
||||||
|
IClusterMember member = ClusterServerMgr.getClusterMember(nodeId);
|
||||||
try {
|
try {
|
||||||
byte[] contents = ClusterServerMgr.getClusterService().execute(callable, ClusterServerMgr.getClusterMember(nodeId)).get();
|
GetLogInfoCallable.LogInfo logInfo = service.execute(infoCallable, member).get();
|
||||||
if (contents == null || contents.length == 0)
|
if (logInfo == null || logInfo.getLength() == 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
try(ServletOutputStream out = response.getOutputStream ())
|
try(ServletOutputStream out = response.getOutputStream ())
|
||||||
{
|
{
|
||||||
response.setContentType("text/plain");
|
response.setContentType("text/plain");
|
||||||
response.setBufferSize(2048);
|
response.setBufferSize(2048);
|
||||||
response.setContentLength(contents.length);
|
response.setContentLength((int) logInfo.getLength());
|
||||||
out.write(contents);
|
for(int i = 0; i < logInfo.getNoOfBlocks(); i++) {
|
||||||
|
ReadLogCallable callable = new ReadLogCallable(logInfo.getFileName(), i);
|
||||||
|
byte[] contents = service.execute(callable, member).get();
|
||||||
|
if (contents == null || contents.length == 0)
|
||||||
|
break;
|
||||||
|
out.write(contents);
|
||||||
|
}
|
||||||
out.flush();
|
out.flush();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
Loading…
Reference in New Issue