🔨 查看执行日志.

This commit is contained in:
lijiahang
2024-03-18 17:51:13 +08:00
parent 2451f4af82
commit 6c0f20a6de
13 changed files with 302 additions and 11 deletions

View File

@@ -31,4 +31,6 @@ public interface Const extends com.orion.lang.constant.Const, FieldConst, CnCons
Integer DEFAULT_VERSION = 1;
String ERROR_LOG = "error.log";
}

View File

@@ -89,4 +89,6 @@ public interface ErrorMessage {
String CHECK_AUTHORIZED_HOST = "请选择已授权的主机";
String FILE_READ_ERROR = "文件读取失败";
}

View File

@@ -6,8 +6,8 @@ Authorization: {{token}}
{
"description": 1,
"timeout": 10,
"command": "echo @{{ hostAddress }}\nsleep 10\necho @{{ str }}",
"parameter": "{\"str\":\"end\"}",
"command": "echo 这是日志@{{ hostAddress }}\nsleep 1\necho @{{ hostName }}",
"parameterSchema": "[]",
"hostIdList": [1,7]
}
@@ -22,4 +22,19 @@ Authorization: {{token}}
}
### 查看执行日志
POST {{baseUrl}}/asset/exec/tail-log
Content-Type: application/json
Authorization: {{token}}
{
"execId": 1
}
### 下载执行日志文件
GET {{baseUrl}}/asset/exec/download-log?id=83
Authorization: {{token}}
###

View File

@@ -7,6 +7,7 @@ import com.orion.ops.framework.web.core.annotation.RestWrapper;
import com.orion.ops.module.asset.define.operator.ExecOperatorType;
import com.orion.ops.module.asset.entity.request.exec.ExecCommandRequest;
import com.orion.ops.module.asset.entity.request.exec.ExecInterruptRequest;
import com.orion.ops.module.asset.entity.request.exec.ExecLogTailRequest;
import com.orion.ops.module.asset.entity.request.exec.ReExecCommandRequest;
import com.orion.ops.module.asset.entity.vo.ExecCommandVO;
import com.orion.ops.module.asset.service.ExecService;
@@ -18,6 +19,7 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
/**
* 批量执行
@@ -74,6 +76,19 @@ public class ExecController {
return HttpWrapper.ok();
}
// TODO tail log
@PostMapping("/tail-log")
@Operation(summary = "查看批量执行日志")
@PreAuthorize("@ss.hasAnyPermission('asset:exec:exec-command', 'asset:exec-log:query')")
public String getExecLogTailToken(@Validated @RequestBody ExecLogTailRequest request) {
return execService.getExecLogTailToken(request);
}
@OperatorLog(ExecOperatorType.DOWNLOAD_HOST_LOG)
@GetMapping("/download-log")
@Operation(summary = "下载执行日志文件")
@PreAuthorize("@ss.hasAnyPermission('asset:exec:exec-command', 'asset:exec-log:query')")
public void downloadLogFile(@RequestParam("id") Long id, HttpServletResponse response) {
execService.downloadLogFile(id, response);
}
}

View File

@@ -0,0 +1,27 @@
package com.orion.ops.module.asset.define.cache;
import com.orion.lang.define.cache.key.CacheKeyBuilder;
import com.orion.lang.define.cache.key.CacheKeyDefine;
import com.orion.lang.define.cache.key.struct.RedisCacheStruct;
import com.orion.ops.module.asset.entity.dto.ExecLogTailDTO;
import java.util.concurrent.TimeUnit;
/**
* 命令执行服务缓存 key
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/3/18 16:31
*/
public interface ExecCacheKeyDefine {
CacheKeyDefine EXEC_TAIL = new CacheKeyBuilder()
.key("exec:tail:{}")
.desc("命令执行日志查看 ${token}")
.type(ExecLogTailDTO.class)
.struct(RedisCacheStruct.STRING)
.timeout(5, TimeUnit.MINUTES)
.build();
}

