稍微介绍一下 java 处理 log 的东西

特别是 slf4j 的种类太多,或许也没有人能很好的说明各自用处。产生混乱的原因有七成是由 slf4j 造成的。这种混乱的局面进行了简单整理。主要分为三个类型的 jar。

1、 接口(Interface)

主要有这些

  • commons-logging
  • slf4j

他们也就只提供了接口的功能,并没实现 log 的输出功能。
接口要是太老的话,自己提供的功能也会有不足的地方。这些 jar 虽然含有有 log 输出的功能,但只是简单的实现。

2、 适配器(Adapter)

主要有这些

  • jcl-over-slf4j.XXX.jar(把 commons-logging 的处理交给 slf4j 来处理)
  • jul-to-slf4j.XXX.jar(把 java.util.logging 的处理交给 slf4j 来处理)
  • log4j-over-slf4j.XXX.jar(把 log4j 的处理交给 slf4j 来处理)

适配器的作用是,代理各接口与实际处理 logger 的任务。从外部看起来都是相同的方法,但是内部却是各自来实现自己的功能。除了处理 log 之外,也有『slf4j-jdk14』这样的兼容不同 JDK 版本来处理的适配器。正因为有了适配器,即使在 commons-logging、log4j 中追加了 slf4j 的时候也能进行工作。

适配器通过设置文件来进行适配,log4j.jar 不存在的时候会读取 log4j.xml 文件。适配器是很认真的来工作的。但是因为有了适配器来进行各种处理,这也是造成了 jar 文件混乱而使得 log 的输出复杂的原因。

另外,「jul」是「java.util.logging」の简称,「jcl」是「Jakarta Commons Logging」的简称而不是「Java Class Library」的简称。这些简称的理解困难也是造成 log 的输出复杂的原因之一。

3、 实现

主要有这些

  • java.util.logging
  • log4j
  • logback

logback 是 log4j 的开发者开发出来的下一代 log 处理。通过对接口的实现,如果做得不好的话会造成处理速度变慢,或者就没有对接口提供的方法进行处理。

slf4j 适配器
  • slf4j-log4j12-XXXX.jar(把 slf4j 的处理交给 log4j1.2 来处理)
  • slf4j-jdk14-XXX.jar(在jdk1.4中,把 slf4j 的处理交给 java.util.logging 来处理)
  • slf4j-nop-XXX.jar(全部的 log 内容都不输出)
  • slf4j-simple-XXX.jar(log 等级为 INFO 以上的内容输出到 System.err 中)
  • slf4j-log4j-XXX.jar(把 slf4j 的处理交给 log4j 来处理)
  • slf4j-jcl-XXX.jar(把 slf4j 的处理交给 commons.logging 来处理)

使用『slf4j』的时候被称为适配器的 jar 有很多个,这是为了使用 slf4j 的时候选择不同的实现方法,因此在 classpath 中不能含有多个。如果有多个适配器的时候,slf4j 无法知道该交给那个适配器来进行处理。
而使用 slf4j + logback 的时候是不需要适配器的,只需要用 slf4j-api。

concrete-bindings

这是 slf4j 官网的图释,然而完全不知道什么是什么。。。

使用 slf4j + logback

接下来让我们试着从『commons-logging + log4j』变到『slf4j + logback』。手动管理 jar 有点困难,下面以使用 maven 来进行讲解。

添加 slf4j 与 logbak 的依赖

在 pom.xml 中添加下面两个依赖

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
去除 commons-logging 与 log4j 的依赖

这个处理实在是很麻烦,无法在 maven 中简单的就去掉,需要对每个 jar 进行去除的处理。这是因为有很多 jar 包中依赖了这两个东西。如果是使用 jdk1.5 以后的话 slf4j-jdk14 也是可以一起删除掉的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
<exclusions>
<exclusion>
<artifactId>log4j</artifactId>
<groupId>log4j</groupId>
</exclusion>
</exclusions>
<exclusions>
<exclusion>
<artifactId>slf4j-jdk14</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>

这个处理使用 eclipse 的 m2e 插件的话可以比较简单的进行。打开 pom.xml 文件,再打开 Dependency Hierarchy 标签,在 Filter 里输入这两个 jar 的名字的话,就会表示出全部依赖的 jar 包。接下来右键点击 jar 包选择「Exclude Maven Artifact…」就可以了。

