Trove 日志

为客户实例上的各种类型日志提供最终用户访问权限。

Launchpad 蓝图:https://blueprints.launchpad.net/trove/+spec/datastore-log-operations

问题描述

在当前实现中,用户无法在不通过 ssh 访问实例的情况下检索任何客户代理日志。用户应该能够检索由数据存储定义的日志。

提议的变更

本文档概述了一个通过将日志存储在 Swift 容器中来提供对客户日志访问权限的方案。

每个数据存储将定义它可以向用户公开的若干日志文件。日志可以是系统日志,例如始终启用的 Trove 客户代理日志,也可以是用户日志,例如可以由用户启用或禁用的 MySQL 慢查询日志。

适当的日志文件内容将通过执行 log-publish 命令从客户实例复制到 Swift 容器,然后可以将其流式传输给用户。后续的 log-publish 命令只会复制自上次 log-publish 命令以来生成的日志条目,以有效地利用网络资源并最大限度地减少对数据库性能的影响。

Swift 日志容器

每个日志将由存储在 Swift 容器中的多个对象(带有预定义的 前缀)组成,每个对象是日志信息的一个子集。通过连接容器中与指定日志相关的对象来重建整个日志;可以通过检查每个对象的对象元数据来确定顺序,或者只需按文件名对对象进行字母排序(假设使用合适的命名约定)。前缀将使用已知模式生成。然后可以使用此前缀从 Swift 检索所有相应的文件。建议的前缀为:‘%(instance_id)s/%(datastore)s-%(log)s/’


每个日志将在容器中存储一个元数据文件,该文件将包含以下信息

日志名称

日志的名称

日志类型

SYS 或 USER

日志文件

从中发布数据的 文件

日志哈希

日志文件的哈希值

日志大小

上次发布时的日志文件大小

日志行数

上次发布时的日志文件行数


可以使用以下命令查看容器中所有具有日志的实例列表

$ swift list database_logs --delimiter='/'

响应

fa8c452e-6568-438a-9f04-b2878290fb90/

可以使用以下命令查看实例的所有日志列表

$ swift list database_logs --delimiter='/' --prefix fa8c452e-6568-438a-9f04-b2878290fb90/

响应

fa8c452e-6568-438a-9f04-b2878290fb90/mysql-guest/
fa8c452e-6568-438a-9f04-b2878290fb90/mysql-guest_metafile

可以使用以下命令查看与特定数据存储日志关联的所有实际日志文件列表

$ swift list database_logs --delimiter='/' --prefix fa8c452e-6568-438a-9f04-b2878290fb90/mysql-guest/

响应

fa8c452e-6568-438a-9f04-b2878290fb90/mysql-guest/log-2015-11-18T21:28:09.975754

配置

数据存储 CONF 设置

  • guest_log_exposed_logs - 用于配置日志组件将公开哪些日志

  • guest_log_container_name - 用于生成将保存日志的容器名称的模式

  • guest_log_long_query_time - 用于标识查询是否花费了很长时间的时间

  • guest_log_limit - 推送到 Swift 的任何块的最大大小(以字节为单位)

  • guest_log_expiry - 日志组件从 Swift 容器中删除的时间(以秒为单位)(0 表示没有过期时间)

guest_log_exposed_logs

guest_log_exposed_logs 配置值将列举数据存储可用的日志。默认情况下,所有数据存储都将支持客户代理日志,以及数据存储的客户代理管理器定义的任何其他日志。

摘自 trove-guestagent.conf

guest_log_exposed_logs = error,guest,slow_query,general

未在 guest_log_exposed_logs 列表中提及的任何 USER 日志默认情况下将被禁用。特殊值“ALL”将启用客户代理管理器支持的所有日志。

管理员用户始终能够查看所有日志,并发布和查看内容,无论它们是否“公开”与否。

guest_log_container_name

用于存储日志组件的容器的名称可以在配置文件中指定。

摘自 trove-guestagent.conf

guest_log_container_name = database_logs

以上值是默认值。

guest_log_long_query_time

