Tmux Plugins


layout: post title: “Tmux 的插件们” tagline: “” description: “介绍目前我在使用的 Tmux 插件们” category: 学习笔记 tags: [tmux, linux, terminal,] last_updated: –

Vim 有插件管理,zsh 也有插件管理,那当然 Tmux 肯定有插件管理,其实学习 Tmux 的过程中,和 Vim 当时一样,所有的拷贝,粘贴的内容都是在 Tmux 和 Vim 的内部,和外部操作系统的粘贴板完全隔离了,我就是为了解决这个问题,才接触到了 Tmux Plugin Manager

Tmux Plugin Manager

安装的方法,在 GitHub 的页面非常清楚,git clone 项目,在 .tmux.conf 文件中加入配置,重新加载配置即可。

# List of plugins
set -g @plugin 'tmux-plugins/tpm'
set -g @plugin 'tmux-plugins/tmux-sensible'

# Other examples:
# set -g @plugin 'github_username/plugin_name'
# set -g @plugin 'git@github.com/user/plugin'
# set -g @plugin 'git@bitbucket.com/user/plugin'

# Initialize TMUX plugin manager (keep this line at the very bottom of tmux.conf)
run '~/.tmux/plugins/tpm/tpm'

新添加插件只需要,在配置文件中增加一行

set -g @plugin '...'

这样,再使用 <prefix> + I 大写的 I (Install) 来安装新插件

卸载插件时,配置文件中移除一行,并使用 <prefix> + alt + u (uninstall) 来卸载。

更新快捷键是 <prefix> + u

tmux-yank

.tmux.conf 中加入

set -g @plugin 'tmux-plugins/tmux-yank'

然后使用 <prefix> + I 来安装 tmux-yank

Linux 平台下需要安装依赖 xsel 或者 xclip

sudo apt-get install xsel # or xclip

快捷键

在 normal mode 下

<prefix> + y 来将命令行内容拷贝到 clipboard 系统粘贴板。

<prefix> + Y 将当前 panel 的 working directory 拷贝到粘贴板

在 copy mode 下

y 拷贝到系统粘贴板

Y 将选中的内容,粘贴到命令行


2017-12-08

log4j 配置

Log4j 是一个可靠的、高效的、快速可扩展的日志框架,Log4j 使用 Java 开发,已经被移植到了很多主流语言,比如 C, C++, Perl, Python, Ruby 等等。Log4j 可以通过外部文件配置来定义行为,Log4j 为日志输出提供了不同的目的地,比如可以将日志输出到控制台,文件,数据库等等。我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,能够更加细致地控制日志的生成过程。这一切都可以通过一个配置文件来灵活地进行配置,而不需要修改应用代码。Log4j是Apache的一个开放源代码项目。

在应用程序中添加日志记录总的来说基于三个目的:

  • 监视代码中变量的变化情况,周期性的记录到文件中供其他应用进行统计分析工作
  • 跟踪代码运行时轨迹,作为日后审计的依据
  • 担当集成开发环境中的调试器的作用,向文件或控制台打印代码的调试信息

Log4j 主要有三个组件:

  • Loggers(记录器) ,作用就是用来记录日志
  • Appenders (输出源) ,用来将采集的日志信息发送到不同的目的地
  • Layouts(布局),用于格式化输出日志

综合使用这三个组件可以轻松的记录信息的类型和级别,并可以在运行时控制日志输出的样式和位置。

Log4j 的特性:

  • 线程安全
  • 为速度优化
  • 同一个记录器支持不同的输出
  • 支持多语言
  • 日志的行为可以通过配置文件在运行时使用
  • 支持不同的 LEVEL
  • 日志的输出可以通过 Layout 类来改变
  • 日志的输出途径可以通过修改 Appender 接口来定义

通常,我们都提供一个名为 log4j.properties 的文件,该文件以 key-value 的方式进行配置。默认情况下,LogManager 会在 CLASSPATH 目录下寻找 log4j.properties 这个文件名。

简单例子

# Define the root logger with appender X
log4j.rootLogger = DEBUG, X

# Set the appender named X to be a File appender
log4j.appender.X=org.apache.log4j.FileAppender
log4j.appender.X.File=${log}/log.out

# Define the layout for X appender
log4j.appender.X.layout=org.apache.log4j.PatternLayout
log4j.appender.X.layout.conversionPattern=%d{yyyy-MM-dd HH:mm:ss} %p [%t] %C{1}.%M(%F:%L) - %m%n

这个例子定义了

  • root logger 的level 是 DEBUG,并且 DEBUG 附加到一个名为 X 的 Appender 上
  • 设置 X Appender
  • 然后设置 X 的 layout

Maven 依赖

在 maven 的 pom.xml dependency 下添加:

<!-- sl4j -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.21</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.21</version>
</dependency>
<!-- log4j -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

slf4j(Simple Logging Facade for Java) 不是一个真正的日志实现,而是一个抽象层,它允许在后台使用任意一个日志类库。slf4j 使得代码能够独立于任意一个特定的日志API。 SLF4J API的特性占位符(place holder),在代码中表示为“{}”的特性。占位符是一个非常类似于在String的format()方法中的%s,因为它会在运行时被某个提供的实际字符串所替换。这不仅降低了你代码中字符串连接次数,而且还节省了新建的String对象。

然后在 CLASSPATH 下添加 log4j.properties 文件。

#config root logger  
log4j.rootLogger = INFO,system.out  
log4j.appender.system.out=org.apache.log4j.ConsoleAppender  
log4j.appender.system.out.layout=org.apache.log4j.PatternLayout  
log4j.appender.system.out.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %p [%t] %C{1}.%M(%F:%L) - %m%n
  
#config this Project.file logger  
log4j.logger.APPENDER_NAME.file=INFO,APPENDER_NAME.file.out  
log4j.appender.APPENDER_NAME.file.out=org.apache.log4j.DailyRollingFileAppender  
log4j.appender.APPENDER_NAME.file.out.File=logContentFile.log  
log4j.appender.APPENDER_NAME.file.out.layout=org.apache.log4j.PatternLayout  

在 Java 代码中使用 slf4j

private static final Logger logger = LoggerFactory.getLogger(Server.class);
logger.info("now {}" , "starting server");

Log4j 支持两种配置文件格式,一种是XML格式的文件,一种是Java properties(key=value)【Java特性文件(键=值)】。先介绍使用Java特性文件做为配置文件的方法

配置详细介绍

配置 Logger   

Loggers 组件在此系统中被分为五个级别:DEBUG、INFO、WARN、ERROR和FATAL。这五个级别是有顺序的

DEBUG < INFO < WARN < ERROR < FATAL

分别用来指定这条日志信息的重要程度,这里Log4j有一个规则:假设Loggers 级别为 P,如果在Loggers中发生了一个级别Q 比 P 高,则记录,否则就不记录。

比如,你定义的级别是info,那么error和warn的日志可以显示而比他低的debug信息就不显示了。

配置根Logger

其语法为:

  log4j.rootLogger = [ level ] , appenderName1, appenderName2, … # level : 是日志记录的优先级,分为OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL或者您定义的级别。Log4j建议只使用四个级别,优先级从高到低分别是ERROR、WARN、INFO、DEBUG。通过在这里定义的级别,您可以控制到应用程序中相应级别的日志信息的开关。比如在这里定义了INFO级别,则应用程序中所有DEBUG级别的日志信息将不被打印出来。

  appenderName:就是指定日志信息输出到哪个地方。可以同时指定多个输出目的地。

例如:log4j.rootLogger=info,A1,B2,C3

  

配置日志信息输出目的地

Log4j 日志系统允许把日志输出到不同的地方,如控制台(Console)、文件(Files)、根据天数或者文件大小产生新的文件、以流的形式发送到其它地方等等。

其语法为:

log4j.appender.appenderName = fully.qualified.name.of.appender.class 
log4j.appender.appenderName.option1 = value1 
log4j.appender.appenderName.optionN = valueN 

