My Hexo Blog


  • 首页

  • 归档

Log4j&&tomcat log调研文档

发表于 2018-05-28

目标:本文目标在于介绍java项目记录日志的主流方式log4j和tomcat log的配置方式和配合使用方法,


log4j(1.2.x)

log4j是Apache公司的一款用于在运行时输出日志的库。log4j的一个鲜明特征是它记录器(Logger)的分层记录,可以控制不同条件下输出日志的粒度,方便调试时获得详细信息和在正式环境减少日志输出量和存储成本。log4j目前支持的输出方式可以是文件,OutputStream,java.io.Writer,远程log4j服务器,远程Unix Syslog守护程序或许多其他输出目标。log4j的旧版本1.x是目前使用较多的旧版本。

log4j实现源码

查看源码有利于了解配置项结构和自定义log4j的日志输出,所以有必要进行源码分析。
为了查看log4j源码,打开我们的项目代码,容易疑惑的是:在我们代码中实际使用的Logger和LoggerFactory是org.slf4j包下的而不是log4j的包。slf4j这是java提供的简单日志接口定义,实际实现需要实现这些接口。用slf4j-log4j12-1.5.10.jar替换slf4j-jdk14-1.5.10.jar即可使用slf4j的接口,log4j的实现。
直接打开项目中引用的log4j.jar就可以看到它的源码,也可以在maven上下载log4j core只看核心类的代码。log4j的日志功能可以抽象出七个核心类/接口:Logger、LoggerRepository、Level、LoggingEvent、Appender、Layout、ObjectRender.类关系如下图:
Alt text
Logger用于对日志记录行为的抽象,提供记录不同级别日志的接口;Level对日志级别的抽象;Appender是对记录日志形式(xml、邮件、控制台输出)的抽象;Layout是对日志每行输出的格式的抽象;而LoggingEvent是对一次日志记录过程中所能取到信息的抽象(数据存储类)。LoggerRepository是Logger实例的容器,读取配置文件的信息也是这个类的功能,而ObjectRender是对日志实例的解析接口,处理传入的各类型Message。
每次执行一次类似于Logger.error()的语句写日志的时候,流程简略为:业务代码中调用Logger.xx()方法,Logger使用Level将此次调用请求等级与配置文件中等级对比,若本次请求等级更高,就继续日志输出的工作。之后Logger创建存储此次日志信息的LogEvent类,并作为参数传给Appender,Appender使用与自身一一关联的Layout类确定输出文本样式,随后由Appender将日志写入该Appender对应的日志输出中。流程图如下图所示。
Alt text

log4j使用配置

Log4j支持两种配置文件格式,一种是XML格式的文件,一种是properties文件。因为IT课现有项目大都使用的properties文件配置方式,所以先从在java项目中使用properties文件配置log4j写起:
导入必需jar包: 导入log4j ,slf4j(可选), slf4j-log4j (可选)三个jar包。
.properties的文件位置:.properties文件应该放在哪并且如何让项目知道它在哪?有三种方式

方法一:在web.xml中加入节点指定.properties文件位置,默认路径从source folder[ source folder:存放java源代码的文件夹,当然也包括一些package文件夹,还可以包含其他文件. 项目构建后,source folder里面的java自动编译成class文件到相应的/web-inf/classes文件夹中,其他文件也会移到/web-inf/classes相应的目录下. 在eclipse等ide中可以对文件夹右键→buildpath→use as source folder指定。一般可以新建一个source folder用于存放所有的配置文件.]开始

1
2
3
4
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>/WEB-INF/log4j.properties</param-value>
</context-param>

方法二:在java代码中使用logger前初始化,默认路径一样从source folder开始,source folder:存放java源代码的文件夹,当然也包括一些package文件夹,还可以包含其他文件. 项目构建后,source folder里面的java自动编译成class文件到相应的/web-inf/classes文件夹中,其他文件也会移到/web-inf/classes相应的目录下. 在eclipse等ide中可以对文件夹右键→buildpath→use as source folder指定。一般可以新建一个source folder用于存放所有的配置文件。

1
PropertyConfigurator.configure("log4j.properties") ;

方法三(现有项目使用的):log4j启动时,默认会寻找source folder下的log4j.xml配置文件,若没有,会寻找log4j.properties文件。然后加载配置。所以只要名字是log4j.properties并发在source folder下就可以了。
文件位置总结:现在部门中ssh项目大多使用的是第三种配置方式,配置简单方便,但是打包项目时一定会把配置文件也打进war包,如果要更改日志配置就得重启服务器。所以平时项目使用第三种配置,当预计日志需求变化较快时,使用前两种配置方式并把配置文件放到项目外更好。

编写.properties文件接下来开始编写properties文件自定义项目的日志输出。配置文件事实上也就是对上文说到的Logger、Appender及Layout进行相应设定。配置文件大致分为三个部分:

配置根Logger:

根Logger的配置格式为
log4j.rootLogger = [ level ] , appenderName1, appenderName2, …
log4j.additivity.org.apache=false:表示Logger不会在父Logger[2父Logger:可以限定某个类或包中使用另一种日志输出规则,如 log4j.logger.cn.server.child=error,E
rootlogger就是child Logger的父Logger]的appender里输出,默认为true。
level :设定日志记录的最低级别,可设的值有OFF、FATAL、ERROR、WARN、INFO、DEBUG、Trace、ALL或者自定义的级别,Log4j建议只使用中间四个(ERROR-DEBUG)级别。通过在这里设定级别,您可以控制应用程序中相应级别的日志信息的开关,比如在这里设定了INFO级别,则应用程序中所有DEBUG级别的日志信息将不会被打印出来。
appenderName:就是指定日志信息要使用哪几种方式输出。可以同时指定多个输出目的地,用逗号隔开。
例如:log4j.rootLogger=INFO,A1,B2,C

配置日志信息输出目的地(appender):

appender的配置格式为:
log4j.appender.appenderName = className
appenderName:自定义appderName,在log4j.rootLogger设置中使用;
className:可设值如下:
(1)org.apache.log4j.ConsoleAppender(控制台)
(2)org.apache.log4j.FileAppender(文件)
(3)org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件)
(4)org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件)
(5)org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)
下面介绍各类Appender具体的配置项:

ConsoleAppender选项:

1
2
3
Threshold=WARN:指定日志信息的最低输出级别,默认为DEBUG。
ImmediateFlush=true:表示所有消息都会被立即输出,设为false则不输出,默认值是true。
Target=System.err:默认值是System.out。

FileAppender选项:

1
2
Append=false:true表示消息增加到指定文件中,false则将消息覆盖指定的文件内容,默认值是true。
File=D:/logs/logging.log4j:指定消息输出到logging.log4j文件中。

DailyRollingFileAppender选项:

1
2
3
4
5
6
7
8
9
10
Append=false:true表示消息增加到指定文件中,false则将消息覆盖指定的文件内容,默认值是true。
File=D:/logs/logging.log4j:指定当前消息输出到logging.log4j文件中。
DatePattern='.'yyyy-MM:每月滚动一次日志文件,即每月产生一个新的日志文件。当前月的日志文件名为logging.log4j,前一个月的日志文件名为logging.log4j.yyyy-MM。
另外,也可以指定按周、天、时、分等来滚动日志文件,对应的格式如下:
1)'.'yyyy-MM:每月
2)'.'yyyy-ww:每周
3)'.'yyyy-MM-dd:每天
4)'.'yyyy-MM-dd-a:每天两次
5)'.'yyyy-MM-dd-HH:每小时
6)'.'yyyy-MM-dd-HH-mm:每分钟

RollingFileAppender选项:

1
2
3
4
Append=false:true表示消息增加到指定文件中,false则将消息覆盖指定的文件内容,默认值是true。
File=D:/logs/logging.log4j:指定消息输出到logging.log4j文件中。
MaxFileSize=100KB:后缀可以是KB, MB 或者GB。在日志文件到达该大小时,将会自动滚动,即将原来的内容移到logging.log4j.1文件中。
MaxBackupIndex=2:指定可以产生的滚动文件的最大数,例如,设为2则可以产生logging.log4j.1,logging.log4j.2两个滚动文件和一个logging.log4j文件。

配置日志信息的输出格式(Layout):

ayout的配置格式为:
log4j.appender.appenderName.layout=className
className:可设值如下:
(1)org.apache.log4j.HTMLLayout(以HTML表格形式布局)
(2)org.apache.log4j.PatternLayout(可以灵活地指定布局模式)
(3)org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串)
(4)org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息)
(1)HTMLLayout选项:
LocationInfo=true:输出java文件名称和行号,默认值是false。
Title=My Logging: 默认值是Log4J Log Messages。
(2)PatternLayout选项:
ConversionPattern=%m%n:设定以怎样的格式显示消息。
格式化符号说明:
%p:输出日志信息的优先级,即DEBUG,INFO,WARN,ERROR,FATAL。
%d:输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,如:%d{yyyy/MM/dd HH:mm:ss,SSS}。
%r:输出自应用程序启动到输出该log信息耗费的毫秒数。
%t:输出产生该日志事件的线程名。
%l:输出日志事件的发生位置,相当于%c.%M(%F:%L)的组合,包括类全名、方法、文件名以及在代码中的行数。例如:test.TestLog4j.main(TestLog4j.java:10)。
%c:输出日志信息所属的类目,通常就是所在类的全名。
%M:输出产生日志信息的方法名。
%F:输出日志消息产生时所在的文件名称。
%L::输出代码中的行号。
%m::输出代码中指定的具体日志信息。
%n:输出一个回车换行符,Windows平台为”rn”,Unix平台为”n”。
%x:输出和当前线程相关联的NDC(嵌套诊断环境),尤其用到像java servlets这样的多客户多线程的应用中。
%%:输出一个”%”字符。
另外,还可以在%与格式字符之间加上修饰符来控制其最小长度、最大长度、和文本的对齐方式。如:
1) c:指定输出category的名称,最小的长度是20,如果category的名称长度小于20的话,默认的情况下右对齐。
2)%-20c:”-“号表示左对齐。
3)%.30c:指定输出category的名称,最大的长度是30,如果category的名称长度大于30的话,就会将左边多出的字符截掉,但小于30的话也不会补空格。

