如何在sendmail发送的SSH连接失败告警邮件中添加主机名?
解决SSH连接失败时邮件包含主机及异常信息的问题
我完全懂你的困扰:现在脚本循环处理多台主机时,连接失败的邮件只有固定文本,根本没法定位到底是哪台主机出了问题,也看不到具体的错误原因。咱们来一步步把这个问题解决掉。
原代码的核心问题
- 异常块里的邮件正文是固定字符串,没有插入主机名/IP这类关键定位信息
- 没有区分具体的异常类型,没法判断是「主机不可达」「认证失败」还是其他问题
- 邮件对象
msg在循环外初始化,多次失败时会重复添加内容(逻辑上不够严谨)
修复后的代码
import csv import paramiko import socket import smtplib from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from datetime import date # 收集所有失败主机信息,最后统一发汇总邮件(推荐这种方式,避免频繁发邮件) failed_hosts = [] # 邮件配置 msg_password = 'something' sender = "servicedesk@mail.com" recipient = "user@mail.com" with open('test.csv', 'r', newline='') as csvinput: reader = csv.DictReader(csvinput) for item in reader: ip = item['IP'] hostname = item['Hostname'] username = item['Username'] password = item['Password'] type_ = item['Type'] today = date.today() if 'Network' in type_: try: # 初始化SSH客户端 remote_conn_pre = paramiko.SSHClient() remote_conn_pre.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # 设置10秒超时,避免脚本卡在无法连接的主机上 remote_conn_pre.connect(ip, username=username, password=password, port=22, timeout=10) print(f"SSH连接成功: {hostname} ({ip})") # 这里可以添加你的交互式会话逻辑 remote_conn_pre.close() # 记得关闭连接 except paramiko.AuthenticationException: err_detail = f"认证失败: {hostname} ({ip}) - 用户名或密码错误" print(err_detail) failed_hosts.append(err_detail) except socket.timeout: err_detail = f"连接超时: {hostname} ({ip}) - 主机可能不可达或22端口未开放" print(err_detail) failed_hosts.append(err_detail) except socket.error as e: err_detail = f"网络错误: {hostname} ({ip}) - 详情: {str(e)}" print(err_detail) failed_hosts.append(err_detail) except paramiko.SSHException as e: err_detail = f"SSH协议错误: {hostname} ({ip}) - 详情: {str(e)}" print(err_detail) failed_hosts.append(err_detail) except Exception as e: # 兜底捕获所有未预料的异常 err_detail = f"未知错误: {hostname} ({ip}) - 详情: {str(e)}" print(err_detail) failed_hosts.append(err_detail) # 所有主机处理完成后,发送汇总邮件 if failed_hosts: msg = MIMEMultipart() msg['From'] = sender msg['To'] = recipient msg['Subject'] = f"SSH连接失败汇总 ({today})" # 构建清晰的邮件正文 body = f"本次检测共发现{len(failed_hosts)}台主机SSH连接失败:\n\n" for idx, err in enumerate(failed_hosts, 1): body += f"{idx}. {err}\n" msg.attach(MIMEText(body, 'plain')) # 发送邮件 try: server = smtplib.SMTP("mailserver.com", 587) server.starttls() server.login(sender, msg_password) server.sendmail(sender, recipient, msg.as_string()) server.quit() print("失败汇总邮件已成功发送") except Exception as e: print(f"发送邮件失败: {str(e)}")
关键改动说明
精准捕获异常类型:
- 区分了认证失败、连接超时、网络错误等不同场景,能给出针对性的错误提示,方便快速排查问题
- 用
Exception兜底,避免脚本因未预料的错误直接崩溃
统一汇总发送:
- 用
failed_hosts列表收集所有失败信息,最后一次性发送汇总邮件,不会频繁打扰收件人 - 如果确实需要每失败一台就立即发邮件,可以把邮件发送逻辑移到每个异常块内,记得每次都重新创建
MIMEMultipart对象(避免重复添加内容)
- 用
强化主机标识:
- 错误信息里同时包含主机名和IP,无论哪种标识方式都能快速定位目标主机
- 邮件标题加入日期,方便区分不同批次的检测结果
代码细节优化:
- 变量名更规范(比如避免用
Type这类和内置关键字冲突的命名) - 添加连接超时设置,避免脚本长时间卡在无响应的主机上
- 连接成功后主动关闭SSH连接,释放资源
- 变量名更规范(比如避免用
内容的提问来源于stack exchange,提问作者Bharath
相关产品推荐
相关产品推荐