其中, Log4j 提供的 appender 有以下几种:

  • org.apache.log4j.ConsoleAppender 输出到控制台
  • org.apache.log4j.FileAppender 输出到文件
  • org.apache.log4j.DailyRollingFileAppender 输出到每天产生一个日志文件
  • org.apache.log4j.RollingFileAppender 文件大小到达指定尺寸的时候产生一个新的文件,可通过 log4j.appender.R.MaxFileSize=100KB 设置文件大小,还可通过 log4j.appender.R.MaxBackupIndex=1 设置为保存一个备份文件
  • org.apache.log4j.WriterAppender 将日志信息以流格式发送到任意指定的地方

例:

log4j.appender.stdout=org.apache.log4j.ConsoleAppender 

定义一个名为 stdout 的输出目的地, 输出到控制台。

其语法为:

log4j.appender.appenderName = fully.qualified.name.of.appender.class

“fully.qualified.name.of.appender.class” 可以指定下面五个目的地中的一个:

  • org.apache.log4j.ConsoleAppender(控制台)
  • org.apache.log4j.FileAppender(文件)
  • org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件)
  • org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件)
  • org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)

输出目的地的选项,可以通过如下语法指定

log4j.appender.appenderName.option = valueN

ConsoleAppender选项

Threshold=WARN:指定日志消息的输出最低层次。 ImmediateFlush=true:默认值是true,意谓着所有的消息都会被立即输出。 Target=System.err:默认情况下是:System.out,指定输出控制台

FileAppender 选项

Threshold=WARN:指定日志消息的输出最低层次。 ImmediateFlush=true:默认值是true,意谓着所有的消息都会被立即输出。 File=mylog.txt:指定消息输出到mylog.txt文件。 Append=false:默认值是true,即将消息增加到指定文件中,false指将消息覆盖指定的文件内容。

Java web 项目里面的日志的位置配置支持变量

如果是要指定日志文件的位置为D盘下的log.txt文件。

log4j.appender.APPENDER_NAME.file.out.File=d:\\log.txt

如果指定日志文件的位置为当前的tomcat的工作目录下的某个文件

log4j.appender.APPENDER_NAME.file.out.File=${catalina.home}/logs/logs_tomcat.log

DailyRollingFileAppender 选项

Threshold=WARN:指定日志消息的输出最低层次。

ImmediateFlush=true:默认值是true,意谓着所有的消息都会被立即输出。

File=mylog.txt:指定消息输出到mylog.txt文件。

Append=false:默认值是true,即将消息增加到指定文件中,false指将消息覆盖指定的文件内容。

DatePattern=’.’yyyy-ww:每周滚动一次文件,即每周产生一个新的文件。当然也可以指定按月、周、天、时和分。即对应的格式如下:

  • ’.’yyyy-MM: 每月
  • ’.’yyyy-ww: 每周
  • ’.’yyyy-MM-dd: 每天
  • ’.’yyyy-MM-dd-a: 每天两次
  • ’.’yyyy-MM-dd-HH: 每小时
  • ’.’yyyy-MM-dd-HH-mm: 每分钟

RollingFileAppender 选项

Threshold=WARN:指定日志消息的输出最低层次。 ImmediateFlush=true:默认值是true,意谓着所有的消息都会被立即输出。 File=mylog.txt:指定消息输出到mylog.txt文件。 Append=false:默认值是true,即将消息增加到指定文件中,false指将消息覆盖指定的文件内容。 MaxFileSize=100KB: 后缀可以是KB, MB 或者是 GB. 在日志文件到达该大小时,将会自动滚动,即将原来的内容移到mylog.log.1文件。 MaxBackupIndex=2:指定可以产生的滚动文件的最大数。

实际应用:

  log4j.appender.A1=org.apache.log4j.ConsoleAppender //这里指定了日志输出的第一个位置A1是控制台ConsoleAppender   

配置日志信息的格式

有时用户希望根据自己的喜好格式化自己的日志输出。Log4j可以在Appenders的后面附加Layouts来完成这个功能。Layouts提供了四种日志输出样式,如根据HTML样式、自由指定样式、包含日志级别与信息的样式和包含日志时间、线程、类别等信息的样式等等。

其语法表示为:

  org.apache.log4j.HTMLLayout 以HTML表格形式布局   org.apache.log4j.PatternLayout 可以灵活地指定布局模式   org.apache.log4j.SimpleLayout 包含日志信息的级别和信息字符串   org.apache.log4j.TTCCLayout 包含日志产生的时间、线程、类别等等信息

配置时使用方式为:

  log4j.appender.appenderName.layout = fully.qualified.name.of.layout.class   log4j.appender.appenderName.layout.option1 = value1

  log4j.appender.appenderName.layout.option = valueN

“fully.qualified.name.of.layout.class” 可以指定下面4个格式中的一个:

  • org.apache.log4j.HTMLLayout(以HTML表格形式布局),
  • org.apache.log4j.PatternLayout(可以灵活地指定布局模式),
  • org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串),
  • org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息)

输出格式

Log4J 采用类似 C 语言中的 printf 函数的打印格式格式化日志信息,打印参数如下:

%m 输出代码中指定的消息
%p 输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL
%r 输出自应用启动到输出该log信息耗费的毫秒数
%c 输出所属的类目,通常就是所在类的全名
%t 输出产生该日志事件的线程名
%n 输出一个回车换行符,Windows平台为“\r\n”,Unix平台为“\n”
%d 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如: %d{yyyy MMM dd HH:mm:ss,SSS} ,输出类似: 2002 年 10 月 18 日 22 : 10 : 28 , 921 
%l 输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数。 

格式化例子:

log4j.appender.APPENDER_NAME.file.out.layout.ConversionPattern=%d{yyyy MMM dd HH:mm:ss,SSS}%5p{ \%F\:\%L }-%m%n

注意:

参数中间可能会有一些数字,比如:%5p 它的意思就是在输出此参数之前加入多少个空格,还有就是里面的“\”的作用是转义字符

HTMLLayout 选项

LocationInfo=true:默认值是false,输出java文件名称和行号 Title=my app file: 默认值是 Log4J Log Messages.

PatternLayout 选项

ConversionPattern=%m%n :指定怎样格式化指定的消息。

XMLLayout 选项

LocationInfo=true:默认值是false,输出java文件和行号

实际应用:

log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-4r %-5p %d{yyyy-MM-dd HH:mm:ssS} %c %m%n

这里需要说明的就是日志信息格式中几个符号所代表的含义:

%p: 输出日志信息优先级,即DEBUG,INFO,WARN,ERROR,FATAL,
%d: 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},输出类似:2002年10月18日 22:10:28,921
%r: 输出自应用启动到输出该log信息耗费的毫秒数
%c: 输出日志信息所属的类目,通常就是所在类的全名
%t: 输出产生该日志事件的线程名
%l: 输出日志事件的发生位置,相当于%C.%M(%F:%L)的组合,包括类目名、发生的线程,以及在代码中的行数。举例:Testlog4.main(TestLog4.java:10)
%x: 输出和当前线程相关联的NDC(嵌套诊断环境),尤其用到像java servlets这样的多客户多线程的应用中。
%%: 输出一个"%"字符
%F: 输出日志消息产生时所在的文件名称
%L: 输出代码中的行号
%m: 输出代码中指定的消息,产生的日志具体信息
%n: 输出一个回车换行符,Windows平台为"\r\n",Unix平台为"\n"输出日志信息换行

可以在%与模式字符之间加上修饰符来控制其最小宽度、最大宽度、和文本的对齐方式。如:

  1. %20c:指定输出category的名称,最小的宽度是20,如果category的名称小于20的话,默认的情况下右对齐。
  2. %-20c:指定输出category的名称,最小的宽度是20,如果category的名称小于20的话,”-“号指定左对齐。
  3. %.30c:指定输出category的名称,最大的宽度是30,如果category的名称大于30的话,就会将左边多出的字符截掉,但小于30的话也不会有空格。
  4. %20.30c:如果category的名称小于20就补空格,并且右对齐,如果其名称长于30字符,就从左边交远销出的字符截掉。

这里上面三个步骤是对前面 Log4j 组件说明的一个简化;下面给出一个具体配置例子,在程序中可以参照执行:

  log4j.rootLogger=INFO,A1,B2   log4j.appender.A1=org.apache.log4j.ConsoleAppender   log4j.appender.A1.layout=org.apache.log4j.PatternLayout   log4j.appender.A1.layout.ConversionPattern=%-4r %-5p %d{yyyy-MM-dd HH:mm:ssS} %c %m%n

