PHP/Laravel中的“异常链”

之前知道,Python 3 中终于实现了对异常链的支持。 最近,在代码中,有一处写着:

try {
// some code
} catch (\Exception $e) {
\Log::error(‘xxx_id’ . $xxx->id);
}

问了之后才发现,这是需要知道是哪个id触发的错误,如果不捕获异常的话,在backtrace信息中可能不会出现此id。 但是,这样做虽然可以达到他的目的,但是掩盖了底层的错误,对后续的追查不利。 然后,作为(伪)Python开发者的我就想起了Python3中的异常链技术,所以找了下PHP中有没有类似的东西,结果发现:自PHP 5.3开始,支持了类似的技术。 解决方案:可以自己扩展Excepiton类,默认的构造方法里面,第三个参数是前一个异常,经过测试,在Laravel框架中,同一条链中的Exception都会展示出来(无论是在Console输出,还是日志输出中)。


好,让我们来自己试一试。 首先写一个Command:

class NestExceptionTest extends Command
{
protected $name = ‘NestExceptionTest’;

public function fire()
{
    try {
        $this->testOneLevel();
    } catch (\\Exception $e) {
        throw new \\Exception("top level", 0, $e);
    }
}

private function testOneLevel()
{
    try {
        $this->testTwoLeve();
    } catch (\\Exception $e) {
        throw new \\Exception('one level', 0, $e);
    }
}

private function testTwoLeve()
{
    throw new \\Exception('two level');
}

}

为了偷懒,就没有自己定义自己的Exception,仅仅是不同的错误信息来区分。 执行此命令,输出如下: Exception output console同时,日志输出如下:

[2015-12-12 13:54:06] production.ERROR: exception ‘Exception’ with message ‘two level’ in /Users/robberphex/projects/xxx/app/Console/Commands/NestExceptionTest.job:31
Stack trace:
#0 /Users/robberphex/projects/xxx/app/Console/Commands/NestExceptionTest.job(23): App\Console\Commands\SiteMap\SiteMapBuild->testTwoLeve()
#1 /Users/robberphex/projects/xxx/app/Console/Commands/NestExceptionTest.job(14): App\Console\Commands\SiteMap\SiteMapBuild->testOneLevel()
#2 [internal function]: App\Console\Commands\SiteMap\SiteMapBuild->fire()
#3 /Users/robberphex/projects/xxx/vendor/laravel/framework/src/Illuminate/Container/Container.php(503): call_user_func_array(Array, Array)
#4 /Users/robberphex/projects/xxx/vendor/laravel/framework/src/Illuminate/Console/Command.php(150): Illuminate\Container\Container->call(Array)
#5 /Users/robberphex/projects/xxx/vendor/symfony/console/Command/Command.php(256): Illuminate\Console\Command->execute(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#6 /Users/robberphex/projects/xxx/vendor/laravel/framework/src/Illuminate/Console/Command.php(136): Symfony\Component\Console\Command\Command->run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#7 /Users/robberphex/projects/xxx/vendor/symfony/console/Application.php(838): Illuminate\Console\Command->run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#8 /Users/robberphex/projects/xxx/vendor/symfony/console/Application.php(189): Symfony\Component\Console\Application->doRunCommand(Object(App\Console\Commands\SiteMap\SiteMapBuild), Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#9 /Users/robberphex/projects/xxx/vendor/symfony/console/Application.php(120): Symfony\Component\Console\Application->doRun(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#10 /Users/robberphex/projects/xxx/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php(107): Symfony\Component\Console\Application->run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#11 /Users/robberphex/projects/xxx/artisan(36): Illuminate\Foundation\Console\Kernel->handle(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#12 {main}

阅读更多

Let’s Encrypt/HTTPS配置记录

前几天收到Let’s Encrypt public beta的邮件,所以就尝试了下ssl+https+http/2。

Let’s Encrypt 配置

首先,我之前遇到过Error creating new authz 的错误,就一直没有尝试,后来发现v2ex上有人通过设置NS记录解决了问题(虽然文中说要设置MX记录,但是我只更换了NS记录到CloudFlare),生效时间可能是24h。 不过由于cf会友好地将所有流量切换到它的反代服务器,所以需要关闭此特性;另外,也需要关闭cf中HTTPS的开关,由自己的主机来处理HTTPS请求(所以我仅仅是将cf作为一个DNS服务器而已)。 另外有一个坑:由于我用的是nginx,letsencrypt这个客户端程序需要自己接受80端口的请求来验证服务器身份,所以需要临时关闭nginx。 最后开始生成服务器证书: 第一步:将letsencrypt项目clone到特定目录下:

git clone https://github.com/letsencrypt/letsencrypt.git /opt/letsencrypt

第二步:cd到该目录,然后执行如下命令生成证书:

./letsencrypt-auto –agree-dev-preview –server https://acme-v01.api.letsencrypt.org/directory certonly

