Mixoo

Hi, 好久不见


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

  • 搜索

odoo 图片拖拽

发表于 2022-09-29 | 分类于 odoo

最近客户提出了一个新的需求,就是希望能够在图片上实现拖拽效果。本着如果有现成的轮子就不在重复创建轮子的原则,我去网上找了一下,然后果然找到了一个类似的模块。

但是原模块只适用于12.0版本,而客户使用的版本是13.0。鉴于12.0和13.0版本的差异其实还没有那么大,于是决定使用12.0版本为基础改造成使其适用于13.0版本。

改造过程就不详细展开了,这里简单介绍一下该模块的使用方法,安装完此模块以后,系统中所有的图片都可以在编辑模式下实现拖拽效果:

本模块已开源(仅13.0版本),如有需要请关注公众号[odoo集结地]回复图片拖拽获取下载地址。

odoo多字段与拼音简写搜索

发表于 2022-09-01 | 分类于 odoo

最近在给客户提到之前的老系统曾经有个很好的功能,就是可以通过客户的名称拼音简写快速搜索到客户资料,问我Odoo可不可以也实现类似的功能,答案当然是可以了,本文就来介绍一下该模块的大体思路.

多字段搜索

首先,我们要知道的一个基础就是Many2one字段的下拉框的工作原理.这里我们简单复述一下(具体内容,读者可以去白皮书查看详细介绍), 我们在点击Many2one字段的时候,首先触发onchange事件,然后将关键字传递给name_search方法, name_search方法在后台匹配到符合条件的结果以后,会调用name_get方法将结果加工成可以在列表中显示出来的格式返回给前端,也就是我们最后看到的样子.

name_search方法最大的问题是只能支持name字段搜索,就拿合作伙伴这个模型来说,你说我想根据地址进行搜索,对不起,不支持.那怎么办呢,这个时候就轮到我们今天的1号模块出场了.

name_searh_options模块是笔者开发的一个专门用来解决这个问题的模块,接下来我们来看一下这个模块是如何工作的.

安装了本模块以后,字段属性里就支持使用name_search属性来指定除了name以外需要额外进行搜索的字段.还是上面的例子, 假如我现在客户的下拉列表中根据省市区来进行搜索, 那么我们的字段就可以这么定义:

1
<field name="partner_id" name_search="state_id,city" />

根据拼音简拼进行搜索

有了name_search_options模块,那么再处理简拼问题就显得简单多了, 笔者这里同样新写了一个模块: mommy_partner_pinyin

首先,我们使用开源库pypinyin来处理汉字转拼音的问题, 这里我们使用了一个后台定时任务针对客户名称批量进行翻译,然后将翻译后的结果放到pinyin_short字段里.

这样,再搭配name_search_options模块, 就可以写成下面的形式:

1
<field name="partner_id" name_search="pinyin_short"/>

最终的效果图:

从销售订单的数据误差看odoo15税率的计算逻辑

发表于 2022-05-16 | 分类于 odoo

我的一个台湾客户前段时间发信息来说,销售单的汇总数据不对,总额比未含税金额和税额的总和多了1。

从图上看,两个订单行的未税金额合计没有问题,税额也对,那么这个奇怪的1是从哪里来的呢?

于是我们根据源码的指示找到了计算税额的方法。15.0这块的计算方法与14.0有了明显的差异,字段名由之前的三个分开的字段换成了一个新的字段——tax_totals_json。

1
2
3
4
5
6
7
8
9
10
11
12
@api.depends('order_line.tax_id', 'order_line.price_unit', 'amount_total', 'amount_untaxed')
def _compute_tax_totals_json(self):
def compute_taxes(order_line):
price = order_line.price_unit * (1 - (order_line.discount or 0.0) / 100.0)
order = order_line.order_id
return order_line.tax_id._origin.compute_all(price, order.currency_id, order_line.product_uom_qty, product=order_line.product_id, partner=order.partner_shipping_id)

account_move = self.env['account.move']
for order in self:
tax_lines_data = account_move._prepare_tax_lines_data_for_totals_from_object(order.order_line, compute_taxes)
tax_totals = account_move._get_tax_totals(order.partner_id, tax_lines_data, order.amount_total, order.amount_untaxed, order.currency_id)
order.tax_totals_json = json.dumps(tax_totals)

其中核心方法是account.tax对象的compute_all方法。

compute_all方法详解

compute_all方法的内容很长,涉及到的逻辑比较多,我们这里简要介绍一下其内部逻辑(仅适用于15.0版本,其他版本可能会有不同)

默认情况下,系统在计算税率的时候会先根据系统设置中的Account精度设置对每行进行计算,然后将每行的计算结果进行汇总,作为整单的汇总结果。但是如果公司设置中的tax_calculation_rounding_method参数被设置为round_globally,系统便会将精度提高5个点,让它看起来就跟先汇总结果再进行税额计算一样。

odoo网站登录时的错误及处理方案

发表于 2022-05-14 | 分类于 odoo

在使用客户发来的数据库进行本地开发环境搭建的时候碰到登录时加载CSS问题失败的问题:

1
2
Could not get content for /website/static/src/scss/options/user_values.custom.web.assets_common.scss defined in bundle 'web.assets_common'.
Could not get content for /website/static/src/scss/options/colors/user_theme_color_palette.custom.web.assets_common.scss defined in bundle 'web.assets_common'.

从报错提示上看是缺少scss文件而导致的, 但是从数据库附件目录中却可以找到这个文件,而且可以正常下载,虽然内容是空的。但这说明问题的根源并非找不到文件。

然后我们打开浏览带的console界面,发现提示website模块并没有正常加载。