根据上面的日志格式,某一个程序的输出结果如下:

0  INFO 2003-06-13 13:23:46968 ClientWithLog4j Client socket: Socket[addr=localhost/127.0.0.1,port=8002,localport=2014]
 DEBUG 2003-06-13 13:23:46984 ClientWithLog4j Server says: 'Java server with log4j, Fri Jun 13 13:23:46 CST 2003'
16  DEBUG 2003-06-13 13:23:46984 ClientWithLog4j GOOD
16  DEBUG 2003-06-13 13:23:46984 ClientWithLog4j Server responds: 'Command 'HELLO' not understood.'
16  DEBUG 2003-06-13 13:23:46984 ClientWithLog4j HELP
16  DEBUG 2003-06-13 13:23:46984 ClientWithLog4j Server responds: 'Vocabulary: HELP QUIT'
16  DEBUG 2003-06-13 13:23:46984 ClientWithLog4j QUIT
  1. 当输出信息于回滚文件时

log4j.appender.ROLLING_FILE=org.apache.log4j.RollingFileAppender //指定以文件的方式输出日志 log4j.appender.ROLLING_FILE.Threshold=ERROR log4j.appender.ROLLING_FILE.File=rolling.log //文件位置,也可以用变量${java.home}、rolling.log log4j.appender.ROLLING_FILE.Append=true log4j.appender.ROLLING_FILE.MaxFileSize=10KB //文件最大尺寸 log4j.appender.ROLLING_FILE.MaxBackupIndex=1 //备份数 log4j.appender.ROLLING_FILE.layout=org.apache.log4j.PatternLayout log4j.appender.ROLLING_FILE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n   

Log4j比较全面的配置

LOG4J的配置之简单使它遍及于越来越多的应用中了:Log4J配置文件实现了输出到控制台、文件、回滚文件、发送日志邮件、输出到数据库日志表、自定义标签等全套功能。

log4j.rootLogger=DEBUG,CONSOLE,FILE,ROLLING_FILE,SOCKET,LF5_APPENDER,MAIL,DATABASE,A1,im 
log4j.addivity.org.apache=true 

# 应用于控制台 
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender 
log4j.appender.Threshold=DEBUG 
log4j.appender.CONSOLE.Target=System.out 
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout 
log4j.appender.CONSOLE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n 
#log4j.appender.CONSOLE.layout.ConversionPattern=[start]%d{DATE}[DATE]%n%p[PRIORITY]%n%x[NDC]%n%t[thread] n%c[CATEGORY]%n%m[MESSAGE]%n%n 

#应用于文件 
log4j.appender.FILE=org.apache.log4j.FileAppender 
log4j.appender.FILE.File=file.log 
log4j.appender.FILE.Append=false 
log4j.appender.FILE.layout=org.apache.log4j.PatternLayout 
log4j.appender.FILE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n 
# Use this layout for LogFactor 5 analysis 

# 应用于文件回滚 
log4j.appender.ROLLING_FILE=org.apache.log4j.RollingFileAppender 
log4j.appender.ROLLING_FILE.Threshold=ERROR 
log4j.appender.ROLLING_FILE.File=rolling.log //文件位置,也可以用变量${java.home}、rolling.log
log4j.appender.ROLLING_FILE.Append=true //true:添加 false:覆盖
log4j.appender.ROLLING_FILE.MaxFileSize=10KB //文件最大尺寸
log4j.appender.ROLLING_FILE.MaxBackupIndex=1 //备份数
log4j.appender.ROLLING_FILE.layout=org.apache.log4j.PatternLayout 
log4j.appender.ROLLING_FILE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n

#应用于socket 
log4j.appender.SOCKET=org.apache.log4j.RollingFileAppender 
log4j.appender.SOCKET.RemoteHost=localhost 
log4j.appender.SOCKET.Port=5001 
log4j.appender.SOCKET.LocationInfo=true 
# Set up for Log Facter 5 
log4j.appender.SOCKET.layout=org.apache.log4j.PatternLayout 
log4j.appender.SOCET.layout.ConversionPattern=[start]%d{DATE}[DATE]%n%p[PRIORITY]%n%x[NDC]%n%t[thread]%n%c[CATEGORY]%n%m[MESSAGE]%n%n

# Log Factor 5 Appender 
log4j.appender.LF5_APPENDER=org.apache.log4j.lf5.LF5Appender 
log4j.appender.LF5_APPENDER.MaxNumberOfRecords=2000 

# 发送日志给邮件 
log4j.appender.MAIL=org.apache.log4j.net.SMTPAppender 
log4j.appender.MAIL.Threshold=FATAL 
log4j.appender.MAIL.BufferSize=10 
log4j.appender.MAIL.From=web@gmail.com
log4j.appender.MAIL.SMTPHost=www.gmail.com
log4j.appender.MAIL.Subject=Log4J Message 
log4j.appender.MAIL.To=web@gmail.com
log4j.appender.MAIL.layout=org.apache.log4j.PatternLayout 
log4j.appender.MAIL.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n 
# 用于数据库 
log4j.appender.DATABASE=org.apache.log4j.jdbc.JDBCAppender 
log4j.appender.DATABASE.URL=jdbc:mysql://localhost:3306/test 
log4j.appender.DATABASE.driver=com.mysql.jdbc.Driver 
log4j.appender.DATABASE.user=root 
log4j.appender.DATABASE.password= 
log4j.appender.DATABASE.sql=INSERT INTO LOG4J (Message) VALUES ('[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n') 
log4j.appender.DATABASE.layout=org.apache.log4j.PatternLayout 
log4j.appender.DATABASE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n

log4j.appender.A1=org.apache.log4j.DailyRollingFileAppender 
log4j.appender.A1.File=SampleMessages.log4j 
log4j.appender.A1.DatePattern=yyyyMMdd-HH'.log4j' 
log4j.appender.A1.layout=org.apache.log4j.xml.XMLLayout 

#自定义Appender 
log4j.appender.im = net.cybercorlin.util.logger.appender.IMAppender 
log4j.appender.im.host = mail.cybercorlin.net 
log4j.appender.im.username = username 
log4j.appender.im.password = password 
log4j.appender.im.recipient = corlin@cybercorlin.net 
log4j.appender.im.layout=org.apache.log4j.PatternLayout 
log4j.appender.im.layout.ConversionPattern =[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n

一个完整的XML例子

<?xml version="1.0" encoding="UTF-8" ?>  
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">  
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">  
  
    <!-- ========================== 自定义输出格式说明================================ -->  
    <!-- %p 输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL -->  
    <!-- #%r 输出自应用启动到输出该log信息耗费的毫秒数  -->  
    <!-- #%c 输出所属的类目,通常就是所在类的全名 -->  
    <!-- #%t 输出产生该日志事件的线程名 -->  
    <!-- #%n 输出一个回车换行符,Windows平台为“\r\n”,Unix平台为“\n” -->  
    <!-- #%d 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},输出类似:2002年10月18日 22:10:28,921  -->  
    <!-- #%l 输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数。举例:Testlog4.main(TestLog4.java:10)  -->  
    <!-- ========================================================================== -->  
      
    <!-- ========================== 输出方式说明================================ -->  
    <!-- Log4j提供的appender有以下几种:  -->  
    <!-- org.apache.log4j.ConsoleAppender(控制台),  -->  
    <!-- org.apache.log4j.FileAppender(文件),  -->  
    <!-- org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件), -->  
    <!-- org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件),  -->  
    <!-- org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)   -->  
    <!-- ========================================================================== -->  
    <!-- 输出到日志文件  -->  
    <appender name="filelog_appender"  
        class="org.apache.log4j.RollingFileAppender">  
        <!-- 设置File参数:日志输出文件名 -->  
        <param name="File" value="log/testlog4jxml_all.log" />  
        <!-- 设置是否在重新启动服务时,在原有日志的基础添加新日志 -->  
        <param name="Append" value="true" />  
        <!-- 设置文件大小 -->  
        <param name="MaxFileSize" value="1MB" />  
        <!-- 设置文件备份 -->  
        <param name="MaxBackupIndex" value="10000" />  
        <!-- 设置输出文件项目和格式 -->  
        <layout class="org.apache.log4j.PatternLayout">  
            <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5p (%c:%L)- %m%n" />  
        </layout>  
    </appender>  
  
    <!-- 输出到日志文件 每天一个日志  -->  
    <appender name="filelog_daily" class="org.apache.log4j.DailyRollingFileAppender">     
        <param name="File" value="log/daily.log" />     
        <param name="DatePattern" value="'daily.'yyyy-MM-dd'.log'" />     
        <layout class="org.apache.log4j.PatternLayout">     
            <param name="ConversionPattern" value="[%d{yyyy-MM-dd HH:mm:ss\} %-5p] [%t] (%c:%L) - %m%n" />     
        </layout>     
    </appender>   
  
    <!-- 输出到控制台中 -->  
    <appender name="console" class="org.apache.log4j.ConsoleAppender">  
        <layout class="org.apache.log4j.PatternLayout">  
            <param name="ConversionPattern"  
                value="%d{yyyy-MM-dd HH:mm:ss} %-5p: %m%n" />  
            <!-- "%-5p: [%t] [%c{3}.%M(%L)] | %m%n" -->  
        </layout>  
    </appender>  
  
    <appender name="EMAIL_QQ" class="org.apache.log4j.net.SMTPAppender">  
        <param name="Threshold" value="INFO"/>  
        <param name="BufferSize" value="128" />  
        <param name="SMTPHost" value="smtp.qq.com" />  
        <param name="SMTPUsername" value="" />  
        <param name="SMTPPassword" value="" />  
        <param name="From" value="" />  
        <param name="To" value="" />  
        <param name="Subject" value="测试邮件发送" />  
        <param name="LocationInfo" value="true" />  
        <param name="SMTPDebug" value="true" />  
        <layout class="org.cjj.log4j.extend.PatternLayout_zh">  
            <param name="ConversionPattern" value="[%d{ISO8601}] %-5p %c %m%n"/>  
        </layout>  
    </appender>  
  