屏蔽框架输出日志干扰

spring、hibernate、dubbo……这些框架使用commons-logging自动往我们的日志里塞内容导致日志过于庞大。让我们难以找到自己抛出的异常日志。
在log4j1.x的处理方法是根据实际使用框架的情况,定义对应的logger将它指定为OFF级别。

1
2
3
4
5
6
7
log4j.logger.org.springframework=OFF  
log4j.logger.org.apache.struts2=OFF
log4j.logger.com.opensymphony.xwork2=OFF
log4j.logger.com.ibatis=OFF
log4j.logger.org.hibernate=OFF
log4j.logger.com.alibaba.dubbo=OFF
log4j.logger.org.apache.zookeeper=OFF

或者用限定包的logger代替rootlogger

1
log4j.logger.com.tplink =org.apache.log4j.ConsoleAppender

配置文件总览

下面将展示log4j的整体配置

同项目内多个logger配置

在配置文件中,新写一个类似于rootlogger定义的语句用来定义子logger

1
2
#指定tplink.Newlogger类专用的logger
log4j.logger.tplink.Newlogger= DEBUG, test

然后在业务代码中我们就可以获取这个自定义的logger

1
Logger logger = LoggerFactory.getLogger(Newlogger.class); //参数是class

或者不是指定类而是一个名字来定义logger

1
log4j.logger.myTest= DEBUG, test2

业务代码中使用String来获取logger

1
Logger logger = LoggerFactory.getLogger(“myTest”); //参数是string

自定义Appender

在源码中有一个AppenderSkeleton骨架类,自己写一个类继承它。除了override方法外,自己需要从配置项读取的变量定义后写好getter和setter,就可以自动注入了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.spi.LoggingEvent;
public class IMAppender extends AppenderSkeleton {
private String username; //从配置文件读取的变量
@Override
public void close() {
// TODO Auto-generated method stub
}
@Override
public boolean requiresLayout() {
// TODO Auto-generated method stub
return false;
}
@Override
protected void append(LoggingEvent event) {
System.out.println("Hello, " + username + " : "+ event.getMessage());
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}

日志性能优化

影响性能最明显的几个步骤:硬盘读写、网络读写、JDBC,所以优化性能从这几点考虑。
设置日志缓存,以及缓存大小

1
2
3
log4j.appender.A3.BufferedIO=true 
#BufferSize单位为字节,默认是8K
log4j.appender.A3.BufferSize=8192

如果日志性能不佳,放弃数据库日志和邮件日志
在log4j1中的异步日志性能较差且容易出现死锁,不建议使用

使用xml配置

因为我们部门在log4j的使用中习惯用.properties文件,而xml在log4j2里被推荐使用,所以在log4j2部分里再作详细介绍。

log4j2

在log4j的官网上已经给出声明,自 2015-8-5起log4j已经是”reach end of life”,推荐将log4j更新到log4j2。log4j2的使用方式相比较1并没有太大改变,除了配置方式推荐为xml外,在与slf4j的协同上没有改变,也不需要改变旧java代码。(如果需要使用log4j2在java代码中的新特性就得替换slf4j的org.slf4j.LoggerFactory为org.apache.logging.log4j.LogManager)

2.1.log4j2优点

log4j2相比1有更好的异步日志性能,下图是官网上的日志吞吐量比较。同时log4j2也解决了1中高并发时出现的大量死锁问题。
Alt text
log4j2支持变量参数的占位符功能

1
logger.debug("Hi, {} {}", u.getA(), u.getB());

log4j2的执行记录跟踪和日志标签
这两个功能类似,都相当于给日志加上分隔符和类似android logcat的日志标签,用于区分不同模块的日志,省去了在日志字符串中作区分的麻烦。如果要使用的话可以参考官网链接Flow Tracing,Markers。

log4j2的使用配置

导入包

在项目中导入log4j-core-2.x.jar log4j-api-2.x.jar两个包。

配置文件

log4j2支持的配置方式有 XML, JSON, YAML,properties(版本2.4后支持properties方式,但是和1中的配置方式也不再相同)。
和1一样log4j2也会在classpath自动寻找配置文件,优先级从高到低分别为log4j2-test.properties,log4j2-test.yaml,log4j2-test.json,log4j2-test.xml,log4j2.properties,log4j2.yaml,log4j2.json,log4j2.xml。在没有找到配置文件时,会使用默认配置,输出到控制台。

xml配置
改为xml配置后,log4j要配置的依然是appender、layout、logger三部分。内容与1中的properties文件没有太大差别。下面举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?xml version="1.0" encoding="UTF-8"?>
<!--先定义默认的日志级别-->
<configuration status="error">
<!--先定义所有的appender-->
<appenders>
<!--这个输出控制台的配置 节点名字是随便起的 name属性下面配置logger要用到-->
<Console name="Console" target="SYSTEM_OUT">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY"/>
<!—和1一样的layout,具体配置也没变-->
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
</Console>
<!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用-->
<File name="log" fileName="log/test.log" append="false">
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
</File>

<!--这个会打印出所有的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFile" fileName="logs/app.log"
filePattern="log/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
<PatternLayout pattern="%d{yyyy-MM-dd 'at' HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/>
<SizeBasedTriggeringPolicy size="50MB"/>
</RollingFile>
</appenders>
<!--然后定义logger-->
<loggers>
<!--建立一个默认的root的logger-->
<root level="trace">
<appender-ref ref="RollingFile"/>
<appender-ref ref="Console"/>
</root>
<!—为某个类定义一个logger,name是该类的包路径,如果没有这个类就会视为普通的string命名的logger,log4j2会根据包名决定继承关系”com.tplink”logger就会是下面logger的父logger,additivity决定日志是否需要按照父logger再打印一次-->
<root name="com.tplink.mi.TestClass" level="trace" additivity="false">
<appender-ref ref="RollingFile"/>
<appender-ref ref="Console"/>
</root>
</loggers>
</configuration>

在例子中可以看到log4j的级别、appender种类和配置项、logger的继承关系、logger和类的绑定都没变。只是appender和logger的配置被不同节点分开,原来用键值对配置的属性改为标签内的属性配置。
在java代码中,如果不使用slf4j的话,使用如下语句创建Logger对象和打印日志

1
2
Logger logger = LogManager.getLogger(Test.class.getName());
logger.error("error");

log4j迁移到log4j2

更改引用的包
更改pom.xml配置,删除log4j的引用和低版本slf4j的引用,换成log4j2.x的引用例如

1
2
3
4
5
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.1</version>
</dependency>

换成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.6.2</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.6.2</version>
</dependency>
<!-- log4j-slf4j-impl(用于log4j2与slf4j集成) -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.6.2</version>
</dependency>

如果不用slf4j那只要引入log4jcore和api两个包就行了

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.6.2</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.6.2</version>
</dependency>

更改配置文件
因为log4j的log4j.properties配置方式在2中不再被支持,所以更新log4j.properties
的配置方式或者删除后换成log4j.xml。新的log4j.properties格式和xml配置类似,也是先定义appender再定义logger。例如官网上的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
status = error
dest = err
name = PropertiesConfig

property.filename = target/rolling/rollingtest.log

filter.threshold.type = ThresholdFilter
filter.threshold.level = debug

appender.console.type = Console
appender.console.name = STDOUT
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = %m%n

appender.rolling.type = RollingFile
appender.rolling.name = RollingFile
appender.rolling.fileName = ${filename}
appender.rolling.filePattern = target/rolling2/test1-%d{MM-dd-yy-HH-mm-ss}-%i.log.gz
appender.rolling.layout.type = PatternLayout
appender.rolling.layout.pattern = %d %p %C{1.} [%t] %m%n
appender.rolling.policies.type = Policies
appender.rolling.policies.time.type = TimeBasedTriggeringPolicy
appender.rolling.policies.time.interval = 2
appender.rolling.policies.time.modulate = true
appender.rolling.policies.size.type = SizeBasedTriggeringPolicy
appender.rolling.policies.size.size=100MB
appender.rolling.strategy.type = DefaultRolloverStrategy
appender.rolling.strategy.max = 5

appender.list.type = List
appender.list.name = List
appender.list.filter.threshold.type = ThresholdFilter
appender.list.filter.threshold.level = error

logger.rolling.name = com.example.my.app
logger.rolling.level = debug
logger.rolling.additivity = false
logger.rolling.appenderRef.rolling.ref = RollingFile

rootLogger.level = info
rootLogger.appenderRef.stdout.ref = STDOUT

更新java代码中的Logger(可选)
之前使用的slf4.org.Logger可以替换成org.apache.logging.log4j.Logger用于使用变量参数的占位符功能。如果应用了slf4j的包,也可以不改任何代码,继续使用slf4.org.Logger。

屏蔽框架输出日志干扰

spring、hibernate、dubbo……这些框架使用commons-logging自动往我们的日志里塞内容导致日志过于庞大。让我们难以找到自己抛出的异常日志。
在log4j2的处理方法是根据实际使用框架的情况,定义对应的logger将它指定为OFF级别。

1
2
<logger name="org.springframework" level="off" additivity="false">
</logger>

或者在要使用的日志中指定name为自己的包名如”com.tplink”,就只输出这个包的日志。
也可以提升整体日志级别到error,因为一般这些框架输出的级别都在debug和info。

1
2
3
<logger name="com.tplink.ebs" level="off" additivity="false">
<appender-ref ref="Console"/>
</logger>

Tomcat日志

本部分基于apache tomcat版本7.0-8.5。

Tomcat日志种类介绍

tomcat下的日志种类繁多,观察tomcat的日志目录(一般为..\logs)
Alt text
可以看到tomcat的日志分为:
|日志种类\作用 | 文件名
| :——– | ——–:
| Cataline引擎的日志文件、控制台输出的日志 | catalina.日期.log
| Tomcat下内部代码丢出的日志| localhost.日期.log
| Tomcat下默认manager应用日志| manager.日期.log
|Access日志|文件名在Servlet.xml配置(一般叫localhost_access_log..日期.log)
|log4j配置存储到该目录下的日志|随配置改变
catalina.log我们在维护时常看的日志之一,因为在我们的旧项目中,如果log4j指定的输出方式为stdout,那么在unix类系统中会被重定向到catalina.log中。除此之外,它还包含catalina引擎——tomcat的servlet容器本身的报错日志。
localhost.log中的内容是jsp页面内部错误的异常。
manager.log 是tomcat自带的manager应用的日志,正式项目中一般不开启manager应用。
localhost_access_log.log 是记录访问者、访问日期、访问目标的日志。

Tomcat日志配置

默认JULI实现下的配置

JULI,指tomcat对java.util.logging API的实现,用于负责日志输出。tomcat也可以通过配置让log4j来负责它的日志。
根据官方文档,Tomcat支持各应用各自独立使用日志框架。而Tomcat本身的通用日志系统,即用来输出上文所介绍的catalina.log, localhost.log,manager.log 的日志系统,是默认通过”JULI”实现的。
默认Tomcat使用JULI的情况下,并且除了常规的全局java.util.logging配置之外,还支持不同的应用使用不同的配置。全局的配置通常在$ {catalina.base} /conf/logging.properties文件中完成。该文件由java.util.logging.config.file系统属性指定,该属性由启动脚本设置。如果它不可读或未配置,则默认使用JRE中的$ {java.home} /lib/logging.properties文件。
在各Web应用程序中。该文件将是WEB-INF / classes / logging.properties。
logging.properties配置和java.util.logging的配置方式相似,一样有区分输出级别,从高到低分别是OFF,SEVERE,WARNING,INFO,CONFIG,FINE,FINER,FINEST或ALL设置。处理程序的日志级别阈值默认为INFO,同样,不同包也可以指定不同的日志级别。

logging.properties的三段结构

logging.properties配置文件由3大部分组成:起始handler申明,配置handler属性,logger适配handler。配置中的handler类似于log4j中的appender,用于指定日志的输出目标;formatter类似于layout,用于指定日志的格式;logger则是记录日志的执行类。
下面是示例logging.properties配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#定义handlers,handlers名称格式为 前缀.决定输出目的的handler类
handlers=1catalina.org.apache.juli.FileHandler,2localhost.org.apache.juli.FileHandler, 3manager.org.apache.juli.FileHandler, java.util.logging.ConsoleHandler
.handlers = 1catalina.org.apache.juli.FileHandler, java.util.logging.ConsoleHandler
############################################################
#配置handler属性
############################################################
# Cataline引擎的日志文件,文件名catalina.日期.log
1catalina.org.apache.juli.FileHandler.level = FINE
1catalina.org.apache.juli.FileHandler.directory = ${catalina.base}/logs
1catalina.org.apache.juli.FileHandler.prefix = catalina.
# Tomcat下内部代码丢出的日志,文件名localhost.日期.log
2localhost.org.apache.juli.FileHandler.level = FINE
2localhost.org.apache.juli.FileHandler.directory = ${catalina.base}/logs
2localhost.org.apache.juli.FileHandler.prefix = localhost.
# Tomcat下默认manager应用日志,文件名manager.日期.log
3manager.org.apache.juli.FileHandler.level = FINE
3manager.org.apache.juli.FileHandler.directory = ${catalina.base}/logs
3manager.org.apache.juli.FileHandler.prefix = manager.
# 控制台输出的日志,Linux下默认重定向到catalina.out
java.util.logging.ConsoleHandler.level = FINE
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter


############################################################
# logger适配handler
############################################################

org.apache.catalina.core.ContainerBase.[Catalina].[localhost].level = INFO
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].handlers = 2localhost.org.apache.juli.FileHandler
# manager的日志
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/manager].level = INFO
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/manager].handlers=3manager.org.apache.juli.FileHandler
# host-manager的日志
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/host-manager].level = INFO
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/host-manager].handlers=4host-manager.org.apache.juli.FileHandler

