java开发有日志存表的吗_Java日志信息存库(logback篇)

一、Logback简介

Logback是由log4j创始人设计的又一个开源日志组件。logback当前分成三个模块:logback-core,logback- classic和logback-access。logback-core是其它两个模块的基础模块。logback-classic是log4j的一个 改良版本。此外logback-classic完整实现SLF4J API使你可以很方便地更换成其它日志系统如log4j或JDK14 Logging。logback-access访问模块与Servlet容器集成提供通过Http来访问日志的功能。

二、编写背景

很不明白客户为什么要求将日志信息写入到数据库中去,还要提供个页面给系统使用人员查看相应日志。作为一个业务人员真的就能看懂系统日志上报错信息是啥意思么,个人深表怀疑。没办法,作为一枚屌丝程序猿,需求下来了只能硬着头皮去开发。

三、编写目的

只相信一句话:好记性不如烂笔头,何况我记性差到前一周写的代码到现在竟毫无印象的境地呢。

四、Java日志信息存库详细解决方案

1.开发环境说明

Eclipse+Tomcat6+JDK1.6+Oracle+logback1.1

2.Java日志存库实现方案

(1)使用logback组件默认的DBAppender类实现

最初需求下来的时候想着logback应该有自己的写数据库的解决办法,于是乎结合源码及度娘终究还是找到了。在logback-classic-1.1.3.jar的ch/qos/logback/classic/db/script/路径下找到了Oracle数据库对应的建表语句脚本oracle.sql,其建表语句如下所示:

--Logback: the reliable, generic, fast and flexible logging framework.--Copyright (C) 1999-2010, QOS.ch. All rights reserved.--

--See http://logback.qos.ch/license.html for the applicable licensing--conditions.

--This SQL script creates the required tables by ch.qos.logback.classic.db.DBAppender--

--It is intended for Oracle 9i, 10g and 11g databases. Tested on version 9.2,--10g and 11g.

--The following lines are useful in cleaning any previously existing tables

--drop TRIGGER logging_event_id_seq_trig;--drop SEQUENCE logging_event_id_seq;--drop table logging_event_property;--drop table logging_event_exception;--drop table logging_event;

CREATE SEQUENCE logging_event_id_seq MINVALUE 1 START WITH 1;CREATE TABLElogging_event

(

timestmpNUMBER(20) NOT NULL,

formatted_messageVARCHAR2(4000) NOT NULL,

logger_nameVARCHAR(254) NOT NULL,

level_stringVARCHAR(254) NOT NULL,

thread_nameVARCHAR(254),

reference_flagSMALLINT,

arg0VARCHAR(254),

arg1VARCHAR(254),

arg2VARCHAR(254),

arg3VARCHAR(254),

caller_filenameVARCHAR(254) NOT NULL,

caller_classVARCHAR(254) NOT NULL,

caller_methodVARCHAR(254) NOT NULL,

caller_lineCHAR(4) NOT NULL,

event_idNUMBER(10) PRIMARY KEY);--the / suffix may or may not be needed depending on your SQL Client--Some SQL Clients, e.g. SQuirrel SQL has trouble with the following--trigger creation command, while SQLPlus (the basic SQL Client which--ships with Oracle) has no trouble at all.

CREATE TRIGGERlogging_event_id_seq_trig

BEFOREINSERT ONlogging_eventFOREACH ROWBEGIN

SELECTlogging_event_id_seq.NEXTVALINTO:NEW.event_idFROMDUAL;END;/

CREATE TABLElogging_event_property

(

event_idNUMBER(10) NOT NULL,

mapped_keyVARCHAR2(254) NOT NULL,

mapped_valueVARCHAR2(1024),PRIMARY KEY(event_id, mapped_key),FOREIGN KEY (event_id) REFERENCESlogging_event(event_id)

);CREATE TABLElogging_event_exception

(

event_idNUMBER(10) NOT NULL,

iSMALLINT NOT NULL,

trace_lineVARCHAR2(254) NOT NULL,PRIMARY KEY(event_id, i),FOREIGN KEY (event_id) REFERENCESlogging_event(event_id)

);

