将传入的文本时间戳从 syslog 转换为 postgresql 的时间戳

convert incoming text timestamp from rsyslog to timestamp for postrgesql

我有来自各种 linux 服务器的日志,这些日志由 rsyslog 提供给 PostgreSQL 数据库。传入的时间戳是一个 rsyslog'd RFC3339 格式的时间,如下所示:2020-10-12T12:01:18.162329+02:00.

在数据库日志记录 table 的原始测试设置中,我将时间戳字段创建为 'text'。我需要解析的大部分内容都正常工作,所以我希望将该时间戳 table 列从文本转换为时间戳数据类型(并尽可能保留亚秒和时区)。

最终结果应该是时间戳数据类型,这样我就可以使用 PostgreSQL 数据函数进行日期范围查询。

这在 PostgreSQL 11 中可行吗?还是以正确的时间戳列数据类型重新创建 table 更好?

在此先感谢您提供任何指示、建议、查找位置或代码片段。

相关的 rsyslog 配置:

$template CustomFormat,"%timegenerated:::date-rfc3339% %syslogseverity-text:::uppercase% %hostname% %syslogtag% %msg%\n"
$ActionFileDefaultTemplate CustomFormat

...

template(name="rsyslog" type="list" option.sql="on") {
  constant(value="INSERT INTO log (timestamp, severity, hostname, syslogtag, message)
    values ('")
    property(name="timegenerated" dateFormat="rfc3339")  constant(value="','")
    property(name="syslogseverity-text" caseConversion="upper")  constant(value="','")
    property(name="hostname")  constant(value="','")
    property(name="syslogtag")  constant(value="','")
    property(name="msg")  constant(value="')")
}

和日志table结构:

CREATE TABLE public.log
(
    id integer NOT NULL DEFAULT nextval('log_id_seq'::regclass),
    "timestamp" text COLLATE pg_catalog."default" DEFAULT timezone('UTC'::text, CURRENT_TIMESTAMP),
    severity character varying(10) COLLATE pg_catalog."default",
    hostname character varying(20) COLLATE pg_catalog."default",
    syslogtag character varying(24) COLLATE pg_catalog."default",
    program character varying(24) COLLATE pg_catalog."default",
    process text COLLATE pg_catalog."default",
    message text COLLATE pg_catalog."default",
    CONSTRAINT log_pkey PRIMARY KEY (id)
)

一些示例数据已经输入 table(忽略消息中的时间戳,它们是我的前任使用独立的手工日志系统完成的):

理论上您可以使用 ALTER TABLE .. ALTER COLUMN ... SET DATA TYPE ... USINGTEXT 列转换为 TIMESTAMP WITH TIME ZONE,例如:

postgres=# CREATE TABLE tstest (tsval TEXT NOT NULL);
CREATE TABLE

postgres=# INSERT INTO tstest values('2020-10-12T12:01:18.162329+02:00');
INSERT 0 1

postgres=# ALTER TABLE tstest
             ALTER COLUMN tsval SET DATA TYPE TIMESTAMP WITH TIME ZONE
             USING tsval::TIMESTAMPTZ;
ALTER TABLE

postgres=# \d tstest
                       Table "public.tstest"
 Column |           Type           | Collation | Nullable | Default
--------+--------------------------+-----------+----------+---------
 tsval  | timestamp with time zone |           | not null |


postgres=# SELECT * FROM tstest ;
             tsval
-------------------------------
 2020-10-12 12:01:18.162329+02
(1 row)

PostgreSQL 可以解析 RFC3339 格式,因此后续插入应该可以正常工作:

postgres=# INSERT INTO tstest values('2020-10-12T12:01:18.162329+02:00');
INSERT 0 1

postgres=# SELECT * FROM tstest ;
             tsval
-------------------------------
 2020-10-12 12:01:18.162329+02
 2020-10-12 12:01:18.162329+02
(2 rows)

但请注意 table 中的任何错误数据(即无法解析为时间戳的值)将导致 ALTER TABLE 操作失败,因此您应该考虑在转换之前验证这些值数据。像 SELECT "timestamp"::TIMESTAMPTZ FROM public.log 这样的东西会失败并出现像 invalid input syntax for type timestamp with time zone: "somebadvalue".

这样的错误

还要记住,这种 ALTER TABLE 需要 table 重写,这可能需要一些时间才能完成(取决于 table 的大小),并且需要ACCESS EXCLUSIVE 锁定,使 table 在操作期间无法访问。

如果你想避免 long-running ACCESS EXCLUSIVE 锁,你可以这样做(未测试):

  • 添加一个新的 TIMESTAMPTZ 列(添加一个列不会重写 table,并且如果您不使用易变的默认值,则相当便宜)
  • 创建触发器以复制插入到原始列中的任何值
  • 复制现有值(使用一堆批量更新,如 UPDATE public.foo SET newlog = log::TIMESTAMPTZ
  • (在单个事务中)删除触发器和现有列,并将新列重命名为旧列