最终我们需要的 jar 包如下

  • slf4j-api-XXX.jar
  • logback-classic-XXX.jar
  • logback-core-XXX.jar
  • jcl-over-slf4j.XXX.jar
  • jul-to-slf4j.XXX.jar
  • log4j-over-slf4j.XXX.jar

虽然说有点多,其实必要的 jar 包只有最初的三个。

替换 log 的配置文件

log4j.xml、log4j.dtd、log4j.properties 不再需要了直接删掉。而使用的『logback.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
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<prudent>true</prudent>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>app.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>

<encoder>
<pattern>%date{yyyy/MM/dd HH:mm:ss:SSS} %.5level - %logger{0}.%.20method %msg%n</pattern>
</encoder>
</appender>

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<target>System.out</target>
<encoder>
<pattern>%date{yyyy/MM/dd HH:mm:ss:SSS} %.5level - %logger{0}.%.20method %msg%n</pattern>
</encoder>
</appender>

<root>
<level value="info" />
<appender-ref ref="FILE" />
<appender-ref ref="STDOUT" />
</root>
</configuration>

如果使用了旧的写法的话tomcat启动的时候会出现警告,这时候需要参考 Logback Error Codes 来进行修改。

从网上的消息来看 ch.qos.logback.classic.PatternLayout 已经是 DEPRECATED(不推荐使用)了,修改这里就好。encoder 与 layout 没有进行自定义化,这两个不指定 class 也没关系。虽然这有点麻烦,但这样就能从旧的方式变成新的方式了吧。

jar 中存在之前处理 log 时出现的情况的处理

即使是按照之前的方法变成了 slf4j+logback 的时候,如果还存在之前处理的log的jar包的话,会出现「Exception in thread “main” java.lang.NoClassDefFoundError: org/apache/commons/logging/LogFactory」这样的问题。这时候就需要使用帅气的适配器来处理了。

所谓的把原来的方法交出来处理,就是使用之前说的适配器『交给 slf4j 来处理』。比如依赖了 commons-logging 却出现了 NoClassDefFoundError 的话,把 jcl-over-slf4j.XXX.jar 加入就行。

  • jcl-over-slf4j.XXX.jar(把 commons-logging 的处理交给 slf4j 来处理)
  • jul-to-slf4j.XXX.jar(把 java.util.logging 的处理交给 slf4j 来处理)
  • log4j-over-slf4j.XXX.jar(把 log4j 的处理交给 slf4j 来处理)

把所有的东西交给 slf4j 来处理,之后再变成 slf4j => logback 的形式来处理的话,就能完全的进行转移了。

简单的使用方法

大概就是如下所示,jcl 的话也差不多。

1
2
3
4
5
6
7
8
9
10
11
12
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Hoge {

private static Logger LOGGER = LoggerFactory.getLogger(Hoge.class);

// 「あいう」と出力されます
public static void main(String[] args) {
LOGGER.info("あ{}う", "い");
}
}

{}是占位符。从 import 来看的话只有 slf4j,logback 相关的 import 都没有。虽然只写了 slf4j 的代码,然而 slf4j 会从 classpath 中找到 logback,然后使用它来实现。虽然表面上看不出来,但是还是的使用了 logback 来进行处理,所以请各位放心。

logger 的变迁

实现
  1. log4j v1 版本的流行
  2. log4j 的作者重新开发出 logback
  3. log2j v2 版本出现 <- 现在的时间点

是这样的感觉,觉得 logback 已经成熟的时候,log4j 的 version 2 就出现了,变成了无法解决谁是业内标准的状态。

接口
  1. commons.logging
  2. SLF4J

接口只有这两个。总之 SLF4J 貌似成为了业内标准,所以使用 SLF4J 的话不会有什么问题。说到 slf4j 的适配器,或许会出现在今后的 JDK1.8 中像 stream、lambda 内部使用的 logger,或者是处理不同的JDK的差异的实现变得更多都说不定。

从这么多 logger 想到的

接口、适配器、实现,这些都虽说是 java 的东西,从个人来说的话 『合并成一个jar,通过设定文件来改变动作的话就好了』。要是按照现在的话每当新的 logger 出现的时候,又会增加适配器,从而 jar 包也会变多。