<!--- 异步测试,当日志达到缓存区大小时候执行所包的appender -->  
    <appender name="ASYNC_test" class="org.apache.log4j.AsyncAppender">     
     <param name="BufferSize" value="10"/>     
     <appender-ref ref="EMAIL_QQ"/>  
   </appender>  
  
 <!-- 设置包限制输出的通道 -->  
    <category name="com.package.name" additivity="false"><!-- 日志输出级别,起码可以有5个级别,可以扩展自己的级别,邮件发送必须是ERROR级别不好用,所以最后自己扩展一个邮件发送级别 -->  
        <level value="ERROR" />  
        <appender-ref ref="filelog_daily" />  
        <appender-ref ref="daily_appender" />  
        <appender-ref ref="console" />  
        <appender-ref ref="ASYNC_test" />  
    </category>  
</log4j:configuration>  

Web配置log4j,需求增加以下内容到 WEB-INF/web.xml

   webAppRootKey smilecargo.root log4jConfigLocation classpath:log4j.xml log4jRefreshInterval 60000    org.springframework.web.util.Log4jConfigListener

${smilecargo.root}是web工程相对路径

问题

配置时出现如下问题:

log4j:ERROR setFile(null,true) call failed.
java.io.FileNotFoundException: /home/work/log/web.log (No such file or directory)
    at java.io.FileOutputStream.open0(Native Method)
    at java.io.FileOutputStream.open(FileOutputStream.java:270)
    at java.io.FileOutputStream.<init>(FileOutputStream.java:213)
    at java.io.FileOutputStream.<init>(FileOutputStream.java:133)
    at org.apache.log4j.FileAppender.setFile(FileAppender.java:294)
    at org.apache.log4j.FileAppender.activateOptions(FileAppender.java:165)
    at org.apache.log4j.DailyRollingFileAppender.activateOptions(DailyRollingFileAppender.java:223)
    at org.apache.log4j.config.PropertySetter.activate(PropertySetter.java:307)
    at org.apache.log4j.xml.DOMConfigurator.parseAppender(DOMConfigurator.java:295)
    at org.apache.log4j.xml.DOMConfigurator.findAppenderByName(DOMConfigurator.java:176)
    at org.apache.log4j.xml.DOMConfigurator.findAppenderByReference(DOMConfigurator.java:191)
    at org.apache.log4j.xml.DOMConfigurator.parseChildrenOfLoggerElement(DOMConfigurator.java:523)
    at org.apache.log4j.xml.DOMConfigurator.parseCategory(DOMConfigurator.java:436)
    at org.apache.log4j.xml.DOMConfigurator.parse(DOMConfigurator.java:1004)
    at org.apache.log4j.xml.DOMConfigurator.doConfigure(DOMConfigurator.java:872)
    at org.apache.log4j.xml.DOMConfigurator.doConfigure(DOMConfigurator.java:778)
    at org.apache.log4j.helpers.OptionConverter.selectAndConfigure(OptionConverter.java:526)
    at org.apache.log4j.LogManager.<clinit>(LogManager.java:127)
    at org.apache.log4j.Logger.getLogger(Logger.java:104)
    at org.apache.commons.logging.impl.Log4JLogger.getLogger(Log4JLogger.java:262)
    at org.apache.commons.logging.impl.Log4JLogger.<init>(Log4JLogger.java:108)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at org.apache.commons.logging.impl.LogFactoryImpl.createLogFromClass(LogFactoryImpl.java:1025)
    at org.apache.commons.logging.impl.LogFactoryImpl.discoverLogImplementation(LogFactoryImpl.java:844)
    at org.apache.commons.logging.impl.LogFactoryImpl.newInstance(LogFactoryImpl.java:541)
    at org.apache.commons.logging.impl.LogFactoryImpl.getInstance(LogFactoryImpl.java:292)
    at org.apache.commons.logging.impl.LogFactoryImpl.getInstance(LogFactoryImpl.java:269)
    at org.apache.commons.logging.LogFactory.getLog(LogFactory.java:655)
    at org.springframework.util.PropertyPlaceholderHelper.<clinit>(PropertyPlaceholderHelper.java:40)
    at org.springframework.web.util.ServletContextPropertyUtils.<clinit>(ServletContextPropertyUtils.java:38)
    at org.springframework.web.util.Log4jWebConfigurer.initLogging(Log4jWebConfigurer.java:128)
    at org.springframework.web.util.Log4jConfigListener.contextInitialized(Log4jConfigListener.java:49)
    at org.eclipse.jetty.server.handler.ContextHandler.callContextInitialized(ContextHandler.java:890)
    at org.eclipse.jetty.servlet.ServletContextHandler.callContextInitialized(ServletContextHandler.java:532)
    at org.eclipse.jetty.server.handler.ContextHandler.startContext(ContextHandler.java:853)
    at org.eclipse.jetty.servlet.ServletContextHandler.startContext(ServletContextHandler.java:344)
    at org.eclipse.jetty.webapp.WebAppContext.startWebapp(WebAppContext.java:1514)
    at org.eclipse.jetty.maven.plugin.JettyWebAppContext.startWebapp(JettyWebAppContext.java:359)
    at org.eclipse.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1476)
    at org.eclipse.jetty.server.handler.ContextHandler.doStart(ContextHandler.java:785)
    at org.eclipse.jetty.servlet.ServletContextHandler.doStart(ServletContextHandler.java:261)
    at org.eclipse.jetty.webapp.WebAppContext.doStart(WebAppContext.java:545)
    at org.eclipse.jetty.maven.plugin.JettyWebAppContext.doStart(JettyWebAppContext.java:434)
    at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68)
    at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:131)
    at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:113)
    at org.eclipse.jetty.server.handler.AbstractHandler.doStart(AbstractHandler.java:113)
    at org.eclipse.jetty.server.handler.ContextHandlerCollection.doStart(ContextHandlerCollection.java:167)
    at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68)
    at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:131)
    at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:113)
    at org.eclipse.jetty.server.handler.AbstractHandler.doStart(AbstractHandler.java:113)
    at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68)
    at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:131)
    at org.eclipse.jetty.server.Server.start(Server.java:449)
    at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:105)
    at org.eclipse.jetty.server.handler.AbstractHandler.doStart(AbstractHandler.java:113)
    at org.eclipse.jetty.server.Server.doStart(Server.java:416)
    at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68)
    at org.eclipse.jetty.maven.plugin.AbstractJettyMojo.startJetty(AbstractJettyMojo.java:467)
    at org.eclipse.jetty.maven.plugin.AbstractJettyMojo.execute(AbstractJettyMojo.java:333)
    at org.eclipse.jetty.maven.plugin.JettyRunMojo.execute(JettyRunMojo.java:180)
    at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:134)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:207)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:153)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:145)
    at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:116)
    at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:80)
    at org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder.build(SingleThreadedBuilder.java:51)
    at org.apache.maven.lifecycle.internal.LifecycleStarter.execute(LifecycleStarter.java:128)
    at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:307)
    at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:193)
    at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:106)
    at org.apache.maven.cli.MavenCli.execute(MavenCli.java:863)
    at org.apache.maven.cli.MavenCli.doMain(MavenCli.java:288)
    at org.apache.maven.cli.MavenCli.main(MavenCli.java:199)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Launcher.java:289)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:229)
    at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(Launcher.java:415)
    at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:356)
    at org.codehaus.classworlds.Launcher.main(Launcher.java:47)