handler申明中给handler命名格式为“前缀. handler类名”:前缀是数字开头小数点结尾的字符串,用于区分同类的不同handler。handler类决定日志输出形式,这个类有如下表格中的几种实现,不同handler的配置项如最右一栏所示:
Alt text
Alt text
Alt text
定义并配置handler之后,需要将handeler绑定给logger,logger的命名方式是org.apache.catalina.core.ContainerBase.[${engine}].[${host}].[${context}] ,其中Engine一般是Catalina——tomcat的servlet引擎,context即是tomcat日志的那些种类:“localhost、host-manager”……

Access Logging的特殊性

在上述日志中,有一个特例Access访问日志:因为它需要以较低的开销快速频繁地记录来访情况,所以它并没有使用JULI来实现它在Servlet.xml中的Context或者 Host或者Engine中配置。在上述的配置节中增加类似的value:

1
2
3
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log." suffix=".txt"
pattern="%h %l %u %t &quot;%r&quot; %s %b" />

Access Logging同样有一系列配置项,以下翻译自官网:
|参数 | 描述
| :——– | ——–:
| className | 实现类的类名,如果要用默认方式实现日志那就一定设为 org.apache.catalina.valves.AccessLogValve
| directory| 文件存放路径,如果是相对路径那么会从$CATALINA_BASE算起。默认值是 “logs”
| prefix| 文件名前缀,默认”access_log.”.
|suffix|文件名后缀,默认为空字符串
|fileDateFormat|自定义在文件名中的时间戳默认值yyyy-MM-dd. 如果你希望每小时刷新一个文件那么改成 yyyy-MM-dd.HH.日期一定会使用 en_US配置本地化。
|rotatable|是否需要划分日志文件,默认: true
|renameOnRotate|是否禁用分割文件中的timestamp,默认: false
|pattern|日志输出的文本的格式
|encoding|编码
|locale|用于在访问日志行中格式化时间戳的语言环境。 使用显式SimpleDateFormat模式(%{xxx} t)配置的任何时间戳都将在此语言环境中进行格式化。 默认情况下使用Java进程的缺省语言环境。
|requestAttributesEnabled|是否显示远程请求传来的参数,默认为false
|conditionIf|启用条件日志记录。 如果设置,只有当ServletRequest.getAttribute()不为null时,才会记录请求。 例如,如果此值设置为important,那么只有ServletRequest.getAttribute(“important”)!= null时才会记录特定的请求。 使用过滤器是一种在许多不同请求上在ServletRequest中设置/取消设置属性的简单方法。
|conditionUnless|和上条类似,不过变为ServletRequest.getAttribute(“important”)== null时才会记录特定的请求。
|condition|和conditionUnless一样,这个属性用于向后兼容
|buffered|决定是否启用缓存,默认: true
其中pattern属性时决定日志输出格式的重要属性,它的语法很复杂,但是可以用短配置简化:shorthand pattern pattern=”common” 代表 ‘%h %l %u %t “%r” %s %b’。更详细的语法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
%a - Remote IP address
%A - Local IP address
%b - Bytes sent, excluding HTTP headers, or '-' if zero
%B - Bytes sent, excluding HTTP headers
%h - Remote host name (or IP address if enableLookups for the connector is false)
%H - Request protocol
%l - Remote logical username from identd (always returns '-')
%m - Request method (GET, POST, etc.)
%p - Local port on which this request was received. See also %{xxx}p below.
%q - Query string (prepended with a '?' if it exists)
%r - First line of the request (method and request URI)
%s - HTTP status code of the response
%S - User session ID
%t - Date and time, in Common Log Format
%u - Remote user that was authenticated (if any), else '-'
%U - Requested URL path
%v - Local server name
%D - Time taken to process the request, in millis
%T - Time taken to process the request, in seconds
%F - Time taken to commit the response, in millis
%I - Current request thread name (can compare later with stacktraces)
%{xxx}i write value of incoming header with name xxx
%{xxx}o write value of outgoing header with name xxx
%{xxx}c write value of cookie with name xxx
%{xxx}r write value of ServletRequest attribute with name xxx
%{xxx}s write value of HttpSession attribute with name xxx
%{xxx}p write local (server) port (xxx==local) or remote (client) port (xxx=remote)
%{xxx}t write timestamp at the end of the request formatted using the enhanced SimpleDateFormat pattern xxx

