디스코드 봇 만들기#32서버 로그·감사(Audit Log) 채널 만들기! 메시지 삭제·수정·입퇴장·추방까지 자동 기록 (파이썬 예제) 🕵️
한눈에 보기
디스코드 기본 감사 로그는 '누가 몇 개 지웠다'까지만 알려주고 지워진 내용은 안 보여줘요. raw 이벤트와 on_audit_log_entry_create를 조합해 삭제된 내용·삭제한 사람·입퇴장·역할 변경·추방까지 한 채널에 임베드로 쌓는 로그봇을 만듭니다.
- Python 3.10.12 · discord.py 2.7.1 · python-dotenv 1.2.3 · 2026-09-25 직접 실행
- 지워진 메시지 '내용'은 봇 캐시에 있을 때만 보여요 → max_messages를 넉넉히, Message Content·Server Members 인텐트 필수
- 누가 지웠는지는 guild.audit_logs()에서 target·channel·시간(15초)으로 찾고, 본인 삭제는 감사 로그에 기록이 안 남아요
- 추방·차단은 on_audit_log_entry_create(2.2+, Intents.moderation)로 실행자·사유까지 실시간으로 받습니다
목차13개 항목

안녕하세요! Mojangcam입니다 😎
지난 #31에서는 로비에 들어가면 자동으로 생기고 비면 사라지는 임시 음성채널(Temp Voice) 봇을 만들었어요. 이번 편은 서버 운영자라면 한 번쯤 꼭 찾게 되는 기능입니다. "이거 누가 지웠어?"
오늘 만들 로그봇은 이런 일을 해요.
- 누군가 메시지를 지우면 지우기 전 내용이 그대로 로그 채널에 남아요
- 관리자가 남의 메시지를 지웠다면 누가 지웠는지까지 찍혀요
- 멤버가 들어오면 계정 생성일(부계정·봇 판별!), 나가면 갖고 있던 역할이 남고요
- 추방·차단은 실행한 관리자와 사유까지 실시간으로 기록됩니다
왜 봇이 필요할까요? 🧠 디스코드에도 서버 설정 → 감사 로그 메뉴가 있긴 한데, 지워진 메시지의 내용은 안 보여줘요. "누가 몇 개 지웠다"까지만 나오죠. 분쟁이 생겼을 때 "아까 그 사람이 욕하고 지웠어요"를 증명하려면 봇이 미리 기록해두는 수밖에 없어요.