log4j:ERROR Either File or DatePattern options are not set for appender [file].

解决方案:

这种情况一般是 log 文件的路径不对,要不是文件路劲不存在,要不就是无权限写入。

reference


2017-12-05 log , java

git 不同阶段撤回

因为平时使用 SmartGit 这样一个 Git client,所以也没有太大注意 Git 中不同阶段撤回的方式,虽然平时接触过 git reset--soft--hard 来撤销已提交的 commit,但没有形成一个系统的知识体系。大家都知道 Git 是一个分布式版本控制,所以 Git 会有一个本地库,和一个远端库,而平时提交代码的时候,一般也都是先从本地工作区提交代码

git add .
git commit -s
git push

这几个步骤,虽然平淡无奇,但是展开说,就体现了 Git 的重要的环节,一段代码的提交顺序:

工作区  ->  git add .  -> 暂存区 -> git commit -> 本地仓库 -> git push -> 远程仓库

这里就要提到 Git 中的四个区:

  • 工作区 working
  • 暂存区 stage
  • 本地仓库 local repository
  • 远程仓库 remote repository

被追踪的文件,在未进入和进入上述四个区之后分别有一个状态,所以一共有五个状态:

  • 未修改 origin
  • 已修改 modified
  • 已暂存 staged
  • 已提交 committed
  • 已推送 pushed

在了解这几个基本概念之后,如何检查本地的修改,以及如何查看不同状态之间的修改,这就要用到 git diff 命令。

直接使用 git diff 命令,能够查看已修改,未暂存的内容

使用 git diff --cache 来查看已暂存,未提交的内容

使用 git diff origin/master master 来查看已提交,未推送的差异。

工作区          暂存区           本地仓库                    远程仓库
    \          /     \          /         \                  /
     \        /       \        /           \                /
     git diff         git diff --cache     git diff origin/master master

图解

git-workspace-index

在知道如何查看四个不同区之间的差异后,如何使用 git reset 来撤销呢?

撤销工作区修改

如果只是在编辑器中修改了文件的内容,还未使用 git add 将修改提交到暂存区,那么可以使用 git checkout . 或者 git checkout -- <file> 来丢弃本地全部修改或者丢弃某文件的修改。

可以将 git add .git checkout . 看做一对反义词,修改完成后,如果想 Git 往前进一步,让修改进入暂存区,执行 git add . 如果向后退则执行 git checkout .

撤销暂存区修改

如果已经执行了 git add,意味着暂存区中已经有了修改,但是需要丢弃暂存区的修改,那么可以执行 git reset

对于已经被 Git 追踪的文件,可以使用

git reset <file>

来单独将文件从暂存区中丢弃,将修改放到工作区。

对于从来没有被 Git 追踪过,是 new file 的文件,则需要使用:

git reset HEAD <file>

来将新文件从暂存区中取出放到工作区。

如果确定暂存区中的修改完全不需要,则可以使用

git reset --hard

直接将修改抛弃,谨慎使用 –hard 命令, 暂存区中所有修改都会被丢弃。修改内容也不会被重新放到工作区。

撤销本地提交

对于已经本地的提交,也就是使用 git add 并且执行了 git commit 的修改,这时候本地的修改已经进入了本地仓库,而这是需要撤销这一次提交,或者本地的多次提交,怎么办?

git reset --hard origin/master

同样还是 git reset 命令,但是多了 origin/masterorigin 表示远端仓库的名字,默认为 origin,可能也有其他自己的名字,origin/master 表示远程仓库,既然本地的修改已经不再需要,那么从远端将代码拉回来就行。

不过不建议直接使用 git reset --hard origin/master 这样太强的命令,如果想要撤销本地最近的一次提交,可以使用

git reset --soft HEAD~1

这行命令表示,将最近一次提交 HEAD~1 从本地仓库回退到暂存区,--soft 不会丢弃修改,而是将修改放到暂存区,后续继续修改,或者丢弃暂存区的修改就可以随意了。如果要撤销本地两次修改,则改成 HEAD~2 即可,其他同类。

不过要注意的是,已经提交到远端的提交,不要使用 git reset 来修改,对于多人协作项目会给其他人带来很多不必要的麻烦。

撤销远端仓库修改

对于已经推送的修改,原则上是不要撤销的,不过 Git 给了使用者充分的自由,在明确自己在做什么的情况下,可以使用 git push -f 使用 force 选项来将本地库 force 覆盖远端仓库,强制 push 到远端。

对于个人,一个人使用的项目使用这样的方式,并没有太大问题,但是如果对于多人项目,如果你强行改变了远端仓库,别人再使用的时候就会出现很多问题,所以使用 git push -f 时一定要想清楚自己在做什么事情。


2017-12-04 git , linux

frp 使用笔记

frp 是中国开发者 fatedier 的作品,frp 是一个高性能的反向代理应用,可以帮助您轻松地进行内网穿透,对外网提供服务,支持 tcp, udp, http, https 等协议类型,并且 web 服务支持根据域名进行路由转发。

frp用法和ngrok相似,但是frp比ngrok更加优秀。 配置过程很简单,但是也遇到一些问题,所以把过程记录下来。

frp 作用

  • 利用处于内网或防火墙后的机器,对外网环境提供 http 或 https 服务。
  • 对于 http, https 服务支持基于域名的虚拟主机,支持自定义域名绑定,使多个域名可以共用一个80端口。
  • 利用处于内网或防火墙后的机器,对外网环境提供 tcp 和 udp 服务,例如在家里通过 ssh 访问处于公司内网环境内的主机。

frp 配置

frps.ini 服务端配置:

[common] 
bind_port = 7000

frpc.ini 客户端配置

[common] 
server_addr = x.x.x.x   # 填写公网服务器IP地址
server_port = 7000 

[ssh-computer-name] 
type = tcp 
local_ip = 127.0.0.1 
local_port = 22 
remote_port = 6000 #配置服务器端口

然后使用 ssh user@x.x.x.x -p 6000 或者 ssh -o Port=6000 user@x.x.x.x 来连接内网的机器。

再配置完之后可以使用 supervisor 来管理,实现进程死掉后自动重启。

reference


2017-11-30 frp , ssh , linux

使用 rebase 来合并多个 commits

Git 作为分布式版本控制系统,所有修改操作都是基于本地的,在团队协作过程中,假设你和你的同伴在本地中分别有各自的新提交,而你的同伴先于你 push 了代码到远程分支上,所以你必须先执行 git pull 来获取同伴的提交,然后才能 push 自己的提交到远程分支。而按照 Git 的默认策略,如果远程分支和本地分支之间的提交线图有分叉的话(即不是 fast-forwarded),Git 会执行一次 merge 操作,因此产生一次没意义的提交记录。

在 pull 操作的时候,使用 git pull --rebase 选项即可很好地解决上述问题,使用 -r 或者 --rebase 的好处是,Git 会使用 rebase 来代替 merge 的策略。

使用 man git-merge 中的示例图说明:

             A---B---C  remotes/origin/master
            /
       D---E---F---G  master

如果执行 git pull 之后,提交线是:

             A---B---C remotes/origin/master
            /         \
       D---E---F---G---H master