值得一提的是,在观察Access Log后可能产生过滤某些请求的需求,而和AccessLogging相关的功能Access Control可以过滤访问请求。详细可以去官网文档查看。

2.2.使用Log4j实现Tomcat日志

本节介绍如何配置Tomcat以对所有Tomcat的内部日志记录使用log4j而不是java.util.logging。
注意:这和我们在项目中使用log4j完全不是一回事,是指用log4j代替JULI实现Tomcat的系统日志。这种替换目的在于使用更加友好的配置方式和更丰富的日志输出方式。
以下步骤描述了如何配置log4j以输出Tomcat的内部日志记录。
创建log4j.properties配置文件
下载log4j v1.2.x
在apache tomcat官网下载tomcat-juli.jar 、 tomcat-juli-adapters.jar
把log4j.jar 和tomcat-juli-adapters.jar放到tomcat下 的lib文件夹里
把bin文件夹下的tomcat-juli.jar用刚刚下载的版本代替
删除conf文件夹下的logging.properties文件,防止tomcat生成空日志文件
开启tomcat
其中log4j.properties的配置和普通log4j一样,只要保证logger的名称是tomcat规定的格式
org.apache.catalina.core.ContainerBase.[${engine}].[${host}].[${context}] 格式。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

log4j.rootLogger = INFO, CATALINA

# Define all the appenders
log4j.appender.CATALINA = org.apache.log4j.DailyRollingFileAppender
log4j.appender.CATALINA.File = ${catalina.base}/logs/catalina
log4j.appender.CATALINA.Append = true
log4j.appender.CATALINA.Encoding = UTF-8
# Roll-over the log once per day
log4j.appender.CATALINA.DatePattern = '.'yyyy-MM-dd'.log'
log4j.appender.CATALINA.layout = org.apache.log4j.PatternLayout
log4j.appender.CATALINA.layout.ConversionPattern = %d [%t] %-5p %c- %m%n

log4j.appender.LOCALHOST = org.apache.log4j.DailyRollingFileAppender
log4j.appender.LOCALHOST.File = ${catalina.base}/logs/localhost
log4j.appender.LOCALHOST.Append = true
log4j.appender.LOCALHOST.Encoding = UTF-8
log4j.appender.LOCALHOST.DatePattern = '.'yyyy-MM-dd'.log'
log4j.appender.LOCALHOST.layout = org.apache.log4j.PatternLayout
log4j.appender.LOCALHOST.layout.ConversionPattern = %d [%t] %-5p %c- %m%n

log4j.appender.MANAGER = org.apache.log4j.DailyRollingFileAppender
log4j.appender.MANAGER.File = ${catalina.base}/logs/manager
log4j.appender.MANAGER.Append = true
log4j.appender.MANAGER.Encoding = UTF-8
log4j.appender.MANAGER.DatePattern = '.'yyyy-MM-dd'.log'
log4j.appender.MANAGER.layout = org.apache.log4j.PatternLayout
log4j.appender.MANAGER.layout.ConversionPattern = %d [%t] %-5p %c- %m%n

log4j.appender.HOST-MANAGER = org.apache.log4j.DailyRollingFileAppender
log4j.appender.HOST-MANAGER.File = ${catalina.base}/logs/host-manager
log4j.appender.HOST-MANAGER.Append = true
log4j.appender.HOST-MANAGER.Encoding = UTF-8
log4j.appender.HOST-MANAGER.DatePattern = '.'yyyy-MM-dd'.log'
log4j.appender.HOST-MANAGER.layout = org.apache.log4j.PatternLayout
log4j.appender.HOST-MANAGER.layout.ConversionPattern = %d [%t] %-5p %c- %m%n

log4j.appender.CONSOLE = org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Encoding = UTF-8
log4j.appender.CONSOLE.layout = org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern = %d [%t] %-5p %c- %m%n

# Configure which loggers log to which appenders
log4j.logger.org.apache.catalina.core.ContainerBase.[Catalina].[localhost] = INFO, LOCALHOST
log4j.logger.org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/manager] =\
INFO, MANAGER
log4j.logger.org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/host-manager] =\
INFO, HOST-MANAGER

Tomcat+log4j日志方案示例

开发环境推荐配置

以下配置的目的:在控制台只输出所有代码中抛出的debug级别及以上的日志信息,并在文件中保留当次启动的输出日志便于复查。
达成上面目标的主要困难是:
1.使用相对路径时,日志文件很难找
这里的处理方法是在windows中干脆用绝对路径
也可以使用log4j2的特性lookup,${log4j:configParentLocation}定位到配置文件的上级目录,${sys:catalina.home}定位到tomcat目录

2.spring、hibernate、dubbo……这些框架使用commons-logging自动往我们的日志里塞内容导致日志过于庞大
这里的处理方法是根据实际使用框架的情况,定义对应的logger将它指定为OFF级别。
或者在要使用的日志中指定name为自己的包名如”com.tplink”,就只输出这个包的日志。
也可以提升整体日志级别到error,因为一般这些框架输出的级别都在debug和info。
pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.6.2</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.6.2</version>
</dependency>
<!-- log4j-slf4j-impl(用于log4j2与slf4j集成) -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.6.2</version>
</dependency>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?xml version="1.0" encoding="UTF-8"?>
<!--先定义默认的日志级别-->
<configuration status="debug">
<!--先定义所有的appender-->
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
<appenders>
<!--这个输出控制台的配置 节点名字是Appender name属性下面配置logger要用到-->
<Console name="Console" target="SYSTEM_OUT" additivity="true">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
</Console>
<!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用-->
<File name="test" fileName="d:/logs/test.log" append="false" additivity="true">
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
</File>
<!—也可以使用项目目录存放日志文件 -->
<!--<File name="test" fileName="${log4j:configParentLocation}/logs/test.log" append="false" additivity="true">-->
<!--<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>-->
<!--</File>
</appenders>
<!--然后定义logger-->
<loggers>
<!--过滤掉spring和hibernate的一些无用的debug信息-->
<logger name="org.springframework" level="off" additivity="false">
</logger>
<logger name="org.hibernate" level="off" additivity="false">
</logger>
<!--建立一个默认的root的logger-->
<root level="all">
<appender-ref ref="test"/>
<appender-ref ref="Console"/>
</root>
</loggers>
</configuration>

查找特定bug时的推荐配置

和开发情况下的配置类似,只是loggers需要限定到疑似出现问题的特定包、类就可以

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?xml version="1.0" encoding="UTF-8"?>
<!--先定义默认的日志级别-->
<configuration status="debug">
<!--先定义所有的appender-->
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
<appenders>
<!--这个输出控制台的配置 节点名字是Appender name属性下面配置logger要用到-->
<Console name="Console" target="SYSTEM_OUT" additivity="true">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
</Console>
<!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用-->
<File name="test" fileName="d:/logs/test.log" append="false" additivity="true">
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
</File>
<!—也可以使用项目目录存放日志文件 -->
<!--<File name="test" fileName="${log4j:configParentLocation}/logs/test.log" append="false" additivity="true">-->
<!--<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>-->
<!--</File> -->
</appenders>
<!--然后定义logger-->

<loggers>
<logger name="com.tplink.ebs" level="off" additivity="false">
<appender-ref ref="Console"/>
</logger>


</loggers>
</configuration>

测试、正式环境推荐配置

和现在项目相比,这种配置的目的是削减catalina.out的输出,作为替换用log4j的文件记录每天的日志。
log4j2.xml
配置的目的是设置两个文件MITP.log和MIFULL.log分别输出自己代码的异常和全部异常,在文件大小达到SizeBasedTriggeringPolicy设置的值后,会按日期分文件夹并存为压缩文件。文件存放地点是tomcat目录下的logs文件夹

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?xml version="1.0" encoding="UTF-8"?>
<!--先定义默认的日志级别-->
<configuration status="error">
<!--先定义所有的appender-->
<appenders>
<!--这个输出控制台的配置 节点名字是Appender name属性下面配置logger要用到-->
<Console name="Console" target="SYSTEM_OUT">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
</Console>


<!--这个会打印出所有的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileFull" fileName="${sys:catalina.home}/logs/MIFull.log"
filePattern="${sys:catalina.home}/logs/$${date:yyyy-MM}/MIFULL-%d{MM-dd-yyyy}-%i.log.gz">
<PatternLayout pattern="%d{yyyy-MM-dd 'at' HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/>
<SizeBasedTriggeringPolicy size="50MB"/>
</RollingFile>

