opt: es查询优化,前端界面优化,解析SQL,复制SQL,去除lucene

This commit is contained in:
2025-05-29 20:08:34 +08:00
parent 5f052bb2b3
commit e0eeb855a7
11 changed files with 241 additions and 394 deletions

View File

@@ -1 +1,4 @@
# 诺力开发平台
## ES日志分支
> 开启了ES除了使用在日志上还可以用于其他功能用户登录记录、操作记录、请求记录等长期存储的数据。

View File

@@ -15,6 +15,7 @@ public enum TagNameEnum {
* LMS系统
*/
LMS("LMS系统"),
TEST("测试标签"),
/**
* 标记符号
*/

View File

@@ -1,31 +0,0 @@
//package org.nl.system.service.lucene;
//
//import com.alibaba.fastjson.JSONObject;
//
//import java.util.List;
//import java.util.Map;
//
///**
// * <p>
// * 、
// * </p>
// *
// * @author generator
// * @since 2023-11-16
// */
//public interface LuceneService {
//
// /**
// * 获取数据分页
// *
// * @param whereJson 条件
// * @return Map<String, Object>
// */
// Map<String, Object> getAll(JSONObject whereJson);
//
// /**
// * 获取枚举的标签名称
// * @return
// */
// List<String> getTagName();
//}

View File

@@ -1,116 +0,0 @@
//package org.nl.system.service.lucene.dto;
//
//import lombok.AllArgsConstructor;
//import lombok.Builder;
//import lombok.Data;
//import lombok.NoArgsConstructor;
//
///**
// * <p>
// * 日志DTO
// * </p>
// *
// * @author generator
// * @since 2023-11-16
// */
//@Data
//@Builder
//@NoArgsConstructor
//@AllArgsConstructor
//public class LuceneLogDto {
//
// /**
// * 日志标识
// */
// private String log_uuid;
// /**
// * 日志类型
// */
// private String logType;
// /**
// * 设备编号
// */
// private String device_code;
// /**
// * 内容详情
// */
// private String content;
// /**
// * 任务编码
// */
// private String task_code;
// /**
// * 指令编码
// */
// private String instruct_code;
// /**
// * 任务标识
// */
// private String task_id;
// /**
// * 载具号
// */
// private String vehicle_code;
// /**
// * 备注
// */
// private String remark;
// /**
// * 日志类型
// */
// private String log_type;
// /**
// * 方法
// */
// private String method;
// /**
// * 请求参数
// */
// private String requestparam;
// /**
// * 响应参数
// */
// private String responseparam;
// /**
// * 请求地址
// */
// private String requesturl;
// /**
// * 状态码
// */
// private String status_code;
// /**
// * 是否删除 1:是0
// */
// private String is_delete;
// /**
// * 创建者
// */
// private String create_by;
// /**
// * 创建时间 YYYY-MM-DD hh:mm:ss
// */
// private String create_time;
// /**
// * 修改者
// */
// private String update_by;
// /**
// * 修改时间
// */
// private String update_time;
//
// public LuceneLogDto (final String opc_server_code,final String opc_plc_code,
// final String device_code,final String to_home,final int last_home,
// final int home) {
// super ();
// this.device_code = device_code;
// this.content = "信号"
// + opc_server_code + "."
// + opc_plc_code + "."
// + device_code + "."
// + to_home + "变更从"
// + last_home + "->"
// + home;
// }
//}

View File

@@ -38,7 +38,11 @@ public class EsLogServiceImpl implements EsLogService {
int size = whereJson.getInteger("size") != null ? whereJson.getInteger("size") : 10;
// 创建分页和排序(按时间倒序)
Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "time"));
Sort sort = Sort.by(
Sort.Order.desc("@timestamp"),
Sort.Order.desc("_doc")
);
Pageable pageable = PageRequest.of(page, size, sort);
// 构建动态查询条件
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
@@ -85,7 +89,7 @@ public class EsLogServiceImpl implements EsLogService {
String endTime = whereJson.getString("endTime");
// 前端传来的数据startTime -> 2025-05-27T16:00:00.000ZendTime -> 2025-05-29T16:00:00.000Z
// es的time字段: 2025-05-29T14:55:47.498+08:00
// todo: 通过时间范围查询
// 通过时间范围查询
boolQuery.must(QueryBuilders.rangeQuery("time")
.gte(startTime)
.lte(endTime)

View File

@@ -1,111 +0,0 @@
//package org.nl.system.service.lucene.impl;
//
//import cn.hutool.core.date.DateTime;
//import cn.hutool.core.date.DateUtil;
//import cn.hutool.core.util.ObjectUtil;
//import lombok.RequiredArgsConstructor;
//import lombok.SneakyThrows;
//import lombok.extern.slf4j.Slf4j;
//import org.apache.lucene.document.Document;
//import org.apache.lucene.document.Field;
//import org.apache.lucene.document.StringField;
//import org.apache.lucene.index.IndexWriter;
//import org.nl.common.enums.LogTypeEnum;
//import org.nl.config.lucene.DynamicLogger;
//import org.nl.config.lucene.LuceneIndexWriter;
//import org.nl.system.service.lucene.LuceneExecuteLogService;
//import org.nl.system.service.lucene.dto.LuceneLogDto;
//import org.slf4j.Logger;
//import org.slf4j.MDC;
//import org.springframework.beans.factory.annotation.Value;
//import org.springframework.stereotype.Service;
//
//import java.io.IOException;
//
///**
// * @author jlm
// * @description 服务实现
// * @date 2023-04-11
// */
//@Service
//@RequiredArgsConstructor
//@Slf4j
//public class LuceneExecuteLogServiceImpl implements LuceneExecuteLogService {
//
// /**
// * 日志目录
// */
// @Value("${logging.file.path}")
// private String logPath;
//
// @Override
// public void deviceItemValue(String device_code, String key, String value) {
// String now = DateUtil.now();
// }
//
// @SneakyThrows
// @Override
// public void deviceExecuteLog(LuceneLogDto luceneLogDto) {
// luceneLogDto.setLogType(LogTypeEnum.DEVICE_LOG.getDesc());
// addIndex(luceneLogDto);
// }
//
// @Override
// public void interfaceExecuteLog(LuceneLogDto luceneLogDto) throws IOException {
// luceneLogDto.setLogType(LogTypeEnum.INTERFACE_LOG.getDesc());
// addIndex(luceneLogDto);
// }
//
// private void addIndex(LuceneLogDto luceneLogDto) throws IOException {
// IndexWriter indexWriter = LuceneIndexWriter.getIndexWriter();
// //创建一个Document对象
// Document document = new Document();
// try {
// //记录索引开始时间
// long startTime = System.currentTimeMillis();
// //向document对象中添加域。
// if (ObjectUtil.isNotEmpty(luceneLogDto.getDevice_code())) {
// document.add(new StringField("device_code", luceneLogDto.getDevice_code(), Field.Store.YES));
// }
// if (ObjectUtil.isNotEmpty(luceneLogDto.getContent())) {
// document.add(new StringField("fieldContent", luceneLogDto.getContent(), Field.Store.YES));
// }
// if (ObjectUtil.isNotEmpty(luceneLogDto.getMethod())) {
// document.add(new StringField("method", luceneLogDto.getMethod(), Field.Store.YES));
// }
// if (ObjectUtil.isNotEmpty(luceneLogDto.getStatus_code())) {
// document.add(new StringField("status_code", luceneLogDto.getStatus_code(), Field.Store.YES));
// }
// if (ObjectUtil.isNotEmpty(luceneLogDto.getRequestparam())) {
// document.add(new StringField("requestparam", luceneLogDto.getRequestparam(), Field.Store.YES));
// }
// if (ObjectUtil.isNotEmpty(luceneLogDto.getResponseparam())) {
// document.add(new StringField("responseparam", luceneLogDto.getResponseparam(), Field.Store.YES));
// }
// document.add(new StringField("logType", luceneLogDto.getLogType(), Field.Store.YES));
// document.add(new StringField("logTime", DateUtil.format(new DateTime(), "yyyy-MM-dd HH:mm:ss.SSS"), Field.Store.YES));
// indexWriter.addDocument(document);
// //记录索引结束时间
// long endTime = System.currentTimeMillis();
// indexWriter.commit();
// //实现日志文件按业务独立生成日志文件到指定路径
// DynamicLogger loggerBuilder =new DynamicLogger(logPath+"\\"+luceneLogDto.getLogType()+"\\");
// Logger logger = loggerBuilder.getLogger(luceneLogDto.getDevice_code());
// logger.info("{}",luceneLogDto.toString());
// } catch (Exception e) {
// log.error(e.getMessage(), e);
// }
// }
//
// @Override
// public void extLog(String name, String message) {
// try {
// MDC.put(name, name);
// log.info("{}", message);
// } catch (Exception e) {
// e.printStackTrace();
// } finally {
// MDC.remove(name);
// }
// }
//}

View File

@@ -1,60 +0,0 @@
//package org.nl.system.service.lucene.impl;
//
//import com.alibaba.fastjson.JSONArray;
//import com.alibaba.fastjson.JSONObject;
//import lombok.RequiredArgsConstructor;
//import lombok.extern.slf4j.Slf4j;
//import org.nl.config.language.LangProcess;
//import org.nl.config.lucene.Searcher;
//import org.nl.config.lucene.TagNameEnum;
//import org.nl.system.service.lucene.LuceneService;
//import org.springframework.beans.factory.annotation.Value;
//import org.springframework.stereotype.Service;
//
//import java.util.Arrays;
//import java.util.List;
//import java.util.Map;
//import java.util.stream.Collectors;
//
///**
// * <p>
// * 、
// * </p>
// *
// * @author generator
// * @since 2023-11-16
// */
//@Service
//@RequiredArgsConstructor
//@Slf4j
//public class LuceneServiceImpl implements LuceneService {
//
// /**
// * 日志索引目录
// */
// @Value("${lucene.index.path}")
// private String luceneUrl;
//
// @Override
// public Map<String, Object> getAll(JSONObject whereJson) {
// JSONObject jo = new JSONObject();
// try {
// JSONObject jsonObject = (JSONObject) Searcher.search(luceneUrl, whereJson);
// JSONArray array = jsonObject.getJSONArray("content");
// int totalElements = Integer.parseInt(jsonObject.get("totalElements").toString());
// jo.put("content", array);
// jo.put("totalElements", totalElements);
// } catch (Exception e) {
// log.error("索引查询为空", e);
// throw new NullPointerException(LangProcess.msg("error_NullPoint"));
// }
//
// return jo;
// }
//
// @Override
// public List<String> getTagName() {
// return Arrays.stream(TagNameEnum.values()).map(TagNameEnum::getTag).collect(Collectors.toList());
// }
//
//}

View File

@@ -1,6 +1,8 @@
package org.nl.system.service.quartz.task;
import lombok.extern.slf4j.Slf4j;
import org.nl.config.lucene.TagNameEnum;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
/**
@@ -13,7 +15,28 @@ import org.springframework.stereotype.Component;
public class TestTask {
public void run(){
MDC.put("tag", TagNameEnum.TEST.getTag());
log.info("run 执行成功");
this.skip1();
}
private void skip1() {
log.info("跳跃这里啦");
this.skip2();
}
private void skip2() {
try {
int a = 1 / 0;
} catch (Exception e) {
log.error("报错啦:{}", e.getStackTrace());
} finally {
skip3();
}
}
private void skip3() {
int a = 12 / 0;
}
public void run1(String str){

View File

@@ -50,31 +50,6 @@ https://juejin.cn/post/6844903775631572999
</encoder>
</appender>
<!-- <appender name="luceneAppender" class="org.nl.config.lucene.LuceneAppender" >-->
<!-- &lt;!&ndash; Filter for INFO level &ndash;&gt;-->
<!-- <filter class="ch.qos.logback.classic.filter.LevelFilter">-->
<!-- <level>INFO</level>-->
<!-- <onMatch>ACCEPT</onMatch>-->
<!-- <onMismatch>NEXT</onMismatch>-->
<!-- </filter>-->
<!-- &lt;!&ndash; Filter for WARN level &ndash;&gt;-->
<!-- <filter class="ch.qos.logback.classic.filter.LevelFilter">-->
<!-- <level>WARN</level>-->
<!-- <onMatch>ACCEPT</onMatch>-->
<!-- <onMismatch>NEXT</onMismatch>-->
<!-- </filter>-->
<!-- &lt;!&ndash; Filter for ERROR level &ndash;&gt;-->
<!-- <filter class="ch.qos.logback.classic.filter.LevelFilter">-->
<!-- <level>ERROR</level>-->
<!-- <onMatch>ACCEPT</onMatch>-->
<!-- <onMismatch>DENY</onMismatch>-->
<!-- </filter>-->
<!-- </appender>-->
<!-- <appender name="asyncLuceneAppender" class="ch.qos.logback.classic.AsyncAppender">-->
<!-- <appender-ref ref="luceneAppender" />-->
<!-- &lt;!&ndash; 设置队列大小 &ndash;&gt;-->
<!-- <queueSize>512</queueSize>-->
<!-- </appender>-->
<!--异步到文件-->
<appender name="asyncFileAppender" class="com.yomahub.tlog.core.enhance.logback.async.AspectLogbackAsyncAppender">
@@ -138,7 +113,6 @@ https://juejin.cn/post/6844903775631572999
<springProfile name="dev">
<root level="DEBUG">
<appender-ref ref="ELASTIC"/>
<!-- <appender-ref ref="asyncLuceneAppender"/>-->
<appender-ref ref="CONSOLE"/>
</root>
<logger name="org.elasticsearch.client.RestClient" level="ERROR" additivity="false">
@@ -146,23 +120,23 @@ https://juejin.cn/post/6844903775631572999
<appender-ref ref="asyncFileAppender"/>
</logger>
<logger name="com.baomidou.mybatisplus.core.MybatisConfiguration" level="ERROR" additivity="false">
<!-- <appender-ref ref="asyncLuceneAppender"/>-->
<appender-ref ref="ELASTIC"/>
<appender-ref ref="asyncFileAppender"/>
</logger>
<logger name="org.redisson.command.RedisExecutor" level="ERROR" additivity="false">
<!-- <appender-ref ref="asyncLuceneAppender"/>-->
<appender-ref ref="ELASTIC"/>
<appender-ref ref="asyncFileAppender"/>
</logger>
<logger name="org.reflections.Reflections" level="ERROR" additivity="false">
<!-- <appender-ref ref="asyncLuceneAppender"/>-->
<appender-ref ref="ELASTIC"/>
<appender-ref ref="asyncFileAppender"/>
</logger>
<logger name="org.redisson.connection.ClientConnectionsEntry" level="ERROR" additivity="false">
<!-- <appender-ref ref="asyncLuceneAppender"/>-->
<appender-ref ref="ELASTIC"/>
<appender-ref ref="asyncFileAppender"/>
</logger>
<logger name="org.mybatis.spring.mapper.ClassPathMapperScanner" level="ERROR" additivity="false">
<!-- <appender-ref ref="asyncLuceneAppender"/>-->
<appender-ref ref="ELASTIC"/>
<appender-ref ref="asyncFileAppender"/>
</logger>
<logger name="org.springframework" level="ERROR" additivity="true">
@@ -230,43 +204,55 @@ https://juejin.cn/post/6844903775631572999
<!--生产环境:打印控制台和输出到文件-->
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="ELASTIC"/>
<appender-ref ref="asyncFileAppender"/>
<!-- <appender-ref ref="asyncLuceneAppender"/>-->
</root>
<logger name="org.springframework" level="ERROR" additivity="false">
<appender-ref ref="ELASTIC"/>
<appender-ref ref="asyncFileAppender"/>
</logger>
<logger name="org.apache" level="ERROR" additivity="false">
<appender-ref ref="ELASTIC"/>
<appender-ref ref="asyncFileAppender"/>
</logger>
<logger name="org.hibernate" level="ERROR" additivity="false">
<appender-ref ref="ELASTIC"/>
<appender-ref ref="asyncFileAppender"/>
</logger>
<logger name="io.netty" level="ERROR" additivity="false">
<appender-ref ref="ELASTIC"/>
<appender-ref ref="asyncFileAppender"/>
</logger>
<logger name="jdbc" level="ERROR" additivity="false">
<appender-ref ref="ELASTIC"/>
<appender-ref ref="asyncFileAppender"/>
</logger>
<logger name="io.lettuce" level="ERROR" additivity="false">
<appender-ref ref="ELASTIC"/>
<appender-ref ref="asyncFileAppender"/>
</logger>
<logger name="com.fasterxml" level="ERROR" additivity="false">
<appender-ref ref="ELASTIC"/>
<appender-ref ref="asyncFileAppender"/>
</logger>
<logger name="org.quartz" level="ERROR" additivity="false">
<appender-ref ref="ELASTIC"/>
<appender-ref ref="asyncFileAppender"/>
</logger>
<logger name="com.google" level="ERROR" additivity="false">
<appender-ref ref="ELASTIC"/>
<appender-ref ref="asyncFileAppender"/>
</logger>
<logger name="springfox" level="ERROR" additivity="false">
<appender-ref ref="ELASTIC"/>
<appender-ref ref="asyncFileAppender"/>
</logger>
<logger name="log4jdbc" level="ERROR" additivity="false">
<appender-ref ref="ELASTIC"/>
<appender-ref ref="asyncFileAppender"/>
</logger>
<logger name="nl.basjes" level="ERROR" additivity="false">
<appender-ref ref="ELASTIC"/>
<appender-ref ref="asyncFileAppender"/>
</logger>
</springProfile>

View File

@@ -64,6 +64,7 @@
"qs": "^6.9.1",
"screenfull": "4.2.0",
"sortablejs": "1.8.4",
"sql-formatter": "^15.6.2",
"throttle-debounce": "^5.0.0",
"vue": "2.6.10",
"vue-bus": "^1.2.1",

View File

@@ -78,7 +78,7 @@
</el-form>
</div>
<!--表格渲染-->
<el-card shadow="hover" style="width: 100%" class="log-warpper">
<el-card shadow="hover" style="width: 100%; height: 620px; overflow-y: auto" class="log-warpper">
<div style="width: 100%">
<div v-for="(log, index) in logs" :key="index">
<div v-if="compressedView" style="margin-bottom: 5px; font-size: 16px;">
@@ -96,91 +96,85 @@
{{ log.message }}
</span>
</div>
<div v-else style="margin-bottom: 5px; font-size: 16px; border: #999999 1px solid; padding: 5px;">
<el-row>
<el-col :span="2">
<div v-else class="log-detailed">
<el-row class="log-row">
<el-col :span="2" class="log-label">
时间
</el-col>
<el-col :span="22">
<span style="color: #000000;">{{ timestampToDate(log.time) }}</span>
<span class="log-time">{{ timestampToDate(log.time) }}</span>
</el-col>
</el-row>
<el-row>
<el-col :span="2">
<el-row class="log-row">
<el-col :span="2" class="log-label">
host
</el-col>
<el-col :span="22">
<span style="color: #048815;">{{ log.host }}</span>
<span class="log-host">{{ log.host }}</span>
</el-col>
</el-row>
<el-row>
<el-col :span="2">
<el-row class="log-row">
<el-col :span="2" class="log-label">
执行IP
</el-col>
<el-col :span="22">
<span style="color: #000569;">{{ log.currIp === null ? '0.0.0.0' : log.currIp }}</span>
<span class="log-ip">{{ log.currIp === null ? '0.0.0.0' : log.currIp }}</span>
</el-col>
</el-row>
<el-row>
<el-col :span="2">
<el-row class="log-row">
<el-col :span="2" class="log-label">
API路径
</el-col>
<el-col :span="22">
<span style="color: #b9de16;">{{ log.apiPath === null ? '无' : log.apiPath }}</span>
<span class="log-api">{{ log.apiPath === null ? '无' : log.apiPath }}</span>
</el-col>
</el-row>
<el-row>
<el-col :span="2">
<el-row class="log-row">
<el-col :span="2" class="log-label">
MDC标签
</el-col>
<el-col :span="22">
<span style="color: #d20118;">{{ log.tag === null ? '未配置MDC' : log.tag }}</span>
<span class="log-tag">{{ log.tag === null ? '未配置MDC' : log.tag }}</span>
</el-col>
</el-row>
<el-row>
<el-col :span="2">
<el-row class="log-row">
<el-col :span="2" class="log-label">
日志级别
</el-col>
<el-col :span="22">
<span style="color: #111fc7;">{{ log.severity }}</span>
<span :class="['log-level', getLevelClass(log.severity)]">{{ log.severity }}</span>
</el-col>
</el-row>
<el-row>
<el-col :span="2">
<el-row class="log-row">
<el-col :span="2" class="log-label">
类名
</el-col>
<el-col :span="22">
<span style="color: #111fc7;">{{ log.logger }}</span>
<span class="log-logger">{{ log.logger }}</span>
</el-col>
</el-row>
<el-row>
<el-col :span="2">
<el-row class="log-row">
<el-col :span="2" class="log-label">
线程名
</el-col>
<el-col :span="22">
<span style="color: #28b23a;">{{ log.thread }}</span>
<span class="log-thread">{{ log.thread }}</span>
</el-col>
</el-row>
<el-row>
<el-col :span="2">
<el-row class="log-row">
<el-col :span="2" class="log-label">
链路标识
</el-col>
<el-col :span="22">
<span style="color: #d51313;">{{ log.tlogTraceId === null ? '无链路id' : log.tlogTraceId }}</span>
<span class="log-trace">{{ log.tlogTraceId === null ? '无链路id' : log.tlogTraceId }}</span>
</el-col>
</el-row>
<el-row>
<el-col :span="2">
<el-row class="log-row">
<el-col :span="2" class="log-label">
日志信息
</el-col>
<el-col :span="22">
<span
style="color: #000000;"
class="sql-message"
@click="handleCopySQL(log.message)"
>
{{ log.message }}
</span>
<span class="log-message sql-message" @click="handleCopySQL(log.message)">{{ transSQL(log.message) }}</span>
</el-col>
</el-row>
</div>
@@ -203,7 +197,6 @@
<script>
import luceneOperation from '@/views/lucene/api/lucene'
import { default as AnsiUp } from 'ansi_up'
export default {
name: 'LuceneLog',
data() {
@@ -245,6 +238,16 @@ export default {
this.getTagList()
},
methods: {
getLevelClass(level) {
if (!level) return 'level-unknown'
switch (level.toUpperCase()) {
case 'ERROR': return 'level-error'
case 'WARN': return 'level-warn'
case 'DEBUG': return 'level-debug'
case 'INFO': return 'level-info'
default: return 'level-unknown'
}
},
timestampToDate(timestamp) {
const date = new Date(timestamp)
const year = date.getFullYear()
@@ -270,7 +273,16 @@ export default {
this.$message.warning('未检测到可复制的SQL语句')
}
},
transSQL(message) {
const sqlMatch = message.match(/\[ SQL: ([^\]]+)\s*\]/)
// 兼容性更好的写法
if (!sqlMatch || !sqlMatch[1]) {
return message
}
const rawSQL = sqlMatch[1].trim()
return rawSQL
},
// 复制文本到剪贴板
copyToClipboard(text) {
// 现代浏览器方法
@@ -341,13 +353,148 @@ export default {
</script>
<style scoped>
/* 可点击样式提示 */
/* 详细视图样式 */
.log-detailed {
padding: 10px;
border: 1px solid #EBEEF5;
border-radius: 4px;
background-color: #fafafa;
}
.log-row {
margin-bottom: 5px;
line-height: 22px;
}
.log-label {
font-weight: 600;
color: #606266;
text-align: right;
padding-right: 12px;
}
/* 日志元素样式 */
.log-time {
color: #303133;
font-weight: bold;
}
.log-ip {
color: #000569;
padding: 2px 4px;
background-color: #f0f9ff;
border-radius: 3px;
}
.log-level {
font-weight: bold;
padding: 2px 6px;
border-radius: 3px;
}
.level-error .log-level,
.log-level.level-error {
background-color: #fef0f0;
color: #F56C6C;
}
.level-warn .log-level,
.log-level.level-warn {
background-color: #fdf6ec;
color: #E6A23C;
}
.level-info .log-level,
.log-level.level-info {
background-color: #ecf5ff;
color: #409EFF;
}
.level-debug .log-level,
.log-level.level-debug {
background-color: #f0f9eb;
color: #67C23A;
}
.log-logger {
color: #3498db;
font-family: monospace;
max-width: 300px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.log-thread {
color: #28b23a;
font-family: monospace;
}
.log-trace {
color: #d51313;
font-family: monospace;
background-color: #fff7f7;
padding: 2px 4px;
border-radius: 3px;
}
.log-host {
color: #048815;
}
.log-api {
color: #8c6a03;
font-family: monospace;
}
.log-tag {
color: #d20118;
}
.log-message {
color: #000000;
display: block;
margin-top: 4px;
padding: 6px;
background-color: #f8f8f8;
border-radius: 3px;
white-space: pre-wrap;
word-break: break-all;
font-family: monospace;
}
/* SQL语句样式 */
.sql-message {
cursor: pointer;
transition: all 0.3s;
position: relative;
background: #f4faff;
color: #2d8cf0;
font-weight: bold;
border-left: 3px solid #2d8cf0;
transition: background 0.2s;
}
.sql-message:hover {
background-color: #f0f9ff;
box-shadow: 0 0 3px rgba(32, 160, 255, 0.5);
background-color: #ecf5ff;
box-shadow: 0 0 6px rgba(32, 160, 255, 0.3);
}
.sql-message:hover::after {
content: "点击复制SQL";
position: absolute;
right: 10px;
top: 3px;
font-size: 12px;
color: #409EFF;
background-color: #ecf5ff;
padding: 2px 5px;
border-radius: 3px;
}
.sql-message.copied {
background: #e1f3d8;
color: #67c23a;
}
</style>