9.4 KiB
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. |
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(). |
NetNeutralityBot |
Currently an empty placeholder class under framework (reserved name; no logic yet). |
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.
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 implementsLifecycleit participates in ordered start/stop.get(Class<T>)— returns the first registered instance assignable toT(interfaces work).start()— sortsLifecyclecomponents by ascendingpriority()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: 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 asSettingsProviderfor 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
SettingsProviderand letApplicationContextsupply 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 (default 9000). |
MYSQL_* |
Database connection settings (see persistence.md). |
NETNEUTRALITY_SENTRY_DSN |
Enables Sentry when present. |
GITLAB_HOST / GITLAB_TOKEN / GITLAB_PROJECT_ID |
GitLab issue reporting (ReportIssueTask). |
NETNEUTRALITY_PO_TOKEN / NETNEUTRALITY_VISITOR_DATA / NETNEUTRALITY_CIPHER_* |
YouTube playback auth/cipher settings for the music source. |
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. |
GuildUtils |
Guild-related helpers (lookups, permission checks). |
JpaUtils |
Session/transaction helpers for the persistence layer. Bound once to a SessionProvider during bootstrap (JpaUtils.bind). |
MusicUtils |
Voice-channel connectivity and DJ/permission checks for music. 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:
Commandimplementations (CommandManager/CommandRegistry.start()),BuiltInTaskandScheduledTaskimplementations (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:
- Log4j2 (configured by
log4j2.properties; SLF4J is bridged vialog4j-slf4j2-impl), - Sentry (when
NETNEUTRALITY_SENTRY_DSNis configured), and - optionally a per-guild Discord debug channel (e.g.
sendMessageToDebugChannel(guild, msg)), gated byGuildService.isDebugEnabled()/OLoggingconfiguration.
The Sentry user context is attached automatically when a
AbstractContext 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 theContextin which it occurred, so downstream handling can respond in the right channel and attribute the error.CommandExecutionException/TaskExecutionException— specializations thrown by the command and task 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— anUncaughtExceptionHandlerinstalled byBaseThreadFactoryso exceptions escaping worker threads are still captured byLogHandler.