<RollingFile name="RollingFileTP" fileName="${sys:catalina.home}/logs/MITP.log"
filePattern="${sys:catalina.home}/logs/$${date:yyyy-MM}/MITP-%d{MM-dd-yyyy}-%i.log.gz">
<PatternLayout pattern="%d{yyyy-MM-dd 'at' HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/>
<SizeBasedTriggeringPolicy size="50MB"/>
</RollingFile>
</appenders>
<!--然后定义logger-->
<loggers>
<!--建立一个默认的root的logger-->
<root level="error" additivity="true">
<appender-ref ref="RollingFileFull"/>
</root>
<logger name="com.tplink" level="error" additivity="true">
<appender-ref ref="RollingFileTP"/>
</logger>
</loggers>
</configuration>

tomcat logging.properties
和默认配置文件比只改了Catalina的输出级别为OFF

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

handlers = 1catalina.org.apache.juli.FileHandler, 2localhost.org.apache.juli.FileHandler, 3manager.org.apache.juli.FileHandler, 4host-manager.org.apache.juli.FileHandler, java.util.logging.ConsoleHandler

.handlers = 1catalina.org.apache.juli.FileHandler, java.util.logging.ConsoleHandler

############################################################
# Handler specific properties.
# Describes specific configuration info for Handlers.
############################################################

1catalina.org.apache.juli.FileHandler.level = FINE
1catalina.org.apache.juli.FileHandler.directory = ${catalina.base}/logs
1catalina.org.apache.juli.FileHandler.prefix = catalina.

2localhost.org.apache.juli.FileHandler.level = FINE
2localhost.org.apache.juli.FileHandler.directory = ${catalina.base}/logs
2localhost.org.apache.juli.FileHandler.prefix = localhost.

3manager.org.apache.juli.FileHandler.level = FINE
3manager.org.apache.juli.FileHandler.directory = ${catalina.base}/logs
3manager.org.apache.juli.FileHandler.prefix = manager.

4host-manager.org.apache.juli.FileHandler.level = FINE
4host-manager.org.apache.juli.FileHandler.directory = ${catalina.base}/logs
4host-manager.org.apache.juli.FileHandler.prefix = host-manager.

java.util.logging.ConsoleHandler.level = OFF
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter


############################################################
# Facility specific properties.
# Provides extra control for each logger.
############################################################
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].level = ERROR
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].handlers = 2localhost.org.apache.juli.FileHandler

org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/manager].level = ERROR
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/manager].handlers = 3manager.org.apache.juli.FileHandler

org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/host-manager].level = ERROR
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/host-manager].handlers = 4host-manager.org.apache.juli.FileHandler

# For example, set the org.apache.catalina.util.LifecycleBase logger to log
# each component that extends LifecycleBase changing state:
#org.apache.catalina.util.LifecycleBase.level = FINE

# To see debug messages in TldLocationsCache, uncomment the following line:
#org.apache.jasper.compiler.TldLocationsCache.level = FINE

使用django从数据库反转成cms系统流程

发表于 2018-05-26

目标:本文原目标为从已有的数据库表生成cms(后台管理)系统;也是使用django快速搭建一个网站的流程文档。


本地环境

不同环境下的操作步骤可能略有差异,先列出本文对应的配置环境以作区分。
开发环境
不同环境下的操作步骤可能略有差异,先列出本文对应的配置环境以作区分。

   
操作系统 Windows10 64bit
Python 2.7.11
Django 1.9.5

部署环境

   
操作系统 Linux CentOS 6.4 64bits Server
Python 2.7.11
Django 1.9.5
Apache 2.2.15
mod_wsgi 4.5.5

实现流程

下文介绍搭建、发布django项目的流程

环境搭建

下载python-2.7.11.amd64.msi安装python,配置环境变量。cms中输入python显示版本号则安装成功。
解压Django-1.9.5.tar.gz后进入文件夹根目录运行命令python setup.py install 安装django。
安装可参考http://www.ziqiangxuetang.com/django/django-install.html
编码和配置可以参考官方文档 https://docs.djangoproject.com/en/1.9/

新建项目与app

cms中使用命令:
进入想要生成项目的目录,
输入django-admin.py startproject xxx 来新建项目
进入项目目录,
输入python manage.py startapp xxx 新建app
这时候的目录结构会是
根目录
——项目名
…
——urls.py
——settings.py
…
——app名
…
——admin.py
——models.py
…

反转数据库表

安装mysql-connector-python-2.1.3-py2.7-winx64.msi (只能64位系统使用,否则安装时找不到python目录,32位可以从mysql官网http://dev.mysql.com/downloads/connector/python/下载)
目前上述mysqldb驱动在不支持python3,可以使用pymysql代替,代码上可以完全不变,只需要在项目同名目录下的urls.py中添加:

1
2
import pymysql
pymysql.install_as_MySQLdb()

更改settings.py的数据库连接配置,

1
2
3
4
5
6
7
8
9
10
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'database name’,
'USER':’user name’,
'PASSWORD':'password',
'HOST':'ip',
'PORT': 'port',
}
}

cmd中运行python manage.py inspectdb (>directory) 反转数据库,如果没有报错的话,控制台/指定目录中就已经生成了翻转后的model代码。
运行python manage.py makemigrations , python manage.py migrate 同步数据库,django会自动在数据库中生成用户及历史记录相关表,如下
Alt text
如果数据表需要更新,或者想使用model生成表时,依然使用这两个命令python manage.py makemigrations , python manage.py migrate更新数据库。
python manage.py migrate 是执行app下的文件夹migrations下的.py文件,如果migrate命令一直在报错,可能是某个py文件的更改与数据库表的状态不同步(例如python manage.py makemigrations后又更改了表结构),那么删掉冲突后的文件,重新makemigrations,就可以解决持续报错的问题。

配置模型

在项目中注册app,在settings.py中加入app

1
2
3
4
5
6
INSTALLED_APPS = [
.
.
.
'pcms', #app名
]

为了决定cms中有哪些模型需要被管理,app目录下的admnin.py中,加入如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from .models import  Article             #article是model名
class ArticleAdmin(admin.ModelAdmin): #如果不配置以下内容,这里的class申明可以不需要
#列表显示项
list_display = ('page_title', 'last_update_date','type')
#新增和修改时的可编辑项
fields = ('content',)
#过滤项,group by
list_filter = ('type',)
#搜索项
search_fields = ('page_title',)
#每一页显示数目
list_per_page = 10

admin.site.register(Article, ArticleAdmin)

更多配置参考 https://docs.djangoproject.com/en/1.9/ref/contrib/admin/actions/

启动服务器

cms中使用python manage.py createsuperuser创建超级管理员,按照提示输入用户名邮箱密码就可以了。
python manage.py runserver 或者 python manage.py runserver 端口号 开启服务器,访问’http://localhost:8000/admin或者http://localhost:端口号/admin/ 进入cms。

更改页面模板

settings.py中设置TEMPLATES=[…]中的dirs可以设置项目模板路径,设置成

1
'DIRS': [os.path.join(BASE_DIR,'templates'),],

指定basedir为根目录下的templates文件夹。在自己的项目根目录下建立templates\admin两个文件夹,用于存放自定义html模板。
python安装目录下的
Python27\Lib\site-packages\Django-1.9.5-py2.7.egg\django\contrib\admin\templates\admin
文件中夹中的html文件都是cms使用的html模板,可以从那里复制文件到templates\admin下并改写。其中{%xxx%}标签进行模块表示、逻辑判断,{{xxx}}标签代表context传到模板里的变量。 配置成功后,django就会优先使用templates\admin下的自定义模板来展示界面了。

配置静态资源根目录(开发状态)

页面中如果需要使用自定义的css、js、图片等,需要配置静态资源路径。
在自动生成的项目中,如下两个配置项应该都已经配置好了:
settings.py的STATIC_URL = ‘/static/‘ 和 INSTALLED_APPS里的 ‘django.contrib.staticfiles’,
现在要做的只有在app下建立目录\static\admin\css,在其中添加自己的css,然后在模板中通过类似于

1
<link rel="stylesheet" type="text/css" href="{% static "admin/css/changelists.css" %}" />

引用。要注意的是,如果自定义css和Python27\Lib\site-packages\Django-1.9.5-py2.7.egg\django\contrib\admin\static\admin\css
下的css文件重名,那么django会优先使用python目录下的,目前我还没发现怎么更改优先级,所以不能重名。

CMS的控件扩展

下拉选择框

更改models.py,先加入选择框属性

1
2
3
4
STATUS_PUBLISH = (
(0, 'UnPublished'),
(1, 'Published'),
)

然后将选择框应用到属性中去

1
2
class Event(models.Model):   
publish = models.IntegerField(choices=STATUS_PUBLISH,…)

这个属性对应的编辑项就是下拉框了

多选框

这个控件需要多对多的表关联
对于数据库表反转生成的项目,我在models.py文件里加入的关联关系在生成表的时候会因为表名前缀冲突而失败,还没有找到解决办法。最好先在表中添加多对多关系,然后反转。
上面的bug可以通过安装South管理model更改来避免http://www.ziqiangxuetang.com/django/django-schema-migration.html
默认多选框的效果是这样的:

但是这个在可选项变多时不大好选择,可以在admin.py中更改ModelAdmin类来更改样式,例如:

