
# 使用函数工作流服务实现CDN日志转存到OBS
#### 方案概述
CDN记录了所有域名（包括已删除域名，如果您开通了企业项目，则已删除域名不支持此功能）被网络用户访问的详细日志，您可以通过CDN控制台查看和下载最近30天的日志，对您的业务资源被访问情况进行详细分析。
日志转存储服务是华为云CDN配合函数工作流，将CDN日志存储到OBS桶，可以帮助您将日志存储更长的时间，便于您基于长时间的日志做出自定义的数据分析，有助于您更好地了解您CDN的服务质量，以及您的终端客户的访问详情，提高您的业务决策能力。
本文以Python3.6为例，为您介绍通过控制台创建FunctionGraph函数和Timer触发器，实现定时将CDN日志转存到OBS。
#### 资源与成本规划
本实践所需资源请见下表。
| 资源                  | 资源说明                            | 每月费用                                                                                                     |
|:---|:---|:---|
| 内容分发网络 CDN          | **流量**：用户访问CDN节点产生的流量，可购买流量包抵扣。 | 具体的计费方式及标准请参考[计费说明](https://support.huaweicloud.com/price-cdn/cdn_01_0155.html)。                         |
| 对象存储服务 OBS          | **存储费用**：日志存储费用，按需计费。           | 具体的计费方式及标准请参考OBS[计费说明](https://support.huaweicloud.com/price-obs/obs_42_0001.html)。                      |
| 函数工作流 FunctionGraph | 请求次数费用、执行时间费用和其他费用，按需计费。        | 具体的计费方式及标准请参考函数工作流[计费项](https://support.huaweicloud.com/price-functiongraph/functiongraph_00_0010.html)。 |
   
#### 实施步骤
1. 当前仅支持日志转存到北京四的OBS桶，需提前**准备好位于华北-北京四的OBS桶** 。
   1. 登录[华为云控制台](https://console.huaweicloud.com)，在左侧导航栏，选择**"** 存储\>对象存储服务 OBS**"。**
   
   2. 单击右上方"创建桶"按钮，进入创建桶页面。
      - 复制桶配置：不设置。
      
      - 区域：华北-北京四。
      
      - 桶名称：cdnlog。
      
      - 数据冗余存储策略：多AZ存储。
      
      - 默认存储类别：标准存储。
      
      - 桶策略：公共读。
       
   
   3. 配置上述参数，单击"立即创建"，完成桶创建。
    
2. **创建自定义策略**
   1. 登录[华为云控制台](https://console.huaweicloud.com)，在左侧导航栏，选择**"** 管理与监管\>统一身份认证服务**"**。
   
   2. 在左侧导航栏，选择"权限管理\>权限"页签，单击右上方的"创建自定义策略"。
      - 策略名称：OBS OperateAccess。
      
      - 策略配置方式：JSON视图。
      
      - 策略内容：
        ```
        {
            "Version": "1.1",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Action": [
                        "obs:object:GetObject",
                        "obs:object:PutObjectAcl",
                        "obs:bucket:HeadBucket",
                        "obs:object:PutObject",
                        "obs:bucket:ListBucket"
                    ]
                }
            ]
        }
        ```
        
       
   
   3. 单击"确定"，完成策略配置。
    
3. **创建委托**
   1. 登录[华为云控制台](https://console.huaweicloud.com)，在左侧导航栏，选择**"** 管理与监管\>统一身份认证服务**"**。
   
   2. 在左侧导航栏，选择"委托"页签，单击右上方的"创建委托"。
   
   3. 在创建委托页面，按照如下参数设置委托。
      - 委托名称：FG_TO_CDN。
      
      - 委托类型：云服务。
      
      - 云服务：函数工作流FunctionGraph。
      
      - 持续时间：永久。
       
   
   4. 单击"下一步"，进入为"FG_TO_CDN"委托配置权限界面。
      - 权限选择：OBS OperateAccess（第一步创建的策略） 、CDN LogsReadOnlyAccess。
      
      
      ![](https://support.huaweicloud.com/bestpractice-cdn/zh-cn_image_0000001265757848.png "点击放大")
      
   
   5. 单击"下一步"，配置作用范围。
      1. 账号未开通企业项目，配置如下：
         - 作用范围：所有资源。
         
         
         ![](https://support.huaweicloud.com/bestpractice-cdn/zh-cn_image_0000001363840353.png "点击放大")
         
      
      2. 账号已开通企业项目，配置如下：
         - 作用范围：指定企业项目资源。
         
         
         ![](https://support.huaweicloud.com/bestpractice-cdn/zh-cn_image_0000001311320526.png "点击放大")
         
      
      3. 选择存放日志的OBS桶所在的企业项目。
       
   
   6. 单击"确认"，完成委托配置。
    
4. **准备函数工作流环境**
   1. 登录[华为云控制台](https://console.huaweicloud.com)，在左侧导航栏，选择**"** 计算\>函数工作流**"，**region选择"北京四"。
   
   2. 单击右上方"创建函数"，进入创建函数界面，选择"空白模板"**。**
      - **函数类型**：事件函数。
      
      - **函数名称**：cdn_to_obs（可自定义）。
      
      - **委托**：选择已创建好的委托"FG_TO_CDN" 。
      
      - **企业项目**：选择"default"。
      
      - **运行时**：选择"Python 3.6"。
       
   
   
   
   3. 单击"创建函数"，进入代码编辑界面，将[代码示例]的代码内容贴入在线IDE。
      ![](https://support.huaweicloud.com/bestpractice-cdn/public_sys-resources/note_3.0-zh-cn.png)
      如果有多个域名的日志需要转存，您需要分别为每个域名创建一个函数工作流。
      
   
   4. 单击"设置"，进入函数设置界面，修改常规设置。
      - **执行超时时间** ：函数运行的超时时间，超时的函数将被强行停止，建议设置为900。
        ![](https://support.huaweicloud.com/bestpractice-cdn/public_sys-resources/note_3.0-zh-cn.png)
        如果您发现转存的日志数量不对，请向"函数工作流"服务提工单，增大执行超时时间。
        
       
   
   5. **添加环境变量**
      1. 在设置界面左侧单击"编辑环境变量"。在设置界面左侧单击"编辑环境变量"。
      
      2. 在右侧抽屉单击"添加环境变量"，相关键值的配置如下。
         - url ：https://cdn.myhuaweicloud.com/v1.0/cdn/logs（CDN日志下载的url）。
         
         - domain_name ：example.com（需要转存日志的CDN加速域名）。
         
         - obsAddress ：\*\*\*.obs.region.myhuaweicloud.com（用于存日志的OBS桶域名，region为OBS桶的region）。
         
         - destBucket ：cdnlog（用于存日志的OBS桶名称）。
          
      
      3. 单击下方"确定"。
       
   
   6. 在设置界面左侧选择"权限"。
      - 选择已创建好的"FG_TO_CDN"委托，单击"保存"。
       
   
   7. 在设置界面左侧选择"触发器**"**
      1. 单击"创建触发器"，设置如下。
         - 触发器类型：定时触发器 (TIMER)。
         
         - 定时器名称：自定义的定时器名称，例如：Timer-0001。
         
         - 触发规则：Cron表达式。
         
         - Cron表达式：0 0 8 \* \* ?（每天早上8点执行一次日志转存储）。
         
         - 是否开启：开启。
          
      
      2. 单击"确定"，完成定时触发器设置。
       
   
   8. **创建测试事件** 。
      1. 返回"代码"界面，单击"测试"按钮。
         - 配置测试事件：创建新的测试事件。
         
         - 事件模板：空白模板。
         
         - 事件名称：test。
         
         - 测试事件：{"message":"CDNLog-OBS"}。
          
      
      2. 单击"保存"，完成测试事件创建。
       
   
   9. **测试函数** 。在函数详情页面，单击左侧下拉框，选择"test"，单击"测试"。
      ![](https://support.huaweicloud.com/bestpractice-cdn/zh-cn_image_0000001349324770.png "点击放大")
      
    
5. **查看配置是否成功**
   1. 登录[华为云控制台](https://console.huaweicloud.com)，在左侧导航栏，选择**"** 存储\>对象存储服务 OBS**"。**
   
   2. 单击您存储日志的桶（本实践为cdnlog），在左侧导航栏选择"对象"。
   
   3. 访问路径：文件夹（桶名称）\>文件夹（加速域名）\>文件夹（日志日期）\>日志内容。
   
   
   ![](https://support.huaweicloud.com/bestpractice-cdn/zh-cn_image_0000001152057811.png "点击放大")
   ![](https://support.huaweicloud.com/bestpractice-cdn/public_sys-resources/note_3.0-zh-cn.png)
   - 当前[代码示例]仅支持转存当前时间前一日的日志，如果您需要转存日志的加速域名前一日没有日志产生，则OBS桶侧不会产生相关文件。
   
   - OBS桶将对转存到桶里的日志收费，具体收费规则请参考[计费说明](https://support.huaweicloud.com/price-obs/obs_42_0001.html)。
     
6. **停止日志转存服务**
   1. 登录[华为云控制台](https://console.huaweicloud.com)，在左侧导航栏，选择"计算\>函数工作流"**，**region选择"北京四"。
   
   2. 在左侧导航栏选择"函数"\>"函数列表"，选中[2]中创建的函数名。
   
   3. 在函数详情页选择"触发器"。 ![](https://support.huaweicloud.com/bestpractice-cdn/zh-cn_image_0000001399568293.png "点击放大")
      
   
   
   
   4. 单击"停用"，完成配置。
    
 
 #### 代码示例
代码如下所示：
```
# -*- coding:utf-8 -*-
import requests
import datetime
import time
import os
import sys
import json
from com.obs.client.obs_client import ObsClient
from urllib.parse import urlparse
if sys.version_info.major == 2 or not sys.version > '3':
    import httplib
else:
    import http.client as httplib
current_file_path = os.path.dirname(os.path.realpath(__file__))
# Adds the current path to search paths to import third-party libraries.
sys.path.append(current_file_path)
TEMP_ROOT_PATH = "/tmp/"  # Downloads a file from OBS to this directory.
region = 'china'  # This parameter does not need to be changed and will be used when FunctionGraph accesses OBS.
secure = True  # This parameter does not need to be changed and will be used when FunctionGraph accesses OBS.
signature = 'v4'  # This parameter does not need to be changed and will be used when FunctionGraph accesses OBS.
port = 443  # This parameter does not need to be changed and will be used when FunctionGraph accesses OBS.
path_style = True  # This parameter does not need to be changed and will be used when FunctionGraph accesses OBS.
def handler(event, context):
    logger = context.getLogger()
    queryDate = context.getUserData('queryDate')
    if queryDate is None:
        yesterday = datetime.date.today() + datetime.timedelta(-1)
        queryDate = yesterday.strftime("%Y-%m-%d")
        timeStamp = int(time.mktime(yesterday.timetuple()) * 1000)
    else:
        date = datetime.datetime.strptime(queryDate, "%Y-%m-%d")
        timeStamp = int(time.mktime(date.timetuple()) * 1000)
    pageSize = 20
    pageNumber = 1
    requests.packages.urllib3.disable_warnings()
    start(context, queryDate, timeStamp, pageSize, pageNumber)
def start(context, queryDate, timeStamp, pageSize, pageNumber):
    logger = context.getLogger()
    logUrl = context.getUserData('url')
    domainName = context.getUserData('domain_name')
    params = {'query_date': timeStamp, 'domain_name': domainName, 'page_size': pageSize, 'page_number': pageNumber, 'enterprise_project_id':'ALL'}
    headers = {'Content-Type': 'application/json;charset=UTF-8', 'X-Auth-Token': context.getToken()}
    res = requests.get(logUrl, params=params, headers=headers, verify=False)
    if res.status_code != 200:
        logger.info("query log urls: " + res.url + ", error: " + res.text)
        return ("query log urls: " + res.url + ", error: " + res.text)
    resJson = json.loads(res.text)
    logger.info(res.text)
    total = resJson['total']
    i = 0
    for val in resJson['logs']:
        i += 1
        logger.info(val["link"])
        url = urlparse(val["link"])
        netlocs = url.netloc.split(":")
        conn = httplib.HTTPConnection(netlocs[0], int(netlocs[1]))
        conn.request('GET', url.path + "?" + url.query)
        objName = os.path.join(val["domain_name"], queryDate, val["name"])
        put_content_to_obs(context, objName, conn.getresponse())
    if pageSize * pageNumber < total:
        start(context, queryDate, timeStamp, pageSize, pageNumber + 1)
def put_content_to_obs(context, objName, content):
    ak = context.getAccessKey()
    sk = context.getSecretKey()
    obsAddress = context.getUserData('obsAddress')
    destBucket = context.getUserData('destBucket')
    TestObs = ObsClient(access_key_id=ak, secret_access_key=sk,
                        is_secure=secure, server=obsAddress, signature=signature, path_style=path_style, region=region,
                        ssl_verify=False, port=port,
                        max_retry_count=5, timeout=20)
    resp = TestObs.putContent(destBucket, objName, content=content)
    if resp.status < 300:
        print('requestId:', resp.requestId)
    else:
        print('errorCode:', resp.errorCode)
        print('errorMessage:', resp.errorMessage)
```