View File

@@ -4,8 +4,7 @@ import com.orion.ops.framework.biz.operator.log.core.annotation.Module;
import com.orion.ops.framework.biz.operator.log.core.factory.InitializingOperatorTypes;
import com.orion.ops.framework.biz.operator.log.core.model.OperatorType;
import static com.orion.ops.framework.biz.operator.log.core.enums.OperatorRiskLevel.H;
import static com.orion.ops.framework.biz.operator.log.core.enums.OperatorRiskLevel.M;
import static com.orion.ops.framework.biz.operator.log.core.enums.OperatorRiskLevel.*;
/**
* 批量执行 操作记录类型
@@ -23,21 +22,24 @@ public class ExecOperatorType extends InitializingOperatorTypes {
public static final String INTERRUPT_HOST = "exec:interrupt-host";
public static final String DELETE_HOST_LOG = "exec:delete-host-log";
public static final String DELETE_LOG = "exec:delete-log";
public static final String CLEAR_LOG = "exec:clear-log";
public static final String DELETE_HOST_LOG = "exec:delete-host-log";
public static final String DOWNLOAD_HOST_LOG = "exec:download-host-log";
@Override
public OperatorType[] types() {
return new OperatorType[]{
new OperatorType(M, EXEC_COMMAND, "执行主机命令"),
new OperatorType(M, INTERRUPT_EXEC, "中断执行命令"),
new OperatorType(M, INTERRUPT_HOST, "中断主机执行命令 ${logId} ${hostName}"),
new OperatorType(H, DELETE_HOST_LOG, "删除主机执行记录 ${logId} ${hostName}"),
new OperatorType(H, DELETE_LOG, "删除执行记录 ${count} 条"),
new OperatorType(H, CLEAR_LOG, "清理执行记录 ${count} 条"),
new OperatorType(H, DELETE_HOST_LOG, "删除主机执行记录 ${logId} ${hostName}"),
new OperatorType(L, DOWNLOAD_HOST_LOG, "下载主机执行日志 ${logId} ${hostName}"),
};
}

View File

@@ -0,0 +1,34 @@
package com.orion.ops.module.asset.entity.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 执行主机日志查看 缓存对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/3/18 16:34
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "ExecHostLogTailDTO", description = "执行主机日志查看 缓存对象")
public class ExecHostLogTailDTO implements Serializable {
@Schema(description = "id")
private Long id;
@Schema(description = "hostId")
private Long hostId;
@Schema(description = "文件路径")
private String path;
}

View File

@@ -0,0 +1,38 @@
package com.orion.ops.module.asset.entity.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
/**
* 执行日志查看 缓存对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/3/18 16:34
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "ExecLogTailDTO", description = "执行日志查看 缓存对象")
public class ExecLogTailDTO implements Serializable {
@Schema(description = "id")
private Long id;
@Schema(description = "用户id")
private Long userId;
@Schema(description = "token")
private String token;
@Schema(description = "执行主机")
private List<ExecHostLogTailDTO> hosts;
}

View File

@@ -0,0 +1,33 @@
package com.orion.ops.module.asset.entity.request.exec;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* 执行日志查看 请求对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/3/11 11:46
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "ExecLogTailRequest", description = "执行日志查看 请求对象")
public class ExecLogTailRequest {
@NotNull
@Schema(description = "执行id")
private Long execId;
@Schema(description = "执行主机id")
private List<Long> execHostIdList;
}

View File

@@ -51,7 +51,6 @@ public class TerminalAccessInterceptor implements HandshakeInterceptor {
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
log.info("afterHandshake");
}
}

View File

@@ -1,8 +1,12 @@
package com.orion.ops.module.asset.service;
import com.orion.ops.module.asset.entity.dto.ExecLogTailDTO;
import com.orion.ops.module.asset.entity.request.exec.ExecCommandRequest;
import com.orion.ops.module.asset.entity.request.exec.ExecLogTailRequest;
import com.orion.ops.module.asset.entity.vo.ExecCommandVO;
import javax.servlet.http.HttpServletResponse;
/**
* 批量执行服务
*
@@ -42,4 +46,28 @@ public interface ExecService {
*/
void interruptHostExec(Long hostLogId);
/**
* 查看执行日志
*
* @param request request
* @return token
*/
String getExecLogTailToken(ExecLogTailRequest request);
/**
* 获取查看执行日志参数
*
* @param token token
* @return log
*/
ExecLogTailDTO getExecLogTailInfo(String token);
/**
* 下载执行日志文件
*
* @param id id
* @param response response
*/
void downloadLogFile(Long id, HttpServletResponse response);
}