1
2
3
class BookAdmin(admin.ModelAdmin):
…
filter_horizontal = ('authors',)

这时样式会变成
Alt text

同理filter_vertical = (‘verticals’,) 样式就是竖过来的。

文件上传

models.py中,更改变量类型

1
models.FileField(upload_to = …)

对应的表单域就变为文件上传控件了
类型改为ImageField限制为图片上传
后台限值文件上传或大小TODO,预测需要自定义上传方法

服务器部署

目前已知有apche和uwsgi两种部署方式,本文在项目中使用过的是apache+mod_wsgi的部署方式。

部署前准备:

所需版本的python源码包、django源码包 、数据库驱动pymysql的源码包、apache模块mod_wsgi的安装包、项目中导入的第三方包列表(如果服务器不连外网则需要源码包和它们引用到的所有源码包)

部署步骤:

关掉selinux,安装mysql。
因为centos上都有旧版本的python,所以先更新python版本,要保留旧版本python推荐看这篇博客:http://www.cnblogs.com/lanxuezaipiao/archive/2012/10/21/2732864.html。
注意该篇博客中的

1
./configure --prefix=/usr/local/python3

要改成

1
./configure --enable-shared --prefix=/usr/local/python3

来确保安装动态链接库。安装后运行python命令看到版本为自己需要的就行了。
编译安装apache和mod_wsgi(apache也可以用yum安装,比较快捷,但是目前内网的apache版本有点旧)。编译安装mod_wsgi后,修改配置文件httpd.conf,添加如下一行:
LoadModule wsgi_module modules/mod_wsgi.so
用来加载wsgi服务。
安装和配置apache和mod_wsgi容易抛的错比较多,包括缺少依赖软件、配置文件出错、没找对网站默认存放目录……安装后最好实际检验一下:
测试是否配置成功,创建一个test.wsgi文件(wsgi文件就是一个python module,只不过扩展名是wsgi而已),文件内容如下:

1
2
3
def application(environ,start_response):
start_response("200 OK",[('content-type',"text/html")])
return ['<html><body>Hello world!</body></html>']

修改httpd.conf配置文件,添加下面一行:

1
WSGIScriptAlias /trac  目录/test.wsgi

重启apache服务器
在地址栏敲入http://127.0.0.1/trac,如果输出“hello world”说明配置成功
然后就是配置自己的项目了,其实要改的只有一个httpd的配置文件,目的是在最后加上
WSGISocketPrefix /var/run/wsgi
WSGIScriptAlias / /var/www/workhours/workhours/wsgi.py
WSGIPythonPath /var/www/workhours/
WSGIDaemonProcess gongshitongji.tp-link.net python-path=/var/www/workhours/:/usr/local/python27/lib/python2.7/site-packages
WSGIProcessGroup gongshitongji.tp-link.net

Alias /static/js/ /var/www/workhours/static/js/
Alias /static/css/ /var/www/workhours/static/css/
WSGISocketPrefix /var/run/wsgi
WSGIScriptAlias / 项目目录/wsgi.py
WSGIPythonPath 项目目录
WSGIDaemonProcess gongshitongji.tp-link.net
python-path=项目目录:python目录/site-packages
WSGIProcessGroup gongshitongji.tp-link.net

Alias /static/js/ 项目目录/static/js/
Alias /static/css/ 项目目录/static/css/
Alias /static/admin/ django目录/static/admin

<Directory /var/www/workhours/workhours/>

Order deny,allow
Allow from all

<Directory /var/www/workhours/static/js>

Order deny,allow
Allow from all

<Directory /var/www/workhours/static/css>

Order deny,allow
Allow from all


这段配置定义了项目目录,静态文件目录。可以直接写在httpd.conf后面也可以自定义一个配置文件然后在httpd.conf中引用。
然后,重启apache,查看项目是否运行成功!
一般这个时候是打开django的报错页面,这时候打开apache的error.log可以看到为什么报错(一般是引用到的包没有找到),然后像开发时一样解决这些报错。
报错解决后,更改settings.py为

1
2
DEBUG=false
ALLOWED_HOSTS = ['*']

就算发布成功了

国际化

admin的国际化可以在settings.py中设置,例如下面配置为中文。

1
LANGUAGE_CODE = 'zh-hans'

某些特定文字还可以通过“更改页面模板”中的手法来更改。models.py中的字段和模型本身的verbose_name也是很重要的汉化手段。

Solr调研文档

发表于 2018-05-24

介绍Solr的功能以及搭建步骤,主要包括管理核心(core)、索引数据、中文分词、按权重搜索、高亮搜索内容等功能
Solr是Apache公司的一个基于Lucene框架的开源全文搜索服务器
Solr如其在官网宣传的,有如下特性

  • 先进的全文搜索能力:支持使用短语、通配符、集合、聚合等进行搜索
  • 为高通量网络环境优化过
  • 标准的开放接口:使用xml、json、http进行搜索和配置
  • 有功能全面的网页管理界面:使用自带的管理网页可以实现Solr支持的大部分功能:监控、配置、搜索
  • 易监控:使用JMX发布标准化的运行数据,自带slf4j日志,直接可在网页中查看
  • 可伸缩高可用:基于apache zookeeper ,易于服务器复制、分发、负载均衡
  • 易修改:每个核心(core)只需要修改两个配置文件就能作绝大部分功能的配置
  • 近实时索引
  • 易使用插件进行拓展:分词器、查询处理器都可以用自己的jar包定制
  • 富文本索引:支持对pdf、word等富文本进行索引

Solr官方文档

Solr官方的quickstart文档http://lucene.apache.org/solr/quickstart.html ,直接搭建一个2个服务器2个shard组成的搜索服务器,帮助对solr的数据索引、搜索、分布式结构有所了解。缺点时文档使用的很多命令只在linux下生效,可以结合下文的使用流程使用windows下能用的命令
Solr官方文档 https://cwiki.apache.org/confluence/display/solr/About+This+Guide 这是在线版的官方文档,有所有功能的介绍,但是举例和异常处理方式不大详细,没有对中文的特殊处理。网络上介绍solr的博客类文档恰好能补充,但因为solr版本繁杂,需要对照官方文档阅读。

Solr初步使用流程

下文介绍一个solr安装使用的例子,目标是对一批简历数据进行搜索。

例中软件环境

软件名称 软件版本
操作系统 Windows10
JAVA版本 Java8
Solr版本 Solr6.6.0
中文分词器 IK

使用流程

1.下载安装Java

目前版本的solr最低需要Java8,下载并安装。

2.下载Solr并解压

官方下载页 http://lucene.apache.org/solr/mirrors-solr-latest-redir.html 下载后是个压缩包,解压后就算安装完成了

3.运行solr服务器

通过bin目录下的solr文件启动solr服务器,例如:

1
bin\solr start -p 8983

其中-p 8983是指定端口的参数,如果不指定默认开启的端口也是8983.

4.创建核心(core)

核心(core)是solr的一种概念,同一个core中使用同一份字段类型定义、同样的查询配置。搜索时也是需要指定某一个core进行查询的。
使用指令:

1
bin\solr create -c corename1

创建一个叫corename1的core。
或者,在http://localhost:8983/solr/#/ 的管理网页上的coreadmin菜单中也可以创建一个core
5.根据目标数据配置schema

#Schema的意义

搜索服务器建立数据索引的过程其实是按照固定格式导入数据,如何定义导入数据的格式就是由schema决定的了。schema定义了一些字段类型(field type)和字段(field),如果把要导入的数据类比为java中的对象,字段类型(field type)和字段(field)就类似于java中的变量类型和变量。在创建一个core之后,会自动在对应的core目录下的conf文件夹生成一个 managed-schema文件,这是由solr实时修改的schema配置文件,原则上不能直接修改。需要配置可以在网页端的schema页面进行修改。

#Schema.xml文件关系

如果要批量修改schema的字段配置,可以直接编辑文件schema.xml。在config目录下和schema配置相关的有这么三个文件:
Alt text
一般技术博客会提到的文件是schema.xml,但是在6.6.0版本中schema.xml文件是不存在的,需要用户自己创建。他们之间的关系如下;Alt text
由关系图可见,要编辑schema文件,需要先删除managed-schema.xml和schema.xml.bak文件,自定义schema.xml,如果服务器重启后正确生成了managed-schema.xml和schema.xml.bak那说明配置成功,否则需要查看报错日志,修改schema.xml中的错误。
在初次配置之后想要修改配置文件,那就只需要修改schema.xml.bak再删除managed-schema.xml重启就行了。

Schema.xml文件内容格式

