文章目录
- 1. 日志
- 2. 如何记录日志
1. 日志
服务器要在无人看管的情况下运行很长时间,通常需要在很久以后对服务器中发生的情况进行调试,这很重要。由于这个原因,建议在存储服务器日志,至少要存储一段时间的日志。日志中通常希望记录的主要内容是:
- 请求
- 服务器错误
实际上,服务器一般会为这两项内容维护两个不同的日志,审计日志中,对应与服务器建立的每一个连接会分别包含一个记录,每个连接完成多个操作的服务器可能对每个操作都有一个记录。例如,dict服务器可能会为客户端查找的每个单词建立一个日志记录。错误日志则主要包含服务器运行期间发送的意外异常,主要记录程序中发生的一些异常。
2. 如何记录日志
在java1.4之前,程序基本使用的是第三方日志库,如log4j或Aache Commons Logging,之后官方提供了java.util.logging包,它能满足绝大多数需求。尽管可以根据需要来加载日志工具,不过需要最容易的办法是为每个类创建一个日志工具,如下:
private final static Logger auditLogger=Logger.getLogger("requests");
日志工具是线程安全的,所以将它们存储在共享静态字段中没有任何问题,实际上,往往需要这么做,因为即使不用在线程之间共享Logger对象,日志文件或数据块也需要共享。这在大量使用多线程的服务器中非常重要。上面的例子输出到一个名为"requests"的日志中,这个日志是什么、放在哪里取决于外部配置。它不一定是一个文件,可能是一个数据块、一个在多个服务器上运行的SOAP服务、同一个主机上运行的另一个java程序,或者是其它形式。一旦又了一个日志工具,可以使用多个方法写入这个日志。最基本的是log()。java.util.logging.Level
中命名常量定义了七个级别,按严重性从高到低依次为:
- Level.OFF
- Level.SERVE
- Level.WARNING
- Level.INFO
- Level.CONFIG
- Level.FINE
- Level.FINER
- Level.FINEST
- Level.ALL
我们通常会对审计日志使用INFO级别,对错误日志使用WARNING级别或SERVER级别。较低级别用于调试,不要在生成系统中使用。可以对各个日志使用任何方便的格式。一般来讲,每个记录应该包含一个时间戳、一个客户端地址,以及所处理的请求的任何特定信息。如果日志消息表示为一个叫错误,则要抛出特定的异常。Java会自动填入记录这个消息所在的代码位置,所以这个方面不用操心。
下面代码展示了如何为daytime服务器增加日志记录
public class QuizCardBuilder {
private final static Logger auditLogger=Logger.getLogger("requests");
private final static Logger errorLogger= Logger.getLogger("errors");
public static void main(String[] args) {
ExecutorService pool=Executors.newFixedThreadPool(50);
try(ServerSocket server=new ServerSocket(8080)){
while(true){
try {
Socket connection=server.accept();
Callable<Void> task=new DaytimeTask(connection);
pool.submit(task);
}catch (IOException ex){
errorLogger.log(Level.SEVERE,"accept error"+ex);
}catch (RuntimeException ex){
errorLogger.log(Level.SEVERE,"unexcepted error"+ex.getMessage(),ex);
}
}
}catch (IOException e) {
errorLogger.log(Level.SEVERE,"Coundn`t start server"+e);
}catch (RuntimeException e){
errorLogger.log(Level.SEVERE,"Coundm`t start server"+e);
}
}
private static class DaytimeTask implements Callable<Void>{
private Socket connection;
DaytimeTask(Socket connection){
this.connection=connection;
}
@Override
public Void call() {
try{
Date now=new Date();
//先写入日志记录以防万一客户端端口连接
auditLogger.info(now+" "+connection.getRemoteSocketAddress());
Writer out=new OutputStreamWriter(connection.getOutputStream());
out.write(now.toString()+"\r\n");
out.flush();
}catch (IOException e){
if(connection!=null){
try {
connection.close();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}finally {
if(connection!=null){
try {
connection.close();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
return null;
}
}
}
多次在终端使用Telnet与dayTime服务器通信
查看结果
上面的日志信息是直接在控制台打印的,如果我们希望将日志文件放到更加持久的位置,虽然我们可以在代码中指定,但是更好的是在配置文件中指定。这样就能改变日志位置而无需重新编译代码。java.util.logging.config.file
系统属性采用常规的属性格式指向控制日志记录的一个文件。可以在启动虚拟机时加入vm配置-Djava.util.logging.config.file=_filename_
参数来设置这个属性。
handlers=java.util.logging.FileHandler
java.util.logging.FileHandler.pattern=/var/logs/daytime/requests.log
java.util.logging.FileHandler.limit=10000000
java.util.logging.FileHandler.count=2
java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter
java.util.logging.FileHandler.append=true
java.util.logging.FileHandler.format=%4$s: %5$s [%1$tc]%n
上面的配置指定来下面的内容:
- 日志要写入文件
- 请求日志应当在/var/logs/daytime/requests.log(INFO级别)
- 错误日志应当在/var/logs/daytime/requests.log(SERVER级别)
- 日志大小限制为10MB,然后轮换
- 维护两个日志:当前日志和之前的日志
- 使用基本文本格式化工具
- 日志的每一行采用消息级别时间戳