View File

@@ -2,11 +2,13 @@ package com.orion.ops.module.asset.service.impl;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.orion.lang.exception.argument.InvalidArgumentException;
import com.orion.lang.function.Functions;
import com.orion.lang.id.UUIds;
import com.orion.lang.utils.Strings;
import com.orion.lang.utils.collect.Lists;
import com.orion.lang.utils.collect.Maps;
import com.orion.lang.utils.io.Files1;
import com.orion.lang.utils.json.matcher.NoMatchStrategy;
import com.orion.lang.utils.json.matcher.ReplacementFormatter;
import com.orion.lang.utils.json.matcher.ReplacementFormatters;
@@ -17,15 +19,20 @@ import com.orion.ops.framework.common.constant.ErrorMessage;
import com.orion.ops.framework.common.file.FileClient;
import com.orion.ops.framework.common.security.LoginUser;
import com.orion.ops.framework.common.utils.Valid;
import com.orion.ops.framework.redis.core.utils.RedisStrings;
import com.orion.ops.framework.security.core.utils.SecurityUtils;
import com.orion.ops.module.asset.dao.ExecHostLogDAO;
import com.orion.ops.module.asset.dao.ExecLogDAO;
import com.orion.ops.module.asset.dao.HostDAO;
import com.orion.ops.module.asset.define.cache.ExecCacheKeyDefine;
import com.orion.ops.module.asset.entity.domain.ExecHostLogDO;
import com.orion.ops.module.asset.entity.domain.ExecLogDO;
import com.orion.ops.module.asset.entity.domain.HostDO;
import com.orion.ops.module.asset.entity.dto.ExecHostLogTailDTO;
import com.orion.ops.module.asset.entity.dto.ExecLogTailDTO;
import com.orion.ops.module.asset.entity.dto.ExecParameterSchemaDTO;
import com.orion.ops.module.asset.entity.request.exec.ExecCommandRequest;
import com.orion.ops.module.asset.entity.request.exec.ExecLogTailRequest;
import com.orion.ops.module.asset.entity.vo.ExecCommandHostVO;
import com.orion.ops.module.asset.entity.vo.ExecCommandVO;
import com.orion.ops.module.asset.enums.ExecHostStatusEnum;
@@ -40,11 +47,14 @@ import com.orion.ops.module.asset.handler.host.exec.handler.IExecTaskHandler;
import com.orion.ops.module.asset.handler.host.exec.manager.ExecManager;
import com.orion.ops.module.asset.service.AssetAuthorizedDataService;
import com.orion.ops.module.asset.service.ExecService;
import com.orion.web.servlet.web.Servlets;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.util.Date;
import java.util.List;
import java.util.Map;
@@ -275,6 +285,92 @@ public class ExecServiceImpl implements ExecService {
}
}
@Override
public String getExecLogTailToken(ExecLogTailRequest request) {
Long execId = request.getExecId();
List<Long> execHostIdList = request.getExecHostIdList();
log.info("ExecService.getExecLogTailToken start execId: {}, execHostIdList: {}", execId, execHostIdList);
// 查询执行日志
ExecLogDO execLog = execLogDAO.selectById(execId);
Valid.notNull(execLog, ErrorMessage.LOG_ABSENT);
// 查询主机日志
List<ExecHostLogDO> hostLogs;
if (execHostIdList == null) {
hostLogs = execHostLogDAO.selectByLogId(execId);
} else {
hostLogs = execHostLogDAO.of()
.createWrapper()
.eq(ExecHostLogDO::getLogId, execId)
.in(ExecHostLogDO::getId, execHostIdList)
.then()
.list();
}
Valid.notEmpty(hostLogs, ErrorMessage.LOG_ABSENT);
// 生成缓存
String token = UUIds.random19();
String cacheKey = ExecCacheKeyDefine.EXEC_TAIL.format(token);
ExecLogTailDTO cache = ExecLogTailDTO.builder()
.token(token)
.id(execId)
.userId(SecurityUtils.getLoginUserId())
.hosts(hostLogs.stream()
.map(s -> ExecHostLogTailDTO.builder()
.id(s.getId())
.hostId(s.getHostId())
.path(s.getLogPath())
.build())
.collect(Collectors.toList()))
.build();
// 设置缓存
RedisStrings.setJson(cacheKey, ExecCacheKeyDefine.EXEC_TAIL, cache);
log.info("ExecService.getExecLogTailToken finish token: {}, execId: {}, execHostIdList: {}", token, execId, execHostIdList);
return token;
}
@Override
public ExecLogTailDTO getExecLogTailInfo(String token) {
String cacheKey = ExecCacheKeyDefine.EXEC_TAIL.format(token);
// 获取缓存
ExecLogTailDTO tail = RedisStrings.getJson(cacheKey, ExecCacheKeyDefine.EXEC_TAIL);
if (tail != null) {
// 删除缓存
RedisStrings.delete(cacheKey);
}
return tail;
}
@Override
public void downloadLogFile(Long id, HttpServletResponse response) {
log.info("ExecService.downloadLogFile id: {}", id);
try {
// 获取主机执行日志
ExecHostLogDO hostLog = execHostLogDAO.selectById(id);
Valid.notNull(hostLog, ErrorMessage.LOG_ABSENT);
String logPath = hostLog.getLogPath();
Valid.notNull(logPath, ErrorMessage.LOG_ABSENT);
// 设置日志参数
OperatorLogs.add(OperatorLogs.LOG_ID, hostLog.getLogId());
OperatorLogs.add(OperatorLogs.HOST_ID, hostLog.getHostId());
OperatorLogs.add(OperatorLogs.HOST_NAME, hostLog.getHostName());
// 获取日志
InputStream in = logsFileClient.getContentInputStream(logPath);
// 返回
Servlets.transfer(response, in, Files1.getFileName(logPath));
} catch (Exception e) {
log.error("ExecService.downloadLogFile error id: {}", id, e);
String errorMessage = ErrorMessage.FILE_READ_ERROR;
if (e instanceof InvalidArgumentException) {
errorMessage = e.getMessage();
}
// 响应错误信息
try {
Servlets.transfer(response, Strings.bytes(errorMessage), Const.ERROR_LOG);
} catch (Exception ex) {
log.error("ExecService.downloadLogFile transfer-error id: {}", id, ex);
}
}
}
/**
* 开始执行命令
*
@@ -306,7 +402,7 @@ public class ExecServiceImpl implements ExecService {
* @return logPath
*/
private String buildLogPath(Long logId, Long hostId) {
String logFile = "/exec/" + logId + "/" + hostId + ".log";
String logFile = "/exec/" + logId + "/" + logId + "_" + hostId + ".log";
return logsFileClient.getReturnPath(logFile);
}

View File

@@ -53,7 +53,7 @@ public class HostSftpLogServiceImpl implements HostSftpLogService {
vo.setExtra(extra);
return vo;
}).collect(Collectors.toList());
// 返回
// 返回 TODO KIT
DataGrid<HostSftpLogVO> result = new DataGrid<>(rows, dataGrid.getTotal());
result.setPage(dataGrid.getPage());
result.setLimit(dataGrid.getLimit());