Schema中主要内容是field和field type的定义。自动生成的managed-schema中有不少通用的field和field type定义,要自定义时可以仿照。
field定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<field name="school" type="text_ik" default="" indexed="true" stored="true"/>
```
这个例子里是五个常用的field属性
| &nbsp; | &nbsp;
| :-------- | --------:
| name | 字段名,和要导入数据的字段名要一一对应
| type | 字段类型,要等于某个fieldtype
| default | 默认值
| indexed | 是否被索引,只有被索引的字段才能用于搜索
| stored | 是否被保存,只有被保存的字段才能被搜索后获取
更多属性见https://cwiki.apache.org/confluence/display/solr/Defining+Fields
field type定义:
正如java中的类型一样,text、long……等常用类型已经在自动生成的配置中有了,对中文搜索来说,最重要的需要新定义的field type应该是中文文本类型了。定义前需要选定中文分词器,我试过两种smartcn和ik分词。
smartcn是solr下载后自带的,在solr-6.6.0\contrib\analysis-extras\lucene-libs\ lucene-analyzers-smartcn-6.6.0.jar。但是smartcn是纯粹的计算分词,没有使用分词表进行拓展。拷贝jar包到solr-6.6.0\server\solr-webapp\webapp\WEB-INF\lib下(solr-6.6.0\server\solr-webapp\下的目录结构类似于j2ee网站的结构),并在schema.xml中如此配置:
``` javascript
<fieldType name="text_smartcn" class="solr.TextField" positionIncrementGap="0">
<analyzer type="index">
<tokenizer class="org.apache.lucene.analysis.cn.smart.HMMChineseTokenizerFactory"/>
</analyzer>
<analyzer type="query">
<tokenizer class="org.apache.lucene.analysis.cn.smart.HMMChineseTokenizerFactory"/>
</analyzer>
</fieldType>

Ik分词版本较多,而且github上的版本已经很旧了,但是它支持使用分词表帮助分词。
依然是把jar包放到solr-6.6.0\server\solr-webapp\webapp\WEB-INF\lib下,把剩下的一个xml和两个dc文件放到solr-6.6.0\server\solr-webapp\webapp\WEB-INF\classes下,然后在schema.xml中加入如下内容

1
2
3
4
5
6
7
8
9
<fieldType name="text_ik" class="solr.TextField">
<analyzer type="index">
<tokenizer class="org.wltea.analyzer.lucene.IKTokenizerFactory" useSmart="false" />
</analyzer>
<analyzer type="query">
<tokenizer class="org.wltea.analyzer.lucene.IKTokenizerFactory" useSmart="true" />
<filter class="solr.SynonymGraphFilterFactory" synonyms="synonyms.txt"/>
</analyzer>
</fieldType>

fieldtype定义好中文类型后,在field中指定对应的类型,之后的索引和搜索对于中文处理就变得简单了。

6 .建立索引/导入数据

Solr支持的导入数据来源有数据库\xml\json\富文本。

导入xml\json\富文本

可以使用网页、命令行方式、api方式导入数据,目前看来在win10中网页导入文本文件有bug,就先介绍命令行方式这种最快的方式。
导入文件要使用post工具,官方文档的指令在windows下不能使用,在命令行中输入类似指令:

1
java -Dc=[corename] -Dauto=yes -Ddata=files -Drecursive=yes -jar example/exampledocs/post.jar G:\abc\*.json

需要填写的两个参数:dc是目标core的名字;最后用空格隔开的是目标文件,支持直接使用文件名和通配符,上面的例子指导入abc目录下所有.json文件。
如果导入的是富文本文件,富文本的内容不会像解析json一样按字段存储,而是存储为文件信息和内容两部分。

导入数据库表

涉及到solr唯二配置文件中的solrconfig.xml,在solrconfig.xml中额外指定dataconfig.xml,用于设置数据库连接参数、查询语句、数据库字段与solr field的对应关系

1
2
3
4
5
<requestHandler name="/dataimport" class="org.apache.solr.handler.dataimport.DataImportHandler">  
  <lst name="defaults">
  <str name="config">conf/dataconfig.xml</str>
  </lst>
 </requestHandler>

以搜索简历为例,dataconfig.xml:中这样设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
<?xml version="1.0" encoding="UTF-8"?>  
<dataConfig>
<!-- The first element is the dataSource, in this case an HSQLDB database.
The path to the JDBC driver and the JDBC URL and login credentials are all specified here.
Other permissible attributes include whether or not to autocommit to Solr, the batchsize
used in the JDBC connection, a 'readOnly' flag.
The password attribute is optional if there is no password set for the DB.
-->
<dataSource driver="oracle.jdbc.driver.OracleDriver" url="jdbc:oracle:thin:@172.29.88.178:1521:DBTEST" user="resumetest" password="resumetest"/>
<!--
Alternately the password can be encrypted as follows. This is the value obtained as a result of the command
openssl enc -aes-128-cbc -a -salt -in pwd.txt
password="U2FsdGVkX18QMjY0yfCqlfBMvAB4d3XkwY96L7gfO2o="
WHen the password is encrypted, you must provide an extra attribute
encryptKeyFile="/location/of/encryptionkey"
This file should a text file with a single line containing the encrypt/decrypt password

-->
<!-- A 'document' element follows, containing multiple 'entity' elements.
Note that 'entity' elements can be nested, and this allows the entity
relationships in the sample database to be mirrored here, so that we can
generate a denormalized Solr record which may include multiple features
for one item, for instance -->
<document>

<!-- The possible attributes for the entity element are described below.
Entity elements may contain one or more 'field' elements, which map
the data source field names to Solr fields, and optionally specify
per-field transformations -->
<!-- this entity is the 'root' entity. -->
<entity name="resume" query="select * from RESUME"
deltaQuery="select id from item where LAST_UPDATE_DATE > '${dataimporter.last_index_time}'" pk="id" transformer="ClobTransformer">
<field column="RESUME_ID" name="id" />
<field column="TAG_ID" name="tagID" />
<field column="STAGE_ID" name="stageId" />
<field column="DEPT_ID" name="deptId" />
<field column="FLAG" name="flag" />
<field column="RESUME_NUMBER" name="resumeNumber" />
<field column="NAME" name="name" />
<field column="SEX" name="sex" />
<field column="POSITION" name="position" />
<field column="POSITION_DETAIL" name="positionDetail" />
<field column="EDUCATION" name="education" />
<field column="SCHOOL" name="school" />
<field column="PHONE" name="phone" />
<field column="EMAIL" name="email" />
<field column="ADDRESS" name="address" />
<field column="BIRTHDAY" name="birthday" />
<field column="HEIGHT" name="height" />
<field column="WEIGHT" name="weight" />
<field column="GRADUATION_DATE" name="graduationDate" />
<field column="HEALTH_CONDITION" name="healthCondition" />
<field column="MARITAL_STATUS" name="maritalStatus" />
<field column="REGISTERED_PLACE_BEFORE" name="registeredPlaceBefore" />
<field column="IDENTITY_CARD_NUMBER" name="identityCardNumber" />
<field column="OVERSEAS_EXPERIENCE" name="overseasExperience" />
<field column="POLITICAL_STATUS" name="politicalStatus" />
<field column="CITY" name="city" />
<field column="EMERGENCY_PHONE" name="emergencyPhone" />
<field column="NATIONALITY" name="nationality" />
<field column="NATIVE_PLACE" name="nativePlace" />
<field column="SELF_EVALUATION_TITLE" name="selfEvaluationTitle" />
<field column="SELF_EVALUATION_CONTENT" name="selfEvalutionContent" clob="true"/>
<field column="SCHOLARSHIP_LEVEL" name="scholarshipLevel" />
<field column="STUDENT_LEADER" name="studentLeader" />
<field column="SCHOOL_ACTIVITY" name="schoolActivity" />
<field column="AWARD" name="award" />
<field column="CERTIFICATE" name="certificate" />
<field column="ENGLISH_LEVEL" name="englishLevel" />
<field column="ENGLISH_SCORE" name="englishScore" />
<field column="ENGLISH_CERTIFICATE1" name="englishCertificate1" />
<field column="ENGLISH_CERTIFICATE2" name="englishCertificate2" />
<field column="LANGUAGE" name="language" />
<field column="COMPUTER_SKILL" name="computerSkill" />
<field column="INTEREST" name="interest" />
<field column="EXPECTED_JOB" name="expectedJob" />
<field column="EXPECTED_CITY" name="expectedCity" />
<field column="EXPECTED_SALARY" name="expectedSalary" />
<field column="CREATED_BY" name="createdBy" />
<field column="CREATION_DATE" name="CreationDate" />
<field column="LAST_UPDATED_BY" name="lastUpdateBy" />
<field column="LAST_UPDATE_DATE" name="lastUpdateDate" />
<field column="VERSION" name="version" />
<field column="MAJOR" name="major" />
<field column="BK_MAJOR" name="bkMajor" />
<field column="TAG_NOTE" name="tagNote" />
<field column="CHECK_IN_FLAG" name="checkinFlag" />
<field column="FROZEN_FLAG" name="frozenFlag" />
</entity>
</document>
</dataConfig>

配置完毕后,使用网页操作导入数据
Alt text

7.进行标准查询

导入数据后,在solr的网页中就可以进行查询了,可配置参数如下Alt text
我填写的这些参数的含义:
|   |  
| :——– | ——–:
| q| 搜索内容
| fq| 对结果进行过滤,对某些字段的严格限制,类似于sql语句中的where field=value
| sort | 排序字段,使用字段+空格+asc/desc表示排序方式
| start rows| 两个分页字段
| fl| 类似于sql的投影,限制返回的字段
| df | 限制被查询的字段
| wt | 结果返回的格式
| indent true or false |控制结果是否缩进
| debug| 返回结果中显示哪些debug信息,query/timing/results/all(true)备选
更多参数见https://cwiki.apache.org/confluence/display/solr/Common+Query+Parameters#CommonQueryParameters-ThesortParameter