结果是多出了 H 这个 无意义的提交。如果执行 git pull -r 的话,提交就是:

                   remotes/origin/master
                       |
       D---E---A---B---C---F'---G'  master

本地的两次提交就使用 rebase 重新添加到了远端的提交之后,多余的 merge 无意义提交消失。

在了解 git pull -r 的前提下,来看一下如何使用 rebase 命令来将本地的多个提交合并为一次提交。

假设本地 Git 仓库中因为临时提交产生了一些 commits

commit 8b465db3672a24710207d91af74d61cee975b208
Author: Ein Verne
Date:   Thu Nov 30 20:25:52 2017 +0800

    Third commit

commit 821476d2b043e85d131483279e23778aa3fd1241
Author: Ein Verne
Date:   Thu Nov 30 14:07:08 2017 +0800

    Second commit


commit 51912266c1634dd2f0848071cc311975b6aad730
Author: Ein Verne
Date:   Thu Nov 23 20:39:42 2017 +0800

    Init commit

假设我们需要将第二次提交 821476d2b043e85d131483279e23778aa3fd1241 和 第三次提交 8b465db3672a24710207d91af74d61cee975b208 合并为一次提交,可以先使用

git rebase -i 5191226

最后一次不需要修改的 commit id,然后进入 vi 的提交信息的编辑模式。

pick 821476d Second commit
pick 8b465db Third commit

# Rebase 5191226..8b465db onto 5191226 (2 command(s))
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

这里可以看到,上方未注释部分填写要执行的命令,下方注释部分为支持的指令说明。指令部分由命令,commit hash 和 commit message 组成。

这里

  • pick 为选择该 commit
  • squash 是这个 commit 会被合并到前一个 commit
  • edit 选中提交,rebase 暂停,修改该 commit 提交内容
  • reword 选中提交,并修改提交信息
  • fixup 与 squash 相同,但不会保存当前 commit 的提交信息
  • exec 执行其他 shell 命令
  • drop 抛弃提交

这里只要将第三次提交前的 pick 修改为 squash,就可以将该 commit 合并到第二次提交。修改之后保存 :wq 退出。

pick 821476d Second commit
squash 8b465db Third commit

然后会进入 commit message 界面,在该界面中修改合适的提交信息,将两次的 commit 合并为一次,保存退出即可完成合并。

注意:git rebase 是一个比较危险的命令,如果一旦中途出现错误,可以使用 git rebase --abort 来终止 rebase,回到没有合并之前的状态。

TIPS

合并本地多次提交

如果想要合并最近的多次提交,在 rebase 进入交互模式时,可以指定范围比如

git rebase -i HEAD~8

选取最近的 8 次提交。

更换本地提交的顺序

在进入 rebase -i 交互模式时,更换提交信息的顺序,保存即可修改本地提交的 commit 顺序。比如

pick 821476d Second commit
pick 8b465db Third commit

修改为

pick 8b465db Third commit
pick 821476d Second commit

可以更换次序。

注意

git rebase 操作应该只用于本地尚未提交到远程仓库的 commit,一旦 push 到远端仓库,则不再允许修改 commit,否则可能会给其他开发者带来很多麻烦。尤其是多人协作时,千万要注意。

reference


2017-11-25 git , linux

每天学习一个命令:zgrep 不解压过滤压缩包中文本

Linux 下按照正则过滤文本的命令 grep 非常强大,grep 能够把正则匹配的行打印出来。

grep 的命令格式是

grep [option] pattern files

他的工作方式是,在一个或者多个文件中根据正则搜索匹配内容,将搜索的结果输出到标准输出,不更改源文件内容。

grep 常用的一些选项

-i   忽略字符大小写区别
-v   显示不包含正则的所有行

zgrep 使用

但如果想要过滤 Nginx 的 access_log.gz 的压缩文件的内容,如果先解压,然后过滤出有用的文本,再把文件压缩回去,这就变的非常不方便。

gunzip access_log.gz
grep "/api" access_log
gzip access_log

需要使用三个命令来实现文件的过滤,其实 Linux 下可以使用 zgrep 来一步完成

zgrep "/api" access_log.gz

和 grep 类似, zgrep 也可以指定多个文件同时进行搜索过滤

zgrep "/api" access_log.gz access_log_1.gz

延伸

既然提到了不解压搜索压缩包内容,.gz 的文件可以使用 zgrep ,而对于 .tar.gz 文件

zcat access.tar.gz | grep -a '/api'
zgrep -a "/api" access.tar.gz

其实这些带 z 的命令都包含在 Zutils 这个工具包中,这个工具包还提供了

zcat  解压文件并将内容输出到标准输出
zcmp  解压文件并且 byte by byte 比较两个文件
zdiff 解压文件并且 line by line 比较两个文件
zgrep 解压文件并且根据正则搜索文件内容
ztest - Tests integrity of compressed files.
zupdate - Recompresses files to lzip format.

这些命令支持 bzip2, gzip, lzip and xz 格式。


2017-11-20 linux , grep , regex , gz

Vim 行选择复制和移动

在熟悉 Vim 基本的 yy (yank current line) 和 p (paste) 的操作前提下,如果现在 Vim 中批量的移动某一些行,或者批量的复制从第 50 行到100 行到第200行后面,以我们之前的知识,可以先跳转到第 50 行 (:50),然后进入 行选择模式 (V) ,往下选择 50 行 (50j),然后再对选中的 50 行进行复制操作 (y),然后再移动到 200 行 (:200) 粘贴 (p)。

计算一下,从选择到复制,上面的操作可以划分为 5 个步骤,需要敲击 :50V50jy:200p ,当然上面的例子只是一个极端的例子,一般这样的需求也不大可能会出现。而如果有命令可以直接拷贝某些行,那么就不需要这么复杂,Vim 提供了整行的复制和移动命令。

:[range]copy {address}

copy 命令, range 表示拷贝的范围,行号范围, address 表示要拷贝到的地方,比如上面的例子就可以使用 :50,100copy200 来完成。

copy 命令可以简写成 :co 或者 :t

几个常用的格式:

:t5   拷贝当前行到第5行的下一行
:t.   拷贝当前行到光标下一行,相当于 `Yp` 或者 `yyp`
:t$   拷贝当前行到文件最后一行
:'<,'>t0   拷贝选取的区域到文件开头,在 Visual 模式下选中文本,输入 `:`,再输入 `t0`

move 命令也和 copy 一样

:[range]m[ove] {address}	

这里只是将拷贝,换为移动。

可以通过 :help copy:help move 来查看帮助

快速选择行

遇到一个操作,比如在一个非常大的文件中,需要快速的选择 1000 行到 4500 行,那么可以用下面的方法快速选择。

方法一

  • 使用 :1000 快速移动到 1000 行
  • 行选择 V4500G 快速从当前行选择到 4500 行(包括)

G Goto line [count]

