-
Notifications
You must be signed in to change notification settings - Fork 0
Logging
LogsParser writes diagnostics through Microsoft.Extensions.Logging. This page documents LogsParserLogging
— the process-wide factory holder every class in the library resolves its logger from — the category names that
appear in the output, the level taxonomy, and how to wire a factory in DI, manually or through Serilog.
public static class LogsParserLoggingNamespace: LogsParser.Diagnostics.
The holder keeps one ILoggerFactory and a ConcurrentDictionary<string, ILogger> cache keyed with
StringComparer.Ordinal. Until a factory is supplied, the holder is NullLoggerFactory.Instance.
| Member | Signature | Description |
|---|---|---|
CreateLogger<T>() |
public static ILogger CreateLogger<T>() |
Returns a cached logger whose category is typeof(T).Name
|
CreateLogger(string) |
public static ILogger CreateLogger(string categoryName) |
Returns a cached logger for an explicit category name |
UseLoggerFactory(ILoggerFactory) |
public static void UseLoggerFactory(ILoggerFactory loggerFactory) |
Replaces the process-wide factory and clears the logger cache |
Reset() |
public static void Reset() |
Restores NullLoggerFactory.Instance and clears the logger cache |
public static ILogger CreateLogger<T>()Delegates to CreateLogger(typeof(T).Name), so the category is the short type name — LogsParserClient,
not LogsParser.LogsParserClient.
| Type parameter | Description |
|---|---|
T |
The type whose short name becomes the log category |
using LogsParser.Diagnostics;
using Microsoft.Extensions.Logging;
ILogger logger = LogsParserLogging.CreateLogger<MyImporter>();
logger.LogInformation("import started");Throws: nothing.
public static ILogger CreateLogger(string categoryName)Resolves the logger from the cache, creating it through the current factory on first use. Repeated calls with
the same category return the same ILogger instance until the factory is replaced or reset.
| Parameter | Type | Default | Description |
|---|---|---|---|
categoryName |
string |
— | Category the logger is created with; used verbatim as the cache key |
using LogsParser.Diagnostics;
using Microsoft.Extensions.Logging;
ILogger logger = LogsParserLogging.CreateLogger("LogsHtmlParser");
logger.LogDebug("parsing a locally cached page");| Throws | When |
|---|---|
ArgumentNullException |
categoryName is null — it is used as a dictionary key |
public static void UseLoggerFactory(ILoggerFactory loggerFactory)Installs loggerFactory as the factory for the whole process and throws away the logger cache, so loggers
created afterwards come from the new factory. The swap is taken under an internal lock.
| Parameter | Type | Default | Description |
|---|---|---|---|
loggerFactory |
ILoggerFactory |
— | Factory every subsequent CreateLogger call resolves from |
using LogsParser.Diagnostics;
using Microsoft.Extensions.Logging;
using var loggerFactory = LoggerFactory.Create(builder => builder
.AddSimpleConsole()
.SetMinimumLevel(LogLevel.Debug));
LogsParserLogging.UseLoggerFactory(loggerFactory);| Throws | When |
|---|---|
ArgumentNullException |
loggerFactory is null
|
public static void Reset()Restores NullLoggerFactory.Instance and clears the cache. It exists mainly for test cleanup: a test that
installs a capturing factory must reset the holder afterwards, otherwise the factory leaks into every other test
in the same process.
using LogsParser.Diagnostics;
try
{
LogsParserLogging.UseLoggerFactory(capturingFactory);
// exercise the library
}
finally
{
LogsParserLogging.Reset();
}Throws: nothing.
There is one factory per process, not one per client or per data source. Three things write to it:
| Action | Effect |
|---|---|
LogsParserLogging.UseLoggerFactory(factory) |
Installs factory globally |
new LogsParserHttpDataSource(..., loggerFactory: factory) |
Calls UseLoggerFactory(factory) when the argument is not null
|
services.AddLogsParser(...) + resolving ILogsParserDataSource
|
The registered factory resolves ILoggerFactory from the container (falling back to NullLoggerFactory.Instance) and installs it |
Two consequences worth planning around:
- Constructing a second
LogsParserHttpDataSourcewith a differentILoggerFactoryrewires logging for everything already constructed, because every class re-resolves its logger on each use. - Resolving the library's data source from a container that has no
ILoggerFactoryregistered installsNullLoggerFactory.Instance, which silently replaces a factory that was installed manually beforehand. Configure logging in the same container, or callUseLoggerFactoryafter the first resolve.
using LogsParser.Diagnostics;
using LogsParser.Models;
using LogsParser.Net;
using Microsoft.Extensions.Logging;
using var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
// Both forms end up at the same global holder.
using var dataSource = new LogsParserHttpDataSource(
credentials: new LogsParserCredentials("my_login", "my_password", "BASE32SECRET"),
loggerFactory: loggerFactory);
LogsParserLogging.UseLoggerFactory(loggerFactory);With no factory configured every category resolves to a NullLogger. Its IsEnabled returns false, so the
library's log calls do not format messages and do not touch any sink. No configuration is required to use the
library, and nothing is written anywhere until a factory is installed.
Categories are short type names, never fully-qualified names. These are the ones that appear in real output:
| Category | Emitted by | What it reports |
|---|---|---|
LogsParserClient |
LogsParser.LogsParserClient |
Call parameters and parsed result counts for every public method |
LogsParserHttpDataSource |
LogsParser.Net.LogsParserHttpDataSource |
Request/response cycle, redirect handling, retries, rate limits |
LogsHtmlParser |
LogsParser.LogsHtmlParser |
HTML sizes, entry counts, skipped rows, missing markup |
ReactShieldBypass |
internal challenge solver | Challenge detection and the outcome of solving it |
CookieStorageExtensions |
internal cookie helpers | Cookie names applied to a request and cookies added or updated from a response |
LogsParserAuthenticator |
internal auth flow | Login and two-factor steps, CSRF extraction, rejections |
ReactShieldBypass, CookieStorageExtensions and LogsParserAuthenticator are internal implementation types —
they are not part of the public API, but their categories still reach the log and can be filtered by name.
Because Microsoft.Extensions.Logging matches filters by category prefix, these short names work directly in
AddFilter and in appsettings.json:
using Microsoft.Extensions.Logging;
using var loggerFactory = LoggerFactory.Create(builder => builder
.AddConsole()
.SetMinimumLevel(LogLevel.Warning)
.AddFilter("LogsParserHttpDataSource", LogLevel.Trace));| Level | Scope | Examples from the source |
|---|---|---|
Trace |
Wire and cookie detail |
Sending GET {RelativeUri} (attempt {Attempt}/{MaxAttempts}); Response {RelativeUri}: StatusCode={StatusCode}, RateLimit={Remaining}/{Max}; Rate limit updated: {Remaining}/{Max}, Reset={Reset}; Cookies applied to request: {CookieCount} cookies [{CookieNames}]; Cookie {Action}: {CookieName}; CSRF token extracted from login page ({TokenLength} chars); TOTP code generated ({CodeLength} digits)
|
Debug |
Flow steps and call parameters |
LogsParserHttpDataSource initialized: BaseUri=…, HasCredentials=…, MaxRetryAttempts=…; GetLogsAsync: ServerId=…, Filters=[…], Period=…, Page=…, Limit=…; Step 1/4: GET /login — loading login page; ParseLogs: parsing HTML ({ContentLength} chars); Request completed: {RelativeUri}, {ContentLength} characters
|
Information |
Results and state transitions |
GetLogsAsync: parsed {EntryCount} entries, MetaInfo={Start}-{End}/{Total}; Authentication required for {RelativeUri}, starting auth flow…; Two-factor confirmation completed for {Login}; React challenge detected for {RelativeUri}, solving…; React challenge solved via indexed array method
|
Warning |
Recoverable degradation |
Rate limit hit for {RelativeUri}, waiting until {ResetAt} ({DelaySeconds:F0}s); Transient failure for {RelativeUri}: retrying in {DelaySeconds}s (attempt {Attempt}/{MaxAttempts}); GetCurrentAccountAsync: account info not found in response; TOTP code rejected for {Login}: redirected back to /authenticator; Failed to parse IP address: '{RawValue}', using loopback fallback
|
Error |
Terminal failures |
Request failed: {RelativeUri} returned {StatusCode}; Account not configured: redirected to /profile for {RelativeUri}; Authentication did not stabilize for {RelativeUri} after {Attempts} attempts; Request failed after {MaxRetryAttempts} attempts: {RelativeUri}; React challenge unsolvable: tried {CandidateCount} candidates from {ValueCount} hex values
|
Every Error entry is followed by a thrown exception — see Exceptions. A Warning is usually
recoverable: the loop keeps going, waits out the rate limit, retries the request, or falls back to a default
value. Some warnings still precede a throw — rejected credentials, a rejected TOTP code and the missing markup
that makes ParseAdminActivity and ParseTopOperations fail are all logged at Warning first.
Useful starting points: Information for production, Debug when a query returns unexpected counts, Trace
when the auth flow or the cookie jar misbehaves.
The library logs the account login and nothing else that identifies a credential:
| Logged | Never logged |
|---|---|
Login from LogsParserCredentials
|
The password |
CSRF token length ({TokenLength} chars) |
The CSRF token value |
Generated TOTP code length ({CodeLength} digits) |
The TOTP secret, or any generated code |
| Cookie names and counts | Any cookie value, including arizonarp_session, XSRF-TOKEN and R3ACTLB
|
| Response status codes and content lengths | Response bodies |
At Trace the output still reveals which pages were requested and which cookies exist by name, so treat trace
logs as session metadata even though they carry no secrets.
AddLogsParser picks up whatever ILoggerFactory the container provides, so AddLogging is the only extra
call needed.
using LogsParser;
using LogsParser.DependencyInjection;
using LogsParser.Models;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
var services = new ServiceCollection();
services.AddLogging(builder => builder
.AddConsole()
.SetMinimumLevel(LogLevel.Debug));
services.AddLogsParser(options =>
{
options.Credentials = new LogsParserCredentials("my_login", "my_password", "BASE32SECRET");
});
await using var provider = services.BuildServiceProvider();
var client = provider.GetRequiredService<LogsParserClient>();
var page = await client.GetLogsAsync(new LogsQuery(ServerId: 201));
Console.WriteLine(page.Entries.Count);Either pass the factory to the data source or install it directly; both reach the same holder.
using LogsParser;
using LogsParser.Abstractions;
using LogsParser.Models;
using LogsParser.Net;
using Microsoft.Extensions.Logging;
using var loggerFactory = LoggerFactory.Create(builder => builder
.AddSimpleConsole(options => options.SingleLine = true)
.SetMinimumLevel(LogLevel.Trace));
using var dataSource = new LogsParserHttpDataSource(
credentials: new LogsParserCredentials("my_login", "my_password", "BASE32SECRET"),
cookieStorage: new MemoryCookieStorage(),
loggerFactory: loggerFactory);
var client = new LogsParserClient(dataSource);
var catalog = await client.GetLogsFilterCatalogAsync();
Console.WriteLine(catalog.Filters.Count);The static parsers have no constructor to pass a factory to, so a program that only calls Parsers installs the factory itself:
using LogsParser;
using LogsParser.Diagnostics;
using Microsoft.Extensions.Logging;
using var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
LogsParserLogging.UseLoggerFactory(loggerFactory);
var page = LogsHtmlParser.ParseLogs(File.ReadAllText("saved-page.html"));Requires the Serilog.Extensions.Logging package, which adapts a Serilog logger to ILoggerFactory.
using LogsParser.Diagnostics;
using Microsoft.Extensions.Logging;
using Serilog;
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console()
.WriteTo.File("logs/logsparser-.log", rollingInterval: RollingInterval.Day)
.CreateLogger();
using var loggerFactory = LoggerFactory.Create(builder => builder.AddSerilog(Log.Logger, dispose: true));
LogsParserLogging.UseLoggerFactory(loggerFactory);Serilog's own filtering works on the same short categories, matched through SourceContext:
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Warning()
.MinimumLevel.Override("LogsParserHttpDataSource", Serilog.Events.LogEventLevel.Debug)
.WriteTo.Console()
.CreateLogger();Quiet the parsers, keep the transport verbose:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
services.AddLogging(builder => builder
.AddConsole()
.SetMinimumLevel(LogLevel.Information)
.AddFilter("LogsParserHttpDataSource", LogLevel.Trace)
.AddFilter("LogsParserAuthenticator", LogLevel.Debug)
.AddFilter("CookieStorageExtensions", LogLevel.Warning)
.AddFilter("LogsHtmlParser", LogLevel.Warning));The same configuration in appsettings.json:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"LogsParserHttpDataSource": "Trace",
"LogsParserAuthenticator": "Debug",
"CookieStorageExtensions": "Warning",
"LogsHtmlParser": "Warning"
}
}
}-
Dependency Injection —
AddLogsParserand how it resolvesILoggerFactory -
Transport and Authentication — what the
LogsParserHttpDataSourceandLogsParserAuthenticatorcategories are reporting on -
Exceptions — the exception thrown after each
Errorentry -
Cookie Storage — the storage behind the
CookieStorageExtensionstrace entries - Getting Started — first request in manual and DI mode
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
- Внедрение зависимостей
- Логирование
- Исключения