8搜索结果的初步优化——edismax搜索和编辑分词表、停用词表

搜索简历的过程中,遇到下面几种情况:
|   |   |  
| :——– | ——–| ——–:
| 搜索“南大”、“北大”,首先出来的是“东南大学”“东北大学”| solr认为文字的完全匹配有最高优先级,就算设置了同义词,搜索“南大”,东南大学和南京大学的比重也是等同的| 对于明显有特指的大学简称,设置单向同义词。在同义词表solr-6.6.0\server\solr\resume5\conf下,新增 北大 => 北京大学 的配置,对于其它常用大学简称,都设置双向同义词 西南财经大学,西南财大,西财,SWUFE
|某些大学缩写不被识别,搜索“南农”,会返回对“南”的搜索和“农”的搜索|正常语境中“X大”“上X”类的词语确实不该独立成词,但是在简历这种情况下需要这些词语成立| 对大学同义词表中的所有简称词,添加到拓展词典\solr-6.6.0\server\solr-webapp\webapp\WEB-INF\classes\mydict.dic中
| 搜索姓名“X波”,结果中地址、专业、简介的“电磁波”干扰了我的目标| 所有字段权值一样,姓名被分词后再各字段里都会出现|使用dismax/edismax搜索方式,给搜索的字段加上权重,给姓名和学校赋予最高权值。因为solr默认的算法设置中,对于“地址”这个20-30字的字段最容易返回,因此适当降低了搜索时对于“地址”的权重
edismax的参数如下:Alt text
常用参数设置:
|   |  
| :——– | ——–:
| q.alt| 当标准查询参数q不存在时生效的参数
| qf| 查询权重,格式字段名^权重 字段名^权重,默认权重为1。没有列出的字段不会被查询
| mm|结果返回数量,可以是数字和百分数的组合 3 代表只留3个;75%表示只留搜索结果的前75%;3<75%表示最少保留max[3,75%]个结果;-号代表要去除的结果数量,如-3代表只去除3个结果
| pf| 排序权重,参数格式和qf完全一样,但是它不会把不符合的结果去除掉,只影响排序。

edismax搜索还可以使用bf参数做自定义权值,参考https://cwiki.apache.org/confluence/display/solr/The+DisMax+Query+Parser ,如果数据里有重要的数字、频率如点击率、发布日期……可以考虑用bf写一个简单的自定义权值。因为简历主要是文本,手写文档关系的算法比较复杂,我选择就使用qf,依赖于lucene的文档关系算法。

9.使用java借助solr搜索

solr的外部调用方式很多,直接使用js写post请求访问solr服务器、对java\ruby\python都有调用API。Java的api比起直接用js,优点是不用自己拼查询url。对于maven项目,只需要添加

1
2
3
4
5
<dependency>
<groupId>org.apache.solr</groupId>
<artifactId>solr-solrj</artifactId>
<version>6.6.0</version>
</dependency>

就足够调用solrj了,但是要下载的文件比较多,更新maven慢。Java代码中关键的代码不多,像下面的代码就已经能发起一次标准查询了。

1
2
3
4
5
6
7
8
9
SolrClient solr = new HttpSolrClient.Builder(solrUrl).build();
SolrQuery query = new SolrQuery();
query.setQuery(searchParams.getQueryString());
query.set("start", start);
query.set("rows", limit);
query.set("wt", "json");

QueryResponse rsp = solr.query(query);
SolrDocumentList docs = rsp.getResults();

Solr的高亮只需要一个hl参数,但是高亮哪些字段受到qf字段(dismax查询的权重字段)、hl.fl字段的影响。在没有设置qf时,hl.fl代表哪些字段会高亮,有qf时以qf为准。

1
2
3
4
5
6
7
8
9
query.setHighlight(true);
query.set("defType", "dismax");
query.set("hl.fl","*");
query.set("hl.usePhraseHighlighter",true);
query.set("hl.highlightMultiTerm",true);

query.set("hl.simple.pre","<span class=\"highlight\">");
query.set("hl.simple.post","</span>");
query.set("qf","school^90 name^90 address^0.05 resumeNumber^1 position^90 education^1 major^5 telephone^1 mail^1");

返回的高亮部分内容是作为一个独立的结构返回的,和我们在baidu、google搜索引擎里看到的结果不大一样。需要在获取后做一个处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
hlDocs.setNumFound(docs.getNumFound());
Map<String,Map<String,List<String>>> tempMap = rsp.getHighlighting();

for(int i=0;i<docs.size();i++) {
SolrDocument doc = docs.get(i);
if (null!=tempMap.get(doc.getFieldValue("id")).get("name")) {
doc.setField("name", tempMap.get(doc.getFieldValue("id")).get("name"));
}
if(null!=tempMap.get(doc.getFieldValue("id")).get("school")) {
doc.setField("school", tempMap.get(doc.getFieldValue("id")).get("school"));
}
if(null!=tempMap.get(doc.getFieldValue("id")).get("address")) {
doc.setField("address", tempMap.get(doc.getFieldValue("id")).get("address"));
}
if(null!=tempMap.get(doc.getFieldValue("id")).get("resumeNumber")) {
doc.setField("resumeNumber", tempMap.get(doc.getFieldValue("id")).get("resumeNumber"));
}
if(null!=tempMap.get(doc.getFieldValue("id")).get("position")) {
doc.setField("position", tempMap.get(doc.getFieldValue("id")).get("position"));
}
if(null!=tempMap.get(doc.getFieldValue("id")).get("education")) {
doc.setField("education", tempMap.get(doc.getFieldValue("id")).get("education"));
}
if(null!=tempMap.get(doc.getFieldValue("id")).get("major")) {
doc.setField("major", tempMap.get(doc.getFieldValue("id")).get("major"));
}
if(null!=tempMap.get(doc.getFieldValue("id")).get("telephone")) {
doc.setField("telephone", tempMap.get(doc.getFieldValue("id")).get("telephone"));
}
if(null!=tempMap.get(doc.getFieldValue("id")).get("mail")) {
doc.setField("mail", tempMap.get(doc.getFieldValue("id")).get("mail"));
}
hlDocs.add(doc);

}
return hlDocs;

到这一步已经能做出一个较为完整的搜索简历的服务了。但是字段的权值、中文分词得不断根据实际数据变化和用户反馈改进才行,下面是一些能提高solr搜索结果满意度的方法。

Solr功能拓展

1.聚类搜索

很多网站提供的分类别搜索,在solr里配置facet相关参数就可以做到。
Alt text
facet常用参数如下:
Alt text
facet.field代表要分类的字段,如果要分类多个字段,需要再设置一个facet字段。返回结果会在通常的查询结果后缀一个分类查询结果,如下图:
Alt text
facet分类还支持根据时间分类、根据数字区间分类、分类过滤,详细内容见https://cwiki.apache.org/confluence/display/solr/Faceting#Faceting-Thefacet.fieldParameter

2.拼音搜索

增加拼音搜索的功能只要有能识别拼音的分词器就行了,找到拼音分词的jar包放到
WEB-INF/lib目录下,配合smartcn分词包或ik分词包使用。然后依然是在schema.xml中定义新的fieldtype

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<fieldType name="text_pinyin" class="solr.TextField" positionIncrementGap="0"> 
<analyzer type="index">
<tokenizer class="org.apache.lucene.analysis.cn.smart.SmartChineseSentenceTokenizerFactory"/>
<filter class="org.apache.lucene.analysis.cn.smart.SmartChineseWordTokenFilterFactory"/>
<filter class="com.shentong.search.analyzers.PinyinTransformTokenFilterFactory" minTermLenght="2" />
<filter class="com.shentong.search.analyzers.PinyinNGramTokenFilterFactory" minGram="6" maxGram="20" />
</analyzer>
<analyzer type="query">
<tokenizer class="org.apache.lucene.analysis.cn.smart.SmartChineseSentenceTokenizerFactory"/>
<filter class="org.apache.lucene.analysis.cn.smart.SmartChineseWordTokenFilterFactory"/>
<filter class="com.shentong.search.analyzers.PinyinTransformTokenFilterFactory" minTermLenght="2" />
<filter class="com.shentong.search.analyzers.PinyinNGramTokenFilterFactory" minGram="6" maxGram="20" />
</analyzer>
</fieldType>

3.相似数据推荐

类似于大多数网站的相似产品推荐,solr也提供了这种功能——morelikethis。通过制定搜索结果和某个字段,solr查找出在该字段上与当前搜索结果类似的结果。如果字段配置了termVectors=”true”,那么推荐会更加准确。下面这个搜索代表搜索resumeid为58901简历的类似简历,以专业为主要关联字段。

1
select? resumeId=58901&mlt=true&mlt.fl=major&mlt.mindf=1&mlt.mintf=1&wt=json

基本参数如下:
|   |  
| :——– | ——–:
| mlt | 是否开启推荐
| mlt| 推荐的字段
| mlt.mintf | 被关联文档里关键词出现次数多于这个值被忽略
| mlt.mindf| 所有文档里出现次数多于这个值的关键词被忽略
更详细的内容见https://cwiki.apache.org/confluence/display/solr/MoreLikeThis

M Yi

3 日志
© 2018 M Yi
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4