该sql脚本共创建了3张表,一个序列和一个触发器。其中主要日志信息记录在logging_event中,触发器是在logging_event数据新增时,将创建的序列值赋值给logging_event表的event_id。

1)JDBC连接池方式

oracle.jdbc.driver.OracleDriver

jdbc:oracle:thin:@127.0.0.1:1521:orcl

tiger

123456

2) JNDI方式

A.Tomcat服务器安装目录/conf/server.xml配置JNDI信息

B.Spring配置文件applicationContext.xml配置JNDI信息

java:comp/env/jdbc/logging

C.logback.xml文件配置JNDI信息

?xml version="1.0" encoding="UTF-8" ?>

java:comp/env/jdbc/logging

注:对于JNDI的配置不熟悉的,可以去找度娘帮忙或参考JNDI官方文档,本文不做细究。

(2)使用自定义DBAppender类实现

当初按上述的配置轻轻松松实现了日志信息存库,但天有不测风云,完成后却被告知客户那边任务触发器不安全,禁用触发器。得知此消息后心里仿佛有千万只草泥马飞奔而过,没办法只有继续想着怎么去改造了。于是就只能去自定义个DBAppender了,既然是自定义就干脆把其他两张表直接删了,仅使用logging_event表,其表结构如下:

字段名

中文说明

类型

为空

TIMESTMP

记录时间

NUMBER(20)

N

FORMATTED_MESSAGE

格式化后的日志信息

CLOB

N

LOGGER_NAME

执行记录请求的logger

VARCHAR2(256)

N

LEVEL_STRING

日志级别

VARCHAR2(256)

N

THREAD_NAME

日志线程名

VARCHAR2(256)

Y

REFERENCE_FLAG

包含标识:1-MDC或上下文属性;2-异常;3-均包含

INTEGER

Y

ARG0

参数1

VARCHAR2(256)

Y

ARG1

参数2

VARCHAR2(256)

Y

ARG2

参数3

VARCHAR2(256)

Y

ARG3

参数4

VARCHAR2(256)

Y

CALLER_FILENAME

文件名

VARCHAR2(256)

N

CALLER_CLASS

VARCHAR2(256)

N

CALLER_METHOD

方法名

VARCHAR2(256)

N

CALLER_LINE

行号

VARCHAR2(256)

N

EVENT_ID

主键ID

NUMBER(10)

N

注:这个表结构与logback提供的默认表结构有细微差别,主要体现在字段类型上,由于FORMATTED_MESSAGE存储的是详细的日志信息,字段类型VARCHAR2(4000)无法存储大文本信息,所以直接改造成了CLOB类型。

1)自定义DBAppender类(ATSDBAppender.java)

