发送Django error log 到企业微信,python+微信实现即时告警

发送Django error log 到企业微信,python+微信实现即时告警

Django的logging系统十分好用,使用file,mail_admins之类的handlers可以快捷地实现保存日志到文件,发送错误日志给管理员的功能。但是,如果能直接将应用的错误日志发送到手机上,实现即时告警,岂不是更好?


注册企业微信1

  1. 首先需要注册一个企业微信账号,地址是:
    https://work.weixin.qq.com/we...

**注意**:
注册过程如果是为企业使用要选择企业,然后上传企业的资质证明,如果是个人注册,选择团队,然后输入自己的身份证号即可完成注册.
  1. 然后进入企业应用页面,添加一个应用,添加完成后,进入应用页面:
    这里写图片描述

  • 这里的agentid和secret需要留意, 后续的发送信息api需要它们。

  • 另外我们还需要corpid,在我的企业-企业信息中可以找到。

使用企业微信API发送消息

  1. 测试使用企业微信API发送消息:
    企业微信API:发送消息

例子:

#! /usr/bin/env python
# -*- coding: utf-8 -*-

import requests
import json


def get_token():

   url = 'https://qyapi.weixin.qq.com/cgi-bin/gettoken'
   values = {
       'corpid': '<YOUR CORPID>',
       'corpsecret': '<YOUR SECRET>',
      }
   req = requests.post(url, params=values)
   data = json.loads(req.text)
   return data["access_token"]


def send_msg():
   url = ("https://qyapi.weixin.qq.com/cgi-bin/message/send"
          "?access_token={}").format(get_token())
   values = {
       # "touser": "@all",
       "toparty": "2",
       "msgtype": "text",
       "agentid": "<YOUR AGENTID>",
       "text": {
           "content": u"报警测试,toparty: 2"
       },
       }

   req = requests.post(url, json.dumps(values))
   print(values, req)


if __name__ == '__main__':
   send_msg()
其中toparty:2,意为向id为2的部门的所有成员发送消息。
使用touser:@all, 可以向所有人发送信息。

发送django的log到企业微信

  1. 配置Django的logging系统
    接下来我们需要配置一下django的logging系统,来发送ERROR级别的log到企业微信。

我的思路是可以参照django自带的AdminEmailHandler写一个WechatAlarmHandler。
代码如下:

import logging
import requests
import json
from copy import copy


from django.core.cache import cache
from django.views.debug import ExceptionReporter


class WechatAlarmHandler(logging.Handler):
    """An exception log handler that sends log entries to wechat alarm bot.
    """

    def __init__(self):
        logging.Handler.__init__(self)

    def emit(self, record):
        try:
            request = record.request
            subject = '%s (%s IP): %s' % (
                record.levelname,
                ('internal' if request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS # NOQA
                 else 'EXTERNAL'),
                record.getMessage()
            )
        except Exception:
            subject = '%s: %s' % (
                record.levelname,
                record.getMessage()
            )
            request = None
        subject = self.format_subject(subject)
        message = self.format(record)
        self.send_msg(subject, message)
        
    def send_msg(self, subject, message=None, *args, **kwargs):
        WechatAlarm().send_msg('{}\n\n{}'.format(subject, message))


    def format_subject(self, subject):
        """
        Escape CR and LF characters.
        """
        return subject.replace('\n', '\\n').replace('\r', '\\r')


class WechatAlarm:
    def __init__(self, corpid='<YOUR CORPID>',
                 corpsecret='<YOUR SECRET>',
                 agentid='<YOUR AGENTID>', partyid='<YOUR PARTYID>'):
        self.corpid = corpid
        self.partyid = partyid
        self.key = 'wechat_send_alarm_key'
        self.corpsecret = corpsecret
        self.agentid = agentid

    def get_token(self):
        token = cache.get(self.key)
        if token:
            return token
        else:
            url = 'https://qyapi.weixin.qq.com/cgi-bin/gettoken'
            values = {
                'corpid': self.corpid,
                'corpsecret': self.corpsecret,
               }
            req = requests.post(url, params=values)
            data = json.loads(req.text)
            cache.set(self.key, data["access_token"], 7200)
            return data["access_token"]

    def send_msg(self, content=None):
        url = ("https://qyapi.weixin.qq.com/cgi-bin/message/send"
               "?access_token={}").format(self.get_token())
        values = {
            # "touser": "@all",
            "toparty": self.partyid,
            "msgtype": "text",
            "agentid": self.agentid,
            "text": {
                "content": content,
            },
            }

        return requests.post(url, json.dumps(values))

再配置一下django的settings里的LOGGING:

GGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '[%(asctime)s](%(levelname)s)<%(name)s.%(funcName)s>{%(process)d/%(thread)d} : %(message)s'
        },
        'simple': {
            'format': '%(levelname)s %(message)s'
        },
    },
    'filters': {
        'require_debug_false': {
            '()': 'django.utils.log.RequireDebugFalse'
        }
    },
    'handlers': {
        'null': {
            'level': 'DEBUG',
            'class': 'django.utils.log.NullHandler',
        },
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        'mail_admins': {
            'level': 'ERROR',
            'class': 'django.utils.log.AdminEmailHandler',
            'filters': ['require_debug_false'],
        },
        'send_wechat': {
            'level': 'ERROR',
            'class': 'utils.log.WechatAlarmHandler',   # your handler path
        },
        'file': {
            'level': 'INFO',
            'class': 'logging.handlers.TimedRotatingFileHandler',
            'formatter': 'verbose',
            'filename': webservice_logfile,
            'when': 'D'
        },
    },
    'loggers': {
        '': {
            'handlers': ['file', 'mail_admins', 'send_wechat'],
            'propagate': True,
            'level': 'ERROR',
        },
        'django': {
            'handlers': ['file', 'mail_admins'],
            'propagate': True,
            'level': 'ERROR',
        },
        'django.request': {
            'handlers': ['file', 'mail_admins', ],
            'level': 'ERROR',
            'propagate': True,
        },
    }
}
即,在handler里增加了一个send_wechat, 在loggers里的handers里增加了send_wechat。

这样django的error log就会通过企业微信发送到手机了。


  1. Python+微信接口实现运维报警