为“slow_query”日志设置的时间量。此值特定于数据存储,并且对于不同的数据存储可能意味着不同的含义。例如,MySQL 有一个慢查询日志,这些查询被写入其中,而 PostgreSQL 将使用该字段来决定将哪些查询写入其通用日志。

日志轮转

许多系统将使用日志轮转来确保日志不超过系统上可用磁盘空间。在任何时候,当前日志文件都可以重命名为“<logfile>.1”(或其他名称),并为正在进行的日志消息启动新的日志文件。

为了考虑到这一点,日志功能将跟踪 log-publish 操作期间存在的当前日志文件的第一行的哈希值。当前的哈希值将存储在与日志文件容器关联的 x-container-meta-log-header-digest 值中。后续的 log-publish 操作将使用哈希值来确定日志是否确实已轮转。如果是,则将清除当前容器并将新日志文件发布到其中。

数据库

n/a

公共 API

对于 log-list

请求

GET v1/instance/{id}/log

响应

{
    'logs' : [
        {
            'name': 'guest',
            'type': 'SYS',
            'status': 'Ready',
            'published': '0',
            'pending': '4234',
            'container': 'None'
            'prefix': 'None',
            'metafile': '<id>/mysql-guest_metafile',
        },
        {
            'name': 'general',
            'type': 'USER',
            'status': 'Disabled',
            'published': '0',
            'pending': '0',
            'container': 'None'
            'prefix': 'None',
            'metafile': '<id>/mysql-general_metafile',
        },
        {
            'name': 'slow_query',
            'type': 'USER',
            'status': 'Partial',
            'published': '1009',
            'pending': '304',
            'container': 'database_logs'
            'prefix': '<id>/mysql-slow_query/',
            'metafile': '<id>/mysql-slow_query_metafile',
        },
    ]
}

对于 log-show

请求

POST v1/instance/{id}/log
{ 'name': 'general' }

响应

{
    'log': {
        'name': 'guest',
        'type': 'SYS',
        'status': 'Partial',
        'published': 218913,
        'pending': 2636234
        'container': 'database_logs',
        'prefix': '<id>/mysql-guest/',
        'metafile': '<id>/mysql-guest_metafile',
    }
}

对于 log-enable

请求

POST v1/instance/{id}/log
{ 'name': 'general', 'enable': 'True' }

响应

{
    'log': {
        'name': 'general',
        'type': 'USER',
        'status': 'Enabled',
        'published': '0',
        'pending': '0',
        'container': 'None'
        'prefix': 'None',
        'metafile': '<id>/mysql-general_metafile',
        }
    ]
}

对于 log-disable

请求

POST v1/instance/{id}/log
{ 'name': 'general', 'disable': 'True' }

响应

{
    'log': {
        'name': 'general',
        'type': 'USER',
        'status': 'Disabled',
        'published': '30103',
        'pending': '0',
        'container': 'log-mysql-general-<id>'
        'prefix': '<id>/mysql-general/',
        'metafile': '<id>/mysql-general_metafile',
        }
    ]
}

对于 log-publish:(请注意,“publish”将自动“enable”日志)

请求

POST v1/instance/{id}/log
{ 'name': 'general', 'publish': 'True' }

响应

{
    'log': {
        'name': 'guest',
        'type': 'SYS',
        'status': 'Published',
        'published': '443',
        'pending': '0',
        'container': 'log-mysql-guest-<id>'
        'prefix': '<id>/mysql-guest/',
        'metafile': '<id>/mysql-guest_metafile',
        }
    ]
}

对于 log-discard

请求

POST v1/instance/{id}/log
{ 'name': 'general', 'discard': 'True' }

响应

{
    'log': {
        'name': 'general',
        'type': 'USER',
        'status': 'Ready',
        'published': '0',
        'pending': '30103',
        'container': 'None'
        'prefix': 'None',
        'metafile': '<id>/mysql-general_metafile',
        }
    ]
}

Python API