packagecom.hundsun.fund.ats.core.system.loggingevent.dao;importjava.lang.reflect.Method;importjava.sql.Connection;importjava.sql.PreparedStatement;importjava.sql.SQLException;importjava.util.HashMap;importjava.util.Map;import staticch.qos.logback.core.db.DBHelper.closeStatement;import staticch.qos.logback.core.db.DBHelper.closeConnection;importch.qos.logback.classic.db.DBHelper;importch.qos.logback.classic.db.names.DBNameResolver;importch.qos.logback.classic.db.names.DefaultDBNameResolver;importch.qos.logback.classic.spi.CallerData;importch.qos.logback.classic.spi.ILoggingEvent;importch.qos.logback.core.db.DBAppenderBase;public class ATSDBAppender extends DBAppenderBase{protectedString insertSQL;protected static finalMethod GET_GENERATED_KEYS_METHOD;privateDBNameResolver dbNameResolver;static final int TIMESTMP_INDEX = 1;static final int FORMATTED_MESSAGE_INDEX = 2;static final int LOGGER_NAME_INDEX = 3;static final int LEVEL_STRING_INDEX = 4;static final int THREAD_NAME_INDEX = 5;static final int REFERENCE_FLAG_INDEX = 6;static final int ARG0_INDEX = 7;static final int ARG1_INDEX = 8;static final int ARG2_INDEX = 9;static final int ARG3_INDEX = 10;static final int CALLER_FILENAME_INDEX = 11;static final int CALLER_CLASS_INDEX = 12;static final int CALLER_METHOD_INDEX = 13;static final int CALLER_LINE_INDEX = 14;static final int EVENT_ID_INDEX = 15;static final StackTraceElement EMPTY_CALLER_DATA =CallerData.naInstance();static{

Method getGeneratedKeysMethod;try{

getGeneratedKeysMethod= PreparedStatement.class.getMethod("getGeneratedKeys", (Class[]) null);

}catch(Exception ex) {

getGeneratedKeysMethod= null;

}

GET_GENERATED_KEYS_METHOD=getGeneratedKeysMethod;

}public voidsetDbNameResolver(DBNameResolver dbNameResolver) {this.dbNameResolver =dbNameResolver;

}

@Overridepublic voidstart() {if (dbNameResolver == null)

dbNameResolver= newDefaultDBNameResolver();

insertSQL=ATSSQLBuilder.buildInsertSQL(dbNameResolver);super.start();

}public voidappend(ILoggingEvent eventObject) {

Connection connection= null;

PreparedStatement insertStatement= null;try{

connection=connectionSource.getConnection();

connection.setAutoCommit(false);

insertStatement=connection.prepareStatement(getInsertSQL());synchronized (this) {

subAppend(eventObject, connection, insertStatement);

}

connection.commit();

}catch(Throwable sqle) {

addError("problem appending event", sqle);

}finally{

closeStatement(insertStatement);

closeConnection(connection);

}

}

@Overrideprotected void subAppend(ILoggingEvent event, Connection connection,PreparedStatement insertStatement) throwsThrowable {

bindLoggingEventWithInsertStatement(insertStatement, event);

bindLoggingEventArgumentsWithPreparedStatement(insertStatement, event.getArgumentArray());

bindCallerDataWithPreparedStatement(insertStatement, event.getCallerData());int updateCount =insertStatement.executeUpdate();if (updateCount != 1) {

addWarn("Failed to insert loggingEvent");

}

}protected voidsecondarySubAppend(ILoggingEvent event,

Connection connection,long eventId) throwsThrowable {

}voidbindLoggingEventWithInsertStatement(PreparedStatement stmt,

ILoggingEvent event)throwsSQLException {stmt.setLong(TIMESTMP_INDEX, event.getTimeStamp());

stmt.setString(FORMATTED_MESSAGE_INDEX, event.getFormattedMessage());

stmt.setString(LOGGER_NAME_INDEX, event.getLoggerName());

stmt.setString(LEVEL_STRING_INDEX, event.getLevel().toString());

stmt.setString(THREAD_NAME_INDEX, event.getThreadName());

stmt.setShort(REFERENCE_FLAG_INDEX, DBHelper.computeReferenceMask(event));

}voidbindLoggingEventArgumentsWithPreparedStatement(PreparedStatement stmt,

Object[] argArray)throwsSQLException {int arrayLen = argArray != null ? argArray.length : 0;for(int i = 0; i < arrayLen && i < 4; i++) {

stmt.setString(ARG0_INDEX+i, asStringTruncatedTo254(argArray[i]));

}if(arrayLen < 4) {for(int i = arrayLen; i < 4; i++) {

stmt.setString(ARG0_INDEX+i, null);

}

}

}

String asStringTruncatedTo254(Object o) {

String s= null;if(o != null) {

s=o.toString();

}if(s == null) {return null;

}if(s.length() <= 254) {returns;

}else{return s.substring(0, 254);

}

}voidbindCallerDataWithPreparedStatement(PreparedStatement stmt,

StackTraceElement[] callerDataArray)throwsSQLException {

StackTraceElement caller=extractFirstCaller(callerDataArray);

stmt.setString(CALLER_FILENAME_INDEX, caller.getFileName());

stmt.setString(CALLER_CLASS_INDEX, caller.getClassName());

stmt.setString(CALLER_METHOD_INDEX, caller.getMethodName());

stmt.setString(CALLER_LINE_INDEX, Integer.toString(caller.getLineNumber()));

}privateStackTraceElement extractFirstCaller(StackTraceElement[] callerDataArray) {

StackTraceElement caller=EMPTY_CALLER_DATA;if(hasAtLeastOneNonNullElement(callerDataArray))

caller= callerDataArray[0];returncaller;

}private booleanhasAtLeastOneNonNullElement(StackTraceElement[] callerDataArray) {return callerDataArray != null && callerDataArray.length > 0 && callerDataArray[0] != null;

}

MapmergePropertyMaps(ILoggingEvent event) {

Map mergedMap = new HashMap();

Map loggerContextMap =event.getLoggerContextVO().getPropertyMap();

Map mdcMap =event.getMDCPropertyMap();if (loggerContextMap != null) {

mergedMap.putAll(loggerContextMap);

}if (mdcMap != null) {

mergedMap.putAll(mdcMap);

}returnmergedMap;

}

@OverrideprotectedMethod getGeneratedKeysMethod() {returnGET_GENERATED_KEYS_METHOD;

}

@OverrideprotectedString getInsertSQL() {returninsertSQL;

}

}

