-
Notifications
You must be signed in to change notification settings - Fork 0
Exceptions RU
Любая доменная ошибка библиотеки наследуется от LogsParserException. Эта страница описывает иерархию, точное
условие возникновения каждого типа, дополнительные члены RateLimitExceededException и правило повторных попыток,
из-за которого базовый тип является несущей конструкцией, а не украшением.
Все десять типов находятся в одном плоском пространстве имён LogsParser.Exceptions независимо от папки, в которой
лежит файл (Exceptions/Http/, Exceptions/Parsing/).
System.Exception
└── LogsParserException — корень доменной иерархии, не sealed
├── HtmlParsingException — sealed
└── LogsParserHttpException — база для всех транспортных ошибок и ошибок аутентификации, не sealed
├── AuthenticationRequiredException — sealed
├── AuthenticationFailedException — sealed
├── TwoFactorAuthenticationException — sealed
├── CsrfTokenNotFoundException — sealed
├── AccountConfigurationException — sealed
├── ReactShieldBypassException — sealed
└── RateLimitExceededException — sealed
Не запечатаны только LogsParserException и LogsParserHttpException, поэтому наследоваться можно лишь от них —
например, при реализации собственного ILogsParserDataSource. Сам LogsParserException библиотека никогда не
выбрасывает: он существует, чтобы его перехватывали и от него наследовались.
Каждый тип помечен [Serializable] и объявляет три стандартных конструктора плюс [Obsolete]-конструктор
десериализации. У LogsParserException и LogsParserHttpException конструктор десериализации объявлен как
protected (от них наследуются), у восьми запечатанных типов — как private.
[Serializable]
public class LogsParserException : Exceptionpublic LogsParserException()public LogsParserException(string message)public LogsParserException(string message, Exception innerException)[Obsolete("Formatter-based serialization is obsolete.")]
protected LogsParserException(SerializationInfo info, StreamingContext context)Единственное отклонение — RateLimitExceededException: конструктор без параметров он сохраняет, но два
конструктора с сообщением заменены перегрузками, которые дополнительно несут данные о лимите запросов, см.
RateLimitExceededException ниже.
Все условия ниже взяты из реальных мест выброса в LogsParserHttpDataSource, во внутреннем
LogsParserAuthenticator, во внутреннем ReactShieldBypass и в LogsHtmlParser.
| Исключение | Когда выбрасывается | Типичная причина | Что с этим делать |
|---|---|---|---|
LogsParserException |
Библиотекой не выбрасывается. | — | Перехватывать как общий обработчик всего, что библиотека считает доменной ошибкой. |
HtmlParsingException |
LogsHtmlParser.ParseAdminActivity не находит поля периода или tbody; LogsHtmlParser.ParseTopOperations не находит дату или tbody. |
На вход попала не та страница (страница ошибки, случайно переданная страница входа) либо изменилась вёрстка сайта. | Убедиться, что HTML действительно является страницей отчёта. Если сайт изменился, нужно обновить регулярные выражения в Parsing/. Обратите внимание: ParseLogs не выбрасывает исключение — он деградирует до пустого результата. |
LogsParserHttpException |
Ответ имеет неуспешный статус, не попадающий ни в один из обрабатываемых случаев (302 на /login, /authenticator, /profile или 429); также оборачивает последнее временное исключение после исчерпания MaxRetryAttempts. |
4xx/5xx от сервиса, ошибка DNS/сокета/TLS, сбой прокси, таймаут. | Для временного случая изучить InnerException. Увеличить MaxRetryAttempts либо починить сетевую доступность. |
AuthenticationRequiredException |
Сервис ответил 302 на /login или на /authenticator, а credentials равен null. |
Источник данных создан без LogsParserCredentials, а сохранённые cookie уже недействительны. |
Создать LogsParserHttpDataSource с учётными данными либо восстановить хранилище cookie с действующей сессией. |
AuthenticationFailedException |
GET /login возвращает неуспешный статус; POST /login перенаправляет обратно на /login; либо в ветку /login вошли, когда счётчик попыток аутентификации запроса (3 попытки, общий для веток /login и /authenticator) уже исчерпан. |
Неверные логин или пароль; страница входа недоступна; сессия не стабилизируется. | Проверить LogsParserCredentials.Login и .Password. Не повторять запрос вслепую — транспорт уже сделал три попытки. |
TwoFactorAuthenticationException |
GET /authenticator завершается неуспешно и без заголовка Location; POST /authenticator перенаправляет обратно на /authenticator; секрет TOTP пуст или содержит символы вне Base32; не разрешается ни America/Juneau, ни Alaskan Standard Time; либо в ветку /authenticator вошли, когда счётчик попыток аутентификации запроса (3 попытки, общий для веток /login и /authenticator) уже исчерпан. |
Неверный или некорректный TotpSecret, расхождение часов, хост без базы часовых поясов (урезанный контейнер, InvariantGlobalization). |
Проверить секрет Base32 и системное время. Установить базу tz на хосте — молчаливого отката на UTC здесь нет намеренно. |
CsrfTokenNotFoundException |
В теле страницы /login или /authenticator отсутствует <meta name="csrf-token" content="...">. |
Ответ не является настоящей страницей формы: заглушка анти-DDoS, страница ошибки или внедрённый HttpClient, обработчик которого следует за перенаправлениями и «проглотил» 302. |
Убедиться, что внедрённый HttpClient использует AllowAutoRedirect = false. Иначе — разобрать, что именно вернул сервис. |
AccountConfigurationException |
Сервис ответил 302 на /profile. |
Сам аккаунт logsparser не настроен на сайте (не выбран доступ к серверам, профиль не заполнен). | Исправить аккаунт в веб-интерфейсе сайта. Повторные попытки не помогут. |
ReactShieldBypassException |
Страница испытания продолжает выдаваться после 3 решённых токенов; из полезной нагрузки извлечено менее 3 hex-кандидатов; ни один кандидат не расшифровывается; либо при решении произошла непредвиденная ошибка (она попадает в InnerException). |
Анти-DDoS-щит изменил формат полезной нагрузки либо IP жёстко заблокирован, а не отправлен на испытание. | Повторить позже с тем же хранилищем cookie. Устойчивый сбой означает, что решатель ReactShieldBypass нуждается в обновлении. |
RateLimitExceededException |
HTTP 429 при WaitForRateLimitReset = false либо 429 без заголовка X-Ratelimit-Reset после исчерпания бюджета попыток. |
Слишком много запросов в пределах окна лимита. | Прочитать RetryAfterSeconds / ResetAt и выдержать паузу либо выставить WaitForRateLimitReset = true и позволить транспорту подождать самому. |
Единственное исключение с дополнительным состоянием.
[Serializable]
public sealed class RateLimitExceededException : LogsParserHttpException| Свойство | Тип | Описание |
|---|---|---|
RetryAfterSeconds |
int |
Сколько секунд ждать до повтора. Берётся из заголовка ответа Retry-After, если он разбирается в положительное целое; иначе вычисляется как 2^(retryAttempt + 1), но не менее 1. Ноль, если использован конструктор без параметров. |
ResetAt |
DateTimeOffset? |
Момент сброса окна, взятый из заголовка X-Ratelimit-Reset (Unix-секунды). null, если этот заголовок ни разу не встречался, а также в конструкторах, которые его не принимают. |
public RateLimitExceededException()public RateLimitExceededException(string message, int retryAfterSeconds)public RateLimitExceededException(string message, int retryAfterSeconds, DateTimeOffset? resetAt)public RateLimitExceededException(string message, int retryAfterSeconds, Exception innerException)| Конструктор | RetryAfterSeconds |
ResetAt |
|---|---|---|
() |
0 |
null |
(string, int) |
аргумент | null |
(string, int, DateTimeOffset?) |
аргумент | аргумент |
(string, int, Exception) |
аргумент | null |
Перегрузок (string message) и (string message, Exception innerException) у этого типа нет. Транспорт всегда
использует трёхаргументную форму (string, int, DateTimeOffset?), поэтому оба свойства заполнены, если заголовок
присутствовал. Тип также переопределяет GetObjectData, сохраняя ResetAt в виде тиков со значением -1 в
качестве признака null.
При HTTP 429 транспорт делает одно из трёх:
-
WaitForRateLimitReset = false— сразу выбрасываетRateLimitExceededException. -
WaitForRateLimitReset = true(по умолчанию) и известенX-Ratelimit-Reset— спит до момента сброса плюс 1 секунда и повторяет запрос, не расходуя попытку, поэтому исключение вообще не возникает. -
WaitForRateLimitReset = true, но момент сброса неизвестен — спитRetryAfterSecondsи расходует одну попытку; когда счётчик достигаетMaxRetryAttempts(по умолчанию 5), выбрасывается исключение.
using LogsParser;
using LogsParser.Exceptions;
using LogsParser.Models;
using LogsParser.Net;
using var dataSource = new LogsParserHttpDataSource(
credentials: new LogsParserCredentials("my_login", "my_password", "BASE32SECRET"),
options: new LogsParserHttpOptions { WaitForRateLimitReset = false });
var client = new LogsParserClient(dataSource);
try
{
var page = await client.GetLogsAsync(new LogsQuery(ServerId: 201));
Console.WriteLine($"{page.Entries.Count} entries");
}
catch (RateLimitExceededException exception)
{
Console.WriteLine($"Rate limited: retry after {exception.RetryAfterSeconds}s, window resets at {exception.ResetAt}");
await Task.Delay(TimeSpan.FromSeconds(exception.RetryAfterSeconds));
}LogsParserHttpDataSource.GetContentAsync оборачивает каждую попытку в try/catch с тремя ветками в таком порядке:
| Перехвачено | Поведение |
|---|---|
LogsParserException (и всё, что от него наследуется) |
Пробрасывается немедленно. Счётчик попыток не увеличивается, задержка не применяется. |
OperationCanceledException |
Пробрасывается без изменений. |
Любое другое Exception
|
Считается временным: счётчик попыток увеличивается, цикл спит 2^retryAttempt секунд и повторяет запрос. На последней попытке исключение оборачивается в LogsParserHttpException, а исходное попадает в InnerException. |
Отсюда следуют два вывода.
- Доменная ошибка не стоит времени. Неверные учётные данные, ненастроенный аккаунт или сломанный разбор завершаются на первой же попытке, а не после пяти повторов с экспоненциальной задержкой.
-
Любое новое исключение обязано наследоваться от
LogsParserException. Доменная ошибка без такого наследования молча становится повторяемой: её перехватывает последняя ветка, повторяет запрос с задержкой и в итоге отдаёт вызывающему коду обёрткуLogsParserHttpExceptionс сообщением о сбое после N попыток. Это касается и исключений, которые вы выбрасываете из собственной реализацииILogsParserDataSource: наследуйтесь отLogsParserExceptionилиLogsParserHttpException, и контракт транспорта останется целым.
Обратите внимание, что ветки аутентификации и анти-DDoS-испытания вообще не попадают в эту классификацию: решение
React-испытания и ожидание сброса лимита выполняют continue без расхода попытки, а ветки аутентификации
ограничены собственным счётчиком из 3 попыток.
Ошибки вызывающего кода выражаются стандартными исключениями BCL и намеренно не входят в доменную иерархию: они сигнализируют об ошибке в вызывающем коде, а не о сбое сервиса.
| Исключение | Где возникает | Условие |
|---|---|---|
ArgumentNullException |
конструктор LogsParserClient
|
dataSource равен null. |
ArgumentNullException |
LogsParserHttpDataSource.GetContentAsync |
request равен null. |
ArgumentNullException |
LogsRequestUriBuilder.BuildLogsUri / BuildAdminActivityUri / BuildTopOperationsUri
|
query равен null. |
ArgumentNullException |
MemoryCookieStorage.SetCookies |
cookies равен null. |
ArgumentNullException |
LogsParserLogging.UseLoggerFactory |
loggerFactory равен null. |
ArgumentOutOfRangeException |
LogsRequestUriBuilder.BuildLogsUri |
ServerId <= 0, Limit <= 0 или Page <= 0. |
ArgumentException |
LogsRequestUriBuilder.BuildLogsUri |
Sort не равен "desc" или "asc"; ключ из AdditionalParameters конфликтует с зарезервированным параметром запроса или не соответствует шаблону dynamic[n]. |
ArgumentException |
LogsHtmlParser.ParseLogs / ParseAdminActivity / ParseTopOperations, LogsFilterCatalogParser.Parse
|
html пуст или состоит из пробельных символов (для null возникает ArgumentNullException). |
Поскольку они не являются LogsParserException, общий обработчик транспорта считает их временными — для цикла
повторов это верное поведение, но означает, что ошибка аргумента внутри собственного источника данных будет
повторена несколько раз, прежде чем о ней сообщат.
Перехватывайте сначала конкретные типы, на которые вы действительно можете отреагировать, а затем
LogsParserException как общий обработчик.
using LogsParser;
using LogsParser.Exceptions;
using LogsParser.Models;
using LogsParser.Net;
using var dataSource = new LogsParserHttpDataSource(
credentials: new LogsParserCredentials("my_login", "my_password", "BASE32SECRET"));
var client = new LogsParserClient(dataSource);
try
{
var page = await client.GetLogsAsync(new LogsQuery(ServerId: 201), cancellationToken: cancellationToken);
Console.WriteLine($"{page.Entries.Count} entries");
}
catch (AuthenticationRequiredException)
{
Console.WriteLine("No credentials configured and the session has expired.");
}
catch (AuthenticationFailedException exception)
{
Console.WriteLine($"Login rejected: {exception.Message}");
}
catch (TwoFactorAuthenticationException exception)
{
Console.WriteLine($"Two-factor confirmation failed: {exception.Message}");
}
catch (AccountConfigurationException)
{
Console.WriteLine("The account is not configured on the site — fix it in the web UI.");
}
catch (RateLimitExceededException exception)
{
Console.WriteLine($"Rate limited, retry after {exception.RetryAfterSeconds}s.");
}
catch (HtmlParsingException exception)
{
Console.WriteLine($"The page layout changed: {exception.Message}");
}
catch (LogsParserException exception)
{
// CsrfTokenNotFoundException, ReactShieldBypassException, LogsParserHttpException
// and anything added later all land here.
Console.WriteLine($"LogsParser failed: {exception.Message}");
}Порядок важен: LogsParserHttpException является базой для семи запечатанных типов, поэтому catch по нему должен
идти после них, а LogsParserException — последним из всех.
OperationCanceledException не входит в иерархию и пробрасывается без изменений при срабатывании
CancellationToken вызывающей стороны: он не повторяется и не оборачивается, и то же верно для
TaskCanceledException, возникающего в задержках повтора и ожидания лимита. Цикл повторов также завершается
OperationCanceledException, если токен уже отменён к началу очередной итерации. Перехватывайте его отдельно и вне
доменных обработчиков:
try
{
var page = await client.GetLogsAsync(new LogsQuery(ServerId: 201), cancellationToken: cancellationToken);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
Console.WriteLine("Cancelled by the caller.");
}
catch (LogsParserException exception)
{
Console.WriteLine($"LogsParser failed: {exception.Message}");
}- Транспорт и аутентификация — цикл повторов, конечный автомат аутентификации и лимит запросов
- LogsParserClient — какой метод какое исключение может вызвать
-
Парсеры — асимметрия «выбросить или деградировать» в
LogsHtmlParser - Запросы и построитель URI — правила проверки, стоящие за исключениями аргументов
- Логирование — записи журнала, которые пишутся непосредственно перед каждым выбросом
- Главная
LogsParser · CC BY-NC 4.0 — non-commercial use only / только некоммерческое использование
English
- Home
- Getting Started
- LogsParserClient
- Requests and URI Builder
- Models
- Parsers
- Transport and Authentication
- Cookie Storage
- Dependency Injection
- Logging
- Exceptions
Русский
- Главная
- Начало работы
- LogsParserClient
- Запросы и построитель URI
- Модели
- Парсеры
- Транспорт и авторизация
- Хранение cookies
- Внедрение зависимостей
- Логирование
- Исключения