依次填写邮箱、域名后,会在 /etc/letsencrypt/live// 目录下生成证书(nginx只用到fullchain.pem和privkey.pem) 注:此命令一定要添加–server选项,否则生成的证书并不被浏览器信任。 证书都已经生成,接下来配置nginx。

nginx配置

注:如果要开HTTP/2的话,就必须要添加nginx mainline的repo了:

[nginx]
name=nginx mainline repo
baseurl=http://nginx.org/packages/mainline/centos/$releasever/$basearch/
gpgcheck=0
enabled=1

阅读更多

SSH 重用连接

有的时候需要经常 ssh 连接到服务器上,比如,我push一个分支,然后合并并push,然后push tag,然后删除该分支。期间至少需要四次SSH连接(不要问我为什么会有这么复杂的流程。。。)

另外,之前通过跳板机直连线上机器的方法,在差的网络环境中仍然很慢,也在考虑如何加速的问题。

综上,每次SSH连接的建立,需要首先建立TCP连接,然后认证之。有的时候网络慢,搞得这个过程很不爽,故有此文。

ssh_config的文档中有如下ControlMaster和ControlPath两个参数。
ControlMaster表示是否需要多个SSH会话重用一个网络连接,这个当然要开启;ControlPath表示这个网络连接绑定到的本地sock地址。

我的配置如下:

1
2
ControlMaster auto
ControlPath ~/.ssh/%r@%h:%p

time ssh remote_host -C pwd命令为例,在默认配置下,一般执行时间是1s;如果开启了ControlMaster,首次连接的时间是1.2s(事实上,我也不知道为什么会稍微多一点);如果重用了之前的连接,则时间降为0.6s

Tips:

ssh -O check reomte_host可以看到共享的连接是不是还在;
ssh -O exit reomte_host可以直接关闭后台共享的连接(会关闭现有的ssh会话);
ssh -O stop remote_host则表示后台共享的连接不会再接受新的会话(停止监听sock文件,下次的连接仍然会创建同样的sock文件)。

另外ControlPersist表示连接失效时间,也建议设置一个合理的值。

阅读更多

git lg 和 git tg

不好意思,在下又要开始聒噪几句了。

git lg —— 更人性化地展示git历史

首先,git log默认太挫了,仅仅是罗列一个个commit,我们可以对其进行定制。 我觉得git log的--graph –oneline –decorate 就工作得不错,就把git log –graph –oneline –decorate 设置为别名lg:

git config --global alias.lg 'log --graph --oneline --decorete'

虽然可以自定义log展示的格式,但是懒得搞……

UPDATE:翻了下oh-my-zsh,抄过来一句:

git config --global alias.lg "log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%ar) <%an>%Creset' --abbrev-commit"

git tg —— 按版本号排序,显示近几个tag

其次,每次打tag的时候都需要 git tag | sort -V 一下,而且还冲掉很多之前的命令和输出。所以要定制一个tg命令来按版本号排序输出最近几次tag。

虽然git有一个设置tag.sort 可以指定按版本号排序,但是仍然会显示所有的tag,这显然不太好。

阅读更多

如何直接通过跳板机ssh到服务器

公司线上服务器都无法直接访问,必须通过一台跳板机来访问。比如要访问机器dev,则必须先ssh到跳板机gateway,然后再ssh到dev机器。

这样做自然可以减少攻击面,但是每次去dev机器执行命令,或者上传文件的时候,都要两次ssh,确实比较麻烦。

故Google之,可以使用ssh config中的ProxyCommand选项,比如我们先定义主机gateway的连接参数:

1
2
3
Host gateway
HostName gw.example.com
User XXX

然后我们需要定义主机dev的连接参数:

1
2
3
4
Host dev
HostName dev.ip.example.com
User XXX
ProxyCommand ssh -q -W %h:%p gateway

这样,ssh dev 命令就会先和gateway建立ssh连接,并把这个中间连接当作一个代理使用。不过需要注意的是,你的公钥除了必须在gateway上有之外,还必须在dev上有,这是和之前不一样的地方。

PS:之前考虑过 ProxyCommand ssh gateway nc %h %p ,但是考虑到机器上可能没有nc命令。后来发现ssh有-W参数,果断用之。

PPS:ProxyCommand参数中的-q是为了防止和跳板机的ssh连接产生多余的输出,比如不加 -q 就会导致每次断开连接的时候会多一句 Killed by signal 1.

如果这样做了,我们就可以玩一些高级的用法了,比如:

阅读更多

Mac Terminal ssh后中文乱码的问题(git&vim)

安装的是英文的 OS X ,SSH到别的机器上的时候,发现 git log 和 vim都乱码了:

中文乱码

之前在 Ubuntu 上也遇到过类似的问题,知道是 $LANG 或者 $LC_* 系列变量设置错误。果然,LC_CTYPEUTF-8 ,而不是预料的 en_US.UTF-8