<think>我们正在处理微信小程序通过wx.uploadFile上传的文件和表单数据。在Django中,我们需要从请求中获取文件和表单数据。 根据引用[1][2][3]可知,微信小程序上传文件时,文件内容在request.FILES中,而表单数据(即formData)在request.POST中。 但是,需要注意的是,wx.uploadFile方法上传时,表单数据是以请求头中的Content-Type: multipart/form-data形式发送的,因此Django能够自动解析。 步骤: 1. 在视图函数中,首先判断请求方法是否为POST。 2. 获取文件:通过request.FILES获取上传的文件对象。小程序端上传文件时,会有一个key(在wx.uploadFile的name参数中指定),通过这个key可以获取文件对象。 3. 获取表单数据:通过request.POST获取表单数据,表单数据中的字段名由小程序端在formData中指定。 4. 处理文件:可以读取文件内容并保存到服务器指定路径,或者进行其他处理(如图像处理、音频处理等)。 5. 处理表单数据:根据字段名获取相应的值。 根据引用[2]和[3]的示例,我们可以这样写: 示例代码: 假设小程序端调用wx.uploadFile时,name参数为'file',formData中包含字段'user_id'和'description'。 视图函数示例: """ import os from django.conf import settings from django.http import JsonResponse def upload_file(request): if request.method == 'POST': # 获取文件 if 'file' in request.FILES: uploaded_file = request.FILES['file'] # 读取文件内容 file_content = uploaded_file.read() # 假设我们保存文件的目录在settings中配置为UPLOAD_DIR upload_dir = getattr(settings, 'UPLOAD_DIR', 'uploads') if not os.path.exists(upload_dir): os.makedirs(upload_dir) # 生成文件名,为了避免重复,可以使用时间戳 import time filename = str(int(time.time())) + '_' + uploaded_file.name file_path = os.path.join(upload_dir, filename) with open(file_path, 'wb') as f: f.write(file_content) else: return JsonResponse({'status': 'error', 'message': 'No file uploaded'}, status=400) # 获取表单数据 user_id = request.POST.get('user_id', '') description = request.POST.get('description', '') # 这里可以进行业务逻辑处理,比如将文件路径和表单数据存入数据库等 return JsonResponse({'status': 'success', 'message': 'File uploaded successfully', 'user_id': user_id, 'description': description}) else: return JsonResponse({'status': 'error', 'message': 'Only POST method is allowed'}, status=405) """ 注意: 1. 文件保存路径需要根据实际情况设置,并确保Django有写入权限。 2. 为了避免文件名冲突,我们使用了时间戳加原始文件名,也可以使用其他方法(如UUID)。 3. 表单数据通过request.POST.get方法获取,可以设置默认值(如空字符串)。 另外,引用[3]中提到的方法1也是类似,通过request.FILES获取文件,然后读取内容并保存到本地。 但是,上述代码中,我们同时处理了文件和表单数据。需要注意的是,wx.uploadFile在微信小程序中上传文件时,确实可以同时携带formData,并且Django的request.POST会包含这些表单数据。 如果小程序端同时上传多个文件,那么request.FILES将是一个类似字典的对象,包含多个文件。我们可以通过不同的key来获取不同的文件(如果上传了多个文件,每个文件都有对应的key)。 例如,如果小程序端上传了两个文件(分别用'file1'和'file2'作为key),那么我们可以这样获取: file1 = request.FILES['file1'] file2 = request.FILES['file2'] 但是,在wx.uploadFile一次请求中,只能上传一个文件(因为它是单文件上传),所以一次请求中request.FILES只有一个文件。如果需要上传多个文件,则需要多次调用wx.uploadFile,或者使用其他方法(如多文件上传组件)。 因此,上述代码中我们假设一次只上传一个文件,且key为'file'。 如果小程序端使用的是多文件上传,那么需要使用wx.uploadFile多次,或者使用其他方式(例如,使用wx.request上传多个文件,但这样需要自己构造multipart/form-data格式,比较复杂)。通常,微信小程序的多文件上传是多次调用wx.uploadFile实现的。 总结: 在Django中处理微信小程序通过wx.uploadFile上传的文件和表单数据,主要从request.FILES中获取文件,从request.POST中获取表单数据。 相关问题: 1. 如何在Django中处理多个文件上传? 2. 如何限制上传文件的大小和类型? 3. 如何将上传的文件保存到云存储(如阿里云OSS、七牛云等)而不是本地? 4. 如何处理大文件上传? 5. 如何实现文件上传的进度条?</think>在 Python Django 框架中处理微信小程序通过 `wx.uploadFile` 上传的文件和表单数据,需要结合 `request.FILES` 和 `request.POST` 对象进行处理。以下是详细实现步骤: --- ### **1. 视图函数处理逻辑** ```python import os from django.conf import settings from django.http import JsonResponse def upload_handler(request): if request.method == "POST": # 1. 获取表单数据 (formData) user_id = request.POST.get('user_id') # 小程序端formData字段 description = request.POST.get('description') # 2. 获取上传的文件 (通过小程序端指定的key) if 'file' not in request.FILES: # 'file' 对应wx.uploadFile的name参数 return JsonResponse({'status': 'error', 'msg': '未检测到文件'}) uploaded_file = request.FILES['file'] # 3. 生成安全的存储路径 save_dir = os.path.join(settings.MEDIA_ROOT, 'uploads/') # 在settings.py中配置MEDIA_ROOT os.makedirs(save_dir, exist_ok=True) # 自动创建目录 # 4. 保存文件(避免文件名冲突) file_path = os.path.join(save_dir, f"{user_id}_{uploaded_file.name}") with open(file_path, 'wb+') as destination: for chunk in uploaded_file.chunks(): # 分块写入大文件 destination.write(chunk) # 5. 返回响应 return JsonResponse({ 'status': 'success', 'file_url': f"/media/uploads/{os.path.basename(file_path)}", # 通过MEDIA_URL访问 'user_id': user_id, 'description': description }) return JsonResponse({'status': 'error', 'msg': '仅支持POST请求'}) ``` --- ### **2. Django 配置 (settings.py)** ```python # 文件存储路径 MEDIA_ROOT = os.path.join(BASE_DIR, 'media') MEDIA_URL = '/media/' # 路由访问配置 (urls.py) from django.conf import settings from django.conf.urls.static import static urlpatterns = [ path('upload/', upload_handler), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) ``` --- ### **3. 微信小程序端调用示例** ```javascript // 小程序端代码 wx.chooseImage({ success: (res) => { const tempFile = res.tempFiles[0]; wx.uploadFile({ url: 'https://yourdomain.com/upload/', filePath: tempFile.path, name: 'file', // 必须与Django视图的request.FILES[key]匹配 formData: { user_id: '123', // 表单数据 description: '测试文件' }, success: (resp) => { console.log(JSON.parse(resp.data)); } }); } }); ``` --- ### **关键点说明** 1. **文件获取** 通过 `request.FILES['file']` 获取文件对象(`'file'` 需与小程序 `name` 参数一致)。 2. **表单数据获取** 通过 `request.POST.get('field_name')` 获取小程序 `formData` 中的字段。 3. **安全存储** - 使用 `MEDIA_ROOT` 统一管理存储路径 - 文件名添加用户ID前缀避免冲突 - 分块写入 (`chunks()`) 防止大文件内存溢出 4. **访问文件** 配置 `MEDIA_URL` 后,可通过 `http://domain.com/media/uploads/filename` 直接访问文件。 --- ### **常见问题处理** - **文件名乱码**: 使用 `uploaded_file.name.encode('iso-8859-1').decode('utf-8')` 转码。 - **文件类型限制**: 在视图中校验文件扩展名: ```python if not uploaded_file.name.endswith(('.jpg', '.png')): return JsonResponse({'status': 'error', 'msg': '仅支持JPG/PNG格式'}) ``` - **大文件优化**: 使用 Nginx 反向代理处理文件上传,避免 Django 阻塞。 --- ### **引用说明** Django 通过 `request.FILES` 和 `request.POST` 分别处理文件和表单数据的特性是框架的标准设计[^1]。实际存储路径配置需结合 `MEDIA_ROOT` 和 `MEDIA_URL`[^2],文件名拼接用户 ID 可有效避免冲突[^3]。 --- ### 相关问题 1. 如何限制微信小程序上传文件的大小和类型? 2. 如何将上传的文件直接保存到云存储(如 AWS S3 或阿里云 OSS)? 3. 如何处理并发上传时的文件覆盖问题? 4. 如何实现文件上传的进度条显示(小程序端 + Django 后台)? 5. 如何验证微信小程序上传请求的合法性(防止恶意请求)? [^1]: Django 官方文档 - 文件上传处理 [^2]: 微信小程序 + Django 文件存储实践 [^3]: 文件名冲突解决方案
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值