-
Notifications
You must be signed in to change notification settings - Fork 0
Exceptions
Every domain error the library raises derives from LogsParserException. This page lists the hierarchy, the exact
condition behind each type, the extra members on RateLimitExceededException, and the retry rule that makes the
base type load-bearing rather than decorative.
All ten types live in the single flat namespace LogsParser.Exceptions, regardless of the folder they sit in
(Exceptions/Http/, Exceptions/Parsing/).
System.Exception
└── LogsParserException — root of the domain hierarchy, not sealed
├── HtmlParsingException — sealed
└── LogsParserHttpException — base for every transport/auth failure, not sealed
├── AuthenticationRequiredException — sealed
├── AuthenticationFailedException — sealed
├── TwoFactorAuthenticationException — sealed
├── CsrfTokenNotFoundException — sealed
├── AccountConfigurationException — sealed
├── ReactShieldBypassException — sealed
└── RateLimitExceededException — sealed
Only LogsParserException and LogsParserHttpException are non-sealed, so they are the only two types you can
derive from — for example when implementing a custom ILogsParserDataSource. LogsParserException itself is never
thrown by the library; it exists to be caught and to be inherited.
Each type is [Serializable] and declares the three standard constructors plus an [Obsolete] serialization
constructor. LogsParserException and LogsParserHttpException declare the serialization constructor as
protected (they are inheritable); the eight sealed types declare it as 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 is the one deviation: it keeps the parameterless constructor but replaces the two
message constructors with overloads that also carry the rate-limit data — see
RateLimitExceededException below.
Every trigger below is the actual condition at the throw site in LogsParserHttpDataSource, the internal
LogsParserAuthenticator, the internal ReactShieldBypass, or LogsHtmlParser.
| Exception | Thrown when | Typical cause | What to do about it |
|---|---|---|---|
LogsParserException |
Never thrown by the library. | — | Catch it as the catch-all for anything this library considers a domain error. |
HtmlParsingException |
LogsHtmlParser.ParseAdminActivity cannot find the period inputs or the tbody; LogsHtmlParser.ParseTopOperations cannot find the date or the tbody. |
The HTML is not the expected page (an error page, a login page passed in by mistake), or the site's markup changed. | Verify the HTML really is the report page. If the site changed, the regexes in Parsing/ need updating. Note that ParseLogs does not throw — it degrades to an empty result. |
LogsParserHttpException |
A response has a status code that is not success and is not one of the handled cases (302 to /login, /authenticator, /profile, or 429); also wraps the last transient exception once MaxRetryAttempts is exhausted. |
4xx/5xx from the service, DNS/socket/TLS failure, proxy failure, timeout. | Inspect InnerException for the transient case. Raise MaxRetryAttempts, or fix connectivity. |
AuthenticationRequiredException |
The service answered 302 to /login or to /authenticator while credentials is null. |
The data source was constructed without LogsParserCredentials, and the stored cookies are no longer valid. |
Construct LogsParserHttpDataSource with credentials, or restore a cookie storage that still holds a valid session. |
AuthenticationFailedException |
GET /login returns a non-success status; POST /login redirects back to /login; or the /login branch was entered after the request's auth counter (3 attempts, shared by the /login and /authenticator branches) was already used up. |
Wrong login or password; the login page is unavailable; the session never stabilises. | Check LogsParserCredentials.Login / .Password. Do not retry blindly — the transport already tried three times. |
TwoFactorAuthenticationException |
GET /authenticator fails without a Location header; POST /authenticator redirects back to /authenticator; the TOTP secret is empty or contains non-Base32 symbols; neither America/Juneau nor Alaskan Standard Time resolves; or the /authenticator branch was entered after the request's auth counter (3 attempts, shared by the /login and /authenticator branches) was already used up. |
A wrong or malformed TotpSecret, clock skew, or a host with no time-zone database (a trimmed container, InvariantGlobalization). |
Verify the Base32 secret and the system clock. Install the tz database on the host — there is deliberately no silent UTC fallback. |
CsrfTokenNotFoundException |
<meta name="csrf-token" content="..."> is absent from the /login or /authenticator page body. |
The response is not the real form page: an anti-DDoS interstitial, an error page, or an injected HttpClient whose handler follows redirects and swallowed the 302. |
Ensure the injected HttpClient uses AllowAutoRedirect = false. Otherwise inspect what the service actually returned. |
AccountConfigurationException |
The service answered 302 to /profile. |
The logsparser account itself is not configured on the site (no server access selected, profile incomplete). | Fix the account in the site's UI. No amount of retrying helps. |
ReactShieldBypassException |
The challenge page keeps being served after 3 solved tokens; the challenge payload yields fewer than 3 hex candidates; no candidate decrypts; or any unexpected error occurs while solving (wrapped as InnerException). |
The anti-DDoS shield changed its payload format, or the IP is being hard-blocked rather than challenged. | Retry later from the same cookie storage. A persistent failure means the ReactShieldBypass solver needs updating. |
RateLimitExceededException |
HTTP 429 with WaitForRateLimitReset = false, or 429 without an X-Ratelimit-Reset header once the retry budget is exhausted. |
Too many requests in the rate-limit window. | Read RetryAfterSeconds / ResetAt and back off, or set WaitForRateLimitReset = true and let the transport wait. |
The only exception carrying extra state.
[Serializable]
public sealed class RateLimitExceededException : LogsParserHttpException| Property | Type | Description |
|---|---|---|
RetryAfterSeconds |
int |
Seconds to wait before retrying. Taken from the Retry-After response header when it parses to a positive integer; otherwise computed as 2^(retryAttempt + 1), at least 1. Zero when the parameterless constructor was used. |
ResetAt |
DateTimeOffset? |
The moment the window resets, taken from the X-Ratelimit-Reset header (Unix seconds). null when that header was never seen, and on the constructors that do not accept it. |
public RateLimitExceededException()public RateLimitExceededException(string message, int retryAfterSeconds)public RateLimitExceededException(string message, int retryAfterSeconds, DateTimeOffset? resetAt)public RateLimitExceededException(string message, int retryAfterSeconds, Exception innerException)| Constructor | RetryAfterSeconds |
ResetAt |
|---|---|---|
() |
0 |
null |
(string, int) |
the argument | null |
(string, int, DateTimeOffset?) |
the argument | the argument |
(string, int, Exception) |
the argument | null |
There is no (string message) and no (string message, Exception innerException) overload on this type. The
transport always uses the three-argument (string, int, DateTimeOffset?) form, so both properties are populated
whenever the header was present. The type also overrides GetObjectData, storing ResetAt as ticks with -1 as
the null sentinel.
On HTTP 429 the transport does one of three things:
-
WaitForRateLimitReset = false— throwsRateLimitExceededExceptionimmediately. -
WaitForRateLimitReset = true(the default) andX-Ratelimit-Resetis known — sleeps until reset + 1 second and retries without consuming a retry attempt, so it never surfaces as an exception. -
WaitForRateLimitReset = truebut no reset timestamp is known — sleepsRetryAfterSecondsand consumes one retry attempt; when the attempt counter reachesMaxRetryAttempts(default 5) it throws.
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 wraps every attempt in a try/catch with three branches, in this order:
| Caught | Behaviour |
|---|---|
LogsParserException (and everything deriving from it) |
Rethrown immediately. The retry attempt counter is not incremented and no delay is applied. |
OperationCanceledException |
Rethrown unchanged. |
Any other Exception
|
Treated as transient: the attempt counter increments, the loop sleeps 2^retryAttempt seconds and retries. On the last attempt it is wrapped in LogsParserHttpException and thrown with the original as InnerException. |
Two consequences follow.
- A domain error never costs latency. Wrong credentials, an unconfigured account or a broken parse fail on the first attempt instead of being retried five times with exponential backoff.
-
Any new exception must derive from
LogsParserException. A domain error that does not becomes silently retryable: it is caught by the last branch, retried with backoff, and finally reaches the caller re-wrapped as aLogsParserHttpExceptionwhose message says the request failed after N attempts. This applies to exceptions you throw from a customILogsParserDataSourceimplementation too — derive fromLogsParserExceptionorLogsParserHttpExceptionand the transport's contract stays intact.
Note that the auth and challenge branches sit outside this classification entirely: solving the React challenge
and waiting out a rate-limit reset continue the loop without consuming a retry attempt, and the auth branches are
bounded by their own counter of 3 attempts.
Caller mistakes surface as the BCL argument exceptions and are deliberately not part of the domain hierarchy — they signal a bug in the calling code, not a failure of the service.
| Exception | Raised by | Condition |
|---|---|---|
ArgumentNullException |
LogsParserClient constructor |
dataSource is null. |
ArgumentNullException |
LogsParserHttpDataSource.GetContentAsync |
request is null. |
ArgumentNullException |
LogsRequestUriBuilder.BuildLogsUri / BuildAdminActivityUri / BuildTopOperationsUri
|
query is null. |
ArgumentNullException |
MemoryCookieStorage.SetCookies |
cookies is null. |
ArgumentNullException |
LogsParserLogging.UseLoggerFactory |
loggerFactory is null. |
ArgumentOutOfRangeException |
LogsRequestUriBuilder.BuildLogsUri |
ServerId <= 0, Limit <= 0, or Page <= 0. |
ArgumentException |
LogsRequestUriBuilder.BuildLogsUri |
Sort is not "desc" or "asc"; an AdditionalParameters key collides with a reserved query parameter or does not match dynamic[n]. |
ArgumentException |
LogsHtmlParser.ParseLogs / ParseAdminActivity / ParseTopOperations, LogsFilterCatalogParser.Parse
|
html is empty or whitespace (null raises ArgumentNullException). |
Because they are not LogsParserException, the transport's catch-all treats them as transient — which is the
correct behaviour for the retry loop, but means an argument bug inside a custom data source will be retried before
it is reported.
Catch the specific types you can actually act on, then LogsParserException as the catch-all.
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}");
}Order matters: LogsParserHttpException is the base of seven of the sealed types, so a catch on it must come
after any of them; LogsParserException must come last of all.
OperationCanceledException is not part of the hierarchy and is rethrown unchanged when the caller's
CancellationToken fires — it is neither retried nor wrapped, and the same holds for the TaskCanceledException
raised by the retry and rate-limit delays. The retry loop also exits with OperationCanceledException if the token
is already cancelled when an iteration starts. Catch it separately, and outside the domain catches:
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}");
}- Transport and Authentication — the retry loop, the auth state machine and rate limiting
- LogsParserClient — which method can raise which exception
-
Parsers — the throw/degrade asymmetry of
LogsHtmlParser - Requests and URI Builder — the validation rules behind the argument exceptions
- Logging — the log entries written just before each throw
- Home
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
- Внедрение зависимостей
- Логирование
- Исключения