Google了半天,都是各种不设置LC_CTYPE、关闭SendEnv选项等。

后来偶然发现自己在 English 下 Region 设置成了 China,改为 US 之后,Terminal 中的 LC_CTYPE 就是 en_US.UTF-8 了,还顺带出现了 LANG 等变量。当然 SSH 之后汉字也可以正常显示了。

Region US

ENV


另外,git status的时候,中文的文件名也不能很好的显示:

1
2
3
4
5
6
7
$ git status
On branch master
Your branch is up to date with 'origin/master'.

Untracked files:
(use "git add <file>..." to include in what will be committed)
"\345\256\236\344\276\213.md"
阅读更多

如何设置 nginx 和 php-fpm 的 Max open files 数量

刚开始也和同事一样,以为 Max open files/etc/security/limits.conf 控制。

不过,同事顺便在 /etc/pam.d/su 文件中添加了session required pam_limits.so ,为了探究为什么要添加这一条规则的时候,发现此设置只有在使用 su 命令切换用户的时候有效,使用 sudo 切换用户就无效了,更不用说像 nginx 这种使用 setuid 方式切换用户的了。

我们可以通过cat /proc/<pid>/limits 来清楚地知道该进程的 resource limit 信息,我就是这样才知道我们一直以来对 nginx 修改的 Max open files 数量并没有生效)

好,错误的方法终于说完了,现在我们说正确的方法:

由于使用 setuid 方式并不会通过 PAM 来设置 Max open files ,所以 nginx 提供了 worker_rlimit_nofile 选项来自己定制 worker 的 Max open files 数量,比如,在 nginx.conf 中添加全局参数:

worker_rlimit_nofile 65535;

同样的,php-fpm 也有类似的参数

rlimit_files

只需要在 /etc/php5/fpm/php-fpm.conf 文件中添加该参数:

rlimit_files = 65535

即可设置 nginx 和 php-fpm 的 Max open files 数量。

阅读更多

如何使用curl下载 Oracle 的 JDK ?

本来我们可以去 Oracle 官网,点击 Accept License 来下载 JDK 的,但是由于网速关系,这种方式很慢,也没有用到多线程下载技术,所以原来一直想用 curl 或者 aria2c 来下载。

但是,如果直接curl地址的话,会遇到错误:

error result

偶然翻 AUR 中 JDK 的 PKGBUILD,发现还有一个 oraclelicense=a 的 Cookie,于是尝试之:

成功下载

成功下载

好像成功了?!

那为什么不用 aria2c 来多线程下载呢?

但是 aria2c 没有 -b 参数。

对于我们,只能使用 --load-cookies=<FILE> 来装载 Mozilla/Firefox(1.x/2.x)/ Netscape 格式的 Cookie 文件了。

阅读更多

在 CentOS 7 上部署 L2TP/IPSec VPN 服务

首先安装strongswan和xl2tpd:

1
yum install strongswan xl2tpd

记得开启IP转发,修改 /etc/sysctl.conf 修改如下:

1
2
3
4
5
6
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.default.rp_filter = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0

并重启使之生效。(执行sysctl -p 应该也是可以的,但一定要验证一下是不是生效了,实在不行就重启吧。)

接下来修改 /etc/strongswan/ipsec.conf 文件,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
config setup

conn %default
ikelifetime=60m
keylife=20m
rekeymargin=3m
keyingtries=1

conn l2tp
keyexchange=ikev1 # IKE版本
left=<对外IP>
leftsubnet=0.0.0.0/0
leftprotoport=17/1701
authby=secret
leftfirewall=no
right=%any
rightprotoport=17/%any
type=transport
auto=add

然后修改 /etc/strongswan/ipsec.secrets 文件(没有此文件就新建一个):

1
2
# ipsec.secrets - strongSwan IPsec secrets file
: PSK "<PSK>"

IPsec的部分就完成了,接下来是L2TP。

阅读更多

Python3.4+uWSGI for Django Website in VirtualEnv (CentOS)

Install Python 3.x

In CentOS, There is no Python3 rpm package in EPEL, RPMFusion, we must install Python 3.x by source. Download Python 3.4.0, and extract it.

wget https://www.python.org/ftp/python/3.4.0/Python-3.4.0.tar.xz
tar -xvf Python-3.4.0.tar.xz
cd Python-3.4.0

switch to Python-3.4.0, compile&install Python 3.4.0 (You are expected have installed gcc, make and sqlite-devel)

./configure
make
make install

As the result, you can see the path of Python 3.4.0 by command which python3.4 Thanks to Python 3.4.0, we needn’t install PIP.

Install virtualenvwrapper&Django

You can install virtualenvwrapper by PIP.(Require root privileges) pip install virtualenvwrapper Don’t forget add two lines to file .bashrc:

VIRTUALENVWRAPPER_PYTHON=/usr/local/bin/python3.4
source /usr/local/bin/virtualenvwrapper.sh

Let it take effect:

阅读更多