然后我们使用-u命令试图升级website模块, 但是在后台发现了如下错误:

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
During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "/root/codes/projects/odoo/odoo-14.0/odoo/addons/base/models/qweb.py", line 331, in _compiled_fn
return compiled(self, append, new, options, log)
File "<template>", line 1, in template_web_login_layout_19
File "/root/codes/projects/odoo/odoo-14.0/odoo/addons/base/models/qweb.py", line 338, in _compiled_fn
raise QWebException("Error to render compiling AST", e, path, node and etree.tostring(node[0], encoding='unicode'), name)
File "/root/codes/projects/odoo/odoo-14.0/odoo/addons/base/models/qweb.py", line 144, in __init__
self.message = "%s\n%s: %s" % (self.message, self.error.__class__.__name__, self.error)
File "/usr/local/lib/python3.8/dist-packages/werkzeug/routing.py", line 307, in __str__
if self.suggested:
File "/usr/local/lib/python3.8/dist-packages/werkzeug/utils.py", line 90, in __get__
value = self.func(obj)
File "/usr/local/lib/python3.8/dist-packages/werkzeug/routing.py", line 281, in suggested
return self.closest_rule(self.adapter)
File "/usr/local/lib/python3.8/dist-packages/werkzeug/routing.py", line 297, in closest_rule
return max(adapter.map._rules, key=_score_rule)
File "/usr/local/lib/python3.8/dist-packages/werkzeug/routing.py", line 288, in _score_rule
* difflib.SequenceMatcher(
File "/usr/lib/python3.8/difflib.py", line 213, in __init__
self.set_seqs(a, b)
File "/usr/lib/python3.8/difflib.py", line 225, in set_seqs
self.set_seq2(b)
File "/usr/lib/python3.8/difflib.py", line 279, in set_seq2
self.__chain_b()
File "/usr/lib/python3.8/difflib.py", line 311, in __chain_b
for i, elt in enumerate(b):
TypeError: 'EndPoint' object is not iterable

Error to render compiling AST
TypeError: 'EndPoint' object is not iterable
Template: web.login_layout
Path: /t/t/div
Node: <div class="oe_website_login_container" t-raw="0"/>

很奇怪,提示我们web.login_layout这个qweb视图有问题,于是我们便到数据库后台去看这个视图的问题:

我们发现web.login_layout有两个继承的视图,由于boa_theme_default这个模块的优先级高于原生website,因此,我们推断正是因为boa这个模块的级别升高导致website模块的内容不能背正常的加载。

至于这两个视图为什么会冲突,经过分析,是因为两个视图都继承了基础视图login_view, 都使用了replace将元素替换掉,因此不论如何调整这两个视图的顺序,均不能解决冲突。

因此,解决方案就只能根据视图内容的结构来进行调整,本例当中,是直接将boa的视图禁用掉了。

odoo15中x2many字段的操作

发表于 2021-12-20

首先我们来回顾一下在14.0和之前的版本中,对于x2many字段的操作:

1
2
3
4
5
6
7
(0,0,{values}) 根据values里面的信息新建一个记录。
(1,ID,{values})更新id=ID的记录(写入values里面的数据)
(2,ID) 删除id=ID的数据(调用unlink方法,删除数据以及整个主从数据链接关系)
(3,ID) 切断主从数据的链接关系但是不删除这个数据
(4,ID) 为id=ID的数据添加主从链接关系。
(5) 删除所有的从数据的链接关系就是向所有的从数据调用(3,ID)
(6,0,[IDs]) 用IDs里面的记录替换原来的记录(就是先执行(5)再执行循环IDs执行(4,ID)

这种硬编码式的命令格式在15.0版本中得到了优化,这就是我们本篇文章的主题。

命令格式

为了方便记忆,官方在15.0对上面的集中命令格式进行封装。

命令符 解释 举例 命令结构
CREATE 根据values里的值创建一条新记录 Commmand.create({‘name’:’张三’}) (0,0,{values})
UPDATE 根据values的值更新id对应的记录 Command.update(1,{‘name’:’王心怡’}) (1,ID,{values})
DELETE 删除id=ID的这条记录 Command.delete(1) (2,ID)
UNLINK 切断主从关系,但不删除该数据 Command.unlink(1) (3,ID)
LINK 为id=ID的数据添加关联关系,3的反向操作 Command.link(1) (4,ID)
CLEAR 删除所有的主从关系,等价于循环调用记录集中的UNLINK方法 Command.clear() (5,)
SET 用IDS中的记录替换原来的记录, 等价于先使用CLEAR命令再循环调用LINK命令 Command.set([1,2,3]) (6,0,[IDS])

也就是说,从15.0开始,我们可以使用更贴近自然语言式的命令字来操作x2many字段了。

实战

下面我们就实际看一下如何使用新命令来替代之前的各种命令,首先我们启动shell环境,这里以开发手册中的书店模型为基础。

创建

1
2
3
4
5
>>> from odoo.fields import Command
>>> book = self.env['book_store.book'].create({"name":"巨人的陨落"})
>>> book.authors = [Command.create({"name":"肯*福莱特"})]
>>> book.authors
book_store.author(1,)

更新

1
2
3
>>> book.authors = [Command.update(1,{"name":"肯·福莱特"})]
>>> book.authors.name
'肯·福莱特'

删除

1
2
>>> book.authors = [Command.delete(1)]
2021-12-20 15:05:36,307 592958 INFO book_store odoo.models.unlink: User #1 deleted book_store.author records with IDs: [1]

连接

1
2
3
4
>>> author = self.env['book_store.author'].create({"name":"Kevin Kong"})
>>> book.authors = [Command.link(author.id)]
>>> book.authors.name
'Kevin Kong'

删除链接

1
2
3
>>> book.authors = [Command.unlink(author.id)]
>>> book.authors.name
False

清空

1
2
3
4
5
6
7
>>> authors = ["肯·福莱特","张三","Kevin Kong"]
>>> book.authors = [Command.create({"name":x}) for x in authors]
>>> book.authors.mapped("name")
['肯·福莱特', '张三', 'Kevin Kong']
>>> book.authors = [Command.clear()]
>>> book.authors.mapped("name")
[]

替换

1
2
3
4
5
6
7
8
9
>>> book.authors = [Command.create({"name":x}) for x in authors]
>>> book.authors.mapped("name")
['肯·福莱特', '张三', 'Kevin Kong']
>>> a = self.env['book_store.author'].create({"name":"马腾"})
>>> b = self.env['book_store.author'].create({"name":"李迅"})
>>> c = self.env['book_store.author'].create({"name":"周杰"})
>>> book.authors = [Command.set([a.id,b.id,c.id])]
>>> book.authors.mapped("name")
['马腾', '李迅', '周杰']

是不是方便了很多呢?

Odoo租赁在工业自动化行业中的应用案例

发表于 2021-12-18

项目背景

客户是上海的一家工业自动化的企业,租借流程是其业务中的一个重要的组成部分,虽然odoo原生支持租借业务,但是由于原生的业务并未生成完整的调拨单,因此给客户在落地过程中造成了困扰。另外由于客户对租借出去和归还回来的产品都需要进行测试之后才能进行入库操作,因此我们的业务改造就基于这两大需求展开。

odoo原生的租赁业务

笔者在B站有从国外搬运过来的原生租借业务的流程说明,有兴趣的读者可以先对原生的业务进行了解,再继续阅读下面的内容。

B站地址: https://www.bilibili.com/video/BV1Bb4y1b7wf/

主要的痛点就是 odoo的原生租赁自动将要租赁的产品生成了库存移动(stock.move)却没有生成调拨单(stock.picking),其不方便的地方就是客户没有仓库的操作控制,租赁界面点完出库,调拨就自动完成了。其次,我们要对调拨单生成的时间点进行控制,以确保客户的测试流程走完之后,才会生成对应的出库调拨和归还调拨.

测试流程

首先,我们需要在原生的租赁业务中加入客户的定制流程:测试。测试单的主要目的是因为客户的产品属于自动化产品需要保证产品的可用性。

其次,我们要保证我们的调拨单的生成要在测试单完成之后,这样才能保证流程的先后顺序。

调拨单

由于原生的租赁业务并没有设计调拨单的生成,因此我们这里的改造就要抛弃原有的业务逻辑,重新写一套自己的业务逻辑,并尽量贴近其原生的逻辑,以保证以最小的改动达到客户的目的。

其中的难点主要在于对租赁状态的判断,原生依靠订单数量、借出数量和归还数量三者的逻辑进行判断,由于我们的逻辑把调拨单规划了进来,因此这块的逻辑也进行了重写,并新增了几个客户定制的状态。

当且仅当测试单完成时,才会出现借出发货按钮,客户点击按钮后弹出正常的借出向导,验证之后生成借出调拨单。

生成的调拨单也自动将目的库位定位到了租借库位,这里有个地方需要注意,因为租借库位从设计上是一个内部库位,因此它是适用于上架策略,由于odoo中的上架策略优先级高于调拨单指定的目的库位,因此这个地方也需要进行修改,以避免因为客户启用了上架策略导致的目标库位不正确的问题。

调拨完成后,可以看到租借状态正常地变成了已借出。

归还调拨类似,不再赘述。

租借押金

客户的租借流程中还涉及到对客户的租赁押金的管理,因此,我们也设计了一套押金管理流程。

转销售

租赁过程中,客户的客户可能即时就把租赁的产品买断,这种业务被称为转销售的业务,我们这里也进行了处理.

总结

原生的业务流程虽然可以使用,但是在落地过程中还v是面临这各种各样的问题,不仅仅是租赁业务如此,odoo中的其他模块亦是如此,因此这就要求客户和技术开发者综合考虑,总结出一套切实可行的实施改造方案,才会让定制化的业务跑得顺畅起来。

Linux 日志切割神器 logrotate 原理介绍和配置详解[转]

发表于 2021-11-10 | 分类于 linux

前言

在 Linux 环境中能够帮助我们分析问题蛛丝马迹的有效办法之一便是日志,常见的如操作系统 syslog 日志 /var/log/messages,应用程序 Nginx 日志 /var/log/nginx/*.log。但如果服务器数量较多,日志文件大小增长较快,不断消耗磁盘空间就会触发告警,如果需要人为定期按照各种维度去手动清理日志就显得十分棘手。为了节省空间和方便整理,可以将日志文件按时间或大小分成多份,删除时间久远的日志文件,这就是通常说的日志滚动 (log rotation)。logrotate(GitHub 地址) 诞生于 1996/11/19 是一个 Linux 系统日志的管理工具,本文会详细介绍 Linux 日志切割神器 logrotate 的原理和配置。

logrotate 是一个 linux 系统日志的管理工具。可以对单个日志文件或者某个目录下的文件按时间 / 大小进行切割,压缩操作;指定日志保存数量;还可以在切割之后运行自定义命令。

logrotate 是基于 crontab 运行的,所以这个时间点是由 crontab 控制的,具体可以查询 crontab 的配置文件 /etc/anacrontab。 系统会按照计划的频率运行 logrotate,通常是每天。在大多数的 Linux 发行版本上,计划每天运行的脚本位于 /etc/cron.daily/logrotate。主流 Linux 发行版上都默认安装有 logrotate 包,如果你的 linux 系统中找不到 logrotate, 可以使用 apt-get 或 yum 命令来安装。

logrotate 运行机制

logrotate 在很多 Linux 发行版上都是默认安装的。系统会定时运行 logrotate,一般是每天一次。系统是这么实现按天执行的。crontab 会每天定时执行 /etc/cron.daily 目录下的脚本,而这个目录下有个文件叫 logrotate。在 centos 上脚本内容是这样的:

系统自带 cron task:/etc/cron.daily/logrotate,每天运行一次。

1
2
3
4
5
6
7
8
9
[root@gop-sg-192-168-56-103 logrotate.d]# cat /etc/cron.daily/logrotate
#!/bin/sh

/usr/sbin/logrotate -s /var/lib/logrotate/logrotate.status /etc/logrotate.conf
EXITVALUE=$?
if [ $EXITVALUE != 0 ]; then
/usr/bin/logger -t logrotate "ALERT exited abnormally with [$EXITVALUE]"
fi
exit 0

可以看到这个脚本主要做的事就是以 /etc/logrotate.conf 为配置文件执行了 logrotate。就是这样实现了每天执行一次 logrotate。

因为我的系统执行 /etc/cron.daily 目录下的脚本不是我想滚动日志的时间,所以我把 /etc/cron.daily/logrotate 拷了出来,改了一下 logrotate 配置文件的路径,然后在 crontab 里加上一条指定时间执行这个脚本的记录,自定义周期滚动日志就大功告成了。这种自定义的方式有两点要注意:

  • 配置文件里一定要配置 rotate 文件数目 这个参数。如果不配置默认是 0 个,也就是只允许存在一份日志,刚切分出来的日志会马上被删除。多么痛的领悟,说多了都是泪。
  • 执行 logrotate 命令最好加 -f 参数,不然有时候配置文件修改的内容不生效。

很多程序的会用到 logrotate 滚动日志,比如 nginx。它们安装后,会在 /etc/logrotate.d 这个目录下增加自己的 logrotate 的配置文件。logrotate 什么时候执行 /etc/logrotate.d 下的配置呢?看到 /etc/logrotate.conf 里这行,一切就不言而喻了。

1
include /etc/logrotate.d

logrotate 原理

logrotate 是怎么做到滚动日志时不影响程序正常的日志输出呢?logrotate 提供了两种解决方案。

  1. create
  2. copytruncate

Linux 文件操作机制

介绍一下相关的 Linux 下的文件操作机制。

Linux 文件系统里文件和文件名的关系如下图。

目录也是文件,文件里存着文件名和对应的 inode 编号。通过这个 inode 编号可以查到文件的元数据和文件内容。文件的元数据有引用计数、操作权限、拥有者 ID、创建时间、最后修改时间等等。文件件名并不在元数据里而是在目录文件中。因此文件改名、移动,都不会修改文件,而是修改目录文件。

借《UNIX 环境高级编程》里的图说一下进程打开文件的机制。

进程每新打开一个文件,系统会分配一个新的文件描述符给这个文件。文件描述符对应着一个文件表。表里面存着文件的状态信息(O_APPEND/O_CREAT/O_DIRECT…)、当前文件位置和文件的 inode 信息。系统会为每个进程创建独立的文件描述符和文件表,不同进程是不会共用同一个文件表。正因为如此,不同进程可以同时用不同的状态操作同一个文件的不同位置。文件表中存的是 inode 信息而不是文件路径,所以文件路径发生改变不会影响文件操作。

create

这也就是默认的方案,可以通过 create 命令配置文件的权限和属组设置;这个方案的思路是重命名原日志文件,创建新的日志文件。详细步骤如下:

  1. 重命名正在输出日志文件,因为重命名只修改目录以及文件的名称,而进程操作文件使用的是 inode,所以并不影响原程序继续输出日志。
  2. 创建新的日志文件,文件名和原日志文件一样,注意,此时只是文件名称一样,而 inode 编号不同,原程序输出的日志还是往原日志文件输出。
  3. 最后通过某些方式通知程序,重新打开日志文件;由于重新打开日志文件会用到文件路径而非 inode 编号,所以打开的是新的日志文件。

如上也就是 logrotate 的默认操作方式,也就是 mv+create 执行完之后,通知应用重新在新文件写入即可。mv+create 成本都比较低,几乎是原子操作,如果应用支持重新打开日志文件,如 syslog, nginx, mysql 等,那么这是最好的方式。

不过,有些程序并不支持这种方式,压根没有提供重新打开日志的接口;而如果重启应用程序,必然会降低可用性,为此引入了如下方式。

copytruncate

该方案是把正在输出的日志拷 (copy) 一份出来,再清空 (trucate) 原来的日志;详细步骤如下:

  1. 将当前正在输出的日志文件复制为目标文件,此时程序仍然将日志输出到原来文件中,此时,原文件名也没有变。
  2. 清空日志文件,原程序仍然还是输出到预案日志文件中,因为清空文件只把文件的内容删除了,而 inode 并没改变,后续日志的输出仍然写入该文件中

如上所述,对于 copytruncate 也就是先复制一份文件,然后清空原有文件。

通常来说,清空操作比较快,但是如果日志文件太大,那么复制就会比较耗时,从而可能导致部分日志丢失。不过这种方式不需要应用程序的支持即可。

配置 logrotate

执行文件: /usr/sbin/logrotate

主配置文件: /etc/logrotate.conf

自定义配置文件: /etc/logrotate.d/*.conf

修改配置文件后,并不需要重启服务。
由于 logrotate 实际上只是一个可执行文件,不是以 daemon 运行。

/etc/logrotate.conf - 顶层主配置文件,通过 include 指令,会引入 /etc/logrotate.d 下的配置文件

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
[root@gop-sg-192-168-56-103 wangao]# cat /etc/logrotate.conf
# see "man logrotate" for details
# rotate log files weekly
weekly

# keep 4 weeks worth of backlogs
rotate 4

# create new (empty) log files after rotating old ones
create

# use date as a suffix of the rotated file
dateext

# uncomment this if you want your log files compressed
#compress

# RPM packages drop log rotation information into this directory
include /etc/logrotate.d

# no packages own wtmp and btmp -- we'll rotate them here
/var/log/wtmp {
monthly
create 0664 root utmp
minsize 1M
rotate 1
}

/var/log/btmp {
missingok
monthly
create 0600 root utmp
rotate 1
}

# system-specific logs may be also be configured here.

/etc/logrotate.d/ 通常一些第三方软件包,会把自己私有的配置文件,也放到这个目录下。 如 yum,zabbix-agent,syslog,nginx 等。

1
2
3
4
5
6
7
8
[root@gop-sg-192-168-56-103 logrotate.d]# cat yum
/var/log/yum.log {
missingok
notifempty
size 30k
yearly
create 0600 root root
}

运行 logrotate

具体 logrotate 命令格式如下:

1
2
3
4
5
6
logrotate [OPTION...] <configfile>
-d, --debug :debug 模式,测试配置文件是否有错误。
-f, --force :强制转储文件。
-m, --mail=command :压缩日志后,发送日志到指定邮箱。
-s, --state=statefile :使用指定的状态文件。
-v, --verbose :显示转储过程。

crontab 定时

通常惯用的做法是配合 crontab 来定时调用。

1
2
crontab -e
*/30 * * * * /usr/sbin/logrotate /etc/logrotate.d/rsyslog > /dev/null 2>&1 &

手动运行

debug 模式:指定 [-d|–debug]

1
logrotate -d <configfile>

并不会真正进行 rotate 或者 compress 操作,但是会打印出整个执行的流程,和调用的脚本等详细信息。

verbose 模式: 指定 [-v|–verbose]

1
logrotate -v <configfile>

会真正执行操作,打印出详细信息(debug 模式,默认是开启 verbose)

logrotate 参数

详细介绍请自行 man logrotate, 或者 在线 man page。

主要介绍下完成常用需求会用到的一些参数。

一个典型的配置文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@localhost ~]# vim /etc/logrotate.d/log_file 

/var/log/log_file {

monthly
rotate 5
compress
delaycompress
missingok
notifempty
create 644 root root
postrotate
/usr/bin/killall -HUP rsyslogd
endscript
}
  • monthly: 日志文件将按月轮循。其它可用值为 daily,weekly 或者 yearly。
  • rotate 5: 一次将存储 5 个归档日志。对于第六个归档,时间最久的归档将被删除。
  • compress: 在轮循任务完成后,已轮循的归档将使用 gzip 进行压缩。
  • delaycompress: 总是与 compress 选项一起用,delaycompress 选项指示 logrotate 不要将最近的归档压缩,压缩 将在下一次轮循周期进行。这在你或任何软件仍然需要读取最新归档时很有用。
  • missingok: 在日志轮循期间,任何错误将被忽略,例如 “文件无法找到” 之类的错误。
  • notifempty: 如果日志文件为空,轮循不会进行。
  • create 644 root root: 以指定的权限创建全新的日志文件,同时 logrotate 也会重命名原始日志文件。
  • postrotate/endscript: 在所有其它指令完成后,postrotate 和 endscript 里面指定的命令将被执行。在这种情况下,rsyslogd 进程将立即再次读取其配置并继续运行

上面的模板是通用的,而配置参数则根据你的需求进行调整,不是所有的参数都是必要的.

1
2
3
4
5
6
7
8
9
/var/log/log_file {
size=50M
rotate 5
dateext
create 644 root root
postrotate
/usr/bin/killall -HUP rsyslogd
endscript
}

在上面的配置文件中,我们只想要轮询一个日志文件,size=50M 指定日志文件大小可以增长到 50MB,dateext 指
示让旧日志文件以创建日期命名。

常见配置参数

  • daily :指定转储周期为每天
  • weekly :指定转储周期为每周
  • monthly :指定转储周期为每月
  • rotate count :指定日志文件删除之前转储的次数,0 指没有备份,5 指保留 5 个备份
  • tabooext [+] list:让 logrotate 不转储指定扩展名的文件,缺省的扩展名是:.rpm-orig, .rpmsave, v, 和~
  • missingok:在日志轮循期间,任何错误将被忽略,例如 “文件无法找到” 之类的错误。
  • size size:当日志文件到达指定的大小时才转储,bytes (缺省) 及 KB (sizek) 或 MB (sizem)
  • compress: 通过 gzip 压缩转储以后的日志
  • nocompress: 不压缩
  • copytruncate:用于还在打开中的日志文件,把当前日志备份并截断
  • nocopytruncate: 备份日志文件但是不截断
  • create mode owner group : 转储文件,使用指定的文件模式创建新的日志文件
  • nocreate: 不建立新的日志文件
  • delaycompress: 和 compress 一起使用时,转储的日志文件到下一次转储时才压缩
  • nodelaycompress: 覆盖 delaycompress 选项,转储同时压缩。
  • errors address : 专储时的错误信息发送到指定的 Email 地址
  • ifempty :即使是空文件也转储,这个是 logrotate 的缺省选项。
  • notifempty :如果是空文件的话,不转储
  • mail address : 把转储的日志文件发送到指定的 E-mail 地址
  • nomail : 转储时不发送日志文件
  • olddir directory:储后的日志文件放入指定的目录,必须和当前日志文件在同一个文件系统
  • noolddir: 转储后的日志文件和当前日志文件放在同一个目录下
  • prerotate/endscript: 在转储以前需要执行的命令可以放入这个对,这两个关键字必须单独成行

更多信息请参考 man logrotate 帮助文档

手动运行 logrotate 演练

logrotate 可以在任何时候从命令行手动调用。
调用 /etc/lograte.d/ 下配置的所有日志:

1
[root@localhost ~]# logrotate /etc/logrotate.conf

要为某个特定的配置调用 logrotate:

1
[root@localhost ~]# logrotate /etc/logrotate.d/log_file

排障过程中的最佳选择是使用 -d 选项以预演方式运行 logrotate。要进行验证,不用实际轮循任何日志文件,
可以模拟演练日志轮循并显示其输出。

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
[root@localhost ~]# logrotate -d /etc/logrotate.d/log_file

reading config file /etc/logrotate.d/log_file
reading config info for /var/log/log_file

Handling 1 logs

rotating pattern: /var/log/log_file monthly (5 rotations)
empty log files are not rotated, old logs are removed
considering log /var/log/log_file
log does not need rotating
not running postrotate script, since no logs were rotated

logrotate -vfd /etc/logrotate.d/apache

error: skipping "/data/log/apache/error.log" because parent directory has insecure permissions (It's world writable or writable by group which is not"root") Set"su"directive in config file to tell logrotate which user/group should be used for rotation.

cat apache
/data/log/apache/*.log {
su root root
daily
rotate 7
missingok
dateext
copytruncate
compress
}

正如我们从上面的输出结果可以看到的,logrotate 判断该轮循是不必要的。如果文件的时间小于一天,这就会发生了。

强制轮循即使轮循条件没有满足,我们也可以通过使用 -f 选项来强制 logrotate 轮循日志文件,-v 参数提供了详细的输出

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
[root@localhost ~]# logrotate -vf /etc/logrotate.d/log_file 

reading config file /etc/logrotate.d/log_file
reading config info for /var/log/log_file

Handling 1 logs

rotating pattern: /var/log/log_file forced from command line (5 rotations)
empty log files are not rotated, old logs are removed
considering log /var/log/log_file
log needs rotating
rotating log /var/log/log_file, log->rotateCount is 5
dateext suffix '-20180503'
glob pattern '-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]'
previous log /var/log/log_file.1 does not exist
renaming /var/log/log_file.5.gz to /var/log/log_file.6.gz (rotatecount 5, logstart 1, i 5),
old log /var/log/log_file.5.gz does not exist
renaming /var/log/log_file.4.gz to /var/log/log_file.5.gz (rotatecount 5, logstart 1, i 4),
old log /var/log/log_file.4.gz does not exist
renaming /var/log/log_file.3.gz to /var/log/log_file.4.gz (rotatecount 5, logstart 1, i 3),
old log /var/log/log_file.3.gz does not exist
renaming /var/log/log_file.2.gz to /var/log/log_file.3.gz (rotatecount 5, logstart 1, i 2),
old log /var/log/log_file.2.gz does not exist
renaming /var/log/log_file.1.gz to /var/log/log_file.2.gz (rotatecount 5, logstart 1, i 1),
old log /var/log/log_file.1.gz does not exist
renaming /var/log/log_file.0.gz to /var/log/log_file.1.gz (rotatecount 5, logstart 1, i 0),
old log /var/log/log_file.0.gz does not exist
log /var/log/log_file.6.gz doesn't exist -- won't try to dispose of it
fscreate context set to unconfined_u:object_r:var_log_t:s0
renaming /var/log/log_file to /var/log/log_file.1
creating new /var/log/log_file mode = 0644 uid = 0 gid = 0
running postrotate script
set default create context

logrotate 配置文件实例

syslog

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@gop-sg-192-168-56-103 logrotate.d]# cat syslog
/var/log/cron
/var/log/maillog
/var/log/messages
/var/log/secure
/var/log/spooler
{
missingok
sharedscripts
postrotate
/bin/kill -HUP `cat /var/run/syslogd.pid 2> /dev/null` 2> /dev/null || true
endscript
}

zabbix-agent

1
2
3
4
5
6
7
8
9
10
[root@gop-sg-192-168-56-103 logrotate.d]# cat zabbix-agent
/var/log/zabbix/zabbix_agentd.log {
weekly
rotate 12
compress
delaycompress
missingok
notifempty
create 0664 zabbix zabbix
}

apache

1
2
3
4
5
6
7
8
9
10
[root@gop-sg-192-168-56-103 logrotate.d]# cat apache 
/var/log/apache/*.log {
su root root
daily
rotate 7
missingok
dateext
copytruncate
compress
}

nginx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@gop-sg-192-168-56-103 logrotate.d]# cat nginx
/var/log/nginx/*.log /var/log/nginx/*/*.log{
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 640 root adm
sharedscripts
postrotate
[ ! -f /var/run/nginx.pid ] || kill -USR1 `cat /var/run/nginx.pid`
endscript
}

influxdb

1
2
3
4
5
6
7
8
9
[root@gop-sg-192-168-56-103 logrotate.d]# cat influxdb
/var/log/influxdb/access.log {
daily
rotate 7
missingok
dateext
copytruncate
compress
}

关于 USR1 信号解释

USR1 亦通常被用来告知应用程序重载配置文件;例如,向 Apache HTTP 服务器发送一个 USR1 信号将导致以下步骤的发生:停止接受新的连接,等待当前连接停止,重新载入配置文件,重新打开日志文件,重启服务器,从而实现相对平滑的不关机的更改。

对于 USR1 和 2 都可以用户自定义的,在 POSIX 兼容的平台上,SIGUSR1 和 SIGUSR2 是发送给一个进程的信号,它表示了用户定义的情况。它们的符号常量在头文件 signal.h 中定义。在不同的平台上,信号的编号可能发生变化,因此需要使用符号名称。

1
2
kill -HUP pid
killall -HUP pName

其中 pid 是进程标识,pName 是进程的名称。

如果想要更改配置而不需停止并重新启动服务,可以使用上面两个命令。在对配置文件作必要的更改后,发出该命令以动态更新服务配置。根据约定,当你发送一个挂起信号 (信号 1 或 HUP) 时,大多数服务器进程 (所有常用的进程) 都会进行复位操作并重新加载它们的配置文件。

logrotate 日志切割轮询

由于 logrotate 是基于 cron 运行的,所以这个日志轮转的时间是由 cron 控制的,具体可以查询 cron 的配置文件 /etc/anacrontab,过往的老版本的文件为(/etc/crontab)

查看轮转文件:/etc/anacrontab

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@gop-sg-192-168-56-103 logrotate.d]# cat /etc/anacrontab
# /etc/anacrontab: configuration file for anacron

# See anacron(8) and anacrontab(5) for details.

SHELL=/bin/sh
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
# the maximal random delay added to the base delay of the jobs
RANDOM_DELAY=45
# the jobs will be started during the following hours only
START_HOURS_RANGE=3-22

#period in days delay in minutes job-identifier command
1 5 cron.daily nice run-parts /etc/cron.daily
7 25 cron.weekly nice run-parts /etc/cron.weekly
@monthly 45 cron.monthly nice run-parts /etc/cron.monthly

使用 anacrontab 轮转的配置文件,日志切割的生效时间是在凌晨 3 点到 22 点之间,而且随机延迟时间是 45 分钟,但是这样配置无法满足我们在现实中的应用

现在的需求是将切割时间调整到每天的晚上 12 点,即每天切割的日志是前一天的 0-24 点之间的内容,操作如下:

1
mv /etc/anacrontab /etc/anacrontab.bak          // 取消日志自动轮转的设置

使用 crontab 来作为日志轮转的触发容器来修改 logrotate 默认执行时间

1
2
3
4
5
6
7
8
9
10
11
[root@gop-sg-192-168-56-103 logrotate.d]# vim /etc/crontab
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
HOME=/

# run-parts
01 * * * * root run-parts /etc/cron.hourly
59 23 * * * root run-parts /etc/cron.daily
22 4 * * 0 root run-parts /etc/cron.weekly
42 4 1 * * root run-parts /etc/cron.monthly

logrotate 常见问题

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
# 如果想了解执行过程可以添加 - v
[root@xxx logrotate.d]# logrotate -v /etc/logrotate.d/influxdb
reading config file influxdb
Allocating hash table for state file, size 15360 B

Handling 1 logs

rotating pattern: /var/log/influxdb/access.log after 1 days (7 rotations)
empty log files are rotated, old logs are removed
considering log /var/log/influxdb/access.log
log does not need rotating (log has been rotated at 2020-2-25 3:0, that is not day ago yet)

# 查看 logrotate 执行状态
[root@xxx]# cat /var/lib/logrotate/logrotate.status
logrotate state -- version 2
"/var/log/yum.log" 2020-1-1-3:35:1
"/var/log/sssd/sssd_nss.log" 2019-4-11-3:41:1
"/var/log/sssd/sssd_pam.log" 2019-4-11-3:41:1
"/var/log/sssd/sssd_sudo.log" 2019-4-11-3:41:1
"/var/log/chrony/*.log" 2019-11-6-3:0:0
"/var/log/sssd/sssd.log" 2019-4-11-3:41:1
"/var/log/spooler" 2020-2-23-3:37:2
"/var/log/influxdb/influxd.log" 2019-4-9-3:0:0
"/var/log/influxdb/access.log" 2020-2-25-3:0:0
"/var/log/sssd/sssd_LDAPGADM.log" 2019-4-11-3:41:1
"/var/log/sssd/sssd_LDAPSGDEV.log" 2019-4-11-3:41:1
"/var/log/btmp" 2020-2-1-3:33:1
"/var/log/sssd/sssd_LDAPOPS.log" 2019-4-11-3:41:1
"/var/log/sssd/sssd_LDAPDEV.log" 2019-4-11-3:0:0
"/var/log/monit.log" 2019-11-10-3:0:0
"/var/log/maillog" 2020-2-23-3:37:2
"/var/log/wpa_supplicant.log" 2019-11-6-3:0:0
"/var/log/sssd/sssd_ssh.log" 2019-4-11-3:41:1
"/var/log/secure" 2020-2-23-3:37:2
"/var/log/messages" 2020-2-23-3:37:2
"/var/log/zabbix/zabbix_agentd.log" 2020-2-23-3:37:2
"/var/log/cron" 2020-2-23-3:37:2

# 特殊情况下添加 - f 强制执行
[root@xxx logrotate.d]# logrotate -f -v influxdb
reading config file influxdb
Allocating hash table for state file, size 15360 B

Handling 1 logs

rotating pattern: /var/log/influxdb/access.log forced from command line (7 rotations)
empty log files are rotated, old logs are removed
considering log /var/log/influxdb/access.log
log needs rotating
rotating log /var/log/influxdb/access.log, log->rotateCount is 7
dateext suffix '-20200225'
glob pattern '-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]'
glob finding old rotated logs failed
copying /var/log/influxdb/access.log to /var/log/influxdb/access.log-20200225
truncating /var/log/influxdb/access.log
compressing log with: /bin/gzip

原文地址: https://wsgzao.github.io/post/logrotate/

TLS VS STARTTLS

发表于 2021-08-05 | 分类于 network

SSL和 TLS

关系SMTP协议的安全性,可以参考这里的文章,默认情况下SMTP协议并非那么安全。因此,黑客很容易窃取我们邮箱里的重要信息,所幸的时至今日的加密方式让他们窃取的工作变得不是那么容易了。

SSL(安全套接字层)和它的继任者(传输层安全协议)是邮件传输过程中的两种加密协议,它们都是通过使用私钥和公钥将信息加密成一串无用的字符,如果在传输过程中某一步被泄露了,他的信息对窃密者来说也毫无意义。

SSL协议是有网景公司与1995年开发,并很快流行于当时的邮件客户端。若干年以后,TLS问世,它相对于SSL来说更加安全和可靠。

SSL已经过时了,但是应用依旧广泛,实际上我们在使用过程中通常都能看到SSL和TLS是以”SSL/TLS”的形式出现。

STARTTLS

STARTSSL并非一个协议而是一个邮件协议的命令字,它用来告诉邮件服务器和客户端希望将不安全的连接升级到安全的SSL或TLS。

需要注意的是STARTTLS并非只能用于TLS,SSL同样可以使用。

STARTTLS不仅能用于SMTP而且可以使用IMAP协议,通常用来从邮件服务器中解析邮件。POP3和其他协议用来解析邮件有类似的命令,STLS。

TLS/SSL和STARTTLS是如何工作的?

就像人类社交一样,邮件客户端和服务器端的通讯也是需要经过”握手”一个礼仪。当邮件被发出时,客户端先验证服务端是否可用。他们交换SSL/TLS的版本是否兼容,服务器返回一个数字证书以确认自己的身份。验证通过后,双方生成一个唯一的校验码用来解密加密的信息。

握手阶段,双方先建立一个连接,但是SMTP默认是不安全的,因此有如下两种方式建立:

  • 可选的SSL/TLS: 客户端发送STARTTLS命令将连接升级为加密连接,如果服务器没有错误,连接则建立,如果发生错误,则回退到纯文本的模式。
  • 强制SSL/TLS:客户端不会询问服务端是否兼容而是直接试图建立安全连接,如果成功,则连接成功,否则等待至直至连接超时。

隐式和显式的SSL/TLS应该使用哪些端口

多年以来25端口作为邮件服务器间的通讯端口,发送和接收了大量的垃圾邮件。后来25端口被限制来只用作回复,而587端口被用来作为发送的主要端口。虽然587没有要求STARTTLS一定要使用,但是仍然要求用户名和密码,如果想要使用显式SSL/TLS应该使用587端口。

曾经有段时间465被建议作为邮件的发送端口,后来由于587端口的使用很快就被撤销了,但是已经有很多客户端和服务器使用了465,因此465被作为587的备用选项(那些希望隐式使用SSL/TLS的用户)。

时至今日,很多邮件客户端和服务端将隐式和显式SSL/TLS统一在了587端口。2018年IETF建议将465作为隐式TLS的端口,STARTTLS也许在不久的将来将退出舞台。

IMAP和POP3对于隐式和显式同样使用了不同的端口,IMAP显式使用143,隐式993,POP3显式使用110隐式使用995.

odoo 访问权限错误

发表于 2021-07-29 | 分类于 odoo

虽然odoo的错误提示已经很完善了,但是还是有些时候不能快速定位问题的原因,比如,我在写一个新的报表的时候,提示了如下这么一个错误:

1
2
3
4
The requested operation ("read" on "Users" (res.users)) was rejected because of the following rules:


(Records: 管理员 (id=2), User: 管理员 (id=2))

看样子是某个规则出了问题,但是却没有提示是哪个规则。

没办法 ,我们只好在代码中加日志输出,然后发现了程序访问了两次res.users的对象,尽管查询的字段都一样,但是返回的结果却是不同的,造成了上面的一个困惑:当前用户是管理员却访问不了管理员对象???

实际上,这个错误提示的并不清晰,虽然提示的是res.users对象,实际的错误确实发生在访问res.users的partner_id字段时,也就是真正的错误原因是res.partner对象引起的。

经过排查,发现错误的原因是限制了管理员的res.partner的访问导致了该问题。

javascript 高级程序设计 第四版 学习笔记 1

发表于 2021-07-26 | 分类于 javascript

for in 和 for of的区别

先来看for-in的定义:for-in 语句是一种严格的迭代语句,用于枚举对象中的非符号键属性。

a = [‘a’,’b’,’c’]

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
for (const i in a){
console.log(i)
}

for (const i of a){
console.log(i)
}

o = {
'a':1,
'b':2,
'c':3,
[x]: 'test'
}

for (const i in o){
console.log(i)
}
-----------
0
1
2
a
b
c
a
b
c

而 for-of 针对的是实现了迭代协议的可迭代对象的遍历,也就是说,for-of只能作用于可迭代对象,如果没有实现迭代协议,那么将会引发错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
o = {
'a':1,
'b':2,
'c':3,
[x]: 'test'
}

for (const i of o){
console.log(i)
}
-----
for (const i of o){
^

TypeError: o is not iterable

如果实现了迭代器的对象,那么可以正常地输出结果:

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
lass Mimo {
constructor(limit) {
this.count = 1;
this.limit = limit;
}

[Symbol.iterator]() {
let count = 1,
limit = this.limit;
return {
next() {
if (count < limit) {
return { done: false, value: count++ }
}
else {
return { done: true, value: undefined }
}
}
}
}
}

let mimo = new Mimo(3);

for (let i of mimo) {
console.log(i);
}
---------
1
2

变量类型

ECMAScript的变量有两种类型,原始值(primitive value)和引用类型(reference value)。原始值有6种类型:

  • Undefined
  • Null
  • Boolean
  • Number
  • String
  • Symbol

这6种原始值是按值存储的,我们访问的就是它存储在变量中的实际值。

引用类型与原生类型不同,操作引用类型实际是是操作的该对象的引用。原始类型的初始化可以只使用原始字面量形式。如果使用的是new 关键字,则JavaScript 会创建一个Object 类型的实例,但其行为类似原始值。

1
2
3
4
5
6
7
8
9
10
11

let a = "abc";
let b = new String("abc")

a.name = "ABC"
b.name = "ABC"

console.log(a.name)
console.log(b.name)
console.log(typeof a)
console.log(typeof b)

原始值包装类型

之前我们碰到这样一个问题:

为什么1.2.toString() 可以直接转换成”1.2”,而 1.toString不可以转换成”1”,除了编译器识别的问题外,你有没有想过1.2 是原始值类型,为什么原始值类型可以直接调用toString方法呢?

实际上,javascript在处理1.2.toString()的过程中,先对1.2进行了包装,让它变成了包装对象,然后调用了对象的toString()方法,类似于下面的代码:

1
2
let s1 =  1.2;
let s2 = new String(s1);

但是它与引用类型的区别在于对象的生命周期,通过new实例化的引用类型会在实例离开作用域之后被销毁,而包装类型只存在于访问它的那行代码执行期间,这样就意味着不能在运行时给原始值添加属性。

1
2
3
let s1 = "some text";
s1.color = "red";
console.log(s1.color);// undefined.

这里的s1实际上是没有color属性的。

内置单例对象

ECMA-262 对内置对象的定义是“任何由ECMAScript 实现提供、与宿主环境无关,并在ECMAScript程序开始执行时就存在的对象”。这就意味着,开发者不用显式地实例化内置对象,因为它们已经实例化好了。前面我们已经接触了大部分内置对象,包括Object、Array 和String。

Global对象

Global是ECMAScript种最特别的对象,因为代码不会特别的访问它。CMA-262 规定Global对象为一种兜底对象,它所针对的是不属于任何对象的属性和方法。

12…25
长腿叔叔

长腿叔叔

长腿叔叔的技术博客

246 日志
39 分类
43 标签
GitHub 淘宝
友情链接
  • BUG集散地
© 2022 长腿叔叔
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.2