Skip to content

🔐 Logging & Privacy

This page documents Magikal's logging and privacy rules.

The goal is simple:

  • logs must be useful for debugging
  • logs must not expose private user data
  • logs must not contain raw Discord IDs, mentions, tokens, or secrets
  • user-related records should use privacy-safe PIDs

Standard logger

Use structlog.

Common pattern:

import structlog

logger = structlog.get_logger(__name__)

Then log with stable event names:

logger.info(event="tickets.open.created", result="ok")

Event names

Use stable, searchable event names.

Good:

logger.info(event="tickets.open.created")
logger.warning(event="tickets.record.update_failed", error=str(e))
logger.info(event="tempvc.owner_transferred")
logger.warning(event="modlog.channel_missing")

Avoid vague logs:

logger.info("done")
logger.warning("failed")
logger.error("something broke")

Good event names make logs searchable with grep.

Example:

sudo journalctl -u magikal-bot -n 300 --no-pager | grep "tickets."

Key/value logging

Use structured key/value fields.

Good:

logger.info(
    event="ticket.closed",
    result="ok",
    ticket_type="recruitment",
)

Avoid string-building logs that include live objects or IDs:

logger.info(f"Closed ticket for user {user.id}")

Privacy-safe PIDs

Use make_pid for user-related log fields and database records.

Helper:

from bot.utils.hasher import make_pid

Example:

user_pid = make_pid(guild.id, member.id)
actor_pid = make_pid(guild.id, interaction.user.id)

Common field names:

Field Meaning
guild_pid Privacy-safe guild identifier
user_pid Privacy-safe target user identifier
actor_pid Privacy-safe staff/action user identifier
channel_pid Privacy-safe channel identifier
member_pid Privacy-safe member identifier

What not to log

Do not log:

  • raw Discord user IDs
  • raw Discord guild IDs
  • raw Discord channel IDs
  • Discord mentions
  • Discord objects
  • bot tokens
  • API keys
  • database URLs
  • OAuth secrets
  • session cookies
  • private message contents
  • recruitment answers unless intentionally stored in approved records
  • full tracebacks in user-facing messages

Discord objects

Never log full Discord objects.

Avoid:

logger.info(event="thing", user=member)
logger.info(event="thing", channel=channel)
logger.info(event="thing", guild=guild)

Use safe fields instead:

logger.info(
    event="thing",
    user_pid=make_pid(guild.id, member.id),
    channel_pid=make_pid(guild.id, channel.id),
    guild_pid=make_pid(guild.id, guild.id),
)

Error logging

Keep errors useful but short.

Good:

logger.warning(event="tickets.safe_edit_failed", error=str(e))

Better if context is needed:

logger.warning(
    event="tickets.safe_edit_failed",
    error=str(e),
    result="ignored_after_channel_delete",
)

Avoid dumping huge objects or raw request payloads.

Expected Discord errors

Expected Discord errors are not always fatal

Some Discord errors are expected in normal flows.

Example:

  • ticket finalisation may delete a channel
  • a later edit may fail with Unknown Channel
  • that should be logged as a handled warning or ignored after delete

Do not treat every Discord HTTP error as a fatal bot error.

Document expected errors in the relevant feature page.

User-facing errors

User-facing messages should be simple.

Good:

Something went wrong while saving that setting. Please try again or check the logs.

Bad:

Full Python traceback...
database connection string...
raw exception with private details...

Log buckets and modlogs

Bot logs and Discord modlogs are different things.

System logs are for debugging the bot.

Discord modlogs are for server staff visibility.

System logs must stay privacy-safe.

Discord modlog embeds may show normal Discord-facing context where appropriate, because they are posted inside the server for staff/admin use.

Existing legacy logs

Some older live files still contain older patterns, including raw guild_id or channel_id fields.

Do not copy those patterns into new code.

When editing older code, improve logging to privacy-safe fields if:

  • it is safe
  • it is in scope
  • it does not risk breaking the feature

Good log examples

Ticket action:

logger.info(
    event="tickets.recruit.approved",
    guild_pid=make_pid(guild.id, guild.id),
    user_pid=make_pid(guild.id, applicant.id),
    actor_pid=make_pid(guild.id, staff.id),
)

Temp VC owner transfer:

logger.info(
    event="tempvc.owner_transferred",
    guild_pid=make_pid(guild.id, guild.id),
    channel_pid=make_pid(guild.id, channel.id),
    old_owner_pid=old_owner_pid,
    new_owner_pid=make_pid(guild.id, new_owner.id),
)

Moderation action:

logger.info(
    event="modaction.persisted",
    action="timeout",
    pid=make_pid(guild.id, member.id),
    actor_pid=make_pid(guild.id, moderator.id),
)

Bad log examples

Do not do this:

logger.info(f"User {member.id} was timed out by {ctx.author.id}")

Do not do this:

logger.info(event="ticket", user=interaction.user)

Do not do this:

logger.error(event="config.failed", database_url=config.database_url)

Do not do this:

logger.warning(event="oauth.failed", token=access_token)

Searching logs

Recent bot logs:

sudo journalctl -u magikal-bot -n 120 --no-pager

Follow live logs:

sudo journalctl -u magikal-bot -f

Search by event prefix:

sudo journalctl -u magikal-bot -n 300 --no-pager | grep "tickets."

Search for errors:

sudo journalctl -u magikal-bot -n 300 --no-pager | grep -i error

Search for tracebacks:

sudo journalctl -u magikal-bot -n 300 --no-pager | grep -i traceback

Sharing logs safely

Before pasting logs into chat:

  1. Check for tokens.
  2. Check for database URLs.
  3. Check for OAuth secrets.
  4. Check for raw Discord IDs.
  5. Check for private text content.
  6. Prefer the smallest useful log section.

Useful standard paste for bot issues:

sudo systemctl status magikal-bot --no-pager
sudo journalctl -u magikal-bot -n 120 --no-pager

New feature checklist

When adding a new cog, command, or panel-backed feature:

  • add a stable event name for important actions
  • log success/failure where useful
  • avoid raw Discord IDs
  • use make_pid for user/member/staff references
  • avoid logging secrets
  • keep error strings short
  • add feature-specific troubleshooting notes to docs if needed

Rule

Logs should help fix problems without exposing people.