虽说各 jar 分开不发生耦合的道理也是正确的,然而从现状来看的话无论是谁都觉得是混乱的状态,不理解 slf4j 而使用的话就会在 tomcat 启动的时候出现

1
Multiple bindings were found on the class path

这样的错误,谁都不会注意到这样的错误的项目有很多。
(这个时候 slf4j 会通过 /dev/null 把输出给取消因此 slf4j 不会生效,只剩下 log4j 来进行处理)

古人说过『自由即是不自由』,比起自由还是希望得到能注重单纯的东西。如果要注重性能的话,那或许还是让同一个公司来制作的话是最好的结果吧。

via javaのロガーが多すぎて訳が解らないので整理して

JAVA 8 开始对 time 添加了不少东西,最常见的就是 LocalDate 这样的东西,其实还是多了点其他好玩的东西,比如说 java.time.format.FormatStyle 这个类。下面的代码中让我们来用 Local(语言, 国家) 之后格式化时间看看输出内容都有些什么。

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
public class FormatStyleTest {
public static void main(String[] args) {
LocalDate ld = LocalDate.now();
Arrays.asList(FormatStyle.FULL, FormatStyle.LONG, FormatStyle.MEDIUM, FormatStyle.SHORT)
.forEach(formatStyle -> {
System.out.println(String.format("--- FormatStyle.%s ---",
formatStyle.toString()));
DateTimeFormatter dtf = DateTimeFormatter.ofLocalizedDate(formatStyle);
Arrays.asList(new String[]{"zh", "CN"},
new String[]{"zh", "HK"},
new String[]{"zh", "TW"},
new String[]{"en", "US"},
new String[]{"en", "UK"},
new String[]{"ja", "JP"})
.forEach(strs -> {
Locale locale = new Locale(strs[0], strs[1]);
System.out.println(String.format("%s %s -> %s",
strs[0],
strs[1],
ld.format(dtf.withLocale(locale))));
});
});

}
}
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
--- FormatStyle.FULL ---
zh CN -> 2018年7月23日 星期一
zh HK -> 2018年07月23日 星期一
zh TW -> 2018年7月23日 星期一
en US -> Monday, July 23, 2018
en UK -> Monday, July 23, 2018
ja JP -> 2018年7月23日
--- FormatStyle.LONG ---
zh CN -> 2018年7月23日
zh HK -> 2018年07月23日 星期一
zh TW -> 2018年7月23日
en US -> July 23, 2018
en UK -> July 23, 2018
ja JP -> 2018/07/23
--- FormatStyle.MEDIUM ---
zh CN -> 2018-7-23
zh HK -> 2018年7月23日
zh TW -> 2018/7/23
en US -> Jul 23, 2018
en UK -> Jul 23, 2018
ja JP -> 2018/07/23
--- FormatStyle.SHORT ---
zh CN -> 18-7-23
zh HK -> 18年7月23日
zh TW -> 2018/7/23
en US -> 7/23/18
en UK -> 7/23/18
ja JP -> 18/07/23

即使是华语圈,出来的结果基本来说都不一样的,而 en US 跟 en UK 都是一样的。

顺便说一句,java 中格式化时间还是有点麻烦。

via java.time.format.FormatStyle を確認

在甲骨文的官网上,java 10 已经没有运行在 Linux ARM 的版本了,而要想在树莓派中运行最新的 java 的话只能下载 java 8。本想从 openjdk 看看有没有可用的版本,结果迷失在各种连接中找不到下载的地址,这。。。

不过现在已经有人给树莓派可用的 java 10 的版本,是一个叫 bellsoft 的给编译的,下载地址在https://github.com/bell-sw/Liberica/releases 这上面。

在树莓派中运行如下命令即可食用

1
2
wget https://github.com/bell-sw/Liberica/releases/download/10.0.1/bellsoft-jdk10.0.1-linux-arm32-vfp-hflt.tar.gz
tar -zxvf bellsoft-jdk10.0.1-linux-arm32-vfp-hflt.tar.gz

别忘了把 java 命令加到 path 中

1
export PATH="jdk-10/bin:$PATH"

使用 java.time.LocalDate 计算从生日起到现在年龄的方法。