2)自定义SQLBuilder类(ATSSQLBuilder.java)

packagecom.hundsun.fund.ats.core.system.loggingevent.dao;importch.qos.logback.classic.db.names.ColumnName;importch.qos.logback.classic.db.names.DBNameResolver;importch.qos.logback.classic.db.names.TableName;public classATSSQLBuilder {staticString buildInsertSQL(DBNameResolver dbNameResolver) {

StringBuilder sqlBuilder= new StringBuilder("INSERT INTO ");

sqlBuilder.append(dbNameResolver.getTableName(TableName.LOGGING_EVENT)).append(" (");

sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.TIMESTMP)).append(", ");

sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.FORMATTED_MESSAGE)).append(", ");

sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.LOGGER_NAME)).append(", ");

sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.LEVEL_STRING)).append(", ");

sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.THREAD_NAME)).append(", ");

sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.REFERENCE_FLAG)).append(", ");

sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.ARG0)).append(", ");

sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.ARG1)).append(", ");

sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.ARG2)).append(", ");

sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.ARG3)).append(", ");

sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.CALLER_FILENAME)).append(", ");

sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.CALLER_CLASS)).append(", ");

sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.CALLER_METHOD)).append(", ");

sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.CALLER_LINE)).append(", ");

sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.EVENT_ID)).append(") ");

sqlBuilder.append("VALUES (?, ?, ? ,?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,LOGGING_EVENT_ID_SEQ.nextval)");returnsqlBuilder.toString();

}

}

注:类中LOGGING_EVENT_ID_SEQ序列需自行创建,logback默认建表语句中有该序列的创建语句,直接拿来使用即可。

3)logback.xml配置

此时JDBC与JNDI方式配置只需要将原name为DB的appender标签class属性的值指向自定义DBAppender即可,其他配置不变,下述代码为JDBC配置示例:

oracle.jdbc.driver.OracleDriver

jdbc:oracle:thin:@127.0.0.1:1521:orcl

tiger

123456

(3)测试类的编写

packagecom.hundsun.fund.ats.modules.server.test;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;public classLogbackTest {public static voidmain(String[] args) {//dblog为logback.xml中logger标签name属性的值

Logger logger= LoggerFactory.getLogger("dblog");

logger.debug("DEBUG级别信息");

logger.warn("WARN级别信息");

logger.info("INFO级别信息");

logger.error("ERROR级别信息");

}

}

五、总结

文中涉及到的Logback日志信息存库的处理只是在源代码的基础上做了点小小的改动而已,并非完整地介绍该组件的功能。想要全面学习Logback日志组件,请参考官方提供的源码和相应的API帮助文档。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/528380.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java中filehandler_java – 如何配置特定FileHandler的属性