def log_list(self, instance):
    """Get a list of all guest logs.

    :param instance: The :class:`Instance` (or its ID) of the database
    instance to get the log from.
    :rtype: list of :class:`DataStoreLog`.
    """

def log_show(self, instance, log):
    """Show details of a log.

    :param instance: The :class:`Instance` (or its ID) of the database
    instance to get the log from.
    :param log: The type of <log> to enable
    :rtype: List of :class:`DataStoreLog`.
    """

def log_enable(self, instance, log):
    """Enable the writing of a log.

    :param instance: The :class:`Instance` (or its ID) of the database
    instance to get the log from.
    :param log: The type of <log> to enable
    :rtype: List of :class:`DataStoreLog`.
    """

def log_disable(self, instance, log):
    """Disable the writing of a log.

    :param instance: The :class:`Instance` (or its ID) of the database
    instance to get the log from.
    :param log: The type of <log> to disable
    :rtype: List of :class:`DataStoreLog`.
    """

def log_publish(self, instance, log, disable=None, discard=None):
    """Publish guest log to Swift container.

    :param instance: The :class:`Instance` (or its ID) of the database
    instance to get the log from.
    :param log: The type of <log> to publish
    :param disable: Turn off <log>
    :param discard: Delete the associated container
    :rtype: List of :class:`DataStoreLog`.
    """

def log_discard(self, instance, log):
    """Discard (delete) the published log container in Swift.

    :param instance: The :class:`Instance` (or its ID) of the database
    instance to get the log from.
    :param log: The type of <log> to discard
    :rtype: List of :class:`DataStoreLog`.
    """

def log_generator(self, instance, log, publish=None, lines=50):
    """Return generator to yield the last <lines> lines of guest log.

    :param instance: The :class:`Instance` (or its ID) of the database
    instance to to get the log from.
    :param log: The type of <log> to publish
    :param publish: Publish updates before displaying log
    :param lines: Display last <lines> lines of log (0 for all lines)
    :rtype: generator function to yield log as chunks.
    """

def log_save(self, instance, log, publish=None, filename=None):
    """Saves a guest log to a file.

    :param instance: The :class:`Instance` (or its ID) of the database
    instance to get the log from.
    :param log: The type of <log> to publish
    :param publish: Publish updates before displaying log
    :rtype: Filename to which log was saved
    """

CLI (python-troveclient)

日志列表

log-list 命令提供有关给定 Trove 实例上可用每个日志的信息。

$ trove log-list <instance>
+------------+------+-------------+-----------+---------+---------------+---------------------+
| Name       | Type | Status      | Published | Pending | Container     | Prefix              |
+------------+------+-------------+-----------+---------+---------------+---------------------+
| error      | SYS  | Unavailable |         0 |       0 | None          |                     |
| general    | USER | Published   |      1009 |       0 | database_logs | <id>/mysql-general/ |
| guest      | SYS  | Ready       |         0 |  499850 | None          |                     |
| slow_query | USER | Disabled    |         0 |       0 | None          |                     |
+------------+------+-------------+-----------+---------+---------------+---------------------+

描述

名称

日志组件的名称

类型

SYS:系统日志,始终开启

USER:由用户管理

状态

已禁用:USER 日志的初始状态

已启用:SYS 日志或没有数据的 USER 日志的初始状态

不可用:没有数据的 SYS 日志

就绪:日志有可用于发布的数据

已发布:日志文件已完全发布

部分:日志文件已部分发布

已轮转:日志文件已轮转,因此下次发布将首先删除容器

需要重启:数据存储需要重启才能开始写入日志文件

重启完成:内部状态,以便客户日志知道再次开始报告实际状态

已发布

已发布到容器的数据量

待处理

log-publish 可发布的可用数据量

容器

保存日志组件的 Swift 容器

前缀

发送到 Swift 以获取相关日志部分的 前缀

注意:在上面的示例中,“容器”和“前缀”的值为“None”,表示该日志尚未对其执行 log-publish 操作

日志显示

log-show 命令提供有关给定 Trove 实例上可用特定日志的完整信息。