1
2
3
4
5
6
7
8
9
10
11
12
public int getAge(int year, int month, int day) {

// 生日
LocalDate birthday = LocalDate.of(year, month, day);

// 当前日期
LocalDate today = LocalDate.now();

long duration = ChronoUnit.YEARS.between(birthday, today);

return (int)duration;
}

提问:在 solr 中进行非查询的 query 是写作 -field:value,或查询的 query 是写作 field:value1 OR field:value2,那么,对同一字段进行多次非或的查询应该如何写呢?

……

从直觉上来说应该是写作 -field:value1 OR -field:value2 ,这么是没问题并且也是可以检索出数据的,但是如果加上其他的条件比如 (-field:value1 OR -field:value2) AND other_or_some_field:value3 这样的话,这样是检索不出来数据的,why??

原因请看 Negative Query Problems 这个 solr 官方的 wiki,上面说这是『Solr Magic』哦,而不是个 bug 哦,好吧。。。

解决方法:多查询条件下,在非查询的前面添加全量查询即可,即 ((*:* AND -field:value1) OR (*:* AND -field:value2)) AND other_field:value3。而 (-field:value1 OR -field:value2) 这个条件跟 -(field:value1 AND field:value2) 其实是等效的逻辑,所以进一步可改写成 (*:* AND -(field:value1 AND field:value2)) AND other_field:value3。也就是说,多条件的情况下,非或条件要写成 (*:* AND -field:value) 这样的形式,并且如果可以的话尽量把非或条件写到最后以避免出现这个 magic。

如果情况允许的话,可以把查询条件用 fq 来进行查询。

……

然后就是,如果用到了 spring-data-solr 来进行 solr 查询的话,(*:* AND -field:value) 的写法如下

1
2
Criteria criteria = Criteria.where(field).is(value);
criteria = Criteria.where(Criteria.WILDCARD).expression(Criteria.WILDCARD).and(criteria.notOperator()).connect();

也就是说,先用 notOperator() 方法给 criteria 用 -() 给包起来,然后跟 *:* 与结合之后,用 connect() 方法把该次查询加上括号。最后加上括号是为了跟其他条件进行拼接,这对括号的本身并不影响查询。

然后就是多个条件的非或查询比如 (*:* AND -(field:value1 AND field:value2)) 的写法如下

1
2
3
Criteria criteria = Criteria.where(field).is(value1);
criteria = criteria.and(Criteria.where(field).is(value2));
criteria = Criteria.where(Criteria.WILDCARD).expression(Criteria.WILDCARD).and(criteria.notOperator()).connect();

This’s “Solr Magic” with negative queries。

0x00 安装系统

Raspbian官方地址 下载镜像,基本上 lite 版的就足够了反正也是用 SSH 来链接到机器上用不到桌面,然后弄到 sd 卡上。

0x01 修改系统设置
  1. 运行 sudo dpkg-reconfigure tzdata 修改机器的 timezone。
  2. 如果有多台树莓派的话建议还是修改一下 hostname 的免得等下找机器麻烦。
    1. 运行 sudo vi /etc/hostname,直接修改
    2. 运行 sudo vi /etc/hosts,修改最后一行
    3. 运行 sudo reboot,重启
0x02 更改源地址
  1. 运行 sudo vi /etc/apt/sources.list
  2. 删除里面内容,直接变成国内源,其中的 jessie 记得替换成当时 Raspbian 的版本。

    1
    2
    3
    4
    # 中国科学技术大学源
    deb http://mirrors.ustc.edu.cn/raspbian/raspbian/ jessie main contrib non-free
    # 大连东软信息学院源
    deb http://mirrors.neusoft.edu.cn/raspbian/raspbian/ jessie main contrib non-free
  3. 运行 sudo vi /etc/apt/sources.list.d/raspi.list,把第一行中的 ui 给删除掉让它变成如下内容

    1
    deb http://archive.raspberrypi.org/debian/ jessie main

    不然没法更新 raspberrypi-kernel 之类的内容(国内源压根就没把这些给同步回来)。

  4. 运行 sudo apt-get update
阅读全文 »

之前用 Octopress 来部署本站点来着,但是依赖的 ruby 版本实在是太古老,heroku 也发过几次邮件说将不支持 ruby 1.9.2 需要升级,但是一直都没管它(原因还是懒癌发作),结果在某一天网站彻底就打不开了。。。