Java日志记录配置文件允许我定义命名记录器的属性,例如name.heikoseeberger.heikotron.level FINEname.heikoseeberger.heikotron.handlers java.util.logging.FileHandler到现在为止还挺好.现在我想配置特定的FileHandler,例如使用特定的输出文件.不幸的是我只知道如何配置已…

android java标准时间_java android中对list的时间进行排序

public class HahaTime { public static void main(String[] args) { /** * 原来的日期 */ List list new ArrayList(); list.add("2014-03-04 22:22:22"); list.add("2014-4-04 22:22:2…

等待读取完毕 java_java – 等待来自客户端读取消息的套接字服务器

您可以简单地执行以下操作&#xff1a;String line;while ((line in.readLine()) ! null) {\\Do stuff}这应该具有预期的行为.编辑&#xff1a;这是我在使用您的代码的评论中谈论的完整示例&#xff1a;package javaapplication12;import java.io.BufferedReader;import java.…

Java可移动性不强_java地位无可撼动的原因

如今&#xff0c;面对曾经在程序员中被各种新技术掩盖直至堙灭的技术值得怀念。犹如COBOL这当年被老程序员们尊为神器的语言如今也基本没有价值。而Java作为现代程序员的中坚力量在这点上会不会成为下一个COBOL&#xff1f;有关JAVA的技术卖出多少本书已经是一个很久远的记忆了…

php rar_PHP: rar:// - Manual

说明The wrapper takes the url encoded path to the RAR archive (relative or absolute),an optional asterik (*), an optional number sign(#) and an optional url encoded entry name, as stored in thearchive. Specifying an entry name requires the number sign; a l…

终端执行php,PHP命令行执行PHP脚本的注意事项总结

文章来给各位同学介绍在PHP命令行执行PHP脚本的注意事项总结,如果你不注意这些东西&#xff0c;很可能服务器安全就出问题哦。如果你使用的wamp集成安装环境的话&#xff0c;那么你php的配置是在D:/wamp/bin/apache/Apache2.2.17/bin你要先把他复制覆盖掉D:/wamp/bin/php/php5.…

敏感词过滤的php代码,PHP敏感词过滤

/*** 禁词过滤* 执行效率&#xff1a;每篇用时0.05秒* author liuxu**/class Logic_BlackWord{const APP_FORUM 1;const APP_BLOG 2;const APP_VOTE 3;/*** 过滤得到禁词* param unknown $txt* return Ambigous */public function getHitList($txt){$hitList array();//对…

? php 为啥报错,如何解决js里面的php代码报错问题

在一个模块中&#xff0c;前端是extjs,后端是php&#xff0c;报错&#xff0c;这是在php的错误警告开到最严格的时候报的错&#xff0c;虽然程序可以跑&#xff0c;可是日志堆积越来越多。Undefined variable&#xff1a;loginUserNameExt.onReady(function () {var app Ext.c…

java其他进程,Java进程优先于其他Windows进程

我的任务是创建一个自定义文件备份服务,允许任何使用此服务的笔记本电脑在连接到我们的网络服务器时备份某些目录.我得到的一个要求是,该服务应该基本上优先于用户在该笔记本电脑上的正常活动而不是阻碍性能……太多了.我用Java创建了这个程序,目的是将它作为服务包装在YAJSW中…

centos php redhat,RHEL / CentOS 安装 OPcache 提升 PHP 效能

OPcache 在 PHP 5.5 (默认没有开启)开始内建在 PHP, 前身是 Zend Optimizer, PHP 5.2, 5.3 及 5.4 则要透过安装 PECL extension 安装。它的作将已经编译的 PHP Script 储存在内存, 当下次存取 PHP Script 时不用重新编译, 这样便可以提高 PHP 的效能。以下是在 CentOS 7 安装指…

php数据回显是什么意思,jquery回显是什么意思

首先来看一下回显是什么意思&#xff1f;在数据提交出现错误的时候, 已填写的信息仍在文本框中, 比如用户登录, 当用户输入错误的密码之后, 用户名仍在文本框, 只是密码框清空。对于一些要填写很多信息的表单, 如果因为一些错误导致已经填写的整个表单信息重新填写, 对于用户非…

Java摩托车汽车轮胎数量,摩托车轮子上的知识,你知道多少?

原标题&#xff1a;摩托车轮子上的知识,你知道多少?摩托车轮子好比人的两条腿&#xff0c;决定了车子跑得远&#xff0c;跑得快和跑得是否安稳。摩友们似乎对轮子的关心不多&#xff0c;因为它是易损件&#xff0c;概念中轮子轮胎出了问题&#xff0c;随便找路边的“风火补胎”…

matlab中的 variable,matlab中的问题Missing variable or function

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼请教各位大虾啦!!matlab中的问题Missing variable or function.??? Error: File: D:\matlab\one.m Line: 17 Column: 11Missing variable or function.是什么意思啊?源程序是:%machine characteristic %p109 machine parameter…

php抽象方法db,PHP笔记之抽象方法抽象类

抽象方法定义&#xff1a;一个方法如果没有方法体&#xff0c;则这个方法就是抽象方法。1.一个方法们没有{}&#xff0c;直接使用分号结束的方法&#xff1b;2.如果是抽象方法&#xff0c;必须使用abstract(抽象关键字来修饰)抽象类1.如果一个类中有一个方法是抽象方法&#xf…

php配置文件加盐解密,PHP实现支持加盐的图片加密解密

一个简单的图片加解密函数&#xff0c;使用client跑&#xff0c;不要使用浏览器跑话不多说&#xff0c;直接上代码/*** Created by hello.* User: qq 845875470* Date: 2016/4/2* Time: 11:21*/$notice <<为了稳定性&#xff0c;必须在客户端跑格式 &#xff1a;php pat…

matlab中的uint8函数,未定义与 'uint8' 类型的输入参数相对应的函数 'fitnessty'

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼程序如下&#xff1a;clear allclcticpopsize15;lanti10;maxgen50;cross_rate0.4;mutation_rate0.1;a00.7;zpopsize5;bestf0;nf0;number0;Iimread(C:\Users\Yu\Pictures\feiji.jpg);if numel(I)>2Irgb2gray(I);end[m,n]size(I);…

php文本文件操作,文本文件操作的php类

var $file;var $index;//建立一个文件并写入输入 function null_write($new) {$ffopen($this->file,w);flock($f,LOCK_EX);fputs($f,$new);fclose($f);} // 添加数据记录到文件末端 function add_write($new) { $ffopen($this->file,a);flock($f,LOCK_EX);fputs($f,$new)…

php创建输入文本框,Asp:文本框与输入文本(PHP开发人员学习ASP)

What is the advantage to using an over using a standard ?>您可以在代码隐藏页面中更容易地引用文本框>注意,您可以通过在控件中使用runat “server”来访问代码隐藏中的任何HTML元素>您可以访问文本框上的更多属性,而不是输入html元素>您可以与其他ASP.NET控件…

oracle没有注册mdsdora,oracle rac一个节点服务注册不上,必须重启监听才能注册上,各位大神帮忙看一下...

环境是windows 2008 oracle 11.2.0.4 rac其中rac1节点服务总是注册不上&#xff0c;rac2节点正常--1、查看监听状态C:\Users\Administrator>lsnrctl statusLSNRCTL for 64-bit Windows: Version 11.2.0.4.0 - Production on 22-1月 -2017 17:24:16Copyright (c) 1991, 2013…

Oracle导入dmp文件报12504,ORA-12504:TNS :监听程序在 CONNECT_DATA 中未获得SERVICE_NAME...

使用本地的SQLPlus连接服务器上的Oracle数据库&#xff0c;这是由于本地有安装了Oracle数据库服务&#xff0c;所以才会系统先提示&#xff1a;ORA-12504:TNS: 监听程序在 CONNECT_DATA 中未获得 SERVICE_NAME再次输入用户名密码系统又提示&#xff1a;ORA-12560: TNS: 协议适配…