이번 편의 로그봇은 #21 금지어 필터, #25 에러 핸들링과 함께 "커뮤니티 통합 관리 봇"의 운영 투명성 모듈 역할을 하게 됩니다.
📌 준비물
- Python 3.9 이상 (3.10+ 권장) —
dict[str, int]표기를 써서 3.9부터 동작해요 - discord.py 2.7.1 (2026-09-25 기준 PyPI 최신) —
on_audit_log_entry_create가 2.2부터 생겼으니 구버전이면 꼭 올려주세요 - python-dotenv 1.x
- 디스코드 개발자 포털에서 만든 봇 + 서버 초대 완료 (권한: 감사 로그 보기, 메시지 보내기, 링크 첨부)
pip install -U discord.py python-dotenv프로젝트 폴더에 .env 파일을 만들고 토큰을 넣어주세요. 토큰을 코드에 직접 쓰면 깃허브에 올리다 유출되기 딱 좋아요 😱
token=여기에_봇_토큰💡 .env는 꼭 .gitignore에 추가해두세요. (echo .env >> .gitignore)
⚠️ 이번 편은 특권 인텐트(Privileged Intents) 두 개가 필요해요. 개발자 포털 → Bot 탭에서 Message Content Intent(지워진 메시지 내용을 보려면 필수)와 Server Members Intent(입퇴장·역할 변경 감지)를 켜주세요. 이걸 안 켜면 삭제 로그에 내용이 텅 비어서 나오거나 입퇴장 이벤트가 아예 안 불려요.
🧩 로그봇이 기록할 것들
오늘 만들 로그는 6종류예요. 색으로 구분해서 스크롤만 해도 뭐가 일어났는지 보이게 할 거예요.
| 이벤트 | 기록 내용 | 임베드 색 |
|---|---|---|
| 메시지 삭제 | 작성자, 채널, 지워지기 전 내용, 첨부파일 이름, 지운 사람 | 빨강 |
| 메시지 수정 | 수정 전·후 내용, 메시지 바로가기 | 주황 |
| 입장·퇴장 | 계정 생성일(신규 계정 판별), 입장일, 갖고 있던 역할 | 초록 / 회색 |
| 닉네임·역할 변경 | 변경 전·후, 추가·제거된 역할 | 파랑 |
| 추방·차단·해제 | 실행한 관리자, 대상, 사유 | 짙은 빨강 |
| 채널 생성·삭제 | 채널 이름·종류 | 초록 / 빨강 |
📌 1단계 - 기본 세팅과 인텐트 3개
# discord.py 2.7.1 / Python 3.9+ / python-dotenv 1.x
import os
import json
import asyncio
import discord
from discord import app_commands
from discord.ext import commands
from dotenv import load_dotenv
load_dotenv()
token = os.getenv("token")
CONFIG_FILE = "log_config.json" # {서버ID(str): 로그채널ID(int)}
MAX_CONTENT = 1000 # 임베드 필드 최대 1024자 → 여유 있게 자르기
intents = discord.Intents.default()
intents.message_content = True # 지워진 메시지 '내용'을 보려면 필수 (특권 인텐트)
intents.members = True # 입퇴장·닉네임·역할 변경 감지 (특권 인텐트)
intents.moderation = True # 추방·차단 + 감사 로그 이벤트
bot = commands.Bot(command_prefix="!", intents=intents, max_messages=5000)
log_channels: dict[str, int] = {}
lock = asyncio.Lock()하나씩 뜯어볼게요 💡
- intents.message_content : 다른 사람이 쓴 메시지의 내용을 봇이 볼 수 있게 해요. 이게 꺼져 있으면 삭제 로그에
content가 빈 문자열로 들어와요. 포털에서도 켜야 하는 특권 인텐트예요. - intents.members :
on_member_join/on_member_remove/on_member_update가 이 인텐트에 묶여 있어요. 역시 특권 인텐트. - intents.moderation :
on_member_ban/on_member_unban그리고 오늘의 주인공on_audit_log_entry_create가 여기 속해요. 이건 특권이 아니라서 코드에서만 켜면 됩니다. (예전 이름intents.bans와 같은 거예요.) - max_messages=5000 : 봇이 기억해두는 메시지 개수예요. 기본값은 1000인데, 삭제된 메시지의 내용은 이 캐시에 있을 때만 볼 수 있어서 넉넉하게 올렸어요.
📌 2단계 - 로그 채널 저장과 임베드 공통 함수
서버마다 로그 채널이 다르니까 {서버ID: 채널ID}를 JSON에 저장해요. #31과 똑같은 패턴이에요.
def save():
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
json.dump(log_channels, f, ensure_ascii=False, indent=2)
def load():
global log_channels
if os.path.exists(CONFIG_FILE):
with open(CONFIG_FILE, encoding="utf-8") as f:
log_channels = json.load(f)
def get_log_channel(guild: discord.Guild):
cid = log_channels.get(str(guild.id))
return guild.get_channel(cid) if cid else None
def make_embed(title: str, color: discord.Color, user=None) -> discord.Embed:
embed = discord.Embed(title=title, color=color, timestamp=discord.utils.utcnow())
if user is not None:
embed.set_author(name=f"{user} ({user.id})", icon_url=user.display_avatar.url)
return embed
async def send_log(guild: discord.Guild, embed: discord.Embed):
channel = get_log_channel(guild)
if channel is None:
return
try:
await channel.send(embed=embed)
except discord.Forbidden:
print(f"⚠️ {guild.name}: 로그 채널에 메시지를 보낼 권한이 없어요.")
def cut(text: str) -> str:
text = text or "(내용 없음 — 임베드/첨부만 있거나 캐시에 없음)"
return text if len(text) <= MAX_CONTENT else text[:MAX_CONTENT] + " …(생략)"- timestamp=discord.utils.utcnow() : 임베드 아래에 "오늘 오후 3:12" 같은 시간이 보는 사람의 시간대로 자동 표시돼요. 로그에서 제일 중요한 게 시간이잖아요!
- set_author(name, icon_url) : 임베드 맨 위에 프로필 사진 +
이름 (ID)를 박아둬요. ID를 같이 남기는 이유는, 나중에 닉네임을 바꾸거나 서버를 나가도 ID는 안 변하기 때문이에요. - send_log의 try/except : 로그 채널 권한을 누가 바꿔버리면 봇이
Forbidden으로 죽을 수 있어요. #25에서 배운 대로 잡아서 콘솔에만 찍고 넘어갑니다. - cut() : 임베드 필드 값은 1024자 제한이 있어요. 넘기면
400 Bad Request가 나니까 미리 잘라요.
📌 3단계 - 핵심! 메시지 삭제·수정 잡기
여기서 raw 이벤트를 써요. on_message_delete는 캐시에 있는 메시지만 알려주는데, on_raw_message_delete는 캐시에 없어도 "어느 채널의 어떤 ID가 지워졌다"까지는 알려주거든요.

