165 lines
12 KiB
Markdown
165 lines
12 KiB
Markdown
# Utilities, Bootstrap, Logging & Exceptions
|
|
|
|
This document covers the cross-cutting glue of the bot: the entry points, configuration, logging /
|
|
error tracking, the shared utility helpers, and the custom exception hierarchy.
|
|
|
|
## Bootstrap
|
|
|
|
**Package:** `me.bobert.netneutrality` / `me.bobert.netneutrality.bootstrap`
|
|
|
|
| Class | Role |
|
|
|--------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
| `App` | The application: holds the process instance and the `JDA` handle, builds the `ApplicationContext`, runs `context.start()` / `context.stop()`, and is the JAR main class (`main()`). See [architecture.md](architecture.md). |
|
|
| `bootstrap.ApplicationContext` | Hand-rolled container: `register(T)`, `get(Class<T>)`, ordered `start()` / `stop()` for `Lifecycle` components. |
|
|
| `bootstrap.Lifecycle` | Optional component contract: `start() throws Exception`, default `stop()`, default `priority()`. |
|
|
| `core.BotState` | Work-in-progress global state helper: exposes a `SAFE` flag and `dump()`, which serializes per-guild/music state to `state.json`. Bound to `GuildServiceProvider` / `MusicService` during bootstrap. |
|
|
|
|
The full startup and shutdown sequences are documented in
|
|
[architecture.md](architecture.md#startup-lifecycle).
|
|
|
|
### `ApplicationContext` + `Lifecycle`
|
|
|
|
Domain services are constructed once in `App.buildContext()`, registered with the context,
|
|
and resolved later by type:
|
|
|
|
- `register(component)` — indexes the concrete class; if the component implements `Lifecycle` it
|
|
participates in ordered start/stop.
|
|
- `get(Class<T>)` — returns the first registered instance assignable to `T` (interfaces work).
|
|
- `start()` — sorts `Lifecycle` components by ascending `priority()` and starts them.
|
|
- `stop()` — stops them in the exact reverse order; individual failures are logged and swallowed so
|
|
remaining components still tear down.
|
|
|
|
Constructor injection for reflectively loaded commands/tasks is handled by
|
|
[`ReflectionHelper`](#reflectionhelper): the richest public constructor whose parameter types all
|
|
resolve via `context.get(type)` is preferred; otherwise the no-arg constructor is used.
|
|
|
|
**Interfaces used for injection** (implemented by the concrete managers registered above):
|
|
|
|
| Interface | Typical implementor |
|
|
|-----------------------------------------|---------------------------------------|
|
|
| `platform.config.SettingsProvider` | `utils.Settings` |
|
|
| `platform.persistence.SessionProvider` | `core.db.SessionManager` |
|
|
| `platform.concurrency.ExecutorProvider` | `core.thread.ThreadManager` |
|
|
| `framework.command.CommandRegistry` | `core.command.CommandManager` |
|
|
| `framework.task.TaskManager` | `core.tasks.TaskManagerImpl` |
|
|
| `feature.music.MusicService` | `music.MusicServiceImpl` |
|
|
| `data.service.GuildServiceProvider` | `db.service.GuildServiceProviderImpl` |
|
|
|
|
There are **no** `getInstance()` / `instance()` shims on the managers above. The only intentional
|
|
static access points left are:
|
|
|
|
- `Settings.getInstance()` — early bootstrap only (`main()`, Sentry, token validation) before the
|
|
context exists; the same instance is registered as `SettingsProvider` for everything else.
|
|
- `App.getInstance()` — application root used by a couple of shutdown entry points
|
|
(`ShutdownCommand`). Prefer constructor injection of the needed collaborators where possible.
|
|
- `JpaUtils.bind(SessionProvider)` / `LogHandler.bind(GuildServiceProvider)` /
|
|
`ChannelMessagePurger.bind(ExecutorProvider)` — single well-defined static bind points for deep
|
|
utility graphs that are not constructor-injected (repositories, static log helpers).
|
|
|
|
## Configuration
|
|
|
|
### `Settings` / `SettingsProvider`
|
|
|
|
**Class:** `me.bobert.netneutrality.platform.config.Settings` implements
|
|
`me.bobert.netneutrality.platform.config.SettingsProvider`.
|
|
|
|
`Settings` is the single source of truth for configuration. It loads values from the `bot.properties`
|
|
file and overlays **environment variables** on top (environment variables win).
|
|
|
|
- **Early bootstrap:** `Settings.getInstance()` (process-wide, created once before the context).
|
|
- **Runtime injection:** depend on `SettingsProvider` and let `ApplicationContext` supply the same
|
|
instance. Prefer this path in commands, tasks, handlers, and services.
|
|
- `Settings.getVersion()` / `Settings.getDiscordInvite()` — thin static helpers used by the banner /
|
|
invite strings; they delegate to the bootstrap instance.
|
|
|
|
An example configuration file is `bot.properties.example`. Key configuration values:
|
|
|
|
| Key | Purpose |
|
|
|------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------|
|
|
| `NETNEUTRALITY_BOT_ENV` | `dev` / `prod`; controls debug logging and the Sentry environment. |
|
|
| `NETNEUTRALITY_BOT_TOKEN` | Discord bot token (**required**). |
|
|
| `NETNEUTRALITY_BOT_PREFIX` | Message-command prefix (default `$`). |
|
|
| `NETNEUTRALITY_BOT_OWNER` | Bot owner user id (used for owner-only commands / permissions). |
|
|
| `NETNEUTRALITY_BOT_INVITE` | Invite link surfaced by help/info. |
|
|
| `NETNEUTRALITY_API_PORT` | Port for the [Web API](web-api.md) (default `9000`). |
|
|
| `MYSQL_*` | Database connection settings (see [persistence.md](persistence.md)). |
|
|
| `NETNEUTRALITY_SENTRY_DSN` | Enables Sentry when present. |
|
|
| `GITLAB_HOST` / `GITLAB_TOKEN` / `GITLAB_PROJECT_ID` | GitLab issue reporting ([`ReportIssueTask`](tasks.md#worked-example--gitlab-bug-report-modal)). |
|
|
| `NETNEUTRALITY_LAVALINK_URI` / `_PASSWORD` / `_NAME` | Connection settings for the [Lavalink node](music.md#settings). |
|
|
| `NETNEUTRALITY_PO_TOKEN` / `NETNEUTRALITY_VISITOR_DATA` / `NETNEUTRALITY_CIPHER_*` | YouTube auth/cipher settings, consumed by the node via `lavalink/application.yml`. |
|
|
|
|
## Utility helpers
|
|
|
|
**Package:** `me.bobert.netneutrality.shared.util` (helpers) / `me.bobert.netneutrality.bootstrap` / `me.bobert.netneutrality.platform.*`
|
|
|
|
| Class | Purpose |
|
|
|---|---|
|
|
| `Settings` | Configuration (see above). |
|
|
| `ReflectionHelper` | Classpath scanning / reflective instantiation (see below). |
|
|
| `ContextUtils` | Boilerplate helpers for building/handling [contexts](contexts.md). |
|
|
| `GuildUtils` | Guild-related helpers (lookups, permission checks). |
|
|
| `JpaUtils` | Session/transaction helpers for the [persistence](persistence.md) layer. Bound once to a `SessionProvider` during bootstrap (`JpaUtils.bind`). |
|
|
| `MusicUtils` | Voice-channel connectivity and DJ/permission checks for [music](music.md). Callers pass the guild `MusicManager` (no static music lookup). |
|
|
| `MiscUtils` | Assorted small helpers. |
|
|
| `ChannelMessagePurger` | Bulk message deletion (used by the purge command). Bound once to an `ExecutorProvider` during bootstrap. |
|
|
| `MessageFunction` | Standardized message/embed formatting. Help text builders take a `CommandRegistry` rather than a static command manager. |
|
|
| `UserFunction` | Standardized user/string formatting. |
|
|
|
|
### `ReflectionHelper`
|
|
|
|
Built on the `org.reflections` library, `ReflectionHelper` discovers and instantiates implementation
|
|
classes in specific packages. It is the mechanism behind the bot's "drop a class in the right package
|
|
and it's picked up" model, used to auto-load:
|
|
|
|
- [`Command`](commands.md) implementations (`CommandManager` / `CommandRegistry.start()`),
|
|
- [`BuiltInTask`](tasks.md#32-built-in-tasks) and [`ScheduledTask`](tasks.md#34-scheduled-tasks)
|
|
implementations (`TaskManager.start()`).
|
|
|
|
When given an `ApplicationContext`, `newInstance(Class, ApplicationContext)` selects the public
|
|
constructor with the most parameters that can all be resolved via `context.get(parameterType)`. If
|
|
no such constructor exists it falls back to the no-arg constructor. That is how music/admin/react
|
|
commands receive `MusicService`, `GuildServiceProvider`, `TaskManager`, etc. without static
|
|
singletons.
|
|
|
|
## Logging & error tracking
|
|
|
|
### `LogHandler`
|
|
|
|
**Class:** `me.bobert.netneutrality.platform.logging.LogHandler`.
|
|
|
|
`LogHandler` is the unified logging/error entry point. It routes messages and exceptions to:
|
|
|
|
1. **Log4j2** (configured by `log4j2.properties`; SLF4J is bridged via `log4j-slf4j2-impl`),
|
|
2. **Sentry** (when `NETNEUTRALITY_SENTRY_DSN` is configured), and
|
|
3. optionally a per-guild **Discord debug channel** (e.g. `sendMessageToDebugChannel(guild, msg)`),
|
|
gated by [`GuildService.isDebugEnabled()`](persistence.md#service-layer) /
|
|
[`OLogging`](persistence.md#entity-catalog) configuration.
|
|
|
|
The Sentry **user** context is attached automatically when a
|
|
[`AbstractContext`](contexts.md#abstractcontext-abstract-class) is created, so captured errors are
|
|
attributed to the Discord user that triggered them.
|
|
|
|
## Exception hierarchy
|
|
|
|
**Package:** `me.bobert.netneutrality.exceptions`
|
|
|
|
```
|
|
RuntimeException
|
|
└── ContextException ── carries a Context (guild/channel/user)
|
|
├── CommandExecutionException ── thrown during command processing
|
|
└── TaskExecutionException ── thrown during task execution
|
|
|
|
GracefulContextError ── user-facing, non-logged failure → ephemeral reply
|
|
ExecutorException ── Thread.UncaughtExceptionHandler → routes crashes to LogHandler
|
|
```
|
|
|
|
- **`ContextException`** — base runtime exception that carries the [`Context`](contexts.md) in which
|
|
it occurred, so downstream handling can respond in the right channel and attribute the error.
|
|
- **`CommandExecutionException`** / **`TaskExecutionException`** — specializations thrown by the
|
|
[command](commands.md) and [task](tasks.md) flows respectively.
|
|
- **`GracefulContextError`** — signals an expected, user-facing problem (e.g. bad input); when caught
|
|
it produces an ephemeral message to the user instead of a full error log/Sentry event.
|
|
- **`ExecutorException`** — an `UncaughtExceptionHandler` installed by
|
|
[`BaseThreadFactory`](threading.md#basethreadfactory) so exceptions escaping worker threads are still
|
|
captured by [`LogHandler`](#loghandler).
|