$ trove log-show <instance> general
 +--------------+-----------------------------+
 | Property     | Value                       |
 +--------------+-----------------------------+
 | name         | slow_query                  |
 | type         | USER                        |
 | status       | Enabled                     |
 | published    | 135                         |
 | pending      | 2156                        |
 | container    | database_logs               |
 | prefix       | <id>/mysql-slow_query/      |
 | metafile     | <id>/mysql-general_metafile |
 +--------------+-----------------------------+

日志启用

log-enable 命令将指示客户代理开始将信息写入指定的日志文件。只有“USER”日志可以启用,因为“SYS”日志默认情况下是启用的(无法禁用)。根据数据存储,这可能会导致日志进入“需要重启”状态,直到重启数据存储才会保持该状态。这可以在每个数据存储的基础上进行配置,只有在没有动态启动日志记录的方法时才应这样做(例如,PostgreSQL 必须重启才能更改日志记录,因此需要此配置)。

$ trove log-enable <instance> general
 +--------------+-----------------------------+
 | Property     | Value                       |
 +--------------+-----------------------------+
 | name         | general                     |
 | type         | USER                        |
 | status       | Enabled                     |
 | published    | 0                           |
 | pending      | 0                           |
 | container    | None                        |
 | prefix       | None                        |
 | metafile     | <id>/mysql-general_metafile |
 +--------------+-----------------------------+

日志禁用

log-disable 命令将指示客户代理停止将信息写入指定的日志文件。只有“USER”日志可以禁用。与 log-enable 类似,这可能会导致日志进入“需要重启”状态。有关更多详细信息,请参阅 log-enable。

$ trove log-disable <instance> general
 +--------------+-----------------------------+
 | Property     | Value                       |
 +--------------+-----------------------------+
 | name         | general                     |
 | type         | USER                        |
 | status       | Disabled                    |
 | published    | 34658                       |
 | pending      | 2532                        |
 | container    | database_logs               |
 | prefix       | <id>/mysql-general/         |
 | metafile     | <id>/mysql-general_metafile |
 +--------------+-----------------------------+

日志发布

log-publish 命令将指示客户代理将指定日志的任何更新推送到 Swift 容器,如果需要,将创建该容器。一个 log-publish 命令可能会导致多个对象推送到 Swift 容器,以便使每个对象低于由 guest_log_limit CONF 值配置的最大对象大小。

log-publish 命令将异步执行。执行 log-publish 命令时,Trove 实例将进入 LOGGING 状态,并在对象推送到日志容器以成功完成命令的执行后返回到 ACTIVE 状态。

当对象推送到 Swift 容器时,将使用 X-Delete-After 标头来指定容器对象的生存时间。这将导致对象在一段时间后自动从容器中删除,如 log_expiry CONF 值所指定。

将支持一个可选的 –disable 参数来禁用特定 USER 日志的日志记录。将支持一个可选的 –discard 参数来首先丢弃(删除)关联的容器。

$ trove log-publish <instance> slow_query
 +--------------+--------------------------------+
 | Property     | Value                          |
 +--------------+--------------------------------+
 | name         | slow_query                     |
 | type         | USER                           |
 | status       | Published                      |
 | published    | 43242                          |
 | pending      | 0                              |
 | container    | database_logs                  |
 | prefix       | <id>/mysql-slow_query/         |
 | metafile     | <id>/mysql-slow_query_metafile |
 +--------------+--------------------------------+

日志丢弃

log-discard 命令将丢弃(删除)当前日志信息所在的容器。

$ trove log-discard <instance> general
 +--------------+-----------------------------+
 | Property     | Value                       |
 +--------------+-----------------------------+
 | name         | general                     |
 | type         | USER                        |
 | status       | Enabled                     |
 | published    | 0                           |
 | pending      | 37190                       |
 | container    | None                        |
 | prefix       | None                        |
 | metafile     | <id>/mysql-general_metafile |
 +--------------+-----------------------------+

日志尾部