async def find_deleter(guild: discord.Guild, author_id: int, channel_id: int):
"""감사 로그에서 '방금 이 사람 메시지를 이 채널에서 지운 관리자'를 찾아요."""
try:
async for entry in guild.audit_logs(limit=5, action=discord.AuditLogAction.message_delete):
same_target = entry.target is not None and entry.target.id == author_id
same_channel = entry.extra.channel.id == channel_id
recent = (discord.utils.utcnow() - entry.created_at).total_seconds() < 15
if same_target and same_channel and recent:
return entry.user
except discord.Forbidden:
return None
return None
@bot.event
async def on_raw_message_delete(payload: discord.RawMessageDeleteEvent):
if payload.guild_id is None: # DM은 무시
return
guild = bot.get_guild(payload.guild_id)
msg = payload.cached_message
embed = make_embed("🗑️ 메시지 삭제", discord.Color.red(), msg.author if msg else None)
embed.add_field(name="채널", value=f"<#{payload.channel_id}>", inline=True)
if msg is None:
embed.add_field(name="내용", value="캐시에 없는 메시지예요 (봇 시작 전 메시지)", inline=False)
embed.set_footer(text=f"메시지 ID: {payload.message_id}")
else:
if msg.author.bot:
return
deleter = await find_deleter(guild, msg.author.id, payload.channel_id)
who = deleter.mention if deleter else "본인 (또는 확인 불가)"
embed.add_field(name="삭제한 사람", value=who, inline=True)
embed.add_field(name="내용", value=cut(msg.content), inline=False)
if msg.attachments:
files = "\n".join(a.filename for a in msg.attachments)
embed.add_field(name="첨부파일", value=files, inline=False)
embed.set_footer(text=f"메시지 ID: {msg.id}")
await send_log(guild, embed)
@bot.event
async def on_raw_message_edit(payload: discord.RawMessageUpdateEvent):
if payload.guild_id is None:
return
before = payload.cached_message
after = payload.message
if before is None or after.author.bot:
return
if before.content == after.content: # 임베드 미리보기 생성 등 '가짜 수정' 걸러내기
return
guild = bot.get_guild(payload.guild_id)
embed = make_embed("✏️ 메시지 수정", discord.Color.orange(), after.author)
embed.add_field(name="채널", value=after.channel.mention, inline=True)
embed.add_field(name="바로가기", value=f"[메시지로 이동]({after.jump_url})", inline=True)
embed.add_field(name="수정 전", value=cut(before.content), inline=False)
embed.add_field(name="수정 후", value=cut(after.content), inline=False)
await send_log(guild, embed)
@bot.event
async def on_raw_bulk_message_delete(payload: discord.RawBulkMessageDeleteEvent):
if payload.guild_id is None:
return
guild = bot.get_guild(payload.guild_id)
embed = make_embed("🧹 메시지 대량 삭제", discord.Color.dark_red())
embed.add_field(name="채널", value=f"<#{payload.channel_id}>", inline=True)
embed.add_field(name="개수", value=f"{len(payload.message_ids)}개", inline=True)
await send_log(guild, embed)하나씩 뜯어볼게요 💡
- payload.cached_message : 지워진 메시지가 봇 캐시에 있었으면
Message객체, 없으면None이에요. 봇을 켜기 전에 올라온 메시지는 캐시에 없으니 "내용 확인 불가"로 남기되, 채널과 메시지 ID는 남겨요. 그래도 조사에 도움이 됩니다. - guild.audit_logs(limit=5, action=...) : 서버 감사 로그를 최신순으로 훑는 비동기 이터레이터예요.
for가 아니라async for로 돌려야 해요. 봇에 감사 로그 보기 권한이 없으면Forbidden이 나니까 try로 감쌌어요. - entry.target / entry.extra.channel / entry.created_at :
message_delete항목은 target이 메시지를 쓴 사람,extra.channel이 지워진 채널,extra.count가 지운 개수예요. 세 가지가 맞고 15초 안에 생긴 항목이면 그entry.user(실행자)가 지운 사람이에요. - "본인 (또는 확인 불가)" : 자기 메시지를 스스로 지우면 감사 로그에 아예 기록이 안 남아요. 그래서 감사 로그에서 못 찾으면 본인 삭제로 봅니다. 다만 같은 관리자가 같은 사람 메시지를 연달아 지우면 디스코드가 기존 항목의 count만 올리고 새 항목을 안 만들어서, 15초 조건에 걸려 놓치는 경우가 있어요. 100% 정확한 건 아니라는 점만 기억해주세요.
- payload.message (수정 이벤트) : 수정 후 메시지예요(2.5부터 추가). 수정 전은
cached_message에서 가져와요. 링크를 붙였을 때 디스코드가 미리보기 임베드를 만들면서 수정 이벤트가 한 번 더 오는데,content가 같으면 건너뛰게 해서 로그가 지저분해지는 걸 막았어요. - on_raw_bulk_message_delete : 관리자가 "메시지 100개 삭제" 같은 대량 삭제(purge)를 하면 개별 삭제 이벤트 대신 이게 한 번 와요. 내용을 전부 남기면 로그가 폭발하니 개수만 기록했어요.
📌 4단계 - 입장·퇴장·닉네임·역할 변경
@bot.event
async def on_member_join(member: discord.Member):
embed = make_embed("📥 멤버 입장", discord.Color.green(), member)
embed.add_field(name="멤버", value=member.mention, inline=True)
embed.add_field(name="계정 생성일", value=discord.utils.format_dt(member.created_at, "R"), inline=True)
embed.add_field(name="현재 인원", value=f"{member.guild.member_count}명", inline=True)
await send_log(member.guild, embed)
@bot.event
async def on_member_remove(member: discord.Member):
embed = make_embed("📤 멤버 퇴장", discord.Color.light_grey(), member)
embed.add_field(name="멤버", value=f"{member} ({member.mention})", inline=True)
if member.joined_at:
embed.add_field(name="입장일", value=discord.utils.format_dt(member.joined_at, "R"), inline=True)
roles = [r.mention for r in member.roles if r != member.guild.default_role]
if roles:
embed.add_field(name="가지고 있던 역할", value=" ".join(roles), inline=False)
await send_log(member.guild, embed)
@bot.event
async def on_member_update(before: discord.Member, after: discord.Member):
if before.nick != after.nick:
embed = make_embed("🏷️ 닉네임 변경", discord.Color.blue(), after)
embed.add_field(name="변경 전", value=before.nick or "(없음)", inline=True)
embed.add_field(name="변경 후", value=after.nick or "(없음)", inline=True)
await send_log(after.guild, embed)
added = [r for r in after.roles if r not in before.roles]
removed = [r for r in before.roles if r not in after.roles]
if added or removed:
embed = make_embed("🎭 역할 변경", discord.Color.blurple(), after)
if added:
embed.add_field(name="➕ 추가", value=" ".join(r.mention for r in added), inline=False)
if removed:
embed.add_field(name="➖ 제거", value=" ".join(r.mention for r in removed), inline=False)
await send_log(after.guild, embed)- format_dt(시각, "R") :
<t:1790291186:R>형식으로 바꿔줘서 디스코드가 "3일 전", "2년 전"처럼 상대 시간으로 보여줘요. 입장 로그에서 계정 생성일이 "12분 전"이면? 봇 계정이나 부계정일 확률이 높죠. 운영자들이 제일 좋아하는 필드예요 👀 - on_member_remove : 스스로 나간 것도, 추방·차단당한 것도 전부 이 이벤트로 와요. 그래서 "왜 나갔는지"는 다음 단계의 감사 로그 이벤트로 따로 기록해요.
- before.roles / after.roles 비교 :
on_member_update는 닉네임·역할·타임아웃·서버 프로필 사진 등 뭐든 바뀌면 불려요. 그래서 안에서 "뭐가 바뀌었는지" 직접 비교해야 해요. 역할 리스트 차집합으로 추가·제거를 구분했어요.
📌 5단계 - 추방·차단은 감사 로그 이벤트로 (누가·왜)
MOD_ACTIONS = {
discord.AuditLogAction.kick: ("👢 추방(Kick)", discord.Color.dark_orange()),
discord.AuditLogAction.ban: ("🔨 차단(Ban)", discord.Color.dark_red()),
discord.AuditLogAction.unban: ("🕊️ 차단 해제(Unban)", discord.Color.teal()),
}
@bot.event
async def on_audit_log_entry_create(entry: discord.AuditLogEntry):
if entry.action not in MOD_ACTIONS:
return
title, color = MOD_ACTIONS[entry.action]
embed = make_embed(title, color, entry.user)
target = entry.target
embed.add_field(name="대상", value=f"{target} (<@{target.id}>)" if target else "(알 수 없음)", inline=True)
embed.add_field(name="실행자", value=entry.user.mention if entry.user else "(알 수 없음)", inline=True)
embed.add_field(name="사유", value=entry.reason or "(사유 없음)", inline=False)
await send_log(entry.guild, embed)- on_audit_log_entry_create : 서버 감사 로그에 새 항목이 생기는 순간 실시간으로 호출돼요(discord.py 2.2+,
Intents.moderation필요, 봇에 감사 로그 보기 권한 필수). 3단계처럼 뒤늦게 뒤지는 게 아니라 디스코드가 먼저 알려주는 거라 훨씬 정확해요. - entry.action : 무슨 행동인지 나타내는
AuditLogAction열거형이에요. 오늘은 추방·차단·해제 세 개만 골랐지만channel_create,role_update,member_role_update등 수십 가지가 있어요. 딕셔너리에 한 줄만 추가하면 로그 종류가 늘어나요. - entry.user / entry.target / entry.reason : 각각 실행한 관리자, 당한 사람, 그리고 관리자가 추방할 때 적은 사유예요. 사유를 안 적으면
None이니 "(사유 없음)"으로 바꿔줬어요.
💡 실행자가 None으로 나와요? 유저 정보 캐시에 없으면 그럴 수 있어요. entry.user_id(2.2+)는 항상 들어 있으니 f"<@{entry.user_id}>"로 멘션만 찍어도 돼요.
📌 6단계 - 채널 생성·삭제 + 로그 채널 설정 명령어
@bot.event
async def on_guild_channel_create(channel: discord.abc.GuildChannel):
embed = make_embed("📁 채널 생성", discord.Color.green())
embed.add_field(name="채널", value=f"{channel.mention} (`{channel.name}`)", inline=True)
embed.add_field(name="종류", value=str(channel.type), inline=True)
await send_log(channel.guild, embed)
@bot.event
async def on_guild_channel_delete(channel: discord.abc.GuildChannel):
embed = make_embed("🗂️ 채널 삭제", discord.Color.red())
embed.add_field(name="채널", value=f"`{channel.name}`", inline=True)
embed.add_field(name="종류", value=str(channel.type), inline=True)
await send_log(channel.guild, embed)
@bot.tree.command(name="로그설정", description="[관리자] 서버 로그를 보낼 채널을 정해요")
@app_commands.describe(채널="로그가 쌓일 텍스트 채널")
@app_commands.checks.has_permissions(administrator=True)
async def set_log(interaction: discord.Interaction, 채널: discord.TextChannel):
me = interaction.guild.me
perms = 채널.permissions_for(me)
if not (perms.send_messages and perms.embed_links):
return await interaction.response.send_message(
f"❌ {채널.mention}에 **메시지 보내기·링크 첨부** 권한이 없어요.", ephemeral=True)
async with lock:
log_channels[str(interaction.guild.id)] = 채널.id
save()
await interaction.response.send_message(f"✅ 이제 서버 로그를 {채널.mention}에 기록할게요!", ephemeral=True)
await 채널.send(embed=make_embed("🟢 로그 채널로 설정됐어요", discord.Color.green(), interaction.user))
@bot.tree.command(name="로그해제", description="[관리자] 서버 로그 기록을 멈춰요")
@app_commands.checks.has_permissions(administrator=True)
async def unset_log(interaction: discord.Interaction):
async with lock:
log_channels.pop(str(interaction.guild.id), None)
save()
await interaction.response.send_message("⏹️ 로그 기록을 껐어요.", ephemeral=True)
@bot.tree.error
async def on_app_command_error(interaction: discord.Interaction, error: app_commands.AppCommandError):
if isinstance(error, app_commands.MissingPermissions):
await interaction.response.send_message("❌ 관리자만 쓸 수 있는 명령어예요.", ephemeral=True)
else:
raise error
@bot.event
async def on_ready():
load()
await bot.tree.sync()
print(f"✅ 로그인: {bot.user} / 로그 설정된 서버 {len(log_channels)}개")
bot.run(token)
- 채널: discord.TextChannel : 슬래시 명령어 인자의 타입을 채널로 두면 디스코드가 채널 선택 UI를 띄워줘요. #15 명령어 그룹 편에서 본 그 기능이에요.
- 채널.permissions_for(me) : 로그 채널에 봇이 실제로 글을 쓸 수 있는지 설정 시점에 미리 검사해요. 나중에 이벤트가 터졌을 때 조용히 실패하는 것보다 지금 알려주는 게 훨씬 친절하죠.
- bot.tree.error : 관리자가 아닌 사람이 명령어를 치면
MissingPermissions가 나는데, 안 잡으면 "상호작용에 실패했습니다"라는 불친절한 메시지만 떠요. #25에서 잡은 패턴 그대로예요.
📎 1~6단계 코드블록을 순서대로 이어 붙이면 그대로 완성본이에요. logbot.py로 저장하고 바로 돌리시면 됩니다.
📌 실행 결과
python logbot.py실행 → 콘솔에✅ 로그인: 봇이름 / 로그 설정된 서버 0개- 관리자가
#서버-로그같은 비공개 채널을 만들고/로그설정 채널:#서버-로그→ 초록색 "🟢 로그 채널로 설정됐어요" 임베드가 도착해요. - 아무 채널에서 메시지를 쓰고 지워보세요 → 🗑️ 빨간 임베드에 지운 내용이 그대로 남아요. 관리자가 남의 메시지를 지우면 "삭제한 사람"에 관리자 멘션이 찍힙니다.
- 메시지를 수정하면 ✏️ 주황 임베드에 수정 전·후가 나란히, 링크를 누르면 그 메시지로 점프해요.
- 부계정으로 서버에 들어왔다 나가보면 📥 입장(계정 생성일 "N분 전"!) → 📤 퇴장 순서로 쌓이고, 추방하면 📤 퇴장 뒤에 👢 추방 임베드가 실행자·사유와 함께 붙어요.
💡 로그 채널은 꼭 관리자만 보이게 권한을 잠가두세요. 지워진 메시지 내용이 그대로 남는 채널이라, 일반 멤버가 보면 "지웠는데 왜 남아있냐"는 항의가 들어오거든요. 한 달에 한 번쯤 오래된 로그를 정리하는 것도 잊지 마시고요.
💡 보너스 - 로그 종류 늘리기
MOD_ACTIONS 딕셔너리에 항목만 추가하면 감사 로그 종류가 늘어나요. 자주 쓰는 것들이에요.
discord.AuditLogAction.member_update: ("⏳ 타임아웃/닉네임 변경", discord.Color.gold()),
discord.AuditLogAction.role_create: ("🎨 역할 생성", discord.Color.green()),
discord.AuditLogAction.role_delete: ("🎨 역할 삭제", discord.Color.red()),
discord.AuditLogAction.message_bulk_delete: ("🧹 대량 삭제 (관리자)", discord.Color.dark_red()),entry.changes.before / entry.changes.after를 찍어보면 "역할 이름이 A에서 B로", "타임아웃이 언제까지" 같은 세부 변경 내용까지 뽑을 수 있어요. 음성채널 입퇴장 로그는 #31에서 쓴 on_voice_state_update에 임베드 한 줄만 붙이면 끝이고요.
📌 자주 나는 에러
- 삭제 로그에 내용이 "(내용 없음)"으로만 나와요 → 원인: 개발자 포털에서 Message Content Intent를 안 켰거나 코드에
intents.message_content = True가 빠짐 → 해결: 둘 다 켜고 봇 재시작. 봇을 켜기 전에 올라온 메시지는 캐시에 없어서 원래 안 보이는 게 정상이에요. - on_member_join / on_member_remove가 안 불려요 → 원인: Server Members Intent 미설정 → 해결: 포털 Bot 탭에서 켜고
intents.members = True확인. 이걸 안 켜면 에러도 없이 조용히 안 불려서 헷갈려요. discord.errors.Forbidden: 403 Forbidden (error code: 50013): Missing Permissions→ 원인: 봇 역할에 감사 로그 보기 권한이 없음 → 해결: 서버 설정 → 역할 → 봇 역할에 "감사 로그 보기" 체크.on_audit_log_entry_create도 이 권한이 없으면 안 불려요.HTTPException: 400 Bad Request (error code: 50035): Invalid Form Body—In embeds.0.fields.2.value: Must be 1024 or fewer in length→ 원인: 임베드 필드에 1024자를 넘는 내용을 넣음 → 해결: 예제의cut()처럼 자르기. 긴 메시지가 잦다면 내용을discord.File로 텍스트 파일 첨부하는 방법도 좋아요.TypeError: 'async_generator' object is not iterable→ 원인:guild.audit_logs()를for로 돌림 → 해결:async for로 바꾸기. 비동기 이터레이터예요.- 수정 로그가 링크 붙일 때마다 두 번 떠요 → 원인: 디스코드가 링크 미리보기(임베드)를 붙이면서 수정 이벤트를 한 번 더 보냄 → 해결: 예제처럼
before.content == after.content면 건너뛰기. discord.errors.PrivilegedIntentsRequired로 봇이 시작조차 안 돼요 → 원인: 코드에서는 특권 인텐트를 켰는데 포털에서는 안 켠 상태 → 해결: 포털 Bot 탭 → Privileged Gateway Intents에서 두 개 모두 ON.
🙌 마무리
오늘은 raw 이벤트 + 감사 로그(Audit Log) 조회 + on_audit_log_entry_create 세 가지를 조합해서, 메시지 삭제·수정부터 입퇴장·역할 변경·추방·차단까지 한 채널에 임베드로 기록하는 로그봇을 완성했어요. 정리하면 이래요.
- 지워진 메시지 내용은 캐시에 있을 때만 볼 수 있다 →
max_messages를 넉넉히, 특권 인텐트 2개 ON - 누가 지웠는지는
guild.audit_logs()에서 target·channel·시간으로 찾는다 (본인 삭제는 기록 없음) - 추방·차단은
on_audit_log_entry_create로 실행자·사유까지 실시간으로 받는다 - 임베드 필드 1024자 제한, 로그 채널은 관리자 전용으로 잠그기
다음 편에서는 #31에서 예고했던 임시 음성방 확장 — 방장 위임·강퇴·초대 전용(화이트리스트) 방과 텍스트 채널이 같이 생기는 "파티방"을 다뤄볼게요. 기대해주세요 🙌
여러분 서버에선 어떤 로그가 제일 필요하세요? 댓글로 알려주시면 보너스 항목에 추가해볼게요 😎
📚 함께 보면 좋은 글
연재 · 32편 중 31번째
디스코드 봇 만들기
- #1디스코드 봇 만들기 - Slash Command 완전 정복!
- #2Slash Command에 옵션(Arguments) 추가하기!
- #3Slash Command에 서브 커맨드(Subcommand) 넣기!
- #4Slash Command로 임베드 전송하기!
- #5버튼(Button) 상호작용 구현하기!
- #6드롭다운(Select Menu) 메뉴 구현하기!
- #7모달(Modal)로 입력 받기 구현하기 (파이썬 예제)
- #8임베드(Embed) 이미지·필드로 꾸미기 (파이썬 예제)
- #9AI(Claude·ChatGPT API) 연동하기! (파이썬 예제)
- #10다국어(i18n) 지원하기! (파이썬 예제)
- #11플라스크(Flask)로 웹 대시보드 만들기! (파이썬 예제)
- #12명령어 쿨다운(Cooldown)·권한(Permission) 관리하기! (파이썬 예제)
- #13역할·채널 자동화로 서버 관리하기! (파이썬 예제)
- #14데이터 저장하기! JSON vs SQLite (파이썬 예제)
- #15슬래시 명령어 그룹(Group)으로 명령어 정리하기! (파이썬 예제)
- #16리마인더·알림 기능 만들기! 봇이 먼저 말 걸게 하기 (파이썬 예제)
- #17RAG로 문서 기반 답변봇 만들기! 우리 서버 전용 FAQ 봇 (파이썬 예제)
- #18투표(Poll) 기능 만들기! 다수결로 정하는 서버 규칙 (파이썬 예제)
- #19Cog로 코드 쪼개기! 1000줄 봇도 안 더러워지는 구조 (파이썬 예제)
- #20자동 환영 메시지·온보딩 만들기! 규칙 동의 버튼으로 자동 역할 부여 (파이썬 예제)
- #21키워드 자동응답·금지어 필터 만들기! 경고 누적으로 자동 제재까지 (파이썬 예제)
- #22슬래시 명령어 자동완성(Autocomplete) 만들기! 금지어를 골라서 추가·삭제 (파이썬 예제)
- #23aiosqlite로 비동기 DB 제대로 설계하기! JSON 탈출 (파이썬 예제)
- #24채널 난독화(Channel Obfuscation) 대응하기! 11/16부터 안 보이는 채널이 생겨요 (파이썬 예제)
- #25봇이 죽지 않게! 에러 핸들링·로깅 체계 잡기 (파이썬 예제)
- #26티켓(Ticket) 문의 시스템 만들기! 버튼 하나로 1:1 상담 채널 자동 생성 (파이썬 예제)
- #27티켓 담당자 배정·우선순위 만들기! 스태프가 여러 명이어도 안 꼬이게 (파이썬 예제)
- #28레벨·경험치(XP) 시스템 만들기! 채팅할수록 레벨업, 랭킹판까지 (파이썬 예제)
- #29화폐·상점(Economy) 시스템 만들기! 코인 벌어서 아이템 사기 (파이썬 예제)
- #30투표·설문(Poll) 봇 만들기! 네이티브 투표 + 버튼으로 실시간 집계까지 (파이썬 예제) 🗳️
- #32서버 로그·감사(Audit Log) 채널 만들기! 메시지 삭제·수정·입퇴장·추방까지 자동 기록 (파이썬 예제) 🕵️지금 읽는 글
- #33임시 음성방 확장, 파티방 만들기! 방장 위임·강퇴·초대 전용(비공개) 방 + 전용 채팅방 자동 생성 (파이썬 예제) 🎉
