Skip to content

Commit

Permalink
Merge pull request #411 from nyxx-discord/dev
Browse files Browse the repository at this point in the history
Release 4.4.0
  • Loading branch information
l7ssha authored Dec 12, 2022
2 parents 0333157 + 4831262 commit 9ea12bc
Show file tree
Hide file tree
Showing 18 changed files with 468 additions and 119 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
## 4.4.0
__12.12.2022__

- feature: Improve error handling and logging (#403)
- bug: Fix build() for GuildEventBuilder
- bug: Update exports

## 4.4.0-dev.0
__20.11.2022__

- feature: Improve error handling and logging (#403)

## 4.3.0
__19.11.2022__

Expand Down
15 changes: 15 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,18 @@ We attempt to conform [Effective Dart Coding Style](https://dart.dev/guides/lang
However, code style rules are not enforcement and code should be readable and easy to maintain.

**One exception to rules above is line limit - we use 160 character line limit instead of 80 chars.**

### Logging
nyxx uses the [`logging`](https://pub.dev/packages/logging) package to do logging, and provides a `Logging` plugin for outputting those logs to the console.

When contributing to the library, please be sure to include logs (if applicable) following these guidelines:
- Use the appropriate logger. The name of the logger helps users filter log messages and quickly identify where a message comes from.
- Use the appropriate level. Log levels are a high-level filter for the verbosity of log output, and knowing what level to send a message at can be difficult, but please try to follow these guidelines:
- `SHOUT` for messages that indicate that something unexpectedly failed. This is the level equivalent to throwing an exception/error.
- `SEVERE` for messages that indicate an assertion/sanity check was violated, or for errors not caused by the user.
- `WARNING` for messages that indicate something *might* fail in the future.
- `INFO` for messages that report useful information to the user, such as changes in connection state.
- `CONFIG` for logging values of unchanging fields that might be useful for debugging. These shouldn't be logged repeatedly.
- `FINE` for messages that report information useful for monitoring what the code is doing.
- `FINER` for messages bringing additional information to `FINE` level messages. You can think of these as `CONFIG` for `FINE` messages.
- `FINEST` for messages useful for tracing code execution.
24 changes: 14 additions & 10 deletions lib/nyxx.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ library nyxx;

export 'src/client_options.dart' show CacheOptions, ClientOptions, GatewayIntents;
export 'src/nyxx.dart' show INyxx, INyxxRest, INyxxWebsocket, NyxxFactory;
export 'src/typedefs.dart' show RawApiMap, RawApiList;
export 'src/core/allowed_mentions.dart' show AllowedMentions;
export 'src/core/discord_color.dart' show DiscordColor;
export 'src/core/channel/invite.dart' show IInviteWithMeta, IInvite;
export 'src/core/snowflake.dart' show Snowflake;
export 'src/core/snowflake_entity.dart' show SnowflakeEntity;
export "src/core/application/app_team.dart" show IAppTeam;
Expand All @@ -22,17 +22,19 @@ export 'src/core/audit_logs/audit_log_options.dart' show IAuditLogOptions;
export 'src/core/channel/cacheable_text_channel.dart' show ICacheableTextChannel;
export 'src/core/channel/channel.dart' show IChannel, ChannelType;
export 'src/core/channel/dm_channel.dart' show IDMChannel;
export 'src/core/channel/invite.dart' show IInviteWithMeta, IInvite;
export 'src/core/channel/text_channel.dart' show ITextChannel;
export 'src/core/channel/guild/forum/forum_channel.dart' show IForumChannel, ForumSortOrder;
export 'src/core/channel/guild/forum/forum_channel_tags.dart' show IForumChannelTags;
export 'src/core/channel/guild/forum/forum_tag.dart' show IForumTag;
export 'src/core/channel/thread_channel.dart' show IThreadMember, IThreadChannel;
export 'src/core/channel/thread_preview_channel.dart' show IThreadPreviewChannel;
export 'src/core/channel/guild/activity_types.dart' show VoiceActivityType;
export 'src/core/channel/guild/category_guild_channel.dart' show ICategoryGuildChannel;
export 'src/core/channel/guild/guild_channel.dart' show IGuildChannel, IMinimalGuildChannel;
export 'src/core/channel/guild/text_guild_channel.dart' show ITextGuildChannel;
export 'src/core/channel/guild/voice_channel.dart' show IVoiceGuildChannel, IStageChannelInstance, IStageVoiceGuildChannel, ITextVoiceTextChannel;
export 'src/core/channel/guild/voice_channel.dart'
show IVoiceGuildChannel, IStageChannelInstance, IStageVoiceGuildChannel, ITextVoiceTextChannel, StageChannelInstancePrivacyLevel;
export 'src/core/channel/guild/forum/forum_channel.dart' show IForumChannel, ForumSortOrder;
export 'src/core/channel/guild/forum/forum_channel_tags.dart' show IForumChannelTags;
export 'src/core/channel/guild/forum/forum_tag.dart' show IForumTag;
export 'src/core/embed/embed.dart' show IEmbed;
export 'src/core/embed/embed_author.dart' show IEmbedAuthor;
export 'src/core/embed/embed_field.dart' show IEmbedField;
Expand All @@ -42,14 +44,15 @@ export 'src/core/embed/embed_thumbnail.dart' show IEmbedThumbnail;
export 'src/core/embed/embed_video.dart' show IEmbedVideo;
export 'src/core/guild/ban.dart' show IBan;
export 'src/core/guild/auto_moderation.dart'
show IActionMetadata, IActionStructure, IAutoModerationRule, ITriggerMetadata, ActionTypes, EventTypes, TriggerTypes;
show IActionMetadata, IActionStructure, IAutoModerationRule, ITriggerMetadata, ActionTypes, EventTypes, TriggerTypes, KeywordPresets;
export 'src/core/guild/client_user.dart' show IClientUser;
export 'src/core/guild/guild.dart' show IGuild;
export 'src/core/guild/guild_feature.dart' show GuildFeature;
export 'src/core/guild/guild_nsfw_level.dart' show GuildNsfwLevel;
export 'src/core/guild/guild_preview.dart' show IGuildPreview;
export 'src/core/guild/premium_tier.dart' show PremiumTier;
export 'src/core/guild/role.dart' show IRole, IRoleTags;
export 'src/core/guild/scheduled_event.dart' show IEntityMetadata, IGuildEvent, IGuildEventUser, GuildEventPrivacyLevel, GuildEventStatus, GuildEventType;
export 'src/core/guild/status.dart' show IClientStatus, UserStatus;
export 'src/core/guild/webhook.dart' show IWebhook, WebhookType;
export 'src/core/guild/guild_welcome_screen.dart' show IGuildWelcomeScreen, IGuildWelcomeChannel;
Expand Down Expand Up @@ -165,10 +168,10 @@ export 'src/internal/interfaces/disposable.dart' show Disposable;
export 'src/internal/interfaces/message_author.dart' show IMessageAuthor;
export 'src/internal/interfaces/send.dart' show ISend;
export 'src/internal/interfaces/mentionable.dart' show Mentionable;
export 'src/internal/response_wrapper/error_response_wrapper.dart' show IHttpErrorData, IFieldError;
export 'src/internal/response_wrapper/thread_list_result_wrapper.dart' show IThreadListResultWrapper;
export 'src/internal/shard/shard.dart' show IShard;
export 'src/internal/shard/shard_manager.dart' show IShardManager;
export 'src/typedefs.dart' show RawApiMap;
export 'src/utils/enum.dart' show IEnum;
export 'src/utils/builders/attachment_builder.dart' show AttachmentBuilder, AttachmentMetadataBuilder;
export 'src/utils/builders/builder.dart' show Builder;
Expand All @@ -177,15 +180,15 @@ export 'src/utils/builders/embed_builder.dart' show EmbedBuilder;
export 'src/utils/builders/embed_field_builder.dart' show EmbedFieldBuilder;
export 'src/utils/builders/embed_footer_builder.dart' show EmbedFooterBuilder;
export 'src/utils/builders/guild_builder.dart' show GuildBuilder, RoleBuilder;
export 'src/utils/builders/channel_builder.dart';
export 'src/utils/builders/channel_builder.dart' show ChannelBuilder, TextChannelBuilder, VoiceChannelBuilder;
export 'src/utils/builders/message_builder.dart' show MessageBuilder, MessageDecoration;
export 'src/utils/builders/member_builder.dart' show MemberBuilder;
export 'src/utils/builders/permissions_builder.dart' show PermissionOverrideBuilder, PermissionsBuilder;
export 'src/utils/builders/presence_builder.dart' show PresenceBuilder, ActivityBuilder;
export 'src/utils/builders/reply_builder.dart' show ReplyBuilder;
export 'src/utils/builders/sticker_builder.dart' show StickerBuilder;
export 'src/utils/builders/thread_builder.dart' show ThreadArchiveTime, ThreadBuilder;
export 'src/utils/builders/guild_event_builder.dart' show GuildEventBuilder;
export 'src/utils/builders/guild_event_builder.dart' show GuildEventBuilder, EntityMetadataBuilder;
export 'src/utils/builders/forum_thread_builder.dart' show ForumThreadBuilder, ForumTagBuilder;
export 'src/utils/builders/auto_moderation_builder.dart' show ActionMetadataBuilder, ActionStructureBuilder, AutoModerationRuleBuilder, TriggerMetadataBuilder;
export 'src/utils/extensions.dart' show IntExtensions, SnowflakeEntityListExtensions, StringExtensions;
Expand All @@ -198,5 +201,6 @@ export 'src/plugin/plugins/cli_integration.dart' show CliIntegration;
export 'src/plugin/plugins/ignore_exception.dart' show IgnoreExceptions;
export 'src/plugin/plugins/logging.dart' show Logging;

// Forward `RetryOptions` to allow the usage of the class without importing the package
// Export classes used in the nyxx API to avoid users having to import the package themselves
export 'package:retry/retry.dart' show RetryOptions;
export 'package:logging/logging.dart' show Level;
15 changes: 10 additions & 5 deletions lib/src/internal/connection_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class ConnectionManager {
late final int recommendedShardsNum;
late final int maxConcurrency;

final Logger _logger = Logger("Client");
final Logger _logger = Logger("Connection Manager");

int _shardsReady = 0;

Expand All @@ -30,7 +30,7 @@ class ConnectionManager {
final httpResponse = await (client.httpEndpoints as HttpEndpoints).getGatewayBot();

if (httpResponse is HttpResponseError) {
throw UnrecoverableNyxxError("Cannot get gateway url: [${httpResponse.code}; ${httpResponse.message}]");
throw UnrecoverableNyxxError("Cannot get gateway url: $httpResponse");
}

final response = httpResponse as HttpResponseSuccess;
Expand All @@ -41,7 +41,13 @@ class ConnectionManager {
recommendedShardsNum = response.jsonBody["shards"] as int;
maxConcurrency = response.jsonBody["session_start_limit"]["max_concurrency"] as int;

_logger.fine("Got gateway info: Url: [$gateway]; Recommended shard num: [$recommendedShardsNum]");
_logger.config([
'Got gateway info:',
'Gateway URL: $gateway',
'Remaining connections: $remaining (reset at $resetAt)',
'Recommended shard count: $recommendedShardsNum',
'Max concurrency: $maxConcurrency',
].join('\n'));

checkForConnections();

Expand All @@ -56,8 +62,7 @@ class ConnectionManager {
}

if (remaining < 10) {
_logger.severe("Exiting to prevent API abuse. 10 connections starts left.");
throw UnrecoverableNyxxError('Exiting nyxx to prevent API ban. Remaining less that 10 connection to gateway');
throw UnrecoverableNyxxError('Exiting nyxx to prevent API ban. Less than 10 connections to gateway ($remaining)');
}
}

Expand Down
2 changes: 1 addition & 1 deletion lib/src/internal/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class Constants {
static const int apiVersion = 10;

/// Version of Nyxx
static const String version = "4.3.0";
static const String version = "4.4.0";

/// Url to Nyxx repo
static const String repoUrl = "https://github.com/nyxx-discord/nyxx";
Expand Down
11 changes: 11 additions & 0 deletions lib/src/internal/http/http_bucket.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:http/http.dart' as http;
import 'package:logging/logging.dart';

import 'package:nyxx/src/internal/http/http_request.dart';

Expand All @@ -24,6 +25,8 @@ class HttpBucket {

String get id => _bucketId;

late final Logger _logger = Logger('HttpBucket $id');

HttpBucket(this._remaining, this._reset, this._resetAfter, this._bucketId);

static HttpBucket? fromResponseSafe(http.StreamedResponse response) {
Expand Down Expand Up @@ -63,11 +66,19 @@ class HttpBucket {

void updateRateLimit(http.StreamedResponse response) {
if (isInBucket(response)) {
_logger.finest('Updating bucket');

_remaining = getRemainingFromHeaders(response.headers) ?? _remaining;

_reset = getResetFromHeaders(response.headers) ?? _reset;

_resetAfter = getResetAfterFromHeaders(response.headers) ?? _resetAfter;

_logger.finest([
'Remaining: $_remaining',
'Reset at: $_reset',
'Reset after: $_resetAfter',
].join('\n'));
}
}
}
48 changes: 35 additions & 13 deletions lib/src/internal/http/http_handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import 'package:logging/logging.dart';
import 'package:nyxx/src/events/http_events.dart';
import 'package:nyxx/src/events/ratelimit_event.dart';
import 'package:nyxx/src/internal/event_controller.dart';
import 'package:nyxx/src/internal/exceptions/http_client_exception.dart';
import 'package:nyxx/src/nyxx.dart';
import 'package:nyxx/src/internal/http/http_bucket.dart';
import 'package:nyxx/src/internal/http/http_request.dart';
Expand Down Expand Up @@ -53,8 +52,26 @@ class HttpHandler {
}

HttpBucket? currentBucket = _bucketByRequestRateLimitId[request.rateLimitId];
logger.fine(
"Executing request: [${request.uri.toString()}]; Bucket ID: [${currentBucket?.id}]; Reset at: [${currentBucket?.reset}]; Remaining: [${currentBucket?.remaining}]; Reset after: [${currentBucket?.resetAfter}]; Body: [${request is BasicRequest && request.body != null ? request.body : 'EMPTY'}]");

logger.fine('Executing request $request');
logger.finer([
'Headers: ${request.headers}',
'Authenticated: ${request.auth}',
if (request.auditLog != null) 'Audit Log Reason: ${request.auditLog}',
'Global rate limit: ${request.globalRateLimit}',
'Rate limit ID: ${request.rateLimitId}',
'Rate limit bucket: ${currentBucket?.id}',
if (currentBucket != null) ...[
'Reset at: ${currentBucket.reset}',
'Reset after: ${currentBucket.resetAfter}',
'Remaining: ${currentBucket.remaining}',
],
if (request is BasicRequest) 'Request body: ${request.body}',
if (request is MultipartRequest) ...[
'Request body: ${request.fields}',
'Files: ${request.files.map((file) => file.filename).join(', ')}',
],
].join('\n'));

// Get actual time and check if request can be executed based on data that bucket already have
// and wait if rate limit could be possibly hit
Expand All @@ -81,25 +98,29 @@ class HttpHandler {
currentBucket?.addInFlightRequest(request);
final response = await client.options.httpRetryOptions.retry(
() async => httpClient.send(await request.prepareRequest()),
onRetry: (ex) => logger.shout('Exception when sending HTTP request (retrying automatically): $ex'),
onRetry: (ex) => logger.warning('Exception when sending HTTP request (retrying automatically)', ex),
);
currentBucket?.removeInFlightRequest(request);
currentBucket = _upsertBucket(request, response);
return _handle(request, response);
}

Future<HttpResponse> _handle(HttpRequest request, http.StreamedResponse response) async {
logger.fine('Handling response (${response.statusCode}) from request $request');
logger.finer('Headers: ${response.headers}');

if (response.statusCode >= 200 && response.statusCode < 300) {
final responseSuccess = HttpResponseSuccess(response);
await responseSuccess.finalize();
final responseSuccess = await HttpResponseSuccess.fromResponse(response);

(client.eventsRest as RestEventController).onHttpResponseController.add(HttpResponseEvent(responseSuccess));
logger.finer("Got successful http response for endpoint: [${response.request?.url.toString()}]; Response: [${responseSuccess.jsonBody}]");
logger.finest('Successful response: $responseSuccess');

return responseSuccess;
}

// Check for 429, emmit events and wait given in response body time
final responseError = await HttpResponseError.fromResponse(response);

// Check for 429, emit events and wait given in response body time
if (response.statusCode == 429) {
final responseBody = jsonDecode(await response.stream.bytesToString());
final retryAfter = Duration(milliseconds: ((responseBody["retry_after"] as double) * 1000).ceil());
Expand All @@ -110,16 +131,17 @@ class HttpHandler {
}

_events.onRateLimitedController.add(RatelimitEvent(request, false, response));
logger.warning("${isGlobal ? "Global " : ""}Rate limited via 429 on endpoint: ${request.uri}. Trying to send request again in $retryAfter");

logger.warning(
"${isGlobal ? "Global " : ""}Rate limited via 429 on endpoint: ${request.uri}. Trying to send request again in $retryAfter",
responseError,
);

return Future.delayed(retryAfter, () => execute(request));
}

final responseError = HttpResponseError(response);
await responseError.finalize();

(client.eventsRest as RestEventController).onHttpErrorController.add(HttpErrorEvent(responseError));
logger.finer("Got failure http response for endpoint: [${response.request?.url.toString()}]; Response: [${responseError.message}]");
logger.finest('Unknown/error response: ${responseError.toString(short: true)}', responseError);

return responseError;
}
Expand Down
3 changes: 3 additions & 0 deletions lib/src/internal/http/http_request.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ abstract class HttpRequest {
{...headers, if (auditLog != null) "X-Audit-Log-Reason": auditLog!, "User-Agent": "Nyxx (${Constants.repoUrl}, ${Constants.version})"};

Future<http.BaseRequest> prepareRequest();

@override
String toString() => '$method $uri';
}

/// [BasicRequest] with json body
Expand Down
Loading

0 comments on commit 9ea12bc

Please sign in to comment.