默认情况下,log-tail 输出日志末尾的 50 行。使用 –lines=n 选项,log-tail 将输出日志的最后 n 行。如果 n 为负数,输出将从第 n 行开始并继续到末尾;–lines=0 将输出整个日志。

使用 –publish 选项,log-tail 将首先执行 log-publish 命令并等待日志发布后再开始输出。

应注意的是,日志的实际显示将在 Python API 中进行。将没有 REST API 来促进日志的显示;此类 API 会由于缓冲和从 Swift 流式传输的要求而给系统带来过大的压力。

$ trove log-tail <instance> slow_query --publish
/usr/local/mysql/libexec/mysqld, Version: 3.23.54-log, started with:
Tcp port: 3306  Unix socket: /tmp/mysql.sock
Time                 Id Command    Argument
# Time: 030207 15:03:33
# User@Host: wsuser[wsuser] @ localhost.localdomain [127.0.0.1]
# Query_time: 13  Lock_time: 0  Rows_sent: 117  Rows_examined: 234
use wsdb;
SELECT l FROM un WHERE ip='209.xx.xxx.xx';

日志保存

与 log-tail 命令类似,log-save 命令将在 python-troveclient 中执行,但 log-tail 将将日志输出到控制台,而 log-save 命令会将日志保存到文件系统中的文件中。这将允许用户下载非常大的日志文件,而不会使客户端或浏览器不堪重负。

使用 –file 选项,日志将保存到命名的文件中。如果没有 –file 选项,日志将输出到当前目录中的 <logname>.log 中。

使用 –publish 选项,log-tail 将首先执行 log-publish 命令并等待日志发布后再开始输出。

应注意的是,日志的实际保存将在 Python API 中进行。将没有 REST API 来促进日志的显示;此类 API 会由于缓冲和从 Swift 流式传输的要求而给系统带来过大的压力。

$ trove log-save <instance> slow_query --file=/tmp/my.log --publish

公共 API 安全

Swift 容器将使用与备份相同的安全凭据创建。

内部 API

将向任务管理器和客户添加适当的 API 方法。

预计不会对现有 API 进行任何更改。

Guest Agent

预计不会出现兼容性问题。

备选方案

替代 API

可以实现一个替代 API,该 API 放弃一些较简单的命令。

注意:在向文档人员描述该过程后,修改了此替代部分,确定拥有“简单”命令可以大大更容易地理解日志过程。


Openstack 功能

API

描述

log-list

如上

log-show

已删除

log-enable

已删除

log-disable

已删除

log-publish

如上

log-discard

已删除

log-tail

如上

log-save

如上

默认发布

有人也提出将 –publish 选项设为 log-tail 和 log-save 命令的默认选项。如果这样做,似乎没有人会执行 –no-publish,并且 log-publish 命令只会执行“log-publish –disable”,因此最好只是消除 –publish/–no-publish 选项,让 log-tail 和 log-save 始终首先发布,并将 log-publish 命令替换为 log-disable。

客户日志的管理员覆盖

操作员可以从返回给用户的日志列表中排除客户代理日志。但是,这样做不会阻止操作员通过管理员帐户查看客户日志。当前管理员用户仍然将日志流式传输到与 Trove 用户相同的租户。将来可以增强此功能,让管理员用户提供可用于托管日志容器的租户。

Dashboard 影响 (UX)

待定 (在批准后添加的部分)

实现

负责人

主要负责人

vgnbkr (morgan@tesora.com)

次要分配人

peterstac (peter@tesora.com) atomic77 (atomic@tesora.com)

文档

laurelm (lmichaels@tesora.com)

里程碑

完成目标里程碑

Mitaka-1

工作项

此组件已基本实现。

升级影响

由于这是全新的功能,预计不会出现升级影响。

依赖项

n/a

测试

将添加一个集成场景测试来检索客户日志,执行命令,再次检索客户日志,并确认捕获了其他日志详细信息。然后将通过 log-discard 命令删除日志,并验证从 Swift 中删除容器中的文件。

文档影响

需要编写描述添加的功能的新文档。

附录