方法二

  • 使用 :1000 移动到 1000 行,然后按下 m + a,标记 a
  • 然后 :4500 移动到 4500 行,按下 V + \` + a 选择

方法三

  • 使用 :1000 移动到 1000 行,然后按下 V
  • 然后输入 3500j 向下移动 3500 行

2017-11-19 vim , linux , copy

精通正则表达式第三版笔记

《精通正则表达式》第3版

技术图书的主要使命是传播专业知识,专业知识分为框架性知识和具体知识。框架性知识需要通过系统的阅读和学习掌握,而大量的具体知识,则主要通过日常生活的积累以及虽则随用随查的学习来填充。

完整的正则表达式由两种字符构成,特殊字符,元字符,另外一种就是普通文本字符。

完整的正则表达式由小的构建模块单元 building block unit 构成,每个单元都很简单,不过他们能够以无穷多种方式组合,所以可以提供无限的可能。

字符组

匹配若干字符之一 [ea] 匹配 a 或者 e

gr[ae]y 表示匹配 gray 或者 grey

元字符 名称 匹配对象
. 单个任意字符
[abc] 字符组 列出的字符
[^abc] 排除字符组 未列出的字符
^   行起始
$   行尾
\<   单词起始
\>   单词结束
| 竖线
() 小括号 限制竖线的作用范围

量词

元字符 次数下限 次数上限 含义
1 可选字符,前面字符出现0次或者1次
+ 1 前面字符出现一次或者多次
* 前面字符出现任意多次,或者不出现

正则表达式可以使用多个括号,使用 \1, \2, \3 等来匹配括号的内容

([a-z])([0-9])\1\2 其中的 \1 代表的就是 [a-z] 匹配的内容,而 \2 就代表 [0-9] 匹配的内容

匹配引号内的字符串

"[^"]*"

两端引号用来匹配字符串开头和结尾的引号,中间 [^"] 用来匹配除引号之外的任何字符,* 用来表示任意数量的非引号字符

匹配 URL

grep, 和 egrep 的历史

正则表达式的流派

正则表达式的处理方式

集成式

Perl 中的例子

if ($line =~ m/^Subject: (.*)/i) {
    $subject = $1 
}

取邮件标题的正则,内建在程序内,隐藏了正则表达式的预处理,匹配,应用,返回结果,减轻了常见任务的难度。

程序式处理和面向对象式处理

由普通函数和方法来提供

Java 中处理正则,Sun 提供了 java.util.regex 包来在 Java 中更加方便的使用正则。

import java.util.regex.*;

Pattern r = Pattern.compile("^Subject: (.*)", Pattern.CASE_INSENSITIVE);
Matcher m = r.matcher(line);
if (m.find()) {
    subject = m.group(1);
}

Perl 隐藏了绝大部分细节, Java 则暴露了一些正则的细节,编译正则表达式到 pattern 对象,将正则和匹配的文本联系到一起,得到 Matcher 对象,在应用正则之前,检查是否存在匹配,返回结果,如果存在匹配,则捕获括号内的子表达式文本。

Java 也提供了函数式处理的例子, Pattern 类提供了静态方法

if (! Pattern.matches("\\s*", line)) {
    // 如果 line 不是空行
}

函数包装一个隐式的正则表达式,返回一个 Boolean。

Sun 也会把正则表达式整合到 Java 的其他部分,比如 String 类中 matches 函数

if (! line.matches("\\s*")) {
    // line 不为空行
}

String 中的方法不适合在对时间要求很高的循环中使用。

Python 中的处理, Python 也使用面向对象的方法

import re

r = re.compile("^Subject: (.*)", re.IGNORECASE)
m = r.search(line)
if m:
    subject = m.group(1)

这个例子和 Java 中的非常类似。

正则匹配规则

优先选择最左端匹配结果

从左往右匹配,左侧的结果优先于右侧

标准量词优先匹配

标准量词 ?, *, +, {m,n} 都是优先匹配 greedy 的。例如 a? 中的 a[0-9]+ 中的 [0-9],在匹配成功之前,进行尝试的次数是有上限和下限的,规则2表明,尝试总是获得最长的匹配。

标准匹配量词的结果可能并非所有可能中最长的,但是它们总是尝试匹配尽可能多的字符,直到匹配上限为止。如果最终结果并非该表达式的所有可能中最长的,原因肯定是匹配字符过多导致匹配失败。

举例, \b\w+s\b 来匹配包含 s 的字符串,比如 regexes\w+ 完全能够匹配整个单词,但如果 \w+ 来匹配整个单词 s 就无法匹配,为了完成匹配, \w+ 必须匹配 regexes ,最后把 s\b 留出来。

NFA 称为“表达式主导”引擎,对应的 DFA 称为 “文本主导” 引擎。

NFA 引擎

NFA 引擎中,每一个子表达式都是独立的,子表达式之间不存在内在的联系,子表达式和正则表达式的控制结构(多选分支、括号以及匹配量词)的层次关系控制了整个匹配过程。NFA 引擎是正则表达式主导,编写正则的人有充分的机会来实现期望的结果

DFA 文本主导

DFA 在扫描字符串时,会记录“当前有效”的所有匹配。比如正则

to(nite|knight|night)

来匹配文本

after ... tonight ...
当文本扫描到 t^onight 时,记录可能的匹配 t^o(nite knight night)
接下来扫描每一个字符都会更新可能的匹配序列,比如扫描到 toni^ght ... 时,可能的匹配就是 to(ni^te knight ni^ght)。此时 knight 就已经无法匹配。当扫描到 g 时只有一个匹配,等完成 h 和 t 的扫描之后,引擎发线匹配完成,报告成功。

对比

一般情况下,文本主导的 DFA 引擎要快一些, NFA 正则表达式引擎,因为需要对同样的文本尝试不同的表达式匹配,可能会产生不同的分支浪费时间。

NFA 匹配的过程中,目标文本中的某个字符串可能会被正则表达式中不同部分重复检查。相反,DFA 引擎是确定性,目标文本中的每个字符只会检查一遍。

这两种技术,都有对应的正式名字:非确定型有穷自动机NFA,和 确定型有穷自动机 DFA。

正则引擎的分类

粗略分为三类

  • DFA 符合或者不符合 POSIX 标准的都属于此类
  • 传统 NFA
  • POSIX NFA

部分程序及其所使用的正则引擎 引擎 | 程序 ————–|————- DFA | 大多数版本的 awk, egrep, flex, lex, MySQL 传统型 NFA | GNU Emacs, Java, 大多数版本的 grep, less, more, .NET ,Perl, PHP,Python, Ruby, 大多数版本的 sed, vi POSIX NFA | mawk, GUN Emacs 明确指定时使用 DFA/NFA 混合 | GNU awk, GNU grep/egrep, Tcl

实用技巧

匹配IP地址

匹配 IPv4 的地址,用点号分开的四个数组,0-255

[01]?\d\d?|2[0-4]\d|25[0-5]

这个表达式能够匹配 0 到 255 之间的数,然后重复 4 遍。这样这个表达式会异常复杂,通常情况下,更合适的做法是不依赖正则完成全部的工作,使用

^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\$

来匹配,然后将匹配的数字拿出来,使用其他程序来进行验证。

匹配堆成括号

匹配括号的内容,或许会想到 \bfoo\([^)])

为了匹配括号

`(.*)`    括号及括号内任何字符
`([^)]*)`  从一个开括号到最近的闭括号
`([^()]*)` 从一个开括号到最近的闭括号,但是不容许其中包含开括号

对于文本

var = foo(bar(this), 3.7) + 2 * (that - 1);

第一个正则会匹配 (bar(this), 3.7) + 2 * (that - 1) , 而第二个正则表达式只会匹配 (bar(this) , 而第三个表达式能够匹配 (this) ,但是如果想要匹配 foo 后面的括号,则无能为力,所以三个表达式都不合格。

Java 正则

通过 java.util.regex 使用正则非常简单,一个接口一个 exception

java.util.regex.Pattern
java.util.regex.Matcher
java.util.regex.MatchResult
java.util.regex.PatternSyntaxException

通过 Pattern 构造编译正则表达式,通过正则匹配构建 Matcher 对象。


2017-11-17 regex , java

每天学习一个命令: mtr 查看路由网络连通性

常用的 ping,tracert,nslookup 一般用来判断主机的网络连通性,其实 Linux 下有一个更好用的网络联通性判断工具,它可以结合ping nslookup tracert 来判断网络的相关特性,这个命令就是 mtr。mtr 全称 my traceroute,是一个把 ping 和 traceroute 合并到一个程序的网络诊断工具。

traceroute默认使用UDP数据包探测,而mtr默认使用ICMP报文探测,ICMP在某些路由节点的优先级要比其他数据包低,所以测试得到的数据可能低于实际情况。

安装

Debian/Ubuntu/Linux Mint 下

sudo apt install mtr-tiny
sudo apt install mtr    # with GUI

使用

简单使用,查看本地到 google.com 的路由连接情况:

mtr google.com

Screenshot from 2017-11-14 09-43-10

输出参数解释:

  • 第一列是IP地址
  • 丢包率:Loss
  • 已发送的包数:Snt
  • 最后一个包的延时:Last
  • 平均延时:Avg
  • 最低延时:Best
  • 最差延时:Wrst
  • 方差(稳定性):StDev

参数说明

report

使用 mtr -r google.com 来打印报告,如果不使用 -r or --report 参数 mtr 会不断动态运行。使用 report 选项, mtr 会向 google.com 主机发送 10 个 ICMP 包,然后直接输出结果。通常情况下 mtr 需要几秒钟时间来输出报告。mtr 报告由一系列跳数组成,每一跳意味着数据包通过节点或者路由器来达到目的主机。

一般情况下 mtr 前几跳都是本地 ISP,后几跳属于服务商比如 Google 数据中心,中间跳数则是中间节点,如果发现前几跳异常,需要联系本地 ISP 服务提供上,相反如果后几跳出现问题,则需要联系服务提供商,中间几跳出现问题,则两边无法完全解决问题。

packetsize

使用 -s 来指定ping数据包的大小

mtr -s 100

100 bytes 数据包会用来发送,测试,如果设置为负数,则每一次发送的数据包的大小都会是一个随机数。

指定发送数量

默认使用 -r 参数来生成报告,只会发送10个数据包,如果想要自定义数据包数量,可以使用 -c 参数

mtr -c 100 google.com

不进行主机解释

使用 -n 选项来让 mtr 只输出 IP,而不对主机 host name 进行解释

mtr -n github.com

延伸

在晚上或者 VPS 交流的时候经常能看到别人用可视化的方式展示路由跳转,其实都是使用的 best trace 这样一个软件。

官网地址: https://www.ipip.net/download.html

对于 Windows,Mac 和 Android 页面上都有相应的GUI客户端,Linux 下可使用命令行:

wget http://cdn.ipip.net/17mon/besttrace4linux.zip
unzip besttrace4linux.zip
chmod +x besttrace32
sudo ./besttrace -q 1 www.google.com

如果下载地址失效了,去官网上找最新的即可。

reference


2017-11-14 linux , network , mtr , ping , tracert , command

Instagram 的两种ID

Instagram 在他的API文档和网站地址栏中对同一个 Post,使用了两种不一样的ID。在他们的 API 文档中,ids 类似于 908540701891980503_1639186 , 但是在网站浏览器中则使用的是 ybyPRoQWzX 这样的ID。如果查看几组 ids,能够确性这两种类型的 ids 之间存在关联,Instagram 内部也不可能为同一个帖子存储两套 ids。

在仔细看一下数字的ID,908540701891980503 显然下划线分割的后面为作者的id,前面18位的id 被转为 ybyPRoQWzX 10 位的长度。URL 中的ID信息的密度显然要比数字的id要高。

如果做一个测试,将数字id由base10转为 base64,就得到

9:0:8:5:4:0:7:0:1:8:9:1:9:8:0:5:0:3_10
50:27:50:15:17:40:16:22:51:23_64

这样两组数据,如果将 base64 的数组和10位的id比较,得到

number letter
50 y
27 b
50 y
15 P
17 R
40 o
16 Q
22 W
51 z
23 X

如果对该映射重新排序,则得到

number letter
15 P
16 Q
17 R
22 W
23 X
27 b
40 o
50 y
50 y
51 z

看到该映射,很容易联想到由 A-Za-z 的关系

number letter
00 A
01 B
02 C
03 D
07 H
08 I
09 J
10 K
11 L
13 N
15 P
16 Q
17 R
20 U
22 W
23 X
24 Y
26 a
27 b
29 d
32 g
34 i
36 k
37 l
40 o
42 q
43 r
47 v
50 y
51 z
56 4
62 -
63 _

这个表和标准 base64 的加密表几乎一致,除了将 +/ 变为了 -_ 。这个变动大概因为 / 在URLs中是特殊字符,而 + 号在 URL 中是特殊的请求字符。

启示

知道了两种ids之间的关系,联系一些已知的事实可以知道

唯一性

Instagram 在全平台使用唯一的ID,在 Web 端是 https://instagram.com/p/ybyPRoQWzX , 而这个 ID 能够被转成数字ID,因此我们也知道这个 ID 也是唯一的。

更加惊奇的是数字ID也能够被用于请求 Instagram 内部的接口,比如查看 Instagram 网站的请求,可以发现请求特定用户的 Posts 的接口为

https://instagram.com/<username>/media/?max_id=<numeric id>

比如说 https://instagram.com/gitamba/media/?max_id=915362118751716223_7985735 而事实上,省略了后面的用户ID 也能更能够照样获得结果。而后面加任意内容都会被忽略

https://instagram.com/gitamba/media/?max_id=915398248830305252_whatever

这个请求的到内容和之前的内容一样。

进一步分析

如果知道 Instagram 产生 posts 的唯一ID 的原理 那么可以从 ID 中获取更多信息。在 Instagram 的文章中,没有提及内部的 epoch 是什么时候,但是可以通过计算来获取一个比较准确的值。

post id (base 10) known post created time (unix time)
908540701891980503 1422526513s
936303077400215759 1425836046s
188449103402467464 1336684905s

上面是三个已知发布时间的IDs, post 的id 是64bit ,如果转成 64 bits

post id (base 10) post id (base 2)
908540701891980503 0000110010011011110010001111010001101000010000010110110011010111
936303077400215759 0000110011111110011010101011000000101010100010111000000011001111
188449103402467464 0000001010011101100000010111011000001010100010111000000010001000

截取前41bits,表示的从 Instagram 内部 epoch 开始流逝的时间,毫秒

first 41 bits of post id time since Instagram epoch
00001100100110111100100011110100011010000 108306491600ms
00001100111111100110101010110000001010101 111616024661ms
00000010100111011000000101110110000010101 22464883733ms

最后在用创建post 的时间 unix time 来减去从 id 中获取的时间,得到

created time time since Instagram epoch Instagram epoch (unix time)
1422526513s 108306491600ms 1314220021.400s
1425836046s 111616024661ms 1314220021.339s
1336684905s 22464883733ms 1314220021.267s

如果计算一切都正确,得到的值应该是相等的。微小的误差来自,创建 post 的时间是秒,而 id中获取的时间是毫秒。

经过一系列计算,大致可以知道 Instagram epoch 时间开始于 1314220021 ,也就是 9:07pm UTC on Wednesday, August 24, 2011 看起来是一个随机的 epoch,但猜测可能是 Instagram 内部将 id 由自增长模式转变为现在模式的时间。

ids for testing

id in base 10 id in base64 converted to chars
936303077400215759 51:62:26:43:00:42:34:56:03:15 z-arAqi4DP
908540701891980503 50:27:50:15:17:40:16:22:51:23 ybyPRoQWzX
283455759575646671 15:47:02:24:11:51:02:56:07:15 PvCYLzC4HP
205004325645942831 11:24:20:37:36:23:34:56:00:47 LYUlkXi4Av
188449103402467464 10:29:32:23:24:10:34:56:02:08 KdgXYKi4CI
409960495 24:27:56:00:47 Yb4Av
167566273 09:63:13:47:01 J_NvB

翻译自: https://carrot.is/coding/instagram-ids


2017-11-13 id , instagram , decode , reverse

电子书

Google+

最近文章

  • 威联通折腾篇五:安装 Transmission 下载 BT 这一篇讲在威联通上安装和使用下载工具 – Transmission。
  • 威联通折腾篇六:文件同步 文件同步应该算是 NAS 最最基本的一个服务了,但是为什么直到篇六才提到他呢,是因为威联通自带的 QSync ,嗯,虽然能用,但是,没有 Linux 客户端,虽然其他平台客户端 OK,但是作为我主力工作的平台没有同步客户端,只能 smb 挂载。而之前搞 zerotier 同局域网速度不佳,其他 frp 内网穿透 也最多拉一些小文件,完全做不到 Dropbox 那样无缝,无痛。
  • 威联通折腾篇四:Container Station 运行 Docker 容器 威联通上有一个 Container Station 的应用,可以直接用官方的 App Center 中下载安装,这其实就是一个 Docker 本地环境,如果熟悉 Docker 使用,那么其实都直接可以 ssh 登录 NAS 然后完全使用命令行来操作。
  • 威联通折腾篇一:使用命令行安装威联通 QNAP 的 qpkg 安装包 如果想要给威联通安装一个 qpkg 的安装包时,最直观界面方式就是在 App Center 中,右上角,将本地的 .qpkg 文件上传到 NAS 并安装。
  • 威联通折腾篇二:使用 frp 内网穿透 这是 QNAP NAS 折腾第二篇,使用 frp 来从外网访问 NAS。威联通自带的 qlink.to 实在是太慢几乎到了无法使用的地步,用 Zerotier 也依然很慢,所以无奈还是用回了 frp.