虽然是免费给部署不过好歹咱也是写过技术文档的人,本着折腾的原则这次打算换其他的框架来弄,所以就这次就用了(凭借着记忆)在推上看到的了基于 nodejs 来开发的 Hexo

按照文档来搭建基本环境很是方便,而且也不存在依赖的 package 不支持 windows 环境的说法,很快的就把之前的环境给在本地配置起来了,而且之前写的 markdown 文档也能直接的就拿过来用(不过话说 categories 跟 tags 不是一个意思的啊。。。)。重要的是这货感觉速度很快的说,本地打开网站也就分分钟的事情,而且部署也方便只要在站点的 _config.yml 文件中配置好了的话一个命令就部署到 heroku 上了。

需要多说一句的是,因为之前的 heroku 项目中已经存在了 ruby 的项目,所以需要先把之前的项目删除掉之后才能把新的给部署上去。(或许也可以直接用 git push -f 命令强推过去不过已经无法实验了)

ほかの言語も勉強しようかな

1)一句话启动http server

1
ruby -rwebrick -e "WEBrick::HTTPServer.new(:DocumentRoot => './', :Port => 8080).start"

2)解决maven下,运行mvn test时出现乱码的问题

1
2
3
4
5
6
7
8
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<forkMode>once</forkMode>
<argLine>-Dfile.encoding=UTF-8</argLine>
</configuration>
</plugin>

3)禁止#to_json方法对中文的escape

1
something.to_json.gsub(/\\u([0-9a-z]{4})/){|s| [$1.to_i(16)].pack("U")}

4)在windows下使用vundle

1
2
3
4
5
6
7
if has("win16") || has("win32") || has("win64")
set rtp+=$VIM\vimfiles\bundle\vundle
call vundle#rc('$VIM\vimfiles\bundle')
else
set rtp+=~/.vim/bundle/vundle/
call vundle#rc()
endif

4)一个比较好看的bash的显示方式

bash中路径太长的话看起来实在是很不舒服,把下面这段代码添加到.bashrc里面试试

1
2
export PS1='%{$fg[magenta]%}%(?..%?%1v )%n%{$reset_color%}@%{$fg[green]%}%m%{$reset_color%} %{$fg[cyan]%}%c%{$reset_color%} ${vcs_info_msg_0_}%# 
$ '

5)一个小知识

  • 日本企业的职称顺序(参考):(会長>)(副会長>)社長>専務>常務>部長>(次長>)課長>係長>平社員。(括号表示并非所有公司都有同样职位)
  • 台湾企业的职称次序(参考):董事长(>执行长)>总经理>副总经理>协理(>襄理)>经理>副经理>课长>副课长>组长>副组长。

###Befor work

1)在Gemfile中添加如下代码

1
2
3
4
group :test do
gem 'rspec'
gem 'capybara'
end

2)在Rakefile中添加如下代码

1
2
3
4
5
6
7
require 'rspec/core/rake_task'
desc "Run specs"
task :spec do
RSpec::Core::RakeTask.new(:spec) do |t|
t.pattern = './spec/**/*_spec.rb'
end
end

3)在/ROOT/spec文件夹下的spec_helper.rb文件中添加如下代码

1
2
3
require 'rspec'
require 'capybara/rspec'
require 'rack/test'

做完以上步骤后就可以进行测试代码了

###Test

1)测试功能

在/ROOT/spec文件夹中添加lib_helper.rb文件,里面中添加如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
require File.dirname(__FILE__) + '/spec_helper'
$LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib')
require 'lib'

describe Lib do
before(:each) do
@lib = Lib.new
end

it "test name" do
@lib.name = "test name"
@lib.name.should == 'test name'
end
end

2)测试页面动作

在/ROOT/spec文件夹中添加root_helper.rb文件,里面中添加如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
require File.dirname(__FILE__) + '/spec_helper'
# 加载sinatra app
require File.join(File.dirname(__FILE__), '..', 'app.rb')

# 采用模块化的方式
Capybara.app = App.new

# 采用传统的方式
Capybara.app = Sinatra::Application.new

feature Root do
context 'login' do
it 'success' do
visit '/login'
within("#login") do
fill_in 'name', :with => 'admin'
fill_in 'password', :with => 'password'
end
click_button 'Login'
current_path.should == '/'
page.should have_content 'Login success'
end
end
end

3)在